@lorekit/cli 1.3.0 → 1.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 +20 -13
- package/bin/lorekit.mjs +154 -8
- package/package.json +1 -1
- package/src/util.mjs +8 -1
package/README.md
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
# @lorekit/cli
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
checks against your LoreKit MCP server — a small, zero-dependency Node CLI.
|
|
3
|
+
> The fastest way to give your AI coding agent a memory that survives the session.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
CI included. This CLI wires an agent up to it in two commands.
|
|
5
|
+
[](https://www.npmjs.com/package/@lorekit/cli)
|
|
6
|
+
[](../../LICENSE)
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
[LoreKit](https://github.com/mthines/lorekit) gives coding agents a **shared,
|
|
9
|
+
persistent memory**: a lesson one agent learns — a migration gotcha, a
|
|
10
|
+
flaky-test fix, a costly wrong assumption — is stored once and recalled by every
|
|
11
|
+
other agent, in every session, on every machine, CI included.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
This is the CLI that connects your agent to it. One command scaffolds
|
|
14
|
+
everything — the memory skill, the MCP connection, and the lifecycle hooks — and
|
|
15
|
+
it's a zero-dependency Node binary that also runs fully offline against local
|
|
16
|
+
markdown files if you never want to sign up for anything.
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
19
|
npm install -g @lorekit/cli
|
|
19
|
-
lorekit install
|
|
20
|
-
lorekit doctor
|
|
20
|
+
lorekit install # wires up the skill, MCP server, and hooks
|
|
21
|
+
lorekit doctor # verify it's all green
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
The quickstart above uses the **global install — recommended.** You get one
|
|
27
|
+
pinned version, the `lorekit` command stays on your `PATH`, and the plugin hooks
|
|
28
|
+
(which call `lorekit hook` on every lifecycle event) fire instantly instead of
|
|
29
|
+
paying an `npx` resolution cost each time. Upgrade later with
|
|
30
|
+
`npm install -g @lorekit/cli@latest`.
|
|
24
31
|
|
|
25
32
|
Prefer not to install anything? Run it on demand with `npx` — same commands,
|
|
26
33
|
always the latest version, fetched and cached per use:
|
package/bin/lorekit.mjs
CHANGED
|
@@ -83,25 +83,164 @@ ${c.bold('Examples')}
|
|
|
83
83
|
npx @lorekit/cli doctor --deep
|
|
84
84
|
npx @lorekit/cli migrate --from .lore # preview a rename
|
|
85
85
|
npx @lorekit/cli migrate --from .lore --to project --yes
|
|
86
|
+
|
|
87
|
+
Run ${c.cyan('lorekit <command> --help')} for command-specific options.
|
|
86
88
|
`;
|
|
87
89
|
|
|
90
|
+
// Per-command help. Keyed by command; `lorekit <command> --help` prints the
|
|
91
|
+
// focused entry instead of the full top-level HELP, so a user only sees the
|
|
92
|
+
// flags that actually apply to what they're running.
|
|
93
|
+
const COMMAND_HELP = {
|
|
94
|
+
install: `${c.bold('lorekit install')} — scaffold the skill, wire the MCP server, install the hooks
|
|
95
|
+
|
|
96
|
+
${c.bold('Usage')}
|
|
97
|
+
npx @lorekit/cli install [options]
|
|
98
|
+
|
|
99
|
+
Scaffolds the lorekit-memory skill, adds the LoreKit MCP server, and wires the
|
|
100
|
+
deterministic hooks (lessons on SessionStart, a nudge on tool failure + at Stop).
|
|
101
|
+
|
|
102
|
+
${c.bold('Options')}
|
|
103
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
104
|
+
--project Install into this project: .claude/skills + .mcp.json (default)
|
|
105
|
+
--global Install for every project: ~/.claude/skills + ~/.claude.json
|
|
106
|
+
-e, --endpoint <url> LoreKit MCP endpoint (else LOREKIT_MCP_URL)
|
|
107
|
+
-t, --token <token> LoreKit token: lk_rw_* read+write, lk_ro_* read-only, lk_wo_* write-only
|
|
108
|
+
--no-hooks Skip wiring the lifecycle hooks (skill stays model-invoked only)
|
|
109
|
+
--force Overwrite existing skill files
|
|
110
|
+
-y, --yes Non-interactive; never prompt (defaults to --project)
|
|
111
|
+
|
|
112
|
+
${c.bold('Examples')}
|
|
113
|
+
npx @lorekit/cli install --endpoint https://ref.supabase.co/functions/v1/mcp --token lk_rw_xxx
|
|
114
|
+
npx @lorekit/cli install --global
|
|
115
|
+
npx @lorekit/cli install --no-hooks --yes
|
|
116
|
+
`,
|
|
117
|
+
uninstall: `${c.bold('lorekit uninstall')} — reverse install for the chosen scope
|
|
118
|
+
|
|
119
|
+
${c.bold('Usage')}
|
|
120
|
+
npx @lorekit/cli uninstall [options]
|
|
121
|
+
|
|
122
|
+
Removes the lorekit-memory skill, the MCP server entry, and the lifecycle hooks.
|
|
123
|
+
Surgical — other servers, hooks, and settings are left untouched.
|
|
124
|
+
|
|
125
|
+
${c.bold('Options')}
|
|
126
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
127
|
+
--project Uninstall from this project (default)
|
|
128
|
+
--global Uninstall the global (~/.claude) setup
|
|
129
|
+
-y, --yes Non-interactive; never prompt
|
|
130
|
+
|
|
131
|
+
${c.bold('Examples')}
|
|
132
|
+
npx @lorekit/cli uninstall --global
|
|
133
|
+
npx @lorekit/cli uninstall --project --yes
|
|
134
|
+
`,
|
|
135
|
+
doctor: `${c.bold('lorekit doctor')} — verify the skill install and the resolved memory backend
|
|
136
|
+
|
|
137
|
+
${c.bold('Usage')}
|
|
138
|
+
npx @lorekit/cli doctor [options]
|
|
139
|
+
|
|
140
|
+
Checks the node runtime, skill install, resolved memory mode, MCP connectivity,
|
|
141
|
+
token, and scope.
|
|
142
|
+
|
|
143
|
+
${c.bold('Options')}
|
|
144
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
145
|
+
--mode <mode> Override the resolved mode: off | local | remote
|
|
146
|
+
-e, --endpoint <url> Endpoint override (else .mcp.json / LOREKIT_MCP_URL)
|
|
147
|
+
-t, --token <token> Token override (else .mcp.json / LOREKIT_TOKEN)
|
|
148
|
+
--store <path> Local project-tier store directory (default: .lorekit)
|
|
149
|
+
--deep Do a write→read→delete round-trip
|
|
150
|
+
|
|
151
|
+
${c.bold('Examples')}
|
|
152
|
+
npx @lorekit/cli doctor
|
|
153
|
+
npx @lorekit/cli doctor --deep
|
|
154
|
+
npx @lorekit/cli doctor --mode local
|
|
155
|
+
`,
|
|
156
|
+
migrate: `${c.bold('lorekit migrate')} — relocate a LoreKit-format local store into the current layout
|
|
157
|
+
|
|
158
|
+
${c.bold('Usage')}
|
|
159
|
+
npx @lorekit/cli migrate --from <path> [options]
|
|
160
|
+
|
|
161
|
+
Dry-run by default; pass --yes (or --apply) to write. Idempotent.
|
|
162
|
+
|
|
163
|
+
${c.bold('Options')}
|
|
164
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
165
|
+
--from <path> Source store to migrate from (required)
|
|
166
|
+
--to <tier> Destination tier: home | project (default routes by scope)
|
|
167
|
+
--apply Apply the migration (alias of --yes)
|
|
168
|
+
-y, --yes Apply the migration; never prompt
|
|
169
|
+
|
|
170
|
+
${c.bold('Examples')}
|
|
171
|
+
npx @lorekit/cli migrate --from .lore # preview a rename
|
|
172
|
+
npx @lorekit/cli migrate --from .lore --to project --yes
|
|
173
|
+
`,
|
|
174
|
+
hook: `${c.bold('lorekit hook')} — hook engine for Claude Code / Cursor / Codex
|
|
175
|
+
|
|
176
|
+
${c.bold('Usage')}
|
|
177
|
+
lorekit hook --adapter <claude|cursor|codex> --event <name> [--dir <path>]
|
|
178
|
+
|
|
179
|
+
Machine-facing: reads the host's JSON on stdin and injects lessons or a
|
|
180
|
+
retrospective nudge on stdout, always exiting 0. Wired into a plugin's hook
|
|
181
|
+
config by \`lorekit install\` — not run by hand.
|
|
182
|
+
|
|
183
|
+
${c.bold('Options')}
|
|
184
|
+
-d, --dir <path> Target project root
|
|
185
|
+
--adapter <name> Host framework: claude | cursor | codex
|
|
186
|
+
--event <name> Host hook event (else read from the stdin payload)
|
|
187
|
+
`,
|
|
188
|
+
mcp: `${c.bold('lorekit mcp')} — local stdio MCP server
|
|
189
|
+
|
|
190
|
+
${c.bold('Usage')}
|
|
191
|
+
lorekit mcp [--dir <path>]
|
|
192
|
+
|
|
193
|
+
Machine-facing: exposes the memory.* tools backed by the resolved store (local
|
|
194
|
+
.lorekit/ offline, or remote passthrough) over JSON-RPC on stdin/stdout, so
|
|
195
|
+
.mcp.json can point at the CLI instead of mcp-remote. Not run by hand.
|
|
196
|
+
|
|
197
|
+
${c.bold('Options')}
|
|
198
|
+
-d, --dir <path> Target project root (default: current directory)
|
|
199
|
+
`,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Every long flag the CLI understands (after alias resolution). Passed to the
|
|
203
|
+
// parser so an unrecognized flag is captured rather than silently ignored — a
|
|
204
|
+
// typo like `--gloabl` should fail loudly, not quietly fall back to --project.
|
|
205
|
+
const KNOWN_FLAGS = [
|
|
206
|
+
'dir', 'project', 'global', 'endpoint', 'token', 'mode', 'store',
|
|
207
|
+
'from', 'to', 'apply', 'yes', 'no-hooks', 'force', 'deep', 'adapter',
|
|
208
|
+
'event', 'help', 'version',
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// Commands that write to disk / talk to the network on a human's behalf. These
|
|
212
|
+
// reject unknown flags; the machine-facing `hook` / `mcp` do not (they must
|
|
213
|
+
// never fail on a stray flag, and only ever receive flags we control).
|
|
214
|
+
const HUMAN_COMMANDS = new Set(['install', 'uninstall', 'doctor', 'migrate']);
|
|
215
|
+
|
|
88
216
|
async function main() {
|
|
89
217
|
const argv = process.argv.slice(2);
|
|
90
218
|
const args = parseArgs(argv, {
|
|
91
219
|
aliases: { d: 'dir', e: 'endpoint', t: 'token', y: 'yes', h: 'help', v: 'version' },
|
|
92
220
|
booleans: ['yes', 'force', 'deep', 'apply', 'help', 'version', 'global', 'project', 'no-hooks'],
|
|
221
|
+
known: KNOWN_FLAGS,
|
|
93
222
|
});
|
|
94
223
|
|
|
224
|
+
const command = args._[0];
|
|
225
|
+
|
|
226
|
+
// Help is intercepted first — before the machine-facing hook/mcp dispatch — so
|
|
227
|
+
// `lorekit <command> --help` always documents the command (even hook/mcp)
|
|
228
|
+
// instead of blocking on stdin. Real hook/mcp invocations never pass --help.
|
|
229
|
+
if (args.help) {
|
|
230
|
+
log(command && COMMAND_HELP[command] ? COMMAND_HELP[command] : HELP);
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
95
234
|
// `hook` is machine-facing: it must never print help/errors to stdout
|
|
96
235
|
// (that would corrupt the JSON the host parses). Handle it before the
|
|
97
|
-
//
|
|
98
|
-
if (
|
|
236
|
+
// usage branch and always resolve to exit 0.
|
|
237
|
+
if (command === 'hook') {
|
|
99
238
|
return hook(args);
|
|
100
239
|
}
|
|
101
240
|
|
|
102
241
|
// `mcp` is machine-facing too: only JSON-RPC frames may reach stdout, so it
|
|
103
|
-
// must bypass the
|
|
104
|
-
if (
|
|
242
|
+
// must bypass the usage branch. It serves stdio until the client closes.
|
|
243
|
+
if (command === 'mcp') {
|
|
105
244
|
return mcpServer(args);
|
|
106
245
|
}
|
|
107
246
|
|
|
@@ -110,11 +249,18 @@ async function main() {
|
|
|
110
249
|
return 0;
|
|
111
250
|
}
|
|
112
251
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (args.help || !command) {
|
|
252
|
+
if (!command) {
|
|
116
253
|
log(HELP);
|
|
117
|
-
return
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Reject unrecognized flags on human-facing commands with an actionable
|
|
258
|
+
// pointer, rather than silently ignoring a typo that would change behavior.
|
|
259
|
+
if (HUMAN_COMMANDS.has(command) && args._unknown.length > 0) {
|
|
260
|
+
const plural = args._unknown.length > 1 ? 's' : '';
|
|
261
|
+
err(`${c.red(`Unknown option${plural}:`)} ${args._unknown.join(', ')}`);
|
|
262
|
+
err(`Run ${c.cyan(`lorekit ${command} --help`)} to see valid options.`);
|
|
263
|
+
return 1;
|
|
118
264
|
}
|
|
119
265
|
|
|
120
266
|
// Human-facing commands are wrapped so we can see which commands people run
|
package/package.json
CHANGED
package/src/util.mjs
CHANGED
|
@@ -119,8 +119,13 @@ export function select(question, options, { defaultIndex = 0 } = {}) {
|
|
|
119
119
|
|
|
120
120
|
// Minimal flag parser: --key value, --key=value, -k value, and bare --flags.
|
|
121
121
|
// `aliases` maps short → long; `booleans` lists flags that take no value.
|
|
122
|
-
|
|
122
|
+
// When `known` is a non-null list of long flag names, any flag whose resolved
|
|
123
|
+
// name isn't in it is collected (as its original token) into `out._unknown` —
|
|
124
|
+
// letting the caller reject typos like `--gloabl` instead of silently ignoring
|
|
125
|
+
// them. `out._unknown` is only present when `known` was supplied.
|
|
126
|
+
export function parseArgs(argv, { aliases = {}, booleans = [], known = null } = {}) {
|
|
123
127
|
const out = { _: [] };
|
|
128
|
+
const unknown = [];
|
|
124
129
|
for (let i = 0; i < argv.length; i++) {
|
|
125
130
|
let token = argv[i];
|
|
126
131
|
if (!token.startsWith('-')) {
|
|
@@ -135,6 +140,7 @@ export function parseArgs(argv, { aliases = {}, booleans = [] } = {}) {
|
|
|
135
140
|
}
|
|
136
141
|
let key = token.replace(/^-+/, '');
|
|
137
142
|
if (aliases[key]) key = aliases[key];
|
|
143
|
+
if (known && !known.includes(key)) unknown.push(token);
|
|
138
144
|
if (booleans.includes(key)) {
|
|
139
145
|
out[key] = true;
|
|
140
146
|
continue;
|
|
@@ -150,5 +156,6 @@ export function parseArgs(argv, { aliases = {}, booleans = [] } = {}) {
|
|
|
150
156
|
}
|
|
151
157
|
out[key] = value;
|
|
152
158
|
}
|
|
159
|
+
if (known) out._unknown = unknown;
|
|
153
160
|
return out;
|
|
154
161
|
}
|