okfy-ai 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
@@ -1,27 +1,59 @@
1
1
  # okfy-ai
2
2
 
3
- Turn docs into agent-readable Open Knowledge Format bundles.
3
+ Turn docs into agent-readable Open Knowledge Format bundles, then serve them to Claude, Codex, Cursor, or any MCP client.
4
4
 
5
- ## Install
5
+ ## Use With Agents
6
6
 
7
- Run without installing:
7
+ Create a bundle:
8
8
 
9
9
  ```bash
10
- npx -y okfy-ai demo
10
+ npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
11
+ npx -y okfy-ai validate ./stripe-checkout-okf
12
+ ```
13
+
14
+ Add it to an MCP client:
15
+
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "stripe-okf": {
20
+ "command": "npx",
21
+ "args": ["-y", "okfy-ai", "serve", "./stripe-checkout-okf", "--mcp"]
22
+ }
23
+ }
24
+ }
11
25
  ```
12
26
 
13
- Or install globally:
27
+ Ask your agent:
28
+
29
+ ```text
30
+ Use the stripe-okf MCP server. Search for Checkout Sessions, read the most relevant concepts, inspect neighbors if needed, and explain the minimum backend flow with source URLs.
31
+ ```
32
+
33
+ ## Client Setup
34
+
35
+ Claude Code:
14
36
 
15
37
  ```bash
16
- npm install -g okfy-ai
17
- okfy demo
38
+ claude mcp add --transport stdio stripe-okf -- npx -y okfy-ai serve ./stripe-checkout-okf --mcp
18
39
  ```
19
40
 
20
- Requires Node.js 20+.
41
+ Codex:
42
+
43
+ ```toml
44
+ [mcp_servers.stripe_okf]
45
+ command = "npx"
46
+ args = ["-y", "okfy-ai", "serve", "./stripe-checkout-okf", "--mcp"]
47
+ startup_timeout_sec = 20
48
+ tool_timeout_sec = 60
49
+ enabled = true
50
+ ```
21
51
 
22
- ## Quick Start
52
+ Claude Desktop, Cursor, and other `mcpServers` clients can use the JSON config above. More setup: https://github.com/0dust/OKFy/blob/main/docs/mcp-clients.md
23
53
 
24
- Convert a docs site into an OKF bundle:
54
+ ## Create Bundles
55
+
56
+ Docs website:
25
57
 
26
58
  ```bash
27
59
  npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
@@ -29,37 +61,58 @@ npx -y okfy-ai validate ./stripe-checkout-okf
29
61
  npx -y okfy-ai inspect ./stripe-checkout-okf
30
62
  ```
31
63
 
32
- Serve it to an MCP client:
64
+ Local Markdown:
33
65
 
34
66
  ```bash
35
- npx -y okfy-ai serve ./stripe-checkout-okf --mcp
67
+ npx -y okfy-ai import ./docs --out ./docs-okf --source-name "Project docs" --force
68
+ npx -y okfy-ai validate ./docs-okf
36
69
  ```
37
70
 
38
- ## Local Markdown
71
+ ## Optional CLI Install
72
+
73
+ You do not need global install for MCP configs. `npx -y okfy-ai ...` is usually better because the MCP client can launch okfy directly.
74
+
75
+ Install only if you want shorter local commands:
39
76
 
40
77
  ```bash
41
- npx -y okfy-ai import ./docs --out ./docs-okf --source-name "Project docs" --force
42
- npx -y okfy-ai validate ./docs-okf
43
- npx -y okfy-ai serve ./docs-okf --mcp
78
+ npm install -g okfy-ai
79
+ okfy demo
44
80
  ```
45
81
 
46
- ## MCP Config
82
+ `okfy-ai` is the npm package name. `okfy` is the installed CLI command.
83
+
84
+ Requires Node.js 20+.
85
+
86
+ After installing, this MCP config is equivalent:
47
87
 
48
88
  ```json
49
89
  {
50
90
  "mcpServers": {
51
91
  "docs-okf": {
52
- "command": "npx",
53
- "args": ["-y", "okfy-ai", "serve", "./docs-okf", "--mcp"]
92
+ "command": "okfy",
93
+ "args": ["serve", "./docs-okf", "--mcp"]
54
94
  }
55
95
  }
56
96
  }
