@ory/lumen-opencode 0.0.26
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/.opencode/plugins/lumen.js +30 -0
- package/.release-please-manifest.json +3 -0
- package/LICENSE +179 -0
- package/README.md +422 -0
- package/package.json +28 -0
- package/scripts/run.bat +60 -0
- package/scripts/run.cmd +11 -0
- package/scripts/run.sh +60 -0
- package/scripts/test_run.sh +163 -0
- package/scripts/test_run_cmd.sh +28 -0
- package/skills/doctor/SKILL.md +24 -0
- package/skills/reindex/SKILL.md +21 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const pluginRoot = path.resolve(__dirname, "../..");
|
|
6
|
+
const runCommand = path.join(
|
|
7
|
+
pluginRoot,
|
|
8
|
+
"scripts",
|
|
9
|
+
process.platform === "win32" ? "run.cmd" : "run.sh",
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const LumenPlugin = async () => {
|
|
13
|
+
return {
|
|
14
|
+
config: async (config) => {
|
|
15
|
+
config.mcp = config.mcp || {};
|
|
16
|
+
if (!config.mcp.lumen) {
|
|
17
|
+
config.mcp.lumen = {
|
|
18
|
+
type: "local",
|
|
19
|
+
command:
|
|
20
|
+
process.platform === "win32"
|
|
21
|
+
? ["cmd", "/c", runCommand, "stdio"]
|
|
22
|
+
: ["sh", runCommand, "stdio"],
|
|
23
|
+
enabled: true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default LumenPlugin;
|
package/LICENSE
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship made available under
|
|
36
|
+
the License, as indicated by a copyright notice that is included in
|
|
37
|
+
or attached to the work (an example is provided in the Appendix below).
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
43
|
+
of this License, Derivative Works shall not include works that remain
|
|
44
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
45
|
+
the Work and Derivative Works thereof.
|
|
46
|
+
|
|
47
|
+
"Contribution" shall mean, as submitted to the Licensor for inclusion
|
|
48
|
+
in the Work by the copyright owner or by an individual or Legal Entity
|
|
49
|
+
authorized to submit on behalf of the copyright owner. For the purposes
|
|
50
|
+
of this definition, "submit" means any form of electronic, verbal, or
|
|
51
|
+
written communication sent to the Licensor or its representatives,
|
|
52
|
+
including but not limited to communication on electronic mailing lists,
|
|
53
|
+
source code control systems, and issue tracking systems that are managed
|
|
54
|
+
by, or on behalf of, the Licensor for the purpose of submitting and
|
|
55
|
+
discussing information to improve the Work, but excluding communication
|
|
56
|
+
that is conspicuously marked or designated in writing by the copyright
|
|
57
|
+
owner as "Not a Contribution."
|
|
58
|
+
|
|
59
|
+
"Contributor" shall mean Licensor and any Legal Entity on behalf of
|
|
60
|
+
whom a Contribution has been received by the Licensor and included
|
|
61
|
+
within the Work.
|
|
62
|
+
|
|
63
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
64
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
65
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
66
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
67
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
68
|
+
Work and such Derivative Works in Source or Object form.
|
|
69
|
+
|
|
70
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
71
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
72
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
73
|
+
(except as stated in this section) patent license to make, have made,
|
|
74
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
75
|
+
where such license applies only to those patent claims licensable
|
|
76
|
+
by such Contributor that are necessarily infringed by their
|
|
77
|
+
Contribution(s) alone or by the combination of their Contribution(s)
|
|
78
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
79
|
+
institute patent litigation against any entity (including a cross-claim
|
|
80
|
+
or counterclaim in a lawsuit) alleging that the Work or any patent
|
|
81
|
+
claim embodied in the Work constitutes direct or contributory patent
|
|
82
|
+
infringement, then any patent licenses granted to You under this
|
|
83
|
+
License for that Work shall terminate as of the date such litigation
|
|
84
|
+
is filed.
|
|
85
|
+
|
|
86
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
87
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
88
|
+
modifications, and in Source or Object form, provided that You
|
|
89
|
+
meet the following conditions:
|
|
90
|
+
|
|
91
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
92
|
+
Works a copy of this License; and
|
|
93
|
+
|
|
94
|
+
(b) You must cause any modified files to carry prominent notices
|
|
95
|
+
stating that You changed the files; and
|
|
96
|
+
|
|
97
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
98
|
+
that You distribute, all copyright, patent, trademark, and
|
|
99
|
+
attribution notices from the Source form of the Work,
|
|
100
|
+
excluding those notices that do not pertain to any part of
|
|
101
|
+
the Derivative Works; and
|
|
102
|
+
|
|
103
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
104
|
+
distribution, You must include a readable copy of the
|
|
105
|
+
attribution notices contained within such NOTICE file, in
|
|
106
|
+
at least one of the following places: within a NOTICE text
|
|
107
|
+
file distributed as part of the Derivative Works; within
|
|
108
|
+
the Source form or documentation, if provided along with the
|
|
109
|
+
Derivative Works; or, within a display generated by the
|
|
110
|
+
Derivative Works, if and wherever such third-party notices
|
|
111
|
+
normally appear. The contents of the NOTICE file are for
|
|
112
|
+
informational purposes only and do not modify the License.
|
|
113
|
+
You may add Your own attribution notices within Derivative
|
|
114
|
+
Works that You distribute, alongside or in addition to the
|
|
115
|
+
NOTICE text from the Work, provided that such additional
|
|
116
|
+
attribution notices cannot be construed as modifying the License.
|
|
117
|
+
|
|
118
|
+
You may add Your own license statement for Your modifications and
|
|
119
|
+
may provide additional grant of rights to use, copy, modify, merge,
|
|
120
|
+
publish, distribute, sublicense, and/or sell copies of the
|
|
121
|
+
Contribution, either on its own or as part of the Work.
|
|
122
|
+
|
|
123
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
124
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
125
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
126
|
+
this License, without any additional terms or conditions.
|
|
127
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
128
|
+
the terms of any separate license agreement you may have executed
|
|
129
|
+
with Licensor regarding such Contributions.
|
|
130
|
+
|
|
131
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
132
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
133
|
+
except as required for reasonable and customary use in describing the
|
|
134
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
135
|
+
|
|
136
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
137
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
138
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
139
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
140
|
+
implied, including, without limitation, any warranties or conditions
|
|
141
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
142
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
143
|
+
appropriateness of using or reproducing the Work and assume any
|
|
144
|
+
risks associated with Your exercise of permissions under this License.
|
|
145
|
+
|
|
146
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
147
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
148
|
+
unless required by applicable law (such as deliberate and grossly
|
|
149
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
150
|
+
liable to You for damages, including any direct, indirect, special,
|
|
151
|
+
incidental, or exemplary damages of any character arising as a
|
|
152
|
+
result of this License or out of the use or inability to use the
|
|
153
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
154
|
+
work stoppage, computer failure or malfunction, or all other
|
|
155
|
+
commercial damages or losses), even if such Contributor has been
|
|
156
|
+
advised of the possibility of such damages.
|
|
157
|
+
|
|
158
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
159
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
160
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
161
|
+
or other liability obligations and/or rights consistent with this
|
|
162
|
+
License. However, in accepting such obligations, You may offer only
|
|
163
|
+
conditions consistent with this License.
|
|
164
|
+
|
|
165
|
+
END OF TERMS AND CONDITIONS
|
|
166
|
+
|
|
167
|
+
Copyright 2026 Aeneas Rekkas
|
|
168
|
+
|
|
169
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
170
|
+
you may not use this file except in compliance with the License.
|
|
171
|
+
You may obtain a copy of the License at
|
|
172
|
+
|
|
173
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
174
|
+
|
|
175
|
+
Unless required by applicable law or agreed to in writing, software
|
|
176
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
177
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
178
|
+
implied. See the License for the specific language governing
|
|
179
|
+
permissions and limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
[](https://github.com/ory/lumen/actions/workflows/ci.yml)
|
|
4
|
+
[](https://goreportcard.com/report/github.com/ory/lumen)
|
|
5
|
+
[](https://pkg.go.dev/github.com/ory/lumen)
|
|
6
|
+
[](https://coveralls.io/github/ory/lumen?branch=main)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Claude reads entire files to find what it needs. Lumen gives it a map.
|
|
10
|
+
|
|
11
|
+
Lumen is a 100% local semantic code search engine for AI coding agents. No API
|
|
12
|
+
keys, no cloud, no external database, just open-source embedding models
|
|
13
|
+
([Ollama](https://ollama.com/) or [LM Studio](https://lmstudio.ai/)), SQLite,
|
|
14
|
+
and your CPU. A single static binary and your own local embedding server.
|
|
15
|
+
|
|
16
|
+
The payoff is measurable and reproducible: across 8 benchmark runs on 8
|
|
17
|
+
languages and real GitHub bug-fix tasks, Lumen cuts cost in **every single
|
|
18
|
+
language** — up to 39%. Output tokens drop by up to 66%, sessions complete up to
|
|
19
|
+
53% faster, and patch quality is maintained in every task. All verified with a
|
|
20
|
+
[transparent, open-source benchmark framework](docs/BENCHMARKS.md) that you can
|
|
21
|
+
run yourself.
|
|
22
|
+
|
|
23
|
+
| | With Lumen | Baseline (no Lumen) |
|
|
24
|
+
| ---------------------- | ----------------------------- | -------------------- |
|
|
25
|
+
| Cost (avg, bug-fix) | **$0.29** (-26%) | $0.40 |
|
|
26
|
+
| Time (avg, bug-fix) | **125s** (-28%) | 174s |
|
|
27
|
+
| Output tokens (avg) | **5,247** (-37%) | 8,323 |
|
|
28
|
+
| JavaScript (marked) | **$0.32, 119s** (-33%, -53%) | $0.48, 255s |
|
|
29
|
+
| Rust (toml) | **$0.38, 204s** (-39%, -34%) | $0.61, 310s |
|
|
30
|
+
| PHP (monolog) | **$0.14, 34s** (-27%, -34%) | $0.19, 52s |
|
|
31
|
+
| TypeScript (commander) | **$0.14, 56s** (-27%, -33%) | $0.19, 84s |
|
|
32
|
+
| Patch quality | **Maintained in all 8 tasks** | — |
|
|
33
|
+
|
|
34
|
+
## Table of contents
|
|
35
|
+
|
|
36
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
|
37
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
38
|
+
|
|
39
|
+
- [Demo](#demo)
|
|
40
|
+
- [Quick start](#quick-start)
|
|
41
|
+
- [What you get](#what-you-get)
|
|
42
|
+
- [How it works](#how-it-works)
|
|
43
|
+
- [Benchmarks](#benchmarks)
|
|
44
|
+
- [Supported languages](#supported-languages)
|
|
45
|
+
- [Configuration](#configuration)
|
|
46
|
+
- [Supported embedding models](#supported-embedding-models)
|
|
47
|
+
- [Controlling what gets indexed](#controlling-what-gets-indexed)
|
|
48
|
+
- [Database location](#database-location)
|
|
49
|
+
- [CLI Reference](#cli-reference)
|
|
50
|
+
- [Troubleshooting](#troubleshooting)
|
|
51
|
+
- [Development](#development)
|
|
52
|
+
|
|
53
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
54
|
+
|
|
55
|
+
## Demo
|
|
56
|
+
|
|
57
|
+
<img src="docs/demo/demo.gif" alt="Lumen demo" width="600"/>
|
|
58
|
+
|
|
59
|
+
_Claude Code asking about the
|
|
60
|
+
[Prometheus](https://github.com/prometheus/prometheus) codebase. Lumen's
|
|
61
|
+
`semantic_search` finds the relevant code without reading entire files._
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
**Prerequisites:**
|
|
66
|
+
|
|
67
|
+
> **Platform support:** Linux, macOS, and Windows. File locking for background
|
|
68
|
+
> indexing coordination uses `flock(2)` on Unix and `LockFileEx` on Windows
|
|
69
|
+
> (via [gofrs/flock](https://github.com/gofrs/flock)).
|
|
70
|
+
|
|
71
|
+
1. [Ollama](https://ollama.com/) installed and running, then pull the default
|
|
72
|
+
embedding model:
|
|
73
|
+
```bash
|
|
74
|
+
ollama pull ordis/jina-embeddings-v2-base-code
|
|
75
|
+
```
|
|
76
|
+
2. One of:
|
|
77
|
+
[Claude Code](https://code.claude.com/docs/en/quickstart),
|
|
78
|
+
[Cursor](https://cursor.com/),
|
|
79
|
+
[Codex](https://developers.openai.com/codex/cli), or
|
|
80
|
+
[OpenCode](https://opencode.ai/)
|
|
81
|
+
|
|
82
|
+
**Note:** Installation differs by platform. Claude Code is installed from a
|
|
83
|
+
plugin marketplace. Codex uses a local MCP server plus native skill discovery.
|
|
84
|
+
OpenCode installs from npm. Cursor packaging is shipped in this repository and
|
|
85
|
+
is ready for Cursor's plugin distribution workflow.
|
|
86
|
+
|
|
87
|
+
**Install:**
|
|
88
|
+
|
|
89
|
+
**Claude Code**
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
/plugin marketplace add ory/claude-plugins
|
|
93
|
+
/plugin install lumen@ory
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Verify by starting a new Claude session and running `/lumen:doctor`.
|
|
97
|
+
|
|
98
|
+
**Cursor**
|
|
99
|
+
|
|
100
|
+
Lumen ships a native Cursor plugin bundle in this repository:
|
|
101
|
+
|
|
102
|
+
- `.cursor-plugin/plugin.json` - plugin manifest
|
|
103
|
+
- `mcp.json` - local `lumen` MCP server wiring
|
|
104
|
+
- `hooks/hooks-cursor.json` - SessionStart hook
|
|
105
|
+
- `skills/` - shared `doctor` and `reindex` skills
|
|
106
|
+
|
|
107
|
+
Use Cursor's plugin installation or distribution workflow with this bundle.
|
|
108
|
+
Detailed packaging notes: [.cursor-plugin/INSTALL.md](.cursor-plugin/INSTALL.md)
|
|
109
|
+
|
|
110
|
+
Verify by opening a new Cursor agent session and asking it to use the `doctor`
|
|
111
|
+
skill or the Lumen `semantic_search` tool.
|
|
112
|
+
|
|
113
|
+
**Codex**
|
|
114
|
+
|
|
115
|
+
Quick install:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
Fetch and follow instructions from https://raw.githubusercontent.com/ory/lumen/refs/heads/main/.codex/INSTALL.md
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Manual install:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
|
|
125
|
+
git clone https://github.com/ory/lumen.git "$CODEX_HOME/lumen"
|
|
126
|
+
mkdir -p "$HOME/.agents/skills"
|
|
127
|
+
ln -s "$CODEX_HOME/lumen/skills" "$HOME/.agents/skills/lumen"
|
|
128
|
+
codex mcp add lumen -- "$CODEX_HOME/lumen/scripts/run.sh" stdio
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Detailed docs: [.codex/INSTALL.md](.codex/INSTALL.md)
|
|
132
|
+
|
|
133
|
+
Verify with:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
codex mcp get lumen
|
|
137
|
+
ls -la "$HOME/.agents/skills/lumen"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**OpenCode**
|
|
141
|
+
|
|
142
|
+
Add `@ory/lumen-opencode` to the `plugin` array in your `opencode.json`:
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"plugin": ["@ory/lumen-opencode"]
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Detailed docs: [.opencode/INSTALL.md](.opencode/INSTALL.md)
|
|
151
|
+
|
|
152
|
+
Verify with:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
opencode mcp list
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Updating**
|
|
159
|
+
|
|
160
|
+
- **Claude Code** - update through Claude's plugin marketplace
|
|
161
|
+
- **Cursor** - refresh or reinstall the bundled plugin through Cursor after
|
|
162
|
+
updating this repository or the published package
|
|
163
|
+
- **Codex** - `cd "${CODEX_HOME:-$HOME/.codex}/lumen" && git pull`
|
|
164
|
+
- **OpenCode** - update the version pin in `opencode.json` (e.g.
|
|
165
|
+
`@ory/lumen-opencode@0.0.27`) and restart OpenCode
|
|
166
|
+
|
|
167
|
+
On first Claude Code or Cursor session start, Lumen:
|
|
168
|
+
|
|
169
|
+
1. Downloads the binary automatically from the
|
|
170
|
+
[latest GitHub release](https://github.com/ory/lumen/releases)
|
|
171
|
+
2. Indexes your project in the background using Merkle tree change detection
|
|
172
|
+
3. Registers a `semantic_search` MCP tool that the host can use automatically
|
|
173
|
+
|
|
174
|
+
In Codex and OpenCode, the same binary download and index seeding happen on the
|
|
175
|
+
first `semantic_search` call.
|
|
176
|
+
|
|
177
|
+
Two shared skills are also available: `doctor` (health check) and `reindex`
|
|
178
|
+
(forced re-indexing). Claude exposes them as `/lumen:doctor` and
|
|
179
|
+
`/lumen:reindex`; the other hosts discover the same shared skill content
|
|
180
|
+
through their native skill systems.
|
|
181
|
+
|
|
182
|
+
The same `semantic_search`, `health_check`, and `index_status` MCP tools plus
|
|
183
|
+
the shared `doctor` and `reindex` skills are exposed through the Codex,
|
|
184
|
+
Cursor, and OpenCode surfaces as well. The first `semantic_search` call seeds
|
|
185
|
+
or refreshes the index automatically.
|
|
186
|
+
|
|
187
|
+
## What you get
|
|
188
|
+
|
|
189
|
+
- **Semantic vector search** — Claude finds relevant functions, types, and
|
|
190
|
+
modules by meaning, not keyword matching
|
|
191
|
+
- **Auto-indexing** — indexes on session start, only re-processes changed files
|
|
192
|
+
via Merkle tree diffing
|
|
193
|
+
- **Incremental updates** — re-indexes only what changed; large codebases
|
|
194
|
+
re-index in seconds after the first run
|
|
195
|
+
- **11 language families** — Go, Python, TypeScript, JavaScript, Rust, Ruby,
|
|
196
|
+
Java, PHP, C/C++, C#
|
|
197
|
+
- **Git worktree support** — worktrees share index data automatically; a new
|
|
198
|
+
worktree seeds from a sibling's index and only re-indexes changed files,
|
|
199
|
+
turning minutes of embedding into seconds
|
|
200
|
+
- **Zero cloud** — embeddings stay on your machine; no data leaves your network
|
|
201
|
+
- **Ollama and LM Studio** — works with either local embedding backend
|
|
202
|
+
|
|
203
|
+
## How it works
|
|
204
|
+
|
|
205
|
+
Lumen sits between your codebase and Claude as an MCP server. When a session
|
|
206
|
+
starts, it walks your project and builds a **Merkle tree** over file hashes:
|
|
207
|
+
only changed files get re-chunked and re-embedded. Each file is split into
|
|
208
|
+
semantic chunks (functions, types, methods) using Go's native AST or tree-sitter
|
|
209
|
+
grammars for other languages. Chunks are embedded and stored in **SQLite +
|
|
210
|
+
sqlite-vec** using cosine-distance KNN for retrieval.
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
Files → semantic chunks → vector embeddings → SQLite/sqlite-vec → KNN search
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
When Claude needs to understand code, it calls `semantic_search` instead of
|
|
217
|
+
reading entire files. The index is stored outside your repo
|
|
218
|
+
(`~/.local/share/lumen/<hash>/index.db`), keyed by project path and model name —
|
|
219
|
+
different models never share an index.
|
|
220
|
+
|
|
221
|
+
## Benchmarks
|
|
222
|
+
|
|
223
|
+
Lumen is evaluated using **bench-swe**: a SWE-bench-style harness that runs
|
|
224
|
+
Claude on real GitHub bug-fix tasks and measures cost, time, output tokens, and
|
|
225
|
+
patch quality — with and without Lumen. All results are reproducible: raw JSONL
|
|
226
|
+
streams, patch diffs, and judge ratings are committed to this repository.
|
|
227
|
+
|
|
228
|
+
**Key results** — 8 runs across 8 languages, hard difficulty, real GitHub
|
|
229
|
+
issues (`ordis/jina-embeddings-v2-base-code`, Ollama):
|
|
230
|
+
|
|
231
|
+
| Language | Cost Reduction | Time Reduction | Output Token Reduction | Quality |
|
|
232
|
+
| ---------- | -------------- | -------------- | ----------------------- | -------------- |
|
|
233
|
+
| Rust | **-39%** | **-34%** | **-31%** (18K → 12K) | Poor (both) |
|
|
234
|
+
| JavaScript | **-33%** | **-53%** | **-66%** (14K → 5K) | Perfect (both) |
|
|
235
|
+
| TypeScript | **-27%** | **-33%** | **-64%** (5K → 1.8K) | Good (both) |
|
|
236
|
+
| PHP | **-27%** | **-34%** | **-59%** (1.9K → 0.8K) | Good (both) |
|
|
237
|
+
| Ruby | **-24%** | **-11%** | -9% (6.1K → 5.6K) | Good (both) |
|
|
238
|
+
| Python | **-20%** | **-29%** | **-36%** (1.7K → 1.1K) | Perfect (both) |
|
|
239
|
+
| Go | **-12%** | -9% | -10% (11K → 10K) | Good (both) |
|
|
240
|
+
| C++ | **-8%** | -3% | +42% (feature task) | Good (both) |
|
|
241
|
+
|
|
242
|
+
**Cost was reduced in every language tested. Quality was maintained in every
|
|
243
|
+
task — zero regressions.** JavaScript and TypeScript show the most dramatic
|
|
244
|
+
efficiency gains: same quality fixes in half the time with two-thirds fewer
|
|
245
|
+
tokens. Even on tasks too hard for either approach (Rust), Lumen cuts the cost
|
|
246
|
+
of failure by 39%.
|
|
247
|
+
|
|
248
|
+
See [docs/BENCHMARKS.md](docs/BENCHMARKS.md) for all 8 per-language deep dives,
|
|
249
|
+
judge rationales, and reproduce instructions.
|
|
250
|
+
|
|
251
|
+
## Supported languages
|
|
252
|
+
|
|
253
|
+
Supports **12 language families** with semantic chunking (9 benchmarked):
|
|
254
|
+
|
|
255
|
+
| Language | Parser | Extensions | Benchmark status |
|
|
256
|
+
| ---------------- | ----------- | ----------------------------------------- | --------------------------------------------- |
|
|
257
|
+
| Go | Native AST | `.go` | Benchmarked: -12% cost, Good quality |
|
|
258
|
+
| Python | tree-sitter | `.py` | Benchmarked: Perfect quality, -36% tokens |
|
|
259
|
+
| TypeScript / TSX | tree-sitter | `.ts`, `.tsx` | Benchmarked: -64% tokens, -33% time |
|
|
260
|
+
| JavaScript / JSX | tree-sitter | `.js`, `.jsx`, `.mjs` | Benchmarked: -66% tokens, -53% time |
|
|
261
|
+
| Dart | tree-sitter | `.dart` | Benchmarked: -76% cost, -82% tokens, -79% time |
|
|
262
|
+
| Rust | tree-sitter | `.rs` | Benchmarked: -39% cost, -34% time |
|
|
263
|
+
| Ruby | tree-sitter | `.rb` | Benchmarked: -24% cost, -11% time |
|
|
264
|
+
| PHP | tree-sitter | `.php` | Benchmarked: -59% tokens, -34% time |
|
|
265
|
+
| C / C++ | tree-sitter | `.c`, `.h`, `.cpp`, `.cc`, `.cxx`, `.hpp` | Benchmarked: -8% cost (C++ feature task) |
|
|
266
|
+
| Java | tree-sitter | `.java` | Supported |
|
|
267
|
+
| C# | tree-sitter | `.cs` | Supported |
|
|
268
|
+
|
|
269
|
+
Go uses the native Go AST parser for the most precise chunks. All other
|
|
270
|
+
languages use tree-sitter grammars. See [docs/BENCHMARKS.md](docs/BENCHMARKS.md)
|
|
271
|
+
for all 9 per-language benchmark deep dives.
|
|
272
|
+
|
|
273
|
+
## Configuration
|
|
274
|
+
|
|
275
|
+
All configuration is via environment variables:
|
|
276
|
+
|
|
277
|
+
| Variable | Default | Description |
|
|
278
|
+
| ------------------------ | ------------------------ | ------------------------------------------ |
|
|
279
|
+
| `LUMEN_EMBED_MODEL` | see note ¹ | Embedding model (must be in registry) |
|
|
280
|
+
| `LUMEN_BACKEND` | `ollama` | Embedding backend (`ollama` or `lmstudio`) |
|
|
281
|
+
| `OLLAMA_HOST` | `http://localhost:11434` | Ollama server URL |
|
|
282
|
+
| `LM_STUDIO_HOST` | `http://localhost:1234` | LM Studio server URL |
|
|
283
|
+
| `LUMEN_MAX_CHUNK_TOKENS` | `512` | Max tokens per chunk before splitting |
|
|
284
|
+
|
|
285
|
+
¹ `ordis/jina-embeddings-v2-base-code` (Ollama),
|
|
286
|
+
`nomic-ai/nomic-embed-code-GGUF` (LM Studio)
|
|
287
|
+
|
|
288
|
+
### Supported embedding models
|
|
289
|
+
|
|
290
|
+
Dimensions and context length are configured automatically per model:
|
|
291
|
+
|
|
292
|
+
| Model | Backend | Dims | Context | Recommended |
|
|
293
|
+
| ------------------------------------ | --------- | ---- | ------- | --------------------------------------------------------------------- |
|
|
294
|
+
| `ordis/jina-embeddings-v2-base-code` | Ollama | 768 | 8192 | **Best default** — lowest cost, no over-retrieval |
|
|
295
|
+
| `qwen3-embedding:8b` | Ollama | 4096 | 40960 | **Best quality** — strongest dominance (7/9 wins), very slow indexing |
|
|
296
|
+
| `nomic-ai/nomic-embed-code-GGUF` | LM Studio | 3584 | 8192 | **Usable** — good quality, but TypeScript over-retrieval raises costs |
|
|
297
|
+
| `qwen3-embedding:4b` | Ollama | 2560 | 40960 | **Not recommended** — highest costs, severe TypeScript over-retrieval |
|
|
298
|
+
| `nomic-embed-text` | Ollama | 768 | 8192 | Untested |
|
|
299
|
+
| `qwen3-embedding:0.6b` | Ollama | 1024 | 32768 | Untested |
|
|
300
|
+
| `all-minilm` | Ollama | 384 | 512 | Untested |
|
|
301
|
+
|
|
302
|
+
Switching models creates a separate index automatically. The model name is part
|
|
303
|
+
of the database path hash, so different models never collide.
|
|
304
|
+
|
|
305
|
+
## Controlling what gets indexed
|
|
306
|
+
|
|
307
|
+
Lumen filters files through six layers: built-in directory and lock file skips →
|
|
308
|
+
`.gitignore` → `.lumenignore` → `.gitattributes` (`linguist-generated`) →
|
|
309
|
+
supported file extension. Only files that pass all layers are indexed.
|
|
310
|
+
|
|
311
|
+
**`.lumenignore`** uses `.gitignore` syntax. Place it in your project root (or
|
|
312
|
+
any subdirectory) to exclude files that aren't in `.gitignore` but are noise for
|
|
313
|
+
code search — generated protobuf files, test snapshots, vendored data, etc.
|
|
314
|
+
|
|
315
|
+
<details>
|
|
316
|
+
<summary>Built-in skips (always excluded)</summary>
|
|
317
|
+
|
|
318
|
+
**Directories:** `.git`, `node_modules`, `vendor`, `dist`, `.cache`, `.venv`,
|
|
319
|
+
`venv`, `__pycache__`, `target`, `.gradle`, `_build`, `deps`, `.idea`,
|
|
320
|
+
`.vscode`, `.next`, `.nuxt`, `.build`, `.output`, `bower_components`, `.bundle`,
|
|
321
|
+
`.tox`, `.eggs`, `testdata`, `.hg`, `.svn`
|
|
322
|
+
|
|
323
|
+
**Lock files:** `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lock`,
|
|
324
|
+
`bun.lockb`, `go.sum`, `composer.lock`, `poetry.lock`, `Pipfile.lock`,
|
|
325
|
+
`Gemfile.lock`, `Cargo.lock`, `pubspec.lock`, `mix.lock`, `flake.lock`,
|
|
326
|
+
`packages.lock.json`
|
|
327
|
+
|
|
328
|
+
</details>
|
|
329
|
+
|
|
330
|
+
## Database location
|
|
331
|
+
|
|
332
|
+
Index databases are stored outside your project:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
~/.local/share/lumen/<hash>/index.db
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Where `<hash>` is derived from the absolute project path, embedding model name,
|
|
339
|
+
and binary version. Different models or Lumen versions automatically get
|
|
340
|
+
separate indexes. No files are added to your repo, no `.gitignore` modifications
|
|
341
|
+
needed.
|
|
342
|
+
|
|
343
|
+
You can safely delete the entire `lumen` directory to clear all indexes, or use
|
|
344
|
+
`lumen purge` to do it automatically.
|
|
345
|
+
|
|
346
|
+
**Git worktrees** are detected automatically. When you create a new worktree
|
|
347
|
+
(`git worktree add` or `claude --worktree`), Lumen finds a sibling worktree's
|
|
348
|
+
existing index and copies it as a seed. The Merkle tree diff then re-indexes
|
|
349
|
+
only the files that actually differ — typically a handful of files instead of
|
|
350
|
+
the entire codebase. No configuration needed; it just works.
|
|
351
|
+
|
|
352
|
+
## CLI Reference
|
|
353
|
+
|
|
354
|
+
Download the binary from the
|
|
355
|
+
[GitHub releases page](https://github.com/ory/lumen/releases) or let the plugin
|
|
356
|
+
install it automatically.
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
lumen help
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Troubleshooting
|
|
363
|
+
|
|
364
|
+
**Ollama not running / "connection refused"**
|
|
365
|
+
|
|
366
|
+
Start Ollama and verify the model is pulled:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
ollama serve
|
|
370
|
+
ollama pull ordis/jina-embeddings-v2-base-code
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Run `/lumen:doctor` inside Claude Code to confirm connectivity.
|
|
374
|
+
|
|
375
|
+
In Cursor, Codex, or OpenCode, use the shared `doctor` skill or call
|
|
376
|
+
`health_check` and `index_status` directly.
|
|
377
|
+
|
|
378
|
+
**Stale index after large refactor**
|
|
379
|
+
|
|
380
|
+
Run `/lumen:reindex` inside Claude Code to force a full re-index, or:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
lumen purge && lumen index .
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
In Codex, use the bundled `reindex` skill to refresh the index through the MCP
|
|
387
|
+
server, or run the same CLI commands for a clean rebuild. The same shared
|
|
388
|
+
`reindex` skill is available in Cursor and OpenCode as well.
|
|
389
|
+
|
|
390
|
+
**Switching embedding models**
|
|
391
|
+
|
|
392
|
+
Set `LUMEN_EMBED_MODEL` to a model from the supported table above. Each model
|
|
393
|
+
gets its own database; the old index is not deleted automatically.
|
|
394
|
+
|
|
395
|
+
**Slow first indexing**
|
|
396
|
+
|
|
397
|
+
The first run embeds every file. Subsequent runs only process changed files
|
|
398
|
+
(typically a few seconds). For large projects (100k+ lines), first indexing can
|
|
399
|
+
take several minutes — this is a one-time cost.
|
|
400
|
+
|
|
401
|
+
## Development
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
git clone https://github.com/ory/lumen.git
|
|
405
|
+
cd lumen
|
|
406
|
+
|
|
407
|
+
# Build locally (CGO required for sqlite-vec)
|
|
408
|
+
make build-local
|
|
409
|
+
|
|
410
|
+
# Run tests
|
|
411
|
+
make test
|
|
412
|
+
|
|
413
|
+
# Run linter
|
|
414
|
+
make lint
|
|
415
|
+
|
|
416
|
+
# Load as a Claude Code plugin from source
|
|
417
|
+
make plugin-dev
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
See [CLAUDE.md](CLAUDE.md) for architecture details, design decisions, and
|
|
421
|
+
contribution guidelines, and [AGENTS.md](AGENTS.md) for repo-specific agent
|
|
422
|
+
instructions.
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ory/lumen-opencode",
|
|
3
|
+
"version": "0.0.26",
|
|
4
|
+
"description": "Precise local semantic code search plugin for OpenCode — indexes with Go AST/tree-sitter and embeds with Ollama or LM Studio",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": ".opencode/plugins/lumen.js",
|
|
7
|
+
"author": "Ory Corp (https://github.com/ory)",
|
|
8
|
+
"homepage": "https://github.com/ory/lumen",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ory/lumen.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "Apache-2.0",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"opencode",
|
|
16
|
+
"semantic-search",
|
|
17
|
+
"code-index",
|
|
18
|
+
"mcp",
|
|
19
|
+
"ollama",
|
|
20
|
+
"rag"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
".opencode/plugins/",
|
|
24
|
+
".release-please-manifest.json",
|
|
25
|
+
"scripts/",
|
|
26
|
+
"skills/"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/scripts/run.bat
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enabledelayedexpansion
|
|
3
|
+
|
|
4
|
+
:: Determine plugin root: prefer an agent-set env var, then fall back to the
|
|
5
|
+
:: repository layout so the same launcher works across supported hosts.
|
|
6
|
+
if defined CLAUDE_PLUGIN_ROOT (
|
|
7
|
+
set "PLUGIN_ROOT=%CLAUDE_PLUGIN_ROOT%"
|
|
8
|
+
) else if defined CURSOR_PLUGIN_ROOT (
|
|
9
|
+
set "PLUGIN_ROOT=%CURSOR_PLUGIN_ROOT%"
|
|
10
|
+
) else (
|
|
11
|
+
set "PLUGIN_ROOT=%~dp0.."
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
:: Architecture detection
|
|
15
|
+
set "ARCH=amd64"
|
|
16
|
+
if "%PROCESSOR_ARCHITECTURE%"=="ARM64" set "ARCH=arm64"
|
|
17
|
+
|
|
18
|
+
:: Environment defaults
|
|
19
|
+
if not defined LUMEN_BACKEND set "LUMEN_BACKEND=ollama"
|
|
20
|
+
if not defined LUMEN_EMBED_MODEL set "LUMEN_EMBED_MODEL=ordis/jina-embeddings-v2-base-code"
|
|
21
|
+
|
|
22
|
+
:: Binary path
|
|
23
|
+
set "BINARY=%PLUGIN_ROOT%\bin\lumen-windows-%ARCH%.exe"
|
|
24
|
+
|
|
25
|
+
:: Download on first run if binary is missing
|
|
26
|
+
if not exist "%BINARY%" (
|
|
27
|
+
set "REPO=ory/lumen"
|
|
28
|
+
|
|
29
|
+
:: Always use the version pinned in the manifest — keeps plugin and binary in sync
|
|
30
|
+
set "MANIFEST=%PLUGIN_ROOT%\.release-please-manifest.json"
|
|
31
|
+
if not exist "!MANIFEST!" (
|
|
32
|
+
echo Error: .release-please-manifest.json not found in %PLUGIN_ROOT% >&2
|
|
33
|
+
exit /b 1
|
|
34
|
+
)
|
|
35
|
+
for /f "tokens=*" %%i in ('findstr /r "\"[.]\"" "!MANIFEST!"') do (
|
|
36
|
+
for /f "tokens=2 delims=:" %%j in ("%%i") do (
|
|
37
|
+
set "VERSION=v%%~j"
|
|
38
|
+
set "VERSION=!VERSION: =!"
|
|
39
|
+
set "VERSION=!VERSION:,=!"
|
|
40
|
+
set "VERSION=!VERSION:"=!"
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if "!VERSION!"=="" (
|
|
45
|
+
echo Error: could not read version from !MANIFEST! >&2
|
|
46
|
+
exit /b 1
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
set "ASSET=lumen-!VERSION:~1!-windows-!ARCH!.exe"
|
|
50
|
+
set "URL=https://github.com/!REPO!/releases/download/!VERSION!/!ASSET!"
|
|
51
|
+
|
|
52
|
+
echo Downloading lumen !VERSION! for windows/!ARCH!... >&2
|
|
53
|
+
if not exist "%PLUGIN_ROOT%\bin" mkdir "%PLUGIN_ROOT%\bin"
|
|
54
|
+
|
|
55
|
+
curl -sfL "!URL!" -o "%BINARY%"
|
|
56
|
+
|
|
57
|
+
echo Installed lumen to %BINARY% >&2
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
"%BINARY%" %*
|
package/scripts/run.cmd
ADDED
package/scripts/run.sh
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Determine plugin root: prefer an agent-set env var, then fall back to the
|
|
5
|
+
# repository layout so the same launcher works for Claude, Codex, Cursor,
|
|
6
|
+
# OpenCode, and direct local invocation.
|
|
7
|
+
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-${CURSOR_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}}"
|
|
8
|
+
|
|
9
|
+
# Platform detection
|
|
10
|
+
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
|
11
|
+
ARCH="$(uname -m)"
|
|
12
|
+
case "$ARCH" in
|
|
13
|
+
x86_64) ARCH="amd64" ;;
|
|
14
|
+
aarch64) ARCH="arm64" ;;
|
|
15
|
+
esac
|
|
16
|
+
|
|
17
|
+
# Environment defaults
|
|
18
|
+
export LUMEN_BACKEND="${LUMEN_BACKEND:-ollama}"
|
|
19
|
+
export LUMEN_EMBED_MODEL="${LUMEN_EMBED_MODEL:-ordis/jina-embeddings-v2-base-code}"
|
|
20
|
+
|
|
21
|
+
# Find binary: check bin/ first, then goreleaser dist/ output, then download
|
|
22
|
+
BINARY=""
|
|
23
|
+
for candidate in \
|
|
24
|
+
"${PLUGIN_ROOT}/bin/lumen" \
|
|
25
|
+
"${PLUGIN_ROOT}/bin/lumen-${OS}-${ARCH}"; do
|
|
26
|
+
if [ -x "$candidate" ]; then
|
|
27
|
+
BINARY="$candidate"
|
|
28
|
+
break
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
# Download on first run if no binary found
|
|
33
|
+
if [ -z "$BINARY" ]; then
|
|
34
|
+
BINARY="${PLUGIN_ROOT}/bin/lumen-${OS}-${ARCH}"
|
|
35
|
+
REPO="ory/lumen"
|
|
36
|
+
|
|
37
|
+
# Always use the version pinned in the manifest — keeps plugin and binary in sync
|
|
38
|
+
MANIFEST="${PLUGIN_ROOT}/.release-please-manifest.json"
|
|
39
|
+
if [ ! -f "$MANIFEST" ]; then
|
|
40
|
+
echo "Error: .release-please-manifest.json not found in ${PLUGIN_ROOT}" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
VERSION="v$(grep '"[.]"' "$MANIFEST" | sed 's/.*"[^"]*"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
|
|
44
|
+
if [ -z "$VERSION" ] || [ "$VERSION" = "v" ]; then
|
|
45
|
+
echo "Error: could not read version from ${MANIFEST}" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
ASSET="lumen-${VERSION#v}-${OS}-${ARCH}"
|
|
50
|
+
URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET}"
|
|
51
|
+
|
|
52
|
+
echo "Downloading lumen ${VERSION} for ${OS}/${ARCH}..." >&2
|
|
53
|
+
mkdir -p "$(dirname "$BINARY")"
|
|
54
|
+
|
|
55
|
+
curl -fL --progress-bar --max-time 300 --retry 3 --retry-delay 2 "$URL" -o "$BINARY"
|
|
56
|
+
chmod +x "$BINARY"
|
|
57
|
+
echo "Installed lumen to ${BINARY}" >&2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
exec "$BINARY" "$@"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tests for run.sh URL construction and OS/arch detection logic.
|
|
3
|
+
# Runs entirely offline — no real HTTP calls are made.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
PASS=0
|
|
7
|
+
FAIL=0
|
|
8
|
+
|
|
9
|
+
ok() {
|
|
10
|
+
echo " PASS: $1"
|
|
11
|
+
PASS=$((PASS + 1))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fail() {
|
|
15
|
+
echo " FAIL: $1"
|
|
16
|
+
echo " expected: $2"
|
|
17
|
+
echo " got: $3"
|
|
18
|
+
FAIL=$((FAIL + 1))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
assert_eq() {
|
|
22
|
+
local desc="$1" expected="$2" got="$3"
|
|
23
|
+
if [ "$expected" = "$got" ]; then
|
|
24
|
+
ok "$desc"
|
|
25
|
+
else
|
|
26
|
+
fail "$desc" "$expected" "$got"
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# asset_name <version_tag> <os> <arch>
|
|
32
|
+
# Mirrors the logic in run.sh: strip leading 'v', build asset filename.
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
asset_name() {
|
|
35
|
+
local version="$1" os="$2" arch="$3"
|
|
36
|
+
local ver_no_v="${version#v}"
|
|
37
|
+
case "$os" in
|
|
38
|
+
windows) echo "lumen-${ver_no_v}-${os}-${arch}.exe" ;;
|
|
39
|
+
*) echo "lumen-${ver_no_v}-${os}-${arch}" ;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# download_url <repo> <version_tag> <os> <arch>
|
|
44
|
+
download_url() {
|
|
45
|
+
local repo="$1" version="$2" os="$3" arch="$4"
|
|
46
|
+
local asset
|
|
47
|
+
asset="$(asset_name "$version" "$os" "$arch")"
|
|
48
|
+
echo "https://github.com/${repo}/releases/download/${version}/${asset}"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# arch normalisation (mirrors run.sh case statement)
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
normalise_arch() {
|
|
55
|
+
case "$1" in
|
|
56
|
+
x86_64) echo "amd64" ;;
|
|
57
|
+
aarch64) echo "arm64" ;;
|
|
58
|
+
*) echo "$1" ;;
|
|
59
|
+
esac
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
echo "=== asset name tests ==="
|
|
63
|
+
assert_eq "macOS arm64 asset" \
|
|
64
|
+
"lumen-0.0.1-alpha.4-darwin-arm64" \
|
|
65
|
+
"$(asset_name "v0.0.1-alpha.4" "darwin" "arm64")"
|
|
66
|
+
|
|
67
|
+
assert_eq "macOS amd64 asset" \
|
|
68
|
+
"lumen-0.0.1-alpha.4-darwin-amd64" \
|
|
69
|
+
"$(asset_name "v0.0.1-alpha.4" "darwin" "amd64")"
|
|
70
|
+
|
|
71
|
+
assert_eq "Linux amd64 asset" \
|
|
72
|
+
"lumen-0.0.1-alpha.4-linux-amd64" \
|
|
73
|
+
"$(asset_name "v0.0.1-alpha.4" "linux" "amd64")"
|
|
74
|
+
|
|
75
|
+
assert_eq "Linux arm64 asset" \
|
|
76
|
+
"lumen-0.0.1-alpha.4-linux-arm64" \
|
|
77
|
+
"$(asset_name "v0.0.1-alpha.4" "linux" "arm64")"
|
|
78
|
+
|
|
79
|
+
assert_eq "Windows amd64 asset (.exe)" \
|
|
80
|
+
"lumen-0.0.1-alpha.4-windows-amd64.exe" \
|
|
81
|
+
"$(asset_name "v0.0.1-alpha.4" "windows" "amd64")"
|
|
82
|
+
|
|
83
|
+
echo ""
|
|
84
|
+
echo "=== download URL tests ==="
|
|
85
|
+
REPO="ory/lumen"
|
|
86
|
+
VERSION="v0.0.1-alpha.4"
|
|
87
|
+
|
|
88
|
+
assert_eq "macOS arm64 URL" \
|
|
89
|
+
"https://github.com/ory/lumen/releases/download/v0.0.1-alpha.4/lumen-0.0.1-alpha.4-darwin-arm64" \
|
|
90
|
+
"$(download_url "$REPO" "$VERSION" "darwin" "arm64")"
|
|
91
|
+
|
|
92
|
+
assert_eq "Linux amd64 URL" \
|
|
93
|
+
"https://github.com/ory/lumen/releases/download/v0.0.1-alpha.4/lumen-0.0.1-alpha.4-linux-amd64" \
|
|
94
|
+
"$(download_url "$REPO" "$VERSION" "linux" "amd64")"
|
|
95
|
+
|
|
96
|
+
assert_eq "Windows amd64 URL" \
|
|
97
|
+
"https://github.com/ory/lumen/releases/download/v0.0.1-alpha.4/lumen-0.0.1-alpha.4-windows-amd64.exe" \
|
|
98
|
+
"$(download_url "$REPO" "$VERSION" "windows" "amd64")"
|
|
99
|
+
|
|
100
|
+
echo ""
|
|
101
|
+
echo "=== arch normalisation tests ==="
|
|
102
|
+
assert_eq "x86_64 → amd64" "amd64" "$(normalise_arch "x86_64")"
|
|
103
|
+
assert_eq "aarch64 → arm64" "arm64" "$(normalise_arch "aarch64")"
|
|
104
|
+
assert_eq "arm64 passthrough" "arm64" "$(normalise_arch "arm64")"
|
|
105
|
+
assert_eq "amd64 passthrough" "amd64" "$(normalise_arch "amd64")"
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
echo "=== binary candidate priority tests ==="
|
|
109
|
+
TMP_DIR="$(mktemp -d)"
|
|
110
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
111
|
+
|
|
112
|
+
BIN_DIR="${TMP_DIR}/bin"
|
|
113
|
+
mkdir -p "$BIN_DIR"
|
|
114
|
+
|
|
115
|
+
# Simulate: only downloaded binary present → should pick lumen-os-arch
|
|
116
|
+
touch "${BIN_DIR}/lumen-linux-amd64"
|
|
117
|
+
chmod +x "${BIN_DIR}/lumen-linux-amd64"
|
|
118
|
+
|
|
119
|
+
FOUND=""
|
|
120
|
+
for candidate in "${BIN_DIR}/lumen" "${BIN_DIR}/lumen-linux-amd64"; do
|
|
121
|
+
if [ -x "$candidate" ]; then FOUND="$candidate"; break; fi
|
|
122
|
+
done
|
|
123
|
+
assert_eq "picks lumen-linux-amd64 when lumen absent" \
|
|
124
|
+
"${BIN_DIR}/lumen-linux-amd64" "$FOUND"
|
|
125
|
+
|
|
126
|
+
# Simulate: both present → local dev build wins
|
|
127
|
+
touch "${BIN_DIR}/lumen"
|
|
128
|
+
chmod +x "${BIN_DIR}/lumen"
|
|
129
|
+
|
|
130
|
+
FOUND=""
|
|
131
|
+
for candidate in "${BIN_DIR}/lumen" "${BIN_DIR}/lumen-linux-amd64"; do
|
|
132
|
+
if [ -x "$candidate" ]; then FOUND="$candidate"; break; fi
|
|
133
|
+
done
|
|
134
|
+
assert_eq "prefers bin/lumen (dev build) over downloaded binary" \
|
|
135
|
+
"${BIN_DIR}/lumen" "$FOUND"
|
|
136
|
+
|
|
137
|
+
echo ""
|
|
138
|
+
echo "=== version resolution tests ==="
|
|
139
|
+
TMP_MANIFEST_DIR="$(mktemp -d)"
|
|
140
|
+
trap 'rm -rf "$TMP_MANIFEST_DIR" "$TMP_DIR"' EXIT
|
|
141
|
+
|
|
142
|
+
MANIFEST="${TMP_MANIFEST_DIR}/.release-please-manifest.json"
|
|
143
|
+
printf '{\n ".": "1.2.3"\n}\n' > "$MANIFEST"
|
|
144
|
+
|
|
145
|
+
resolved_version_from_manifest() {
|
|
146
|
+
local manifest="$1"
|
|
147
|
+
local ver="v$(grep '"[.]"' "$manifest" | sed 's/.*"[^"]*"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
|
|
148
|
+
echo "$ver"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
assert_eq "manifest version resolution" \
|
|
152
|
+
"v1.2.3" \
|
|
153
|
+
"$(resolved_version_from_manifest "$MANIFEST")"
|
|
154
|
+
|
|
155
|
+
assert_eq "pre-release version preserved" \
|
|
156
|
+
"v0.0.1-alpha.4" \
|
|
157
|
+
"$(printf '{\n ".": "0.0.1-alpha.4"\n}\n' | grep '"[.]"' | sed 's/.*"[^"]*"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/v\1/')"
|
|
158
|
+
|
|
159
|
+
echo ""
|
|
160
|
+
echo "=== summary ==="
|
|
161
|
+
echo " passed: $PASS"
|
|
162
|
+
echo " failed: $FAIL"
|
|
163
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
TMP_DIR="$(mktemp -d)"
|
|
6
|
+
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
7
|
+
|
|
8
|
+
cp "${SCRIPT_DIR}/run.cmd" "${TMP_DIR}/run.cmd"
|
|
9
|
+
chmod +x "${TMP_DIR}/run.cmd"
|
|
10
|
+
|
|
11
|
+
cat > "${TMP_DIR}/run.sh" <<'EOF'
|
|
12
|
+
#!/usr/bin/env bash
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
printf 'delegated:%s\n' "$*"
|
|
15
|
+
EOF
|
|
16
|
+
chmod +x "${TMP_DIR}/run.sh"
|
|
17
|
+
|
|
18
|
+
OUTPUT="$("${TMP_DIR}/run.cmd" stdio --flag)"
|
|
19
|
+
EXPECTED="delegated:stdio --flag"
|
|
20
|
+
|
|
21
|
+
if [ "$OUTPUT" != "$EXPECTED" ]; then
|
|
22
|
+
echo "FAIL: run.cmd should delegate to run.sh when exec'd directly on Unix"
|
|
23
|
+
echo "expected: $EXPECTED"
|
|
24
|
+
echo "got: $OUTPUT"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
echo "PASS: run.cmd delegates to run.sh when exec'd directly on Unix"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doctor
|
|
3
|
+
description: Run a health check on the bundled Lumen semantic search setup for the current project, verify backend reachability and index freshness, and summarize remediation steps.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lumen Doctor
|
|
7
|
+
|
|
8
|
+
Run a health check on the bundled Lumen semantic search setup for the current
|
|
9
|
+
project.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. Call the Lumen `health_check` tool to verify the embedding service is
|
|
14
|
+
reachable.
|
|
15
|
+
2. Call the Lumen `index_status` tool with `path` or `cwd` set to the current
|
|
16
|
+
working directory to check index freshness.
|
|
17
|
+
3. Report a concise summary:
|
|
18
|
+
- Embedding service status, backend, host, and model
|
|
19
|
+
- Index totals: files, chunks, last indexed time, stale or fresh
|
|
20
|
+
- Any MCP or plugin setup issue that blocks the tools
|
|
21
|
+
4. If no index exists yet, explain that the Lumen `semantic_search` tool seeds
|
|
22
|
+
the index on first use.
|
|
23
|
+
5. If the user wants eager indexing instead of waiting for the next search,
|
|
24
|
+
suggest running `lumen index .` in the repository root.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reindex
|
|
3
|
+
description: Refresh or rebuild the bundled Lumen index for the current project, preferring MCP-driven refreshes and using the CLI only for an explicit clean rebuild.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lumen Reindex
|
|
7
|
+
|
|
8
|
+
Refresh or rebuild the bundled Lumen index for the current project.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Call the Lumen `index_status` tool for the current working directory so you
|
|
13
|
+
can report the current state before making changes.
|
|
14
|
+
2. If the user wants the index refreshed or seeded, call the Lumen
|
|
15
|
+
`semantic_search` tool with a broad natural-language query and set `path` or
|
|
16
|
+
`cwd` to the current working directory. The search tool refreshes stale or
|
|
17
|
+
missing indexes automatically.
|
|
18
|
+
3. If the user explicitly asks for a clean rebuild, explain that
|
|
19
|
+
`lumen purge && lumen index .` deletes cached indexes before rebuilding,
|
|
20
|
+
then run it via the shell.
|
|
21
|
+
4. After the refresh or rebuild, report the new index status.
|