lat.md 0.4.0 → 0.4.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.
- package/README.md +10 -1
- package/dist/src/cli/check.js +9 -2
- package/dist/src/cli/index.js +10 -1
- package/dist/src/cli/init.js +13 -13
- package/dist/src/cli/search.js +11 -4
- package/dist/src/config.d.ts +4 -2
- package/dist/src/config.js +27 -3
- package/dist/src/mcp/server.js +11 -2
- package/package.json +1 -1
- package/templates/AGENTS.md +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ npm install -g lat.md
|
|
|
29
29
|
|
|
30
30
|
Or use directly with `npx lat.md@latest <command>`.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
After installing, run `lat init` in the repo you want to use lat in.
|
|
33
33
|
|
|
34
34
|
## How it works
|
|
35
35
|
|
|
@@ -58,6 +58,15 @@ npx lat.md search "how do we auth?" # semantic search via embeddings
|
|
|
58
58
|
npx lat.md prompt "fix [[OAuth Flow]]" # expand [[refs]] in a prompt for agents
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
Semantic search (`lat search`) requires an OpenAI (`sk-...`) or Vercel AI Gateway (`vck_...`) API key. The key is resolved in order:
|
|
64
|
+
|
|
65
|
+
1. `LAT_LLM_KEY` env var — direct value
|
|
66
|
+
2. `LAT_LLM_KEY_FILE` env var — path to a file containing the key
|
|
67
|
+
3. `LAT_LLM_KEY_HELPER` env var — shell command that prints the key (10s timeout)
|
|
68
|
+
4. Config file — saved by `lat init`. Run `lat config` to see its location.
|
|
69
|
+
|
|
61
70
|
## Development
|
|
62
71
|
|
|
63
72
|
Requires Node.js 22+ and pnpm.
|
package/dist/src/cli/check.js
CHANGED
|
@@ -289,10 +289,17 @@ export async function checkAllCmd(ctx) {
|
|
|
289
289
|
process.exit(1);
|
|
290
290
|
console.log(ctx.chalk.green('All checks passed'));
|
|
291
291
|
const { getLlmKey } = await import('../config.js');
|
|
292
|
-
|
|
292
|
+
let hasKey = false;
|
|
293
|
+
try {
|
|
294
|
+
hasKey = !!getLlmKey();
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// key resolution failed (e.g. empty file) — treat as missing
|
|
298
|
+
}
|
|
299
|
+
if (!hasKey) {
|
|
293
300
|
console.log(ctx.chalk.yellow('Warning:') +
|
|
294
301
|
' No LLM key found — semantic search (lat search) will not work.' +
|
|
295
|
-
'
|
|
302
|
+
' Provide a key via LAT_LLM_KEY, LAT_LLM_KEY_FILE, LAT_LLM_KEY_HELPER, or run ' +
|
|
296
303
|
ctx.chalk.cyan('lat init') +
|
|
297
304
|
' to configure.');
|
|
298
305
|
}
|
package/dist/src/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
if (!process.argv.includes('--verbose')) {
|
|
4
4
|
process.noDeprecation = true;
|
|
5
5
|
}
|
|
6
|
-
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { Command } from 'commander';
|
|
@@ -145,4 +145,13 @@ program
|
|
|
145
145
|
const { startMcpServer } = await import('../mcp/server.js');
|
|
146
146
|
await startMcpServer();
|
|
147
147
|
});
|
|
148
|
+
program
|
|
149
|
+
.command('config')
|
|
150
|
+
.description('Show configuration file path')
|
|
151
|
+
.action(async () => {
|
|
152
|
+
const { getConfigPath } = await import('../config.js');
|
|
153
|
+
const configPath = getConfigPath();
|
|
154
|
+
const exists = existsSync(configPath);
|
|
155
|
+
console.log(`Config file: ${configPath}${exists ? '' : ' (not found)'}`);
|
|
156
|
+
});
|
|
148
157
|
await program.parseAsync();
|
package/dist/src/cli/init.js
CHANGED
|
@@ -106,31 +106,31 @@ function ensureGitignored(root, entry) {
|
|
|
106
106
|
function mcpCommand() {
|
|
107
107
|
return { command: resolve(process.argv[1]), args: ['mcp'] };
|
|
108
108
|
}
|
|
109
|
-
function hasMcpServer(configPath) {
|
|
109
|
+
function hasMcpServer(configPath, key) {
|
|
110
110
|
if (!existsSync(configPath))
|
|
111
111
|
return false;
|
|
112
112
|
try {
|
|
113
113
|
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
114
|
-
return !!cfg?.
|
|
114
|
+
return !!cfg?.[key]?.lat;
|
|
115
115
|
}
|
|
116
116
|
catch {
|
|
117
117
|
return false;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
-
function addMcpServer(configPath) {
|
|
121
|
-
let cfg = {
|
|
120
|
+
function addMcpServer(configPath, key) {
|
|
121
|
+
let cfg = { [key]: {} };
|
|
122
122
|
if (existsSync(configPath)) {
|
|
123
123
|
const raw = readFileSync(configPath, 'utf-8');
|
|
124
124
|
try {
|
|
125
125
|
cfg = JSON.parse(raw);
|
|
126
|
-
if (!cfg
|
|
127
|
-
cfg
|
|
126
|
+
if (!cfg[key])
|
|
127
|
+
cfg[key] = {};
|
|
128
128
|
}
|
|
129
129
|
catch (e) {
|
|
130
130
|
throw new Error(`Cannot parse ${configPath}: ${e.message}`);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
cfg.
|
|
133
|
+
cfg[key].lat = mcpCommand();
|
|
134
134
|
mkdirSync(join(configPath, '..'), { recursive: true });
|
|
135
135
|
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + '\n');
|
|
136
136
|
}
|
|
@@ -182,11 +182,11 @@ async function setupClaudeCode(root, template) {
|
|
|
182
182
|
console.log(chalk.dim(' Agents can call `lat` from the command line, but an MCP server gives lat'));
|
|
183
183
|
console.log(chalk.dim(' more visibility and makes agents more likely to use it proactively.'));
|
|
184
184
|
const mcpPath = join(root, '.mcp.json');
|
|
185
|
-
if (hasMcpServer(mcpPath)) {
|
|
185
|
+
if (hasMcpServer(mcpPath, 'mcpServers')) {
|
|
186
186
|
console.log(chalk.green(' MCP server') + ' already configured');
|
|
187
187
|
}
|
|
188
188
|
else {
|
|
189
|
-
addMcpServer(mcpPath);
|
|
189
|
+
addMcpServer(mcpPath, 'mcpServers');
|
|
190
190
|
console.log(chalk.green(' MCP server') + ' registered in .mcp.json');
|
|
191
191
|
created.push('.mcp.json');
|
|
192
192
|
}
|
|
@@ -213,11 +213,11 @@ async function setupCursor(root) {
|
|
|
213
213
|
console.log(chalk.dim(' Agents can call `lat` from the command line, but an MCP server gives lat'));
|
|
214
214
|
console.log(chalk.dim(' more visibility and makes agents more likely to use it proactively.'));
|
|
215
215
|
const mcpPath = join(root, '.cursor', 'mcp.json');
|
|
216
|
-
if (hasMcpServer(mcpPath)) {
|
|
216
|
+
if (hasMcpServer(mcpPath, 'mcpServers')) {
|
|
217
217
|
console.log(chalk.green(' MCP server') + ' already configured');
|
|
218
218
|
}
|
|
219
219
|
else {
|
|
220
|
-
addMcpServer(mcpPath);
|
|
220
|
+
addMcpServer(mcpPath, 'mcpServers');
|
|
221
221
|
console.log(chalk.green(' MCP server') + ' registered in .cursor/mcp.json');
|
|
222
222
|
created.push('.cursor/mcp.json');
|
|
223
223
|
}
|
|
@@ -248,11 +248,11 @@ async function setupCopilot(root) {
|
|
|
248
248
|
console.log(chalk.dim(' Agents can call `lat` from the command line, but an MCP server gives lat'));
|
|
249
249
|
console.log(chalk.dim(' more visibility and makes agents more likely to use it proactively.'));
|
|
250
250
|
const mcpPath = join(root, '.vscode', 'mcp.json');
|
|
251
|
-
if (hasMcpServer(mcpPath)) {
|
|
251
|
+
if (hasMcpServer(mcpPath, 'servers')) {
|
|
252
252
|
console.log(chalk.green(' MCP server') + ' already configured');
|
|
253
253
|
}
|
|
254
254
|
else {
|
|
255
|
-
addMcpServer(mcpPath);
|
|
255
|
+
addMcpServer(mcpPath, 'servers');
|
|
256
256
|
console.log(chalk.green(' MCP server') + ' registered in .vscode/mcp.json');
|
|
257
257
|
created.push('.vscode/mcp.json');
|
|
258
258
|
}
|
package/dist/src/cli/search.js
CHANGED
|
@@ -7,12 +7,19 @@ import { loadAllSections, flattenSections } from '../lattice.js';
|
|
|
7
7
|
import { formatResultList } from '../format.js';
|
|
8
8
|
import { getLlmKey, getConfigPath } from '../config.js';
|
|
9
9
|
export async function searchCmd(ctx, query, opts) {
|
|
10
|
-
|
|
10
|
+
let key;
|
|
11
|
+
try {
|
|
12
|
+
key = getLlmKey();
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
console.error(chalk.red(err.message));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
11
18
|
if (!key) {
|
|
12
|
-
console.error(chalk.red('
|
|
13
|
-
'
|
|
19
|
+
console.error(chalk.red('No API key configured.') +
|
|
20
|
+
' Provide a key via LAT_LLM_KEY, LAT_LLM_KEY_FILE, LAT_LLM_KEY_HELPER, or run ' +
|
|
14
21
|
chalk.cyan('lat init') +
|
|
15
|
-
' to save
|
|
22
|
+
' to save one in ' +
|
|
16
23
|
chalk.dim(getConfigPath()) +
|
|
17
24
|
'.');
|
|
18
25
|
process.exit(1);
|
package/dist/src/config.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export declare function writeConfig(config: LatConfig): void;
|
|
|
8
8
|
/**
|
|
9
9
|
* Returns the LLM key from (in priority order):
|
|
10
10
|
* 1. LAT_LLM_KEY environment variable
|
|
11
|
-
* 2.
|
|
11
|
+
* 2. LAT_LLM_KEY_FILE — path to a file containing the key
|
|
12
|
+
* 3. LAT_LLM_KEY_HELPER — shell command that prints the key
|
|
13
|
+
* 4. llm_key field in ~/.config/lat/config.json
|
|
12
14
|
*
|
|
13
|
-
* Returns undefined if
|
|
15
|
+
* Returns undefined if none is set.
|
|
14
16
|
*/
|
|
15
17
|
export declare function getLlmKey(): string | undefined;
|
package/dist/src/config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
1
2
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import xdg from '@folder/xdg';
|
|
@@ -28,14 +29,37 @@ export function writeConfig(config) {
|
|
|
28
29
|
/**
|
|
29
30
|
* Returns the LLM key from (in priority order):
|
|
30
31
|
* 1. LAT_LLM_KEY environment variable
|
|
31
|
-
* 2.
|
|
32
|
+
* 2. LAT_LLM_KEY_FILE — path to a file containing the key
|
|
33
|
+
* 3. LAT_LLM_KEY_HELPER — shell command that prints the key
|
|
34
|
+
* 4. llm_key field in ~/.config/lat/config.json
|
|
32
35
|
*
|
|
33
|
-
* Returns undefined if
|
|
36
|
+
* Returns undefined if none is set.
|
|
34
37
|
*/
|
|
35
38
|
export function getLlmKey() {
|
|
36
39
|
const envKey = process.env.LAT_LLM_KEY;
|
|
37
40
|
if (envKey)
|
|
38
41
|
return envKey;
|
|
42
|
+
const file = process.env.LAT_LLM_KEY_FILE;
|
|
43
|
+
if (file) {
|
|
44
|
+
const content = readFileSync(file, 'utf-8').trim();
|
|
45
|
+
if (!content) {
|
|
46
|
+
throw new Error(`LAT_LLM_KEY_FILE (${file}) is empty.`);
|
|
47
|
+
}
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
50
|
+
const helper = process.env.LAT_LLM_KEY_HELPER;
|
|
51
|
+
if (helper) {
|
|
52
|
+
const result = execSync(helper, {
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
timeout: 10_000,
|
|
55
|
+
}).trim();
|
|
56
|
+
if (!result) {
|
|
57
|
+
throw new Error('LAT_LLM_KEY_HELPER command returned an empty string.');
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
39
61
|
const config = readConfig();
|
|
40
|
-
|
|
62
|
+
if (config.llm_key)
|
|
63
|
+
return config.llm_key;
|
|
64
|
+
return undefined;
|
|
41
65
|
}
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -70,13 +70,22 @@ export async function startMcpServer() {
|
|
|
70
70
|
.describe('Max results (default 5)'),
|
|
71
71
|
}, async ({ query, limit }) => {
|
|
72
72
|
const { getLlmKey } = await import('../config.js');
|
|
73
|
-
|
|
73
|
+
let key;
|
|
74
|
+
try {
|
|
75
|
+
key = getLlmKey();
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: err.message }],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
74
83
|
if (!key) {
|
|
75
84
|
return {
|
|
76
85
|
content: [
|
|
77
86
|
{
|
|
78
87
|
type: 'text',
|
|
79
|
-
text: 'No LLM key found.
|
|
88
|
+
text: 'No LLM key found. Provide a key via LAT_LLM_KEY, LAT_LLM_KEY_FILE, LAT_LLM_KEY_HELPER, or run `lat init`.',
|
|
80
89
|
},
|
|
81
90
|
],
|
|
82
91
|
isError: true,
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -29,7 +29,7 @@ lat check # validate all links and code refs
|
|
|
29
29
|
|
|
30
30
|
Run `lat --help` when in doubt about available commands or options.
|
|
31
31
|
|
|
32
|
-
If `lat search` fails because
|
|
32
|
+
If `lat search` fails because no API key is configured, explain to the user that semantic search requires a key provided via `LAT_LLM_KEY` (direct value), `LAT_LLM_KEY_FILE` (path to key file), or `LAT_LLM_KEY_HELPER` (command that prints the key). Supported key prefixes: `sk-...` (OpenAI) or `vck_...` (Vercel). If the user doesn't want to set it up, use `lat locate` for direct lookups instead.
|
|
33
33
|
|
|
34
34
|
# Syntax primer
|
|
35
35
|
|