57
97
  ```
58
98
 
59
- Ask your agent:
99
+ ## Demo
60
100
 
61
- ```text
62
- Use the docs-okf MCP server. Search for the relevant topic, read the best matching concepts, inspect neighbors if needed, and answer with source URLs.
101
+ ```bash
102
+ npx -y okfy-ai demo
103
+ ```
104
+
105
+ ## No-Install MCP Config
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "docs-okf": {
111
+ "command": "npx",
112
+ "args": ["-y", "okfy-ai", "serve", "./docs-okf", "--mcp"]
113
+ }
114
+ }
115
+ }
63
116
  ```
64
117
 
65
118
  ## CLI Commands
Binary file
Binary file
@@ -455,6 +455,7 @@ async function crawlWebsite(options) {
455
455
  let skipped = 0;
456
456
  let failed = 0;
457
457
  const limit = pLimit(options.concurrency ?? 4);
458
+ options.onProgress?.({ type: "start", seed, maxPages, maxDepth });
458
459
  while (queue.length > 0 && visited.size < maxPages) {
459
460
  const batch = queue.splice(0, Math.min(queue.length, maxPages - visited.size));
460
461
  const results = await Promise.all(
@@ -464,9 +465,11 @@ async function crawlWebsite(options) {
464
465
  visited.add(item.url);
465
466
  if (!shouldVisit(item.url, seed, options, robots)) {
466
467
  skipped += 1;
468
+ options.onProgress?.({ type: "skipped", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
467
469
  return;
468
470
  }
469
471
  planned.push(item.url);
472
+ options.onProgress?.({ type: "fetch", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
470
473
  try {
471
474
  const fetched = await fetchText(item.url);
472
475
  const contentType = contentTypeFromHeader(fetched.contentType);
@@ -483,6 +486,7 @@ async function crawlWebsite(options) {
483
486
  };
484
487
  const doc = normalizeDocument(raw);
485
488
  if (!options.dryRun) documents.push(doc);
489
+ let discovered = 0;
486
490
  if (item.depth < maxDepth) {
487
491
  const links = options.dryRun && contentType === "html" ? extractRawHtmlLinks(fetched.text) : doc.links;
488
492
  for (const link of links) {
@@ -491,14 +495,24 @@ async function crawlWebsite(options) {
491
495
  if (!queued.has(next) && shouldVisit(next, seed, options, robots) && queued.size < maxPages * 4) {
492
496
  queued.add(next);
493
497
  queue.push({ url: next, depth: item.depth + 1 });
498
+ discovered += 1;
494
499
  }
495
500
  } catch {
496
501
  skipped += 1;
497
502
  }
498
503
  }
499
504
  }
505
+ options.onProgress?.({
506
+ type: "fetched",
507
+ url: item.url,
508
+ fetched: options.dryRun ? planned.length : documents.length,
509
+ queued: queue.length,
510
+ discovered,
511
+ maxPages
512
+ });
500
513
  } catch {
501
514
  failed += 1;
515
+ options.onProgress?.({ type: "failed", url: item.url, fetched: documents.length, queued: queue.length, maxPages });
502
516
  }
503
517
  })
504
518
  )
@@ -509,6 +523,7 @@ async function crawlWebsite(options) {
509
523
  return { pagesFetched: planned.length, skipped, failed, written: [], documents: [], dryRunPages: planned.slice(0, maxPages) };
510
524
  }
511
525
  if (documents.length === 0) throw new Error("Crawl generated zero concepts.");
526
+ options.onProgress?.({ type: "writing", concepts: documents.length, outDir: options.outDir });
512
527
  const written = await writeOkfBundle(documents, {
513
528
  outDir: options.outDir,
514
529
  title: options.title,
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  inspectBundle,
6
6
  serveMcpStdio,
7
7
  validateBundle
8
- } from "./chunk-C46QXZDU.js";
8
+ } from "./chunk-6AP7LVJG.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import fs from "fs";
@@ -15,6 +15,16 @@ import { Command } from "commander";
15
15
  import pc from "picocolors";
16
16
  var program = new Command();
17
17
  var packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
18
+ var isTty = Boolean(process.stderr.isTTY);
19
+ function readPackageVersion() {
20
+ try {
21
+ const raw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf8");
22
+ const parsed = JSON.parse(raw);
23
+ return parsed.version ?? "0.0.0";
24
+ } catch {
25
+ return "0.0.0";
26
+ }
27
+ }
18
28
  function collect(value, previous) {
19
29
  previous.push(value);
20
30
  return previous;
@@ -46,14 +56,50 @@ function printStats(stats) {
46
56
  for (const [domain, count] of Object.entries(stats.sourceDomains)) console.log(` ${domain}: ${count}`);
47
57
  }
48
58
  }
49
- program.name("okfy").description("Turn docs into agent memory with Open Knowledge Format and MCP.").version("0.1.0");
59
+ function printStatus(message) {
60
+ process.stderr.write(`${message}
61
+ `);
62
+ }
63
+ function printCrawlProgress(event) {
64
+ const clear = isTty ? "\r\x1B[K" : "";
65
+ switch (event.type) {
66
+ case "start":
67
+ process.stderr.write(`okfy crawl: starting ${event.seed} (max ${event.maxPages} pages, depth ${event.maxDepth})
68
+ `);
69
+ break;
70
+ case "fetch":
71
+ process.stderr.write(`${clear}okfy crawl: fetching ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}`);
72
+ if (!isTty) process.stderr.write("\n");
73
+ break;
74
+ case "fetched":
75
+ process.stderr.write(
76
+ `${clear}okfy crawl: fetched ${event.fetched}/${event.maxPages}, queued ${event.queued}, discovered +${event.discovered}: ${event.url}
77
+ `
78
+ );
79
+ break;
80
+ case "skipped":
81
+ process.stderr.write(`${clear}okfy crawl: skipped ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}
82
+ `);
83
+ break;
84
+ case "failed":
85
+ process.stderr.write(`${clear}okfy crawl: failed ${event.fetched}/${event.maxPages}, queued ${event.queued}: ${event.url}
86
+ `);
87
+ break;
88
+ case "writing":
89
+ process.stderr.write(`${clear}okfy crawl: writing ${event.concepts} concepts to ${event.outDir}
90
+ `);
91
+ break;
92
+ }
93
+ }
94
+ program.name("okfy").description("Turn docs into agent memory with Open Knowledge Format and MCP.").version(readPackageVersion());
50
95
  program.command("crawl").argument("<url>", "Docs URL to crawl").requiredOption("--out <dir>", "Output OKF bundle directory").option("--max-pages <n>", "Maximum pages", (value) => Number(value), 100).option("--max-depth <n>", "Maximum crawl depth", (value) => Number(value), 4).option("--include <pattern>", "Include glob or regex", collect, []).option("--exclude <pattern>", "Exclude glob or regex", collect, []).option("--same-origin", "Stay on same origin", true).option("--no-same-origin", "Allow cross-origin links").option("--respect-robots", "Respect robots.txt", true).option("--no-respect-robots", "Ignore robots.txt").option("--concurrency <n>", "Fetch concurrency", (value) => Number(value), 4).option("--title <name>", "Bundle title").option("--force", "Overwrite output directory", false).option("--dry-run", "List pages that would be crawled", false).option("--allow-private-network", "Allow localhost/private IP crawl targets", false).option("--stable-timestamps", "Use a deterministic timestamp in generated frontmatter", false).action(async (url, options) => {
51
96
  try {
52
97
  const result = await crawlWebsite({
53
98
  seedUrl: url,
54
99
  outDir: options.out,
55
100
  ...options,
56
- timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0
101
+ timestamp: options.stableTimestamps ? "2026-06-14T00:00:00.000Z" : void 0,
102
+ onProgress: printCrawlProgress
57
103
  });
58
104
  if (options.dryRun) {
59
105
  console.log("okfy crawl dry run");
@@ -75,6 +121,8 @@ program.command("crawl").argument("<url>", "Docs URL to crawl").requiredOption("
75
121
  });
76
122
  program.command("import").argument("<path>", "Local docs folder or file").requiredOption("--out <dir>", "Output OKF bundle directory").option("--source-name <name>", "Source name").option("--include <glob>", "Include glob", collect, []).option("--exclude <glob>", "Exclude glob", collect, []).option("--force", "Overwrite output directory", false).option("--stable-timestamps", "Use a deterministic timestamp in generated frontmatter", false).action(async (input, options) => {
77
123
  try {
124
+ printStatus(`okfy import: reading ${input}`);
125
+ printStatus(`okfy import: writing bundle to ${options.out}`);
78
126
  const result = await importLocal({
79
127
  inputPath: input,
80
128
  outDir: options.out,
@@ -85,19 +133,25 @@ program.command("import").argument("<path>", "Local docs folder or file").requir
85
133
  console.log(`Source: ${input}`);
86
134
  console.log(`Concepts: ${result.written.length} written`);
87
135
  console.log(`Output: ${options.out}`);
136
+ printStatus(`okfy import: done, wrote ${result.written.length} concepts`);
88
137
  } catch (error) {
89
138
  console.error(pc.red(error?.message ?? "Import failed."));
90
139
  process.exitCode = 1;
91
140
  }
92
141
  });
93
142
  program.command("validate").argument("<bundle>", "OKF bundle directory").option("--json", "Print JSON report", false).action(async (bundle, options) => {
143
+ printStatus(`okfy validate: checking ${bundle}`);
94
144
  const report = await validateBundle(bundle);
95
145
  printValidation(report, options.json);
146
+ printStatus(`okfy validate: ${report.valid ? "valid" : "invalid"}, ${report.conceptCount} concepts`);
96
147
  if (!report.valid) process.exitCode = 1;
97
148
  });
98
149
  program.command("inspect").argument("<bundle>", "OKF bundle directory").action(async (bundle) => {
99
150
  try {
100
- printStats(await inspectBundle(bundle));
151
+ printStatus(`okfy inspect: reading ${bundle}`);
152
+ const stats = await inspectBundle(bundle);
153
+ printStats(stats);
154
+ printStatus(`okfy inspect: done, ${stats.conceptCount} concepts, ${stats.linkCount} links`);
101
155
  } catch (error) {
102
156
  console.error(pc.red(error?.message ?? "Inspect failed."));
103
157
  process.exitCode = 1;
@@ -114,7 +168,11 @@ program.command("serve").argument("<bundle>", "OKF bundle directory").option("--
114
168
  process.exitCode = 1;
115
169
  return;
116
170
  }
171
+ printStatus(`okfy serve: loading ${bundle}`);
172
+ printStatus(`okfy serve: starting MCP stdio server "${options.name}"`);
117
173
  await serveMcpStdio({ bundleDir: bundle, name: options.name, maxResultChars: options.maxResultChars });
174
+ printStatus("okfy serve: ready on stdio (stdout is reserved for MCP JSON-RPC)");
175
+ printStatus("okfy serve: tools bundle_summary, search_concepts, read_concept, get_neighbors, list_types, list_tags");
118
176
  });
119
177
  function resolveDemoBundle() {
120
178
  const relativeBundle = "examples/bundles/okfy-docs";
@@ -137,7 +195,7 @@ program.command("demo").description("Run offline demo against committed example
137
195
  console.log("MCP config:");
138
196
  console.log(
139
197
  JSON.stringify(
140
- { mcpServers: { "okfy-docs": { command: "npx", args: ["okfy-ai", "serve", bundle, "--mcp"] } } },
198
+ { mcpServers: { "okfy-docs": { command: "npx", args: ["-y", "okfy-ai", "serve", bundle, "--mcp"] } } },
141
199
  null,
142
200
  2
143
201
  )
package/dist/index.d.ts CHANGED
@@ -86,6 +86,42 @@ type CrawlOptions = {
86
86
  dryRun?: boolean;
87
87
  allowPrivateNetwork?: boolean;
88
88
  timestamp?: string;
89
+ onProgress?: (event: CrawlProgressEvent) => void;
90
+ };
91
+ type CrawlProgressEvent = {
92
+ type: "start";
93
+ seed: string;
94
+ maxPages: number;
95
+ maxDepth: number;
96
+ } | {
97
+ type: "fetch";
98
+ url: string;
99
+ fetched: number;
100
+ queued: number;
101
+ maxPages: number;
102
+ } | {
103
+ type: "fetched";
104
+ url: string;
105
+ fetched: number;
106
+ queued: number;
107
+ discovered: number;
108
+ maxPages: number;
109
+ } | {
110
+ type: "skipped";
111
+ url: string;
112
+ fetched: number;
113
+ queued: number;
114
+ maxPages: number;
115
+ } | {
116
+ type: "failed";
117
+ url: string;
118
+ fetched: number;
119
+ queued: number;
120
+ maxPages: number;
121
+ } | {
122
+ type: "writing";
123
+ concepts: number;
124
+ outDir: string;
89
125
  };
90
126
  type CrawlResult = {
91
127
  pagesFetched: number;
@@ -176,4 +212,4 @@ type WriteBundleOptions = {
176
212
  };
177
213
  declare function writeOkfBundle(docs: NormalizedDocument[], options: WriteBundleOptions): Promise<string[]>;
178
214
 
179
- export { BundleSearch, type BundleStats, type Concept, type ContentType, type CrawlOptions, type CrawlResult, type ImportOptions, type KnowledgeGraph, type NormalizedDocument, type RawDocument, type SearchResult, type ServeOptions, type ValidationIssue, type ValidationReport, type WriteBundleOptions, buildGraph, crawlWebsite, createMcpServer, descriptionFromMarkdown, extractHeadings, extractInternalLinks, extractMarkdownLinks, importLocal, inferTags, inferType, inspectBundle, normalizeDocument, readBundle, readConceptFile, serveMcpStdio, validateBundle, writeOkfBundle };
215
+ export { BundleSearch, type BundleStats, type Concept, type ContentType, type CrawlOptions, type CrawlProgressEvent, type CrawlResult, type ImportOptions, type KnowledgeGraph, type NormalizedDocument, type RawDocument, type SearchResult, type ServeOptions, type ValidationIssue, type ValidationReport, type WriteBundleOptions, buildGraph, crawlWebsite, createMcpServer, descriptionFromMarkdown, extractHeadings, extractInternalLinks, extractMarkdownLinks, importLocal, inferTags, inferType, inspectBundle, normalizeDocument, readBundle, readConceptFile, serveMcpStdio, validateBundle, writeOkfBundle };
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  serveMcpStdio,
18
18
  validateBundle,
19
19
  writeOkfBundle
20
- } from "./chunk-C46QXZDU.js";
20
+ } from "./chunk-6AP7LVJG.js";
21
21
  export {
22
22
  BundleSearch,
23
23
  buildGraph,
@@ -3,19 +3,23 @@
3
3
  okfy exposes OKF bundles through a local stdio MCP server:
4
4
 
5
5
  ```bash
