@lon-ask/dockit 0.1.0 → 0.1.2

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 (137) hide show
  1. package/README.md +482 -337
  2. package/SKILL.md +94 -103
  3. package/apps/client/index.html +12 -0
  4. package/apps/client/package.json +26 -0
  5. package/apps/client/src/App.tsx +18 -0
  6. package/apps/client/src/api/client.ts +54 -0
  7. package/apps/client/src/components/BuildPanel.tsx +77 -0
  8. package/apps/client/src/components/DocViewer.tsx +76 -0
  9. package/apps/client/src/components/EntryDetail.tsx +322 -0
  10. package/apps/client/src/components/EntryForm.tsx +117 -0
  11. package/apps/client/src/components/EntryList.tsx +165 -0
  12. package/apps/client/src/components/GlobalSearchBar.tsx +166 -0
  13. package/apps/client/src/components/Layout.tsx +57 -0
  14. package/apps/client/src/components/SearchBar.tsx +103 -0
  15. package/apps/client/src/components/SourceForm.tsx +497 -0
  16. package/apps/client/src/hooks/useTheme.ts +19 -0
  17. package/apps/client/src/index.css +77 -0
  18. package/apps/client/src/main.tsx +13 -0
  19. package/apps/client/src/types.ts +105 -0
  20. package/apps/client/vite.config.ts +13 -0
  21. package/apps/server/dist/core/domain/entry.js +20 -0
  22. package/apps/server/dist/core/domain/entry.js.map +1 -0
  23. package/apps/server/dist/core/domain/errors.js +33 -0
  24. package/apps/server/dist/core/domain/errors.js.map +1 -0
  25. package/apps/server/dist/core/domain/knowledge-graph.js +2 -0
  26. package/apps/server/dist/core/domain/knowledge-graph.js.map +1 -0
  27. package/apps/server/dist/core/domain/types.js +2 -0
  28. package/apps/server/dist/core/domain/types.js.map +1 -0
  29. package/apps/server/dist/core/ports/IBuildRepository.js +2 -0
  30. package/apps/server/dist/core/ports/IBuildRepository.js.map +1 -0
  31. package/apps/server/dist/core/ports/IDocumentNormalizer.js +2 -0
  32. package/apps/server/dist/core/ports/IDocumentNormalizer.js.map +1 -0
  33. package/apps/server/dist/core/ports/IDocumentStore.js +2 -0
  34. package/apps/server/dist/core/ports/IDocumentStore.js.map +1 -0
  35. package/apps/server/dist/core/ports/IEntryReadModel.js +2 -0
  36. package/apps/server/dist/core/ports/IEntryReadModel.js.map +1 -0
  37. package/apps/server/dist/core/ports/IEntryRepository.js +2 -0
  38. package/apps/server/dist/core/ports/IEntryRepository.js.map +1 -0
  39. package/apps/server/dist/core/ports/IKnowledgeGraph.js +2 -0
  40. package/apps/server/dist/core/ports/IKnowledgeGraph.js.map +1 -0
  41. package/apps/server/dist/core/ports/IPathResolver.js +2 -0
  42. package/apps/server/dist/core/ports/IPathResolver.js.map +1 -0
  43. package/apps/server/dist/core/ports/ISearchEngine.js +2 -0
  44. package/apps/server/dist/core/ports/ISearchEngine.js.map +1 -0
  45. package/apps/server/dist/core/ports/ISourceProcessor.js +2 -0
  46. package/apps/server/dist/core/ports/ISourceProcessor.js.map +1 -0
  47. package/apps/server/dist/core/ports/ISourceRepository.js +2 -0
  48. package/apps/server/dist/core/ports/ISourceRepository.js.map +1 -0
  49. package/apps/server/dist/core/usecases/BuildUseCase.js +76 -0
  50. package/apps/server/dist/core/usecases/BuildUseCase.js.map +1 -0
  51. package/apps/server/dist/core/usecases/ConfigUseCase.js +62 -0
  52. package/apps/server/dist/core/usecases/ConfigUseCase.js.map +1 -0
  53. package/apps/server/dist/core/usecases/SearchUseCase.js +17 -0
  54. package/apps/server/dist/core/usecases/SearchUseCase.js.map +1 -0
  55. package/apps/server/dist/index.js +86 -0
  56. package/apps/server/dist/index.js.map +1 -0
  57. package/apps/server/dist/infrastructure/filesystem/FileSystemDocumentStore.js +25 -0
  58. package/apps/server/dist/infrastructure/filesystem/FileSystemDocumentStore.js.map +1 -0
  59. package/apps/server/dist/infrastructure/graph/GraphSearchDecorator.js +42 -0
  60. package/apps/server/dist/infrastructure/graph/GraphSearchDecorator.js.map +1 -0
  61. package/apps/server/dist/infrastructure/graph/GraphifyKnowledgeGraph.js +145 -0
  62. package/apps/server/dist/infrastructure/graph/GraphifyKnowledgeGraph.js.map +1 -0
  63. package/apps/server/dist/infrastructure/graph/index.js +3 -0
  64. package/apps/server/dist/infrastructure/graph/index.js.map +1 -0
  65. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteBuildRepository.js +21 -0
  66. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteBuildRepository.js.map +1 -0
  67. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteEntryReadModel.js +11 -0
  68. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteEntryReadModel.js.map +1 -0
  69. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteEntryRepository.js +59 -0
  70. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteEntryRepository.js.map +1 -0
  71. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteSourceRepository.js +47 -0
  72. package/apps/server/dist/infrastructure/persistence/sqlite/SqliteSourceRepository.js.map +1 -0
  73. package/apps/server/dist/infrastructure/persistence/sqlite/connection.js +50 -0
  74. package/apps/server/dist/infrastructure/persistence/sqlite/connection.js.map +1 -0
  75. package/apps/server/dist/infrastructure/search/SearchEngineFactory.js +32 -0
  76. package/apps/server/dist/infrastructure/search/SearchEngineFactory.js.map +1 -0
  77. package/apps/server/dist/infrastructure/search/json/JsonSearchEngine.js +147 -0
  78. package/apps/server/dist/infrastructure/search/json/JsonSearchEngine.js.map +1 -0
  79. package/apps/server/dist/infrastructure/search/vector/EmbeddingService.js +23 -0
  80. package/apps/server/dist/infrastructure/search/vector/EmbeddingService.js.map +1 -0
  81. package/apps/server/dist/infrastructure/search/vector/VectorSearchEngine.js +378 -0
  82. package/apps/server/dist/infrastructure/search/vector/VectorSearchEngine.js.map +1 -0
  83. package/apps/server/dist/infrastructure/source-processors/AntoraSourceProcessor.js +11 -0
  84. package/apps/server/dist/infrastructure/source-processors/AntoraSourceProcessor.js.map +1 -0
  85. package/apps/server/dist/infrastructure/source-processors/AsciidocSourceProcessor.js +9 -0
  86. package/apps/server/dist/infrastructure/source-processors/AsciidocSourceProcessor.js.map +1 -0
  87. package/apps/server/dist/infrastructure/source-processors/DocumentNormalizer.js +11 -0
  88. package/apps/server/dist/infrastructure/source-processors/DocumentNormalizer.js.map +1 -0
  89. package/apps/server/dist/infrastructure/source-processors/GithubMarkdownSourceProcessor.js +9 -0
  90. package/apps/server/dist/infrastructure/source-processors/GithubMarkdownSourceProcessor.js.map +1 -0
  91. package/apps/server/dist/infrastructure/source-processors/MavenSourceProcessor.js +9 -0
  92. package/apps/server/dist/infrastructure/source-processors/MavenSourceProcessor.js.map +1 -0
  93. package/apps/server/dist/infrastructure/source-processors/PathResolver.js +5 -0
  94. package/apps/server/dist/infrastructure/source-processors/PathResolver.js.map +1 -0
  95. package/apps/server/dist/infrastructure/source-processors/SourceCodeSourceProcessor.js +261 -0
  96. package/apps/server/dist/infrastructure/source-processors/SourceCodeSourceProcessor.js.map +1 -0
  97. package/apps/server/dist/infrastructure/source-processors/ZipSourceProcessor.js +9 -0
  98. package/apps/server/dist/infrastructure/source-processors/ZipSourceProcessor.js.map +1 -0
  99. package/apps/server/dist/mcp-http.js +93 -0
  100. package/apps/server/dist/mcp-http.js.map +1 -0
  101. package/apps/server/dist/mcp.js +339 -0
  102. package/apps/server/dist/mcp.js.map +1 -0
  103. package/apps/server/dist/routes/build.js +89 -0
  104. package/apps/server/dist/routes/build.js.map +1 -0
  105. package/apps/server/dist/routes/entries.js +52 -0
  106. package/apps/server/dist/routes/entries.js.map +1 -0
  107. package/apps/server/dist/routes/graph.js +58 -0
  108. package/apps/server/dist/routes/graph.js.map +1 -0
  109. package/apps/server/dist/routes/search.js +24 -0
  110. package/apps/server/dist/routes/search.js.map +1 -0
  111. package/apps/server/dist/routes/sources.js +100 -0
  112. package/apps/server/dist/routes/sources.js.map +1 -0
  113. package/apps/server/dist/routes/viewer.js +22 -0
  114. package/apps/server/dist/routes/viewer.js.map +1 -0
  115. package/apps/server/dist/services/antora.js +222 -0
  116. package/apps/server/dist/services/antora.js.map +1 -0
  117. package/apps/server/dist/services/asciidoc.js +206 -0
  118. package/apps/server/dist/services/asciidoc.js.map +1 -0
  119. package/apps/server/dist/services/configLoader.js +150 -0
  120. package/apps/server/dist/services/configLoader.js.map +1 -0
  121. package/apps/server/dist/services/githubMarkdown.js +221 -0
  122. package/apps/server/dist/services/githubMarkdown.js.map +1 -0
  123. package/apps/server/dist/services/maven.js +148 -0
  124. package/apps/server/dist/services/maven.js.map +1 -0
  125. package/apps/server/dist/services/normalizer.js +42 -0
  126. package/apps/server/dist/services/normalizer.js.map +1 -0
  127. package/apps/server/dist/services/paths.js +5 -0
  128. package/apps/server/dist/services/paths.js.map +1 -0
  129. package/apps/server/dist/services/textExtractor.js +46 -0
  130. package/apps/server/dist/services/textExtractor.js.map +1 -0
  131. package/apps/server/dist/services/zip.js +63 -0
  132. package/apps/server/dist/services/zip.js.map +1 -0
  133. package/apps/server/package.json +38 -0
  134. package/apps/server/src/infrastructure/search/vector/EmbeddingService.ts +1 -1
  135. package/bin/commands/dev.ts +2 -2
  136. package/bin/commands/serve.ts +2 -2
  137. package/package.json +18 -3
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dockit
3
- description: Documentation index and search tool that provides on-demand access to up-to-date framework and library documentation for LLM context
3
+ description: Documentation index and search tool providing on-demand framework/library docs and source code knowledge graphs for LLM context
4
4
  license: MIT
