lat.md 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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
- For semantic search (`lat search`), set the `LAT_LLM_KEY` environment variable with an OpenAI (`sk-...`) or Vercel AI Gateway (`vck_...`) API key.
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.
@@ -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
- if (!getLlmKey()) {
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
- ' Set LAT_LLM_KEY env var or run ' +
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
  }
@@ -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();
@@ -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
- const key = getLlmKey();
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('LAT_LLM_KEY is not set.') +
13
- ' Set the LAT_LLM_KEY env var, or run ' +
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 a key in ' +
22
+ ' to save one in ' +
16
23
  chalk.dim(getConfigPath()) +
17
24
  '.');
18
25
  process.exit(1);
@@ -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. llm_key field in ~/.config/lat/config.json
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 neither is set.
15
+ * Returns undefined if none is set.
14
16
  */
15
17
  export declare function getLlmKey(): string | undefined;
@@ -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. llm_key field in ~/.config/lat/config.json
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 neither is set.
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
- return config.llm_key || undefined;
62
+ if (config.llm_key)
63
+ return config.llm_key;
64
+ return undefined;
41
65
  }
@@ -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
- const key = getLlmKey();
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. Set LAT_LLM_KEY env var or run `lat init` to save a key in ~/.config/lat/config.json.',
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lat.md",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "A knowledge graph for your codebase, written in markdown",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.30.2",
@@ -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 `LAT_LLM_KEY` is not set, explain to the user that semantic search requires an API key (`export LAT_LLM_KEY=sk-...` for OpenAI or `export LAT_LLM_KEY=vck_...` for Vercel). If the user doesn't want to set it up, use `lat locate` for direct lookups instead.
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