6
- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
6
+ okfy serve ./tmp/okfy-docs --mcp
7
7
  ```
8
8
 
9
- Use stdio for local bundles. MCP stdio means the client launches `okfy` as a subprocess, sends JSON-RPC messages on stdin, and reads JSON-RPC responses on stdout. okfy logs should go to stderr.
9
+ Shell examples assume `npm install -g okfy-ai`, then the `okfy` CLI command.
10
+
11
+ MCP config examples use `npx -y okfy-ai` by default. That is normal for stdio MCP servers because the MCP client can launch the npm package without requiring a global install.
12
+
13
+ Use stdio for local bundles. MCP stdio means the client launches a local command as a subprocess, sends JSON-RPC messages on stdin, and reads JSON-RPC responses on stdout. okfy logs should go to stderr.
10
14
 
11
15
  ## Prepare a Bundle
12
16
 
13
17
  Offline fixture:
14
18
 
15
19
  ```bash
16
- npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force
17
- npx -y okfy-ai validate ./tmp/okfy-docs
18
- npx -y okfy-ai inspect ./tmp/okfy-docs
20
+ okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
21
+ okfy validate ./tmp/okfy-docs
22
+ okfy inspect ./tmp/okfy-docs
19
23
  ```
20
24
 
21
25
  Expected output:
@@ -29,15 +33,22 @@ Broken links: 0
29
33
  Docs-site crawl:
30
34
 
31
35
  ```bash