5
5
  compatibility: opencode
6
6
  metadata:
@@ -8,147 +8,138 @@ metadata:
8
8
  workflow: documentation
9
9
  ---
10
10
 
11
- ## What I do
12
- - Search and retrieve documentation for frameworks and libraries (e.g., Quarkus, Spring Boot, React)
13
- - Provide API documentation, class references, and configuration guides
14
- - Fetch full document content for LLM context via CLI or MCP tools
11
+ ## What dockit does
15
12
 
16
- ## When to use me
17
- Use this skill when the user asks about:
18
- - How to use a specific framework or library
19
- - API documentation, class references, or configuration reference
20
- - Any technology listed in available dockit entries
13
+ Dockit is a local documentation hub. It indexes documentation from multiple sources (GitHub Markdown, AsciiDoc, Antora, Maven Javadoc, ZIP archives) and builds source code knowledge graphs (Tree-sitter AST via Graphify). It provides hybrid TF-IDF + vector semantic search across all indexed content.
21
14
 
22
- ## Primary Method: CLI Commands
15
+ Run it from the terminal — no server process required. No internet needed after build.
23
16
 
24
- The `dockit` CLI is the recommended way to search and retrieve documentation.
25
-
26
- ### `dockit list`
27
- Lists all configured documentation entries. Run this first to discover what's available.
28
-
29
- ### `dockit search [<entry>] <query>`
30
- Searches documentation. Always provide the entry name as the first argument when you know which framework the question is about.
17
+ ## Installation
31
18
 
