kontex-cli 1.0.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/dist/index.js +330 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __require = import.meta.require;
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
|
|
8
|
+
// src/commands/login.ts
|
|
9
|
+
import { login } from "kontex-core";
|
|
10
|
+
function registerLoginCommand(program) {
|
|
11
|
+
program.command("login").description("Authenticate with GitHub using OAuth Device Flow").action(async () => {
|
|
12
|
+
try {
|
|
13
|
+
await login();
|
|
14
|
+
} catch (e) {
|
|
15
|
+
console.error("Login failed:", e instanceof Error ? e.message : e);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/commands/logout.ts
|
|
22
|
+
import { logout } from "kontex-core";
|
|
23
|
+
function registerLogoutCommand(program) {
|
|
24
|
+
program.command("logout").description("Remove GitHub OAuth token from the OS keychain").action(async () => {
|
|
25
|
+
try {
|
|
26
|
+
await logout();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error("Logout failed:", e instanceof Error ? e.message : e);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/commands/init.ts
|
|
35
|
+
import { initProject } from "kontex-core";
|
|
36
|
+
function registerInitCommand(program) {
|
|
37
|
+
program.command("init").description("Initialize kontex in the current project").option("--ai", "Run LLM extraction pass").option("--no-hooks", "Skip git hook installation").option("--force", "Re-run init even if .context/ exists").action(async (opts) => {
|
|
38
|
+
try {
|
|
39
|
+
await initProject(process.cwd(), { ai: opts.ai, noHooks: opts.noHooks, force: opts.force });
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.error("Init failed:", e instanceof Error ? e.message : e);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/commands/compile.ts
|
|
48
|
+
import { compile, loadConfig } from "kontex-core";
|
|
49
|
+
function registerCompileCommand(program) {
|
|
50
|
+
program.command("compile").description("Regenerate .context/KONTEX.md").option("--budget <tokens>", "Override token budget", parseInt).action(async (opts) => {
|
|
51
|
+
try {
|
|
52
|
+
const config = loadConfig(process.cwd());
|
|
53
|
+
if (opts.budget)
|
|
54
|
+
config.compile.tokenBudget = opts.budget;
|
|
55
|
+
await compile(process.cwd(), config);
|
|
56
|
+
console.log("\u2713 KONTEX.md compiled");
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error("Compile failed:", e instanceof Error ? e.message : e);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/commands/find.ts
|
|
65
|
+
import { findMemories } from "kontex-core";
|
|
66
|
+
function registerFindCommand(program) {
|
|
67
|
+
program.command("find <query>").description("Semantic search across memory").option("--limit <n>", "Number of results", parseInt, 5).action(async (query, opts) => {
|
|
68
|
+
try {
|
|
69
|
+
const results = await findMemories(query, opts.limit, process.cwd());
|
|
70
|
+
if (results.length === 0) {
|
|
71
|
+
console.log("No matching memories found.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log(`
|
|
75
|
+
Found ${results.length} result(s):
|
|
76
|
+
`);
|
|
77
|
+
for (const [i, r] of results.entries()) {
|
|
78
|
+
console.log(`${i + 1}. ${r.verified ? "\u2713" : "\u25CB"} ${r.uri} (${r.type}, similarity: ${r.similarity.toFixed(2)})`);
|
|
79
|
+
console.log(` ${r.content.slice(0, 150).replace(/\n/g, " ")}
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error("Search failed:", e instanceof Error ? e.message : e);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/commands/status.ts
|
|
90
|
+
import { loadConfig as loadConfig2, loadAllEntries, isAuthenticated } from "kontex-core";
|
|
91
|
+
import { existsSync, statSync } from "fs";
|
|
92
|
+
import { join } from "path";
|
|
93
|
+
function registerStatusCommand(program) {
|
|
94
|
+
program.command("status").description("Show memory store health").action(async () => {
|
|
95
|
+
try {
|
|
96
|
+
const cwd = process.cwd();
|
|
97
|
+
const config = loadConfig2(cwd);
|
|
98
|
+
const entries = loadAllEntries(cwd);
|
|
99
|
+
console.log(`Auth: ${await isAuthenticated() ? "authenticated" : "not authenticated \u2014 run `kontex login`"}`);
|
|
100
|
+
const verified = entries.filter((e) => e.verified);
|
|
101
|
+
console.log(`Memory entries: ${entries.length} (${verified.length} verified, ${entries.length - verified.length} unverified)`);
|
|
102
|
+
const types = new Map;
|
|
103
|
+
for (const e of entries)
|
|
104
|
+
types.set(e.type, (types.get(e.type) ?? 0) + 1);
|
|
105
|
+
if (types.size)
|
|
106
|
+
console.log(`Types: ${[...types.entries()].map(([t, c]) => `${t}: ${c}`).join(" | ")}`);
|
|
107
|
+
const customMd = join(cwd, ".context", "KONTEX.md");
|
|
108
|
+
if (existsSync(customMd)) {
|
|
109
|
+
const s = statSync(customMd);
|
|
110
|
+
console.log(`KONTEX.md: compiled ${timeSince(s.mtime)}`);
|
|
111
|
+
} else
|
|
112
|
+
console.log("KONTEX.md: not found \u2014 run `kontex compile`");
|
|
113
|
+
console.log(`LLM: ${config.llm.provider} / ${config.llm.model}`);
|
|
114
|
+
const stale = entries.filter((e) => e.stale);
|
|
115
|
+
if (stale.length > 0)
|
|
116
|
+
console.log(`
|
|
117
|
+
\u26A0 ${stale.length} stale entries \u2014 run \`kontex audit\``);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error("Status failed:", e instanceof Error ? e.message : e);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function timeSince(date) {
|
|
125
|
+
const s = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
126
|
+
if (s < 60)
|
|
127
|
+
return `${s}s ago`;
|
|
128
|
+
const m = Math.floor(s / 60);
|
|
129
|
+
if (m < 60)
|
|
130
|
+
return `${m}m ago`;
|
|
131
|
+
const h = Math.floor(m / 60);
|
|
132
|
+
if (h < 24)
|
|
133
|
+
return `${h}h ago`;
|
|
134
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/commands/audit.ts
|
|
138
|
+
import { loadAllEntries as loadAllEntries2 } from "kontex-core";
|
|
139
|
+
import { readFileSync, unlinkSync } from "fs";
|
|
140
|
+
import { join as join2 } from "path";
|
|
141
|
+
import readline from "readline";
|
|
142
|
+
function registerAuditCommand(program) {
|
|
143
|
+
program.command("audit").description("Interactive review of stale and unverified entries").action(async () => {
|
|
144
|
+
try {
|
|
145
|
+
const cwd = process.cwd();
|
|
146
|
+
const entries = loadAllEntries2(cwd);
|
|
147
|
+
const stale = entries.filter((e) => e.stale);
|
|
148
|
+
const unverified = entries.filter((e) => !e.verified && !e.stale);
|
|
149
|
+
if (stale.length === 0 && unverified.length === 0) {
|
|
150
|
+
console.log("\u2713 Memory store is healthy.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
console.log(`
|
|
154
|
+
kontex audit
|
|
155
|
+
`);
|
|
156
|
+
if (stale.length > 0)
|
|
157
|
+
console.log(`\u26A0 ${stale.length} stale entries`);
|
|
158
|
+
if (unverified.length > 0)
|
|
159
|
+
console.log(`\u25CB ${unverified.length} unverified entries`);
|
|
160
|
+
console.log(`
|
|
161
|
+
---
|
|
162
|
+
`);
|
|
163
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
164
|
+
const ask = (q) => new Promise((r) => rl.question(q, r));
|
|
165
|
+
for (const entry of stale) {
|
|
166
|
+
console.log(`
|
|
167
|
+
\u26A0 STALE: ${entry.uri} (${entry.type})`);
|
|
168
|
+
console.log(` ${entry.l0 || entry.content.slice(0, 100)}`);
|
|
169
|
+
const action = await ask(" [d]elete [k]eep [v]iew > ");
|
|
170
|
+
if (action === "d") {
|
|
171
|
+
if (await ask(" Confirm? (y/n) > ") === "y") {
|
|
172
|
+
try {
|
|
173
|
+
unlinkSync(join2(cwd, ".context", `${entry.uri}.md`));
|
|
174
|
+
console.log(" \u2713 Deleted");
|
|
175
|
+
} catch {
|
|
176
|
+
console.log(" \u2717 Not found");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else if (action === "v") {
|
|
180
|
+
try {
|
|
181
|
+
console.log(`
|
|
182
|
+
` + readFileSync(join2(cwd, ".context", `${entry.uri}.md`), "utf-8"));
|
|
183
|
+
} catch {
|
|
184
|
+
console.log(" \u2717 Not found");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const entry of unverified) {
|
|
189
|
+
console.log(`
|
|
190
|
+
\u25CB UNVERIFIED: ${entry.uri} (${entry.type}, refs: ${entry.ref_count})`);
|
|
191
|
+
console.log(` ${entry.l0 || entry.content.slice(0, 100)}`);
|
|
192
|
+
const action = await ask(" [d]elete [p]romote [k]eep > ");
|
|
193
|
+
if (action === "d") {
|
|
194
|
+
if (await ask(" Confirm? (y/n) > ") === "y") {
|
|
195
|
+
try {
|
|
196
|
+
unlinkSync(join2(cwd, ".context", `${entry.uri}.md`));
|
|
197
|
+
console.log(" \u2713 Deleted");
|
|
198
|
+
} catch {
|
|
199
|
+
console.log(" \u2717 Not found");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else if (action === "p") {
|
|
203
|
+
try {
|
|
204
|
+
const { default: matter } = await import("gray-matter");
|
|
205
|
+
const filePath = join2(cwd, ".context", `${entry.uri}.md`);
|
|
206
|
+
const parsed = matter(readFileSync(filePath, "utf-8"));
|
|
207
|
+
parsed.data.verified = true;
|
|
208
|
+
parsed.data.updated = new Date().toISOString();
|
|
209
|
+
const { writeFileSync } = await import("fs");
|
|
210
|
+
writeFileSync(filePath, matter.stringify(parsed.content, parsed.data), "utf-8");
|
|
211
|
+
console.log(" \u2713 Promoted");
|
|
212
|
+
} catch {
|
|
213
|
+
console.log(" \u2717 Failed");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
rl.close();
|
|
218
|
+
console.log(`
|
|
219
|
+
\u2713 Audit complete.
|
|
220
|
+
`);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
console.error("Audit failed:", e instanceof Error ? e.message : e);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/commands/daemon.ts
|
|
229
|
+
import { runDecay, loadConfig as loadConfig3 } from "kontex-core";
|
|
230
|
+
function registerDaemonCommand(program) {
|
|
231
|
+
program.command("daemon").argument("[action]", "start | stop | status", "start").description("Background compression daemon").action(async (action) => {
|
|
232
|
+
const cwd = process.cwd();
|
|
233
|
+
if (action === "start") {
|
|
234
|
+
console.log(`Running decay cycle...
|
|
235
|
+
`);
|
|
236
|
+
const result = await runDecay(cwd, loadConfig3(cwd));
|
|
237
|
+
console.log(`Promoted: ${result.promoted.length}
|
|
238
|
+
Expired: ${result.expired.length}
|
|
239
|
+
Archived: ${result.archived.length}
|
|
240
|
+
Flagged stale: ${result.flaggedStale.length}`);
|
|
241
|
+
console.log(`
|
|
242
|
+
\u2713 Decay cycle complete.`);
|
|
243
|
+
} else if (action === "status") {
|
|
244
|
+
console.log("Daemon status: manual mode");
|
|
245
|
+
} else if (action === "stop") {
|
|
246
|
+
console.log("Daemon stopped.");
|
|
247
|
+
} else {
|
|
248
|
+
console.error(`Unknown action: ${action}`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/commands/hooks.ts
|
|
255
|
+
import { HOOK_TEMPLATES } from "kontex-core";
|
|
256
|
+
import { writeFileSync, mkdirSync, existsSync as existsSync2, chmodSync } from "fs";
|
|
257
|
+
import { join as join3 } from "path";
|
|
258
|
+
function registerHooksCommand(program) {
|
|
259
|
+
program.command("hooks").argument("<action>", "install").description("Manage git hooks").action((action) => {
|
|
260
|
+
if (action !== "install") {
|
|
261
|
+
console.error(`Unknown action: ${action}`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
const cwd = process.cwd();
|
|
265
|
+
if (!existsSync2(join3(cwd, ".git"))) {
|
|
266
|
+
console.error("Not a git repository.");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
const hooksDir = join3(cwd, ".git", "hooks");
|
|
270
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
271
|
+
for (const [name, content] of Object.entries(HOOK_TEMPLATES)) {
|
|
272
|
+
const p = join3(hooksDir, name);
|
|
273
|
+
writeFileSync(p, content + `
|
|
274
|
+
`, "utf-8");
|
|
275
|
+
chmodSync(p, 493);
|
|
276
|
+
}
|
|
277
|
+
console.log("\u2713 Installed git hooks (pre-commit, post-commit, post-merge)");
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/commands/mcp.ts
|
|
282
|
+
import { createMCPServer } from "kontex-core";
|
|
283
|
+
function registerMcpCommand(program) {
|
|
284
|
+
program.command("mcp").description("Start the MCP server (used by AI tools)").action(async () => {
|
|
285
|
+
try {
|
|
286
|
+
await createMCPServer(process.env.KONTEX_WORKSPACE || process.cwd());
|
|
287
|
+
} catch (e) {
|
|
288
|
+
console.error("MCP server failed:", e instanceof Error ? e.message : e);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/commands/hook.ts
|
|
295
|
+
import { handlePreCommit, handlePostCommit } from "kontex-core";
|
|
296
|
+
function registerHookCommand(program) {
|
|
297
|
+
const hookCmd = program.command("hook").description("Internal: called by git hooks");
|
|
298
|
+
hookCmd.command("pre-commit").option("--staged <files>", "Staged files").action(async (opts) => {
|
|
299
|
+
try {
|
|
300
|
+
await handlePreCommit(opts.staged ?? "", process.cwd());
|
|
301
|
+
} catch {}
|
|
302
|
+
});
|
|
303
|
+
hookCmd.command("post-commit").option("--sha <sha>", "Commit SHA").option("--author <email>", "Author").action(async (opts) => {
|
|
304
|
+
try {
|
|
305
|
+
await handlePostCommit(opts.sha ?? "", opts.author ?? "", process.cwd());
|
|
306
|
+
} catch {}
|
|
307
|
+
});
|
|
308
|
+
hookCmd.command("post-merge").action(async () => {
|
|
309
|
+
try {
|
|
310
|
+
const { handlePostMerge } = await import("kontex-core");
|
|
311
|
+
await handlePostMerge(process.cwd());
|
|
312
|
+
} catch {}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/index.ts
|
|
317
|
+
var program = new Command;
|
|
318
|
+
program.name("kontex").description("Persistent, git-native memory for AI-assisted development").version("1.0.0");
|
|
319
|
+
registerLoginCommand(program);
|
|
320
|
+
registerLogoutCommand(program);
|
|
321
|
+
registerInitCommand(program);
|
|
322
|
+
registerCompileCommand(program);
|
|
323
|
+
registerFindCommand(program);
|
|
324
|
+
registerStatusCommand(program);
|
|
325
|
+
registerAuditCommand(program);
|
|
326
|
+
registerDaemonCommand(program);
|
|
327
|
+
registerHooksCommand(program);
|
|
328
|
+
registerMcpCommand(program);
|
|
329
|
+
registerHookCommand(program);
|
|
330
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kontex-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for kontex — thin commander wrapper over kontex-core",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ArekBee/kontex.git",
|
|
10
|
+
"directory": "packages/kontex-cli"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/ArekBee/kontex#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ArekBee/kontex/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"kontex",
|
|
18
|
+
"cli",
|
|
19
|
+
"ai",
|
|
20
|
+
"memory",
|
|
21
|
+
"context",
|
|
22
|
+
"llm",
|
|
23
|
+
"mcp",
|
|
24
|
+
"coding-assistant"
|
|
25
|
+
],
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"bin": {
|
|
32
|
+
"kontex": "dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --packages external",
|
|
42
|
+
"test": "bun test",
|
|
43
|
+
"dev": "bun --watch src/index.ts",
|
|
44
|
+
"lint": "eslint src/"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"kontex-core": "workspace:*",
|
|
48
|
+
"commander": "^13.1.0",
|
|
49
|
+
"chalk": "^5.4.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"bun-types": "^1.1.0",
|
|
53
|
+
"typescript": "^5.7.0"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
}
|
|
58
|
+
}
|