32
- npx -y okfy-ai crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
33
- npx -y okfy-ai validate ./stripe-checkout-okf
34
- npx -y okfy-ai serve ./stripe-checkout-okf --mcp
36
+ okfy crawl https://docs.stripe.com/checkout --out ./stripe-checkout-okf --max-pages 25
37
+ okfy validate ./stripe-checkout-okf
38
+ okfy serve ./stripe-checkout-okf --mcp
35
39
  ```
36
40
 
37
41
  ## Claude Code
38
42
 
39
43
  Add okfy as a local stdio server:
40
44
 
45
+ ```bash
46
+ claude mcp add --transport stdio okfy-docs -- okfy serve ./tmp/okfy-docs --mcp
47
+ claude mcp list
48
+ ```
49
+
50
+ No-install equivalent:
51
+
41
52
  ```bash
42
53
  claude mcp add --transport stdio okfy-docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
43
54
  claude mcp list
@@ -85,9 +96,10 @@ Troubleshooting:
85
96
 
86
97
  - `spawn npx ENOENT`: install Node.js >=20 and ensure `npx` is on `PATH`.
87
98
  - Server pending: run `/mcp`; approve project-scoped `.mcp.json` if prompted.
88
- - Empty tools: run `npx -y okfy-ai validate ./tmp/okfy-docs`; invalid bundles should fail before serving.
99
+ - Empty tools: run `okfy validate ./tmp/okfy-docs`; invalid bundles should fail before serving.
89
100
  - Output too large: lower `--max-result-chars`, or ask the agent to search before reading concepts.