32
19
  ```bash
33
- # Scoped to a specific entry (recommended)
34
- dockit search react "how to create a hook"
35
- dockit search quarkus "configure cache"
36
-
37
- # Global search — top result per entry (when unsure which entry)
38
- dockit search "cache"
20
+ npm install -g @lon-ask/dockit
39
21
  ```
40
22
 
41
- ### `dockit search [<entry>] <query> --get-top [N]`
42
- Searches and fetches full document content for the top N results (default 3). This is the **primary command for LLMs** — it combines search + content retrieval in one step.
23
+ Or use `npx` without installing:
43
24
 
44
25
  ```bash
45
- # Get full content for top 3 results
46
- dockit search react "useState" --get-top
47
-
48
- # Get full content for top 5 results, as JSON
49
- dockit search react "hooks" --get-top 5 --json
26
+ npx @lon-ask/dockit <command>
50
27
  ```
51
28
 
52
- ### `dockit get <entry> <path>`
53
- Fetches full content of a specific document by path (from search results).
29
+ All data is stored in `~/.dockit/` by default. Override with `DOCKIT_DATA_DIR`.
30
+
31
+ ## When to use dockit
32
+
33
+ Use when you need:
34
+ - Up-to-date framework/library documentation instead of stale training data
35
+ - API reference, class docs, or configuration guides
36
+ - Source code structure analysis (imports, calls, inheritance graphs)
37
+ - To find which files/modules a function touches in a codebase
54
38
 
