@fadhilp/lantern 0.0.0-stage → 0.4.0
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/LICENSE +21 -0
- package/bin/binary.js +25 -0
- package/bin/darwin-arm64/lantern +0 -0
- package/bin/darwin-x64/lantern +0 -0
- package/bin/lantern.js +24 -0
- package/bin/linux-arm64-gnu/lantern +0 -0
- package/bin/linux-arm64-musl/lantern +0 -0
- package/bin/linux-x64-gnu/lantern +0 -0
- package/bin/linux-x64-musl/lantern +0 -0
- package/bin/postinstall.js +61 -0
- package/bin/win32-arm64-msvc/lantern.exe +0 -0
- package/bin/win32-x64-msvc/lantern.exe +0 -0
- package/package.json +21 -4
- package/skills/lantern/SKILL.md +98 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FadhilP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/binary.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const { platform, arch } = process;
|
|
7
|
+
|
|
8
|
+
/** Platform directory names to try, best first. A static musl binary also runs on glibc. */
|
|
9
|
+
function candidates() {
|
|
10
|
+
const base = `${platform}-${arch}`;
|
|
11
|
+
if (platform === "win32") return [`${base}-msvc`];
|
|
12
|
+
if (platform !== "linux") return [base];
|
|
13
|
+
const glibc = process.report?.getReport?.().header?.glibcVersionRuntime;
|
|
14
|
+
return glibc ? [`${base}-gnu`, `${base}-musl`] : [`${base}-musl`];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The package holds one directory per platform next to this file.
|
|
18
|
+
function findBinary() {
|
|
19
|
+
const exe = platform === "win32" ? "lantern.exe" : "lantern";
|
|
20
|
+
return candidates()
|
|
21
|
+
.map((directory) => path.join(__dirname, directory, exe))
|
|
22
|
+
.find((binary) => fs.existsSync(binary));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { findBinary };
|
|
Binary file
|
|
Binary file
|
package/bin/lantern.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const { findBinary } = require("./binary.js");
|
|
6
|
+
|
|
7
|
+
const binary = findBinary();
|
|
8
|
+
if (!binary) {
|
|
9
|
+
console.error(
|
|
10
|
+
`lantern: no prebuilt binary for ${process.platform}-${process.arch}; ` +
|
|
11
|
+
"install from source with `cargo install --git https://github.com/FadhilP/lantern lantern-cli`",
|
|
12
|
+
);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
17
|
+
if (result.error) {
|
|
18
|
+
console.error(`lantern: ${result.error.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (result.signal) {
|
|
22
|
+
process.kill(process.pid, result.signal);
|
|
23
|
+
}
|
|
24
|
+
process.exit(result.status ?? 1);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// On a global npm install, npm's `lantern` command starts Node, which then starts the binary.
|
|
4
|
+
// This script points the command at the binary. If it does nothing or fails, bin/lantern.js still works.
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const path = require("node:path");
|
|
7
|
+
const { findBinary } = require("./binary.js");
|
|
8
|
+
|
|
9
|
+
function relinkUnix(prefix, binary) {
|
|
10
|
+
// The binary path is a real path, so the link directory must be one too, or the relative link is wrong.
|
|
11
|
+
const directory = fs.realpathSync(path.join(prefix, "bin"));
|
|
12
|
+
const link = path.join(directory, "lantern");
|
|
13
|
+
if (!fs.lstatSync(link).isSymbolicLink()) return;
|
|
14
|
+
const replacement = `${link}.${process.pid}.tmp`;
|
|
15
|
+
fs.symlinkSync(path.relative(directory, fs.realpathSync(binary)), replacement);
|
|
16
|
+
// A rename replaces the old link in one step, so the command never goes missing.
|
|
17
|
+
fs.renameSync(replacement, link);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function rewriteWindowsShims(prefix, binary) {
|
|
21
|
+
if (!fs.existsSync(path.join(prefix, "lantern.cmd"))) return;
|
|
22
|
+
const relative = path.relative(prefix, binary);
|
|
23
|
+
const posixRelative = relative.replaceAll("\\", "/");
|
|
24
|
+
// npm reads the target back from these files to check that they belong to this package
|
|
25
|
+
// (read-cmd-shim), so each keeps the shape npm writes: "%~dp0\<path>" %*, "$basedir/<path>" $args,
|
|
26
|
+
// and "$basedir/<path>" "$@".
|
|
27
|
+
const shims = {
|
|
28
|
+
"lantern.cmd": `@ECHO off\r\n"%~dp0\\${relative}" %*\r\n`,
|
|
29
|
+
// A script in a pipeline gets the piped text in $input; the binary needs it on stdin.
|
|
30
|
+
"lantern.ps1": [
|
|
31
|
+
"#!/usr/bin/env pwsh",
|
|
32
|
+
"$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent",
|
|
33
|
+
`if ($MyInvocation.ExpectingInput) { $input | & "$basedir/${posixRelative}" $args } else { & "$basedir/${posixRelative}" $args }`,
|
|
34
|
+
"exit $LASTEXITCODE",
|
|
35
|
+
"",
|
|
36
|
+
].join("\r\n"),
|
|
37
|
+
// Git Bash and MSYS run this file, not lantern.cmd. ${0%/*} avoids a dirname process, which is slow on Windows.
|
|
38
|
+
lantern: [
|
|
39
|
+
"#!/bin/sh",
|
|
40
|
+
'case "$0" in */*) basedir="${0%/*}" ;; *) basedir=. ;; esac',
|
|
41
|
+
`exec "$basedir/${posixRelative}" "$@"`,
|
|
42
|
+
"",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
};
|
|
45
|
+
for (const [name, content] of Object.entries(shims)) {
|
|
46
|
+
fs.writeFileSync(path.join(prefix, name), content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const prefix = process.env.npm_config_global_prefix;
|
|
51
|
+
const binary = findBinary();
|
|
52
|
+
const globalNpmInstall = process.env.npm_config_global === "true" && Boolean(prefix);
|
|
53
|
+
|
|
54
|
+
if (globalNpmInstall && binary) {
|
|
55
|
+
try {
|
|
56
|
+
if (process.platform === "win32") rewriteWindowsShims(prefix, binary);
|
|
57
|
+
else relinkUnix(prefix, binary);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(`lantern: the command will start through Node (${error.message}).`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fadhilp/lantern",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Read-only code discovery for large codebases",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/FadhilP/lantern.git"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"lantern": "bin/lantern.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"skills"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node bin/postinstall.js"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lantern
|
|
3
|
+
description: Find code fast in large repositories with the `lantern` CLI. Use it to find where a symbol is defined, where text or a regex occurs, who references a name, what calls a function or what it calls, and what a file contains. Prefer it over grep or reading whole files when you explore unfamiliar code, trace a call path, or need results that fit a small output budget.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Lantern
|
|
7
|
+
|
|
8
|
+
`lantern` is a read-only code index. It never writes inside the repository. Every query refreshes the index first (only changed files are read), so results match the disk.
|
|
9
|
+
|
|
10
|
+
## Pick the command
|
|
11
|
+
|
|
12
|
+
| Question | Command |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Where is `X` defined? (instead of `grep -r "class X"`) | `lantern symbols X` (add `--kind class`) |
|
|
15
|
+
| What is the source of `X`? | `lantern symbols X --body` |
|
|
16
|
+
| Where does this text or pattern occur? | `lantern code "text"` or `lantern code "re" --regex` |
|
|
17
|
+
| Who uses `X`? | `lantern refs X` |
|
|
18
|
+
| What calls `X`, and what does `X` call? | `lantern graph X` |
|
|
19
|
+
| What is in this file? | `lantern outline path/to/file` |
|
|
20
|
+
| Is this repository indexed, and how big is it? | `lantern status` |
|
|
21
|
+
|
|
22
|
+
Lantern has no file-name search. To find a file by name, use your file-glob tool or `git ls-files '*name*'`.
|
|
23
|
+
|
|
24
|
+
Pick by what you search for:
|
|
25
|
+
|
|
26
|
+
- **A function, method, class, or type:** use `symbols`, `refs`, or `graph`. They skip comments and strings and name the enclosing definition.
|
|
27
|
+
- **A field or object key** (`row.technician_id`, `{ cedex: ... }`), or text that is not a name (strings, comments, config, SQL, docs): use `code`. `refs` does not record field or property uses.
|
|
28
|
+
- **To read a definition:** use `symbols X --body`. It prints the found definitions with their source lines, so you do not need a separate read.
|
|
29
|
+
- **Before you read a file:** run `outline`, then read only the line range you need. Do not `cat` a whole file or read a large `sed -n 1,200p` range.
|
|
30
|
+
- **To read around a match:** use `code --context N`. You do not need a separate read.
|
|
31
|
+
|
|
32
|
+
Run the command from inside the repository, or pass `-C <dir>`. `-C` takes a directory, not a context count; for context lines use `--context N`. The first query on a new repository builds the index; later queries are fast. You do not need to run `lantern index` first.
|
|
33
|
+
|
|
34
|
+
**Several queries in a row:** each query refreshes the index first (about 0.2 s on a large repository). When you chain queries and the files do not change between them, add `--stale-ok` to every query after the first: `lantern symbols A; lantern refs A --stale-ok`. Before a loop, run `lantern index > /dev/null` once, then use `--stale-ok` on every query in the loop. On a repository that was never indexed, `--stale-ok` exits 1.
|
|
35
|
+
|
|
36
|
+
## Read the output
|
|
37
|
+
|
|
38
|
+
The default output is compact text, made for agents. Paths are relative to the current directory (`../` for a file outside it), so you can pass a printed path to `--path` or `outline` from the same directory. Lines are 1-based.
|
|
39
|
+
|
|
40
|
+
- `symbols`: `path:line-end_line kind[ in Container]: signature`, one per line. With `--body`, `line:text` source lines follow each definition, then `-- body cut: N more lines` when it is longer than 100 lines.
|
|
41
|
+
- `code`: a heading line with the file path (`path (N matches, M shown)` when `--per-file` cut matches), then `line:text` for each match and `line-text` for `--context` lines.
|
|
42
|
+
- `refs`: a heading line with the file path, then `line kind[ in Container.name]: text` for each reference.
|
|
43
|
+
- `outline`: `line-end_line kind name`, indented two spaces per nesting level.
|
|
44
|
+
- `graph`: one line per node, `id kind Container.name path:line`, with roots marked `(root)`. Then one line per edge, `from -> to kind at line N`, where line N is in the file of the `from` node. Then `unresolved: ...`.
|
|
45
|
+
- `no results` means nothing matched. After `symbols`, `refs`, or `graph`, a hint gives the `lantern code` command to search for the name as text. Run it when the name can be a field, a key, or a string. After `symbols Container.name`, when other containers define the name, the hint lists them; query again with one of them. After a literal `code` search, a hint says when the pattern has regex syntax (`a|b`, `getStats\(`): run it again with `--regex`. After `outline`, a hint means no parser covers the file type (CSS, HTML); use `lantern code` with `--path` for that file. If the hint says parsing ran out of time, symbols can be missing: run `lantern index --rebuild`, then query again. With `--json`, an empty page or graph carries the hint in a `hint` field. When results look incomplete, check `unparsed_files` in `lantern status`.
|
|
46
|
+
- A last line `-- more: --cursor <value>` means more rows exist. `-- truncated` on a graph means `--max-nodes` or `--max-bytes` cut it.
|
|
47
|
+
|
|
48
|
+
For the next page, run the same command with `--cursor <value>`. `index`, `status`, and `prune` print a JSON report.
|
|
49
|
+
|
|
50
|
+
Add `--json` only when a program parses the output. Each command then prints one JSON document; null fields and `truncated: false` are left out. List pages are `{"schema":1,"freshness","indexed_at","truncated"?,"next_cursor"?,"hint"?,"results":[...]}`. Rows:
|
|
51
|
+
- `symbols`: `name, kind, container?, path, line, end_line, signature, body?: {line, text}`.
|
|
52
|
+
- `code`: `path, match_count, matches: [{line, column, text, before?, after?}]`.
|
|
53
|
+
- `refs`: `path, line, column, kind, text, enclosing?: {name, container?, kind}`.
|
|
54
|
+
- `outline`: top-level symbols with nested `children`; the page also has `path` and `language`.
|
|
55
|
+
- `graph`: `roots`, `nodes: [{id, name, container?, kind, path, line}]`, `edges: [{from, to, kind, line}]`, `truncated?`, `unresolved?`.
|
|
56
|
+
|
|
57
|
+
## Keep output small
|
|
58
|
+
|
|
59
|
+
- `--limit N` sets rows per page. Defaults: 30 (`code`: 10 files). Max 10 000.
|
|
60
|
+
- `--max-bytes N` cuts the page until the output fits in N bytes (accepts `K`/`M`). Cut rows come back on the next page. If not even one row fits, the command exits 2; raise the value.
|
|
61
|
+
- `--path GLOB` narrows by path, relative to the current directory. Repeat it to match any of several globs (`--path 'src/**' --path 'lib/**'`). A glob without `/` matches at any depth (`*.ts`); with `/` it is anchored (`src/**/*.ts`).
|
|
62
|
+
- `--language L` (for `symbols` and `code`): `c cpp csharp dart go java javascript php python ruby rust typescript tsx`. `typescript` also selects `tsx`. `javascript` includes `.jsx` files.
|
|
63
|
+
- `code --per-file N` caps matches per file (default 20); `match_count` still counts all of them. `code --context N` adds up to N lines around each match (at most 50).
|
|
64
|
+
- `symbols --body` drops the default `--limit` to 5. Each body has at most 100 lines. A body starts at the first decorator, attribute, annotation, or doc comment right before the definition (a blank line ends them), so it can start before the row's `line`.
|
|
65
|
+
- `graph --depth N` (1–3, default 1) and `--max-nodes N` (default 200) bound the graph.
|
|
66
|
+
|
|
67
|
+
## Command notes
|
|
68
|
+
|
|
69
|
+
- **symbols**: matches by exact name, prefix, initials, or substring (3 characters or more); letters with gaps between them do not match. Ranks exact name, then case-insensitive exact, then prefix, then initials (`gubi` finds `getUserById`), then substring. Narrow with `--kind function|method|class|interface|struct|enum|trait|type|module|constant|variable|field`. Qualify with a container: `UserStore.getUserById` or `Store::get`.
|
|
70
|
+
- **code**: literal and case-insensitive by default. For a pattern that starts with `-`, use `-e`: `lantern code -e "--flag"`. Add `--regex` for a Rust-syntax regex and `--case-sensitive` when case matters. Without `--regex`, `|`, `\(`, and `.*` match those characters literally. Matching is per line. `--sort path` gives a stable order instead of relevance.
|
|
71
|
+
- **refs**: exact, case-sensitive name. `--kind call|type|import|implementation`. Every language records `call` references. `type` references exist only for Dart, Go, and TypeScript/TSX; `import` references only for Dart and JavaScript/TypeScript/TSX; `implementation` references for Dart, Java, JavaScript/TypeScript/TSX, PHP, and Rust. In other languages, find type uses and imports with `lantern code`. `enclosing` names the definition that contains the reference.
|
|
72
|
+
- **graph**: `--direction callers|callees|both`. It follows calls only; use `refs` for type uses, imports, and `extends`/`implements` where the language records them (see **refs**). A node with kind `file` stands for calls outside any definition. Names in `unresolved` have no definition in the index (often library calls). Code outside tests never resolves to a definition in a test file.
|
|
73
|
+
- **outline**: accepts a path relative to the current directory or an absolute path. For a path that is not in the index, the error lists indexed files with the same file name.
|
|
74
|
+
|
|
75
|
+
## Know the limits
|
|
76
|
+
|
|
77
|
+
- **Name resolution, not types.** `refs` and `graph` link by name (`"resolution": "name"`). Two unrelated functions with the same name share references and edges. Check the node paths before you trust an edge; narrow with `--path` or `--kind`.
|
|
78
|
+
- **Generated files** (lockfiles, files marked `linguist-generated` or `linguist-vendored` in `.gitattributes`) are left out of `symbols`, `refs`, and `graph`, and ranked last in `code`. Add `--include-generated` to include them.
|
|
79
|
+
- **What is indexed.** In a Git repository: tracked files plus untracked files that Git does not ignore. Outside Git: all files except `.gitignore` matches and the defaults (dot-files, `node_modules/`, `dist/`, `build/`, `coverage/`, `target/`, `vendor/`). `.lanternignore` (gitignore syntax) excludes more in both. Files over 1 MiB (`--max-file-size`) and binary files are not searchable as text.
|
|
80
|
+
- **Nested repositories** (submodules that are checked out) are indexed with the repository that contains them; their paths carry the submodule prefix.
|
|
81
|
+
- **Windows: files open for writing.** In a Git repository on Windows, Lantern takes file metadata from directory listings, as Git does with `core.fscache`. It does not see an edit to a file that another process still holds open for writing until that process closes the file. It also does not see an edit made through another name of a hard-linked file until that name is opened. Editor saves close the file, so they are not affected.
|
|
82
|
+
|
|
83
|
+
## Handle errors
|
|
84
|
+
|
|
85
|
+
Errors go to stderr as `lantern: <message>` (with `--json`: `{"error":{"code":"...","message":"..."}}`). Act on the exit code:
|
|
86
|
+
|
|
87
|
+
| Exit | Code | What to do |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| 1 | `failure` | Read the message. "not indexed" with `--stale-ok`: drop `--stale-ok` or run `lantern index`. |
|
|
90
|
+
| 2 | `usage` | Fix the arguments (bad flag, limit, glob, cursor from another query, `--max-bytes` too small). |
|
|
91
|
+
| 3 | `root_moved` | A folder that was indexed alone is now inside a Git repository. Run from the intended root. |
|
|
92
|
+
| 4 | `stale_cursor` | The index changed between pages. Run the query again without `--cursor`. |
|
|
93
|
+
| 75 | `temporary` | Another refresh holds the lock, files kept changing, or (`index` only) a reader blocked cleanup. Retry after a short wait. |
|
|
94
|
+
|
|
95
|
+
## Speed
|
|
96
|
+
|
|
97
|
+
- Use `--stale-ok` when you run many queries in a row and the files have not changed (see **Several queries in a row**). It skips the refresh and labels results `stale`.
|
|
98
|
+
- In a very large repository the first query can take minutes. Run `lantern index` once up front if you can.
|
package/README.md
DELETED