90
101
  - Wrong bundle path: use an absolute path in config if client starts from another working directory.
102
+ - Already installed globally: use `"command": "okfy"` and args `["serve", "./tmp/okfy-docs", "--mcp"]`.
91
103
 
92
104
  ## Claude Desktop
93
105
 
@@ -133,6 +145,7 @@ Troubleshooting:
133
145
  - Server exits immediately: run exact command in terminal and fix bundle validation errors.
134
146
  - No okfy tools visible: restart Claude Desktop after config changes.
135
147
  - Relative path fails: use absolute bundle path.
148
+ - Already installed globally: use `"command": "okfy"` and args `["serve", "/absolute/path/to/okfy/tmp/okfy-docs", "--mcp"]`.
136
149
 
137
150
  ## Codex
138
151
 
@@ -169,6 +182,13 @@ npx -y okfy-ai serve ./tmp/okfy-docs --mcp
169
182
 
170
183
  CLI alternative:
171
184
 
185
+ ```bash
186
+ codex mcp add okfy_docs -- okfy serve ./tmp/okfy-docs --mcp
187
+ codex mcp --help
188
+ ```
189
+
190
+ No-install CLI alternative:
191
+
172
192
  ```bash
173
193
  codex mcp add okfy_docs -- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
174
194
  codex mcp --help
@@ -204,6 +224,7 @@ Troubleshooting:
204
224
  - Tool timeout: increase `tool_timeout_sec` for large bundles.
205
225
  - Relative path wrong: set `cwd` or use absolute bundle path.
206
226
  - Need current server list: run `/mcp` in TUI.
227
+ - Already installed globally: use `command = "okfy"` and `args = ["serve", "./tmp/okfy-docs", "--mcp"]`.
207
228
 
208
229
  ## Generic MCP stdio
209
230
 
@@ -255,6 +276,7 @@ Troubleshooting:
255
276
  - `tools/list` empty: confirm `okfy serve` was started with `--mcp`.
256
277
  - Search returns weak matches: run `okfy inspect` and verify titles, descriptions, and tags were generated.
257
278
  - Agent reads too much: ask it to call `search_concepts` first and `read_concept` with `max_chars`.
279
+ - Already installed globally: use `"command": "okfy"` and args `["serve", "./tmp/okfy-docs", "--mcp"]`.
258
280
 
259
281
  ## Available okfy MCP Tools
260
282
 
@@ -63,7 +63,7 @@ Purpose: deterministic offline input for `okfy import`.
63
63
  Source command:
64
64
 
65
65
  ```bash