55
- ### `dockit build <entry>` / `dockit status <entry>`
56
- Builds documentation or checks build status.
39
+ ## Core Workflow
57
40
 
58
- ## Recommended Workflow
41
+ ### Step 1: Discover available documentation
59
42
 
60
- ### Step 1: Identify the entry
61
- Determine which documentation entry is relevant:
62
- - "How do I use useState in React?" → entry: `react`
63
- - "How to configure cache in Quarkus?" → entry: `quarkus`
43
+ ```bash
44
+ dockit list
45
+ # or
46
+ npx @lon-ask/dockit list
47
+ ```
64
48
 
65
- If unsure, run `dockit list` to see available entries.
49
+ ### Step 2: Global search (find the right entry)
66
50
 
67
- ### Step 2: Search pattern
68
- **Global search** (no entry) — returns top result per entry:
69
51
  ```bash
70
52
  dockit search "cache"
53
+ # Returns top result per built entry
71
54
  ```
72
55
 
73
- **Scoped search** (with entry) — dive deeper:
56
+ ### Step 3: Scoped search with full content
57
+
58
+ ```bash
59
+ dockit search quarkus "configure cache" --get-top 3
60
+ ```
61
+
62
+ The `--get-top` flag fetches full document text for the top N results. This is the primary command for LLMs — it combines search + retrieval in one invocation.
63
+
64
+ ### Step 4: Knowledge graph queries (source-code entries)
65
+
66
+ For entries with `source-code` sources:
67
+
74
68
  ```bash
75
- dockit search quarkus "cache" --get-top 3
69
+ dockit graph query my-project "database" --limit 10 # find nodes by name/file/type
70
+ dockit graph gods my-project # most-connected nodes
71
+ dockit graph path my-project "app.ts" "database.ts" # dependency path
72
+ dockit graph explain my-project "createApp" # node details + connections
76
73
  ```
77
74
 
78
- Always scope to the entry once you know which framework the user is asking about.
75
+ ## CLI Reference
76
+
77
+ | Command | Purpose |
78
+ |---------|---------|
79
+ | `dockit list` | List all configured entries |
80
+ | `dockit search [<entry>] <query>` | Search docs (scoped or global) |
81
+ | `dockit search [<entry>] <query> --get-top [N]` | Search + full content for top N |
82
+ | `dockit get <entry> <path>` | Fetch specific document by path |
83
+ | `dockit build <entry>` | Build/rebuild documentation |
84
+ | `dockit status <entry>` | Check build status |
85
+ | `dockit init --path <dir> [--code-path <sub>]` | Index a local project |
86
+ | `dockit graph query <entry> <query>` | Search graph nodes |
87
+ | `dockit graph path <entry> <from> <to>` | Dependency path between nodes |
88
+ | `dockit graph gods <entry>` | Highest-degree (most connected) nodes |
89
+ | `dockit graph explain <entry> <node>` | Node details with edges |
90
+
91
+ ## Query Refinement
79
92
 