66
- npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps
66
+ okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps
67
67
  ```
68
68
 
69
69
  Expected concept count:
@@ -81,14 +81,14 @@ valid
81
81
  Validate:
82
82
 
83
83
  ```bash
84
- npx -y okfy-ai validate ./tmp/okfy-docs
85
- npx -y okfy-ai inspect ./tmp/okfy-docs
84
+ okfy validate ./tmp/okfy-docs
85
+ okfy inspect ./tmp/okfy-docs
86
86
  ```
87
87
 
88
88
  Serve through MCP:
89
89
 
90
90
  ```bash
91
- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
91
+ okfy serve ./tmp/okfy-docs --mcp
92
92
  ```
93
93
 
94
94
  Suggested agent questions:
@@ -14,8 +14,8 @@ timestamp: "2026-06-14T00:00:00.000Z"
14
14
  Use `okfy import` when docs already live in a local project checkout, wiki export, Obsidian vault, or static-site source folder.
15
15
 
16
16
  ```bash
17
- npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force
18
- npx -y okfy-ai validate ./tmp/okfy-docs
17
+ okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
18
+ okfy validate ./tmp/okfy-docs
19
19
  ```
20
20
 
21
21
  Expected result:
@@ -15,7 +15,7 @@ timestamp: "2026-06-14T00:00:00.000Z"
15
15
  After generating an OKF bundle, serve it over stdio MCP:
16
16
 
17
17
  ```bash
18
- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
18
+ okfy serve ./tmp/okfy-docs --mcp
19
19
  ```
20
20
 
21
21
  Agents should not read the whole bundle first. The efficient flow is:
@@ -3,8 +3,8 @@
3
3
  Use `okfy import` when docs already live in a local project checkout, wiki export, Obsidian vault, or static-site source folder.
4
4
 
5
5
  ```bash
6
- npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force
7
- npx -y okfy-ai validate ./tmp/okfy-docs
6
+ okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force
7
+ okfy validate ./tmp/okfy-docs
8
8
  ```
9
9
 
10
10
  Expected result:
@@ -3,7 +3,7 @@
3
3
  After generating an OKF bundle, serve it over stdio MCP:
4
4
 
5
5
  ```bash
6
- npx -y okfy-ai serve ./tmp/okfy-docs --mcp
6
+ okfy serve ./tmp/okfy-docs --mcp
7
7
  ```
8
8
 
9
9
  Agents should not read the whole bundle first. The efficient flow is:
@@ -1,5 +1,5 @@
1
1
  {
2
- "sourceCommand": "npx -y okfy-ai import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps",
2
+ "sourceCommand": "okfy import ./examples/local-markdown --out ./tmp/okfy-docs --force --stable-timestamps",
3
3
  "expectedConceptCount": 9,
4
4
  "expectedValidationStatus": "valid",
5
5
  "suggestedAgentQuestions": [
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "okfy-ai",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Convert docs into Open Knowledge Format bundles and serve them to MCP agents.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "okfy": "dist/cli.js"
7
+ "okfy": "dist/cli.js",
8
+ "okfy-ai": "dist/cli.js"
8
9
  },
9
10
  "files": [
10
11
  "dist",
11
12
  "README.md",
12
13
  "LICENSE",
13
- "assets/logo.svg",
14
+ "assets/logo-dark.png",
15
+ "assets/logo-light.png",
14
16
  "assets/demo.gif",
15
17
  "docs/mcp-clients.md",
16
18
  "examples"
package/assets/logo.svg DELETED
@@ -1,14 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="720" height="220" viewBox="0 0 720 220" role="img" aria-labelledby="title desc">
2
- <title id="title">okfy</title>
3
- <desc id="desc">okfy wordmark with linked knowledge nodes</desc>
4
- <rect width="720" height="220" rx="28" fill="#f7f5ef"/>
5
- <g transform="translate(54 44)">
6
- <path d="M34 28h74c22 0 40 18 40 40v20c0 22-18 40-40 40H34C12 128-6 110-6 88V68c0-22 18-40 40-40Z" fill="#20342d"/>
7
- <circle cx="36" cy="78" r="18" fill="#d6f36a"/>
8
- <circle cx="106" cy="58" r="12" fill="#f7f5ef"/>
9
- <circle cx="108" cy="100" r="12" fill="#f7f5ef"/>
10
- <path d="M52 73 94 61M53 84l43 13" stroke="#d6f36a" stroke-width="9" stroke-linecap="round"/>
11
- <path d="M190 124V31h32v36h10l26-36h37l-35 46 38 47h-39l-28-36h-9v36h-32Zm123 0V31h77v27h-45v15h39v26h-39v25h-32Zm95 0 34-52-32-41h38l15 21 15-21h36l-33 43 38 50h-39l-19-26-18 26h-35Zm127 0V92l-36-61h37l16 32 17-32h35l-37 61v32h-32Z" fill="#20342d"/>
12
- </g>
13
- <text x="360" y="184" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="20" letter-spacing="1.5" fill="#5f5a4f">OPEN KNOWLEDGE FORMAT FOR AGENTS</text>
14
- </svg>