80
- ### Step 3: Refine the query
81
- Strip conversational filler. Keep only technical terms:
93
+ Strip conversational filler. Keep only technical keywords:
82
94
 
83
- | User Question | Good Query |
95
+ | User question | Good query |
84
96
  |---------------|------------|
85
97
  | "How do I create a custom hook in React?" | `"create custom hook"` |
86
- | "What is the latest Quarkus feature for caching?" | `"cache latest feature"` |
98
+ | "What's the Quarkus caching configuration?" | `"caching configuration"` |
99
+ | "How does the auth middleware work?" | `"auth middleware"` |
100
+ | "Find all files that import database.ts" | `graph query my-project "database.ts"` |
101
+
102
+ ## Entry Types and Behavior
103
+
104
+ | Entry has | `dockit search` | `dockit graph` |
105
+ |-----------|----------------|-----------------|
106
+ | Docs only | Returns results | No graph available |
107
+ | Source code only | Returns empty | Use graph tools |
108
+ | Docs + code | Returns results (graph-boosted) | Graph tools work |
109
+
110
+ ## Build Status
111
+
112
+ Entries start as `pending`. Build them before searching:
87
113
 
88
- ### Step 4: Handle missing builds
89
- If an entry shows status `pending` or `error`, build it first:
90
114
  ```bash
91
- dockit build react
92
- dockit status react
115
+ dockit build quarkus
116
+ dockit status quarkus # wait for "ready"
93
117
  ```
94
118
 
95
- ## Alternative: MCP Tools
119
+ If an entry shows `error`, check the build log via `dockit status <entry> --json`.
120
+
121
+ ## MCP Tools
96
122
 
97
- If Dockit is configured as an MCP server, use `dockit_*` tools instead of CLI commands:
123
+ If configured as an MCP server, use these tools instead of CLI:
98
124
 
99
125
  | MCP Tool | CLI Equivalent |
100
126
  |----------|----------------|
101
127
  | `dockit_list_entries` | `dockit list` |
128
+ | `dockit_find_entry` | — |
102
129
  | `dockit_global_search` | `dockit search "query"` |
103
130
  | `dockit_search` | `dockit search <entry> "query"` |
104
131
  | `dockit_get_doc` | `dockit get <entry> <path>` |
105
- | `dockit_build` / `dockit_build_status` | `dockit build` / `dockit status` |
106
- | `dockit_graph_query` | (MCP only) |
107
- | `dockit_graph_path` | (MCP only) |
108
- | `dockit_graph_explain` | (MCP only) |
109
- | `dockit_graph_gods` | (MCP only) |
110
-
111
- ## Source Code Entries (Knowledge Graph)
112
-
113
- For entries with `source-code` sources, the primary query mechanism is the **knowledge graph** instead of text search. Graphify's Tree-sitter AST pass parses 15+ languages and produces structural edges (*calls*, *imports*, *inherits*).
114
-
115
- ### Graph MCP Tools
116
-
117
- | Tool | Description |
118
- |------|-------------|
119
- | `dockit_graph_query <entry> <query>` | Search graph nodes by name, file, or type |
120
- | `dockit_graph_path <entry> <from> <to>` | Find shortest dependency path between two nodes |
121
- | `dockit_graph_explain <entry> <node>` | Get node details with edges and connections |
122
- | `dockit_graph_gods <entry>` | List most connected (highest-degree) nodes |
123
-
124
- ### Behavior by Entry Type
132
+ | `dockit_build` | `dockit build <entry>` |
133
+ | `dockit_build_status` | `dockit status <entry>` |
134
+ | `dockit_graph_query` | `dockit graph query` |
135
+ | `dockit_graph_path` | `dockit graph path` |
136
+ | `dockit_graph_explain` | `dockit graph explain` |
137
+ | `dockit_graph_gods` | `dockit graph gods` |
125
138
 
126
- | Entry Type | Search | Graph Query |
127
- |------------|--------|-------------|
128
- | Source-code only | `dockit search` returns empty | Use `dockit_graph_*` tools |
129
- | Mixed (docs + code) | `dockit search` works + results graph-boosted | `dockit_graph_*` tools work |
130
- | Docs only | `dockit search` works | No graph tools |
139
+ ## Key Constraints
131
140
 
132
- ## Notes
133
141
  - Documentation is plain text extracted from HTML
134
- - Content is truncated at 50KB per document
135
- - Entries start as `pending` and must be built before searchable
136
-
137
- ## Always Show Source
138
-
139
- After answering with documentation content, always display the source in a table at the end:
140
-
141
- | Field | Value |
142
- |-------|-------|
143
- | **Type** | `<source type>` |
144
- | **Label** | `<source label>` |
145
- | **Repo** | `<repoUrl>` |
146
- | **Source Path** | `<sourcePath>` |
147
- | **Version** | `<entry version>` |
148
-
149
- To get source details, use `--json` flag with search or check `dockit list --json`. Source fields come from the entry's `sources` array in `dockit.yaml`:
150
- - `type` — source type (e.g., `github-markdown`, `asciidoc`, `maven`, `source-code`)
151
- - `label` — human-readable label
152
- - `repoUrl` or `localPath` — repository URL or local path
153
- - `sourcePath` — path within the repo
154
- - Entry `version` — the version of the documentation entry
142
+ - Content is truncated at 50 KB per document
143
+ - Documents must be built before searchable (status = `ready`)
144
+ - Knowledge graph requires `graphify` Python package and source-code source type
145
+ - All data is local. No cloud, no API keys required
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Dockit — Documentation Hub</title>
7
+ </head>
8
+ <body class="antialiased">
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@dockit/client",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "lucide-react": "^0.469.0",
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0",
15
+ "react-router-dom": "^7.1.1"
16
+ },
17
+ "devDependencies": {
18
+ "@tailwindcss/vite": "^4.0.0",
19
+ "@types/react": "^19.0.3",
20
+ "@types/react-dom": "^19.0.2",
21
+ "@vitejs/plugin-react": "^4.3.4",
22
+ "tailwindcss": "^4.0.0",
23
+ "typescript": "^5.7.3",
24
+ "vite": "^6.0.7"
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ import { Routes, Route } from 'react-router-dom';
2
+ import Layout from './components/Layout';
3
+ import EntryList from './components/EntryList';
4
+ import EntryForm from './components/EntryForm';
5
+ import EntryDetail from './components/EntryDetail';
6
+
7
+ export default function App() {
8
+ return (
9
+ <Layout>
10
+ <Routes>
11
+ <Route path="/" element={<EntryList />} />
12
+ <Route path="/entries/new" element={<EntryForm />} />
13
+ <Route path="/entries/:id/edit" element={<EntryForm />} />
14
+ <Route path="/entries/:id" element={<EntryDetail />} />
15
+ </Routes>
16
+ </Layout>
17
+ );
18
+ }
@@ -0,0 +1,54 @@
1
+ import type { Entry, EntryDetail, Source, SourceConfig, BuildStatusResponse, SearchResult } from '../types';
2
+
3
+ const BASE = '/api';
4
+
5
+ async function request<T>(url: string, options?: RequestInit): Promise<T> {
6
+ const res = await fetch(`${BASE}${url}`, {
7
+ headers: { 'Content-Type': 'application/json' },
8
+ ...options,
9
+ });
10
+ if (!res.ok) {
11
+ const body = await res.json().catch(() => ({ error: res.statusText }));
12
+ throw new Error(body.error || `Request failed: ${res.status}`);
13
+ }
14
+ return res.json();
15
+ }
16
+
17
+ export const api = {
18
+ entries: {
19
+ list: () => request<Entry[]>('/entries'),
20
+ get: (id: string) => request<EntryDetail>(`/entries/${id}`),
21
+ create: (data: { name: string; version: string; description?: string }) =>
22
+ request<Entry>('/entries', { method: 'POST', body: JSON.stringify(data) }),
23
+ update: (id: string, data: { name?: string; version?: string; description?: string }) =>
24
+ request<Entry>(`/entries/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
25
+ delete: (id: string) =>
26
+ request<{ success: boolean }>(`/entries/${id}`, { method: 'DELETE' }),
27
+ },
28
+
29
+ sources: {
30
+ create: (entryId: string, data: { type: string; label: string; config: SourceConfig }) =>
31
+ request<Source>(`/entries/${entryId}/sources`, { method: 'POST', body: JSON.stringify(data) }),
32
+ update: (id: string, data: { label?: string; config?: SourceConfig }) =>
33
+ request<Source>(`/sources/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
34
+ delete: (id: string) =>
35
+ request<{ success: boolean }>(`/sources/${id}`, { method: 'DELETE' }),
36
+ },
37
+
38
+ build: {
39
+ trigger: (entryId: string) =>
40
+ request<{ buildId: string; status: string }>(`/entries/${entryId}/build`, { method: 'POST' }),
41
+ status: (entryId: string) =>
42
+ request<BuildStatusResponse>(`/entries/${entryId}/build-status`),
43
+ cliScript: (entryId: string) =>
44
+ fetch(`${BASE}/entries/${entryId}/cli-script`).then((r) => r.text()),
45
+ },
46
+
47
+ search: (entryId: string, q: string) =>
48
+ request<SearchResult[]>(`/entries/${entryId}/search?q=${encodeURIComponent(q)}`),
49
+
50
+ searchGlobal: (q: string) =>
51
+ request<Array<SearchResult & { entryId: string; entryName: string; entryVersion: string }>>(`/search?q=${encodeURIComponent(q)}`),
52
+
53
+ bundleUrl: (entryId: string) => `${BASE}/bundle/${entryId}/`,
54
+ };
@@ -0,0 +1,77 @@
1
+ import { useEffect, useState, useRef } from 'react';
2
+ import { Terminal, ChevronDown, ChevronRight, Loader2, CheckCircle, AlertCircle } from 'lucide-react';
3
+ import type { BuildStatusResponse } from '../types';
4
+ import { api } from '../api/client';
5
+
6
+ interface Props {
7
+ entryId: string;
8
+ refreshKey: number;
9
+ }
10
+
11
+ export default function BuildPanel({ entryId, refreshKey }: Props) {
12
+ const [status, setStatus] = useState<BuildStatusResponse | null>(null);
13
+ const [open, setOpen] = useState(false);
14
+ const logEndRef = useRef<HTMLDivElement>(null);
15
+ const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
16
+
17
+ useEffect(() => {
18
+ const fetch = async () => {
19
+ try {
20
+ const data = await api.build.status(entryId);
21
+ setStatus(data);
22
+ if (data.status === 'building') {
23
+ setOpen(true);
24
+ }
25
+ if (data.status === 'ready' || data.status === 'error') {
26
+ if (intervalRef.current) {
27
+ clearInterval(intervalRef.current);
28
+ }
29
+ }
30
+ } catch {
31
+ // ignored
32
+ }
33
+ };
34
+ fetch();
35
+ intervalRef.current = setInterval(fetch, 1500);
36
+ return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
37
+ }, [entryId, refreshKey]);
38
+
39
+ if (!status || status.status === 'none') return null;
40
+
41
+ const accentColor = status.status === 'ready' ? 'bg-success' :
42
+ status.status === 'error' ? 'bg-danger' : 'bg-warning';
43
+
44
+ return (
45
+ <div className="bg-surface ring-1 ring-border rounded-lg overflow-hidden">
46
+ <button
47
+ onClick={() => setOpen(!open)}
48
+ className="w-full flex items-center gap-2 px-3 py-2 hover:bg-bg-alt transition-colors text-left"
49
+ >
50
+ {open ? <ChevronDown size={14} className="text-text-muted" /> : <ChevronRight size={14} className="text-text-muted" />}
51
+ <Terminal size={14} className="text-text-dim" />
52
+ <span className="text-xs font-medium text-text">Build Log</span>
53
+ <span className="flex items-center gap-1 ml-auto">
54
+ {status.status === 'building' && <Loader2 size={12} className="animate-spin text-warning" />}
55
+ {status.status === 'ready' && <CheckCircle size={12} className="text-success" />}
56
+ {status.status === 'error' && <AlertCircle size={12} className="text-danger" />}
57
+ <span className={`text-[11px] font-medium ${
58
+ status.status === 'ready' ? 'text-success' :
59
+ status.status === 'error' ? 'text-danger' :
60
+ 'text-warning'
61
+ }`}>
62
+ {status.status.charAt(0).toUpperCase() + status.status.slice(1)}
63
+ </span>
64
+ </span>
65
+ </button>
66
+ {open && (
67
+ <div className="relative">
68
+ <div className={`absolute left-0 top-0 bottom-0 w-0.5 ${accentColor}`} />
69
+ <div className="bg-terminal-bg text-terminal-fg p-3 pl-4 max-h-48 overflow-auto font-mono text-[11px] leading-relaxed">
70
+ <pre className="whitespace-pre-wrap">{status.log || 'Waiting for build to start...'}</pre>
71
+ <div ref={logEndRef} />
72
+ </div>
73
+ </div>
74
+ )}
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,76 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { ExternalLink, Maximize2, Minimize2, FileWarning, FileText } from 'lucide-react';
3
+ import { api } from '../api/client';
4
+
5
+ interface Props {
6
+ entryId: string;
7
+ selectedFile?: string;
8
+ }
9
+
10
+ export default function DocViewer({ entryId, selectedFile }: Props) {
11
+ const [expanded, setExpanded] = useState(false);
12
+ const [error, setError] = useState(false);
13
+ const url = selectedFile
14
+ ? `${api.bundleUrl(entryId)}${selectedFile}`
15
+ : api.bundleUrl(entryId);
16
+
17
+ useEffect(() => { setError(false); }, [selectedFile]);
18
+
19
+ if (!selectedFile) {
20
+ return (
21
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-center">
22
+ <div className="w-14 h-14 rounded-2xl bg-bg-alt flex items-center justify-center">
23
+ <FileText size={26} className="text-text-muted" />
24
+ </div>
25
+ <div>
26
+ <p className="text-sm text-text-dim font-medium">No document selected</p>
27
+ <p className="text-xs text-text-muted mt-1">Search for a document and click a result to view it here.</p>
28
+ </div>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ if (error) {
34
+ return (
35
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-center">
36
+ <div className="w-14 h-14 rounded-2xl bg-bg-alt flex items-center justify-center">
37
+ <FileWarning size={26} className="text-text-muted" />
38
+ </div>
39
+ <div>
40
+ <p className="text-sm text-text-dim font-medium">Could not load document</p>
41
+ <p className="text-xs text-text-muted mt-1">The documentation may not be built yet. Click &ldquo;Build&rdquo; to generate it.</p>
42
+ </div>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <div className={`flex flex-col h-full ${expanded ? 'fixed inset-0 z-40 bg-bg p-4' : ''}`}>
49
+ <div className="flex items-center gap-2 mb-2 shrink-0">
50
+ <span className="text-xs text-text-muted font-mono truncate flex-1">{selectedFile}</span>
51
+ <button
52
+ onClick={() => setExpanded(!expanded)}
53
+ className="p-1 rounded text-text-muted hover:text-text hover:bg-bg-alt transition-colors"
54
+ title={expanded ? 'Exit fullscreen' : 'Fullscreen'}
55
+ >
56
+ {expanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
57
+ </button>
58
+ <a
59
+ href={url}
60
+ target="_blank"
61
+ rel="noopener noreferrer"
62
+ className="p-1 rounded text-text-muted hover:text-text hover:bg-bg-alt transition-colors"
63
+ title="Open in new tab"
64
+ >
65
+ <ExternalLink size={14} />
66
+ </a>
67
+ </div>
68
+ <iframe
69
+ src={url}
70
+ onError={() => setError(true)}
71
+ className="flex-1 w-full ring-1 ring-border rounded-lg bg-surface min-h-0"
72
+ title="Documentation Viewer"
73
+ />
74
+ </div>
75
+ );
76
+ }