invoket 0.1.7 → 0.1.9
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 +339 -101
- package/package.json +1 -1
- package/src/cli.ts +35 -594
- package/src/context.ts +12 -3
- package/src/parser.ts +601 -0
package/README.md
CHANGED
|
@@ -1,74 +1,91 @@
|
|
|
1
1
|
# invoket
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript task runner for Bun. Write typed methods, get a CLI for free.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- **Type-safe CLI parsing** — TypeScript types determine how arguments are parsed
|
|
8
|
-
- **Zero configuration** — Just write a `Tasks` class with typed methods
|
|
9
|
-
- **JSON support** — Object and array parameters are automatically parsed from JSON
|
|
10
|
-
- **Namespace support** — Organize tasks with `db:migrate` style namespaces
|
|
11
|
-
- **Rest parameters** — Support for `...args` variadic parameters
|
|
12
|
-
- **Auto-generated help** — JSDoc descriptions become CLI help text
|
|
5
|
+
```typescript
|
|
6
|
+
import { Context } from "invoket/context";
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
export class Tasks {
|
|
9
|
+
/** Deploy to an environment */
|
|
10
|
+
async deploy(c: Context, env: string, force: boolean = false) {
|
|
11
|
+
await c.run(`deploy.sh ${env}${force ? " --force" : ""}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
$ invt deploy prod --force
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
No config files. No argument parser boilerplate. Your TypeScript types *are* the CLI definition.
|
|
21
|
+
|
|
22
|
+
## Why not just write a script?
|
|
23
|
+
|
|
24
|
+
You could. But then you write arg parsing, help text, and error handling every time. With invoket, you write a method and get all three for free. Your `tasks.ts` becomes a growing toolbox — every task documented, discoverable via `invt --help`, and callable by name with typed arguments.
|
|
25
|
+
|
|
26
|
+
One script solves one problem. A `tasks.ts` file is a project's command centre.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
21
29
|
|
|
22
30
|
```bash
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
invt db:migrate up # Run namespaced task
|
|
26
|
-
invt --version # Show version
|
|
31
|
+
bun add -d invoket # Add to project
|
|
32
|
+
bun link invoket # Or link globally for development
|
|
27
33
|
```
|
|
28
34
|
|
|
29
|
-
##
|
|
35
|
+
## Quick Start
|
|
30
36
|
|
|
31
|
-
Create
|
|
37
|
+
Create `tasks.ts`:
|
|
32
38
|
|
|
33
39
|
```typescript
|
|
34
40
|
import { Context } from "invoket/context";
|
|
35
41
|
|
|
36
|
-
interface SearchParams {
|
|
37
|
-
query: string;
|
|
38
|
-
limit?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* My project tasks
|
|
43
44
|
*/
|
|
44
45
|
export class Tasks {
|
|
45
|
-
/**
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Say hello
|
|
48
|
+
* @flag name -n
|
|
49
|
+
* @flag count -c
|
|
50
|
+
*/
|
|
51
|
+
async hello(c: Context, name: string, count: number = 1) {
|
|
47
52
|
for (let i = 0; i < count; i++) {
|
|
48
53
|
console.log(`Hello, ${name}!`);
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
/** Search with JSON parameters */
|
|
53
|
-
async search(c: Context, entity: string, params:
|
|
58
|
+
async search(c: Context, entity: string, params: { query: string; limit?: number }) {
|
|
54
59
|
console.log(`Searching ${entity}: ${params.query}`);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
/** Install packages
|
|
62
|
+
/** Install packages */
|
|
58
63
|
async install(c: Context, ...packages: string[]) {
|
|
59
64
|
for (const pkg of packages) {
|
|
60
|
-
await c.run(`
|
|
65
|
+
await c.run(`bun add ${pkg}`);
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
}
|
|
64
69
|
```
|
|
65
70
|
|
|
71
|
+
Run it:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
invt # Show help
|
|
75
|
+
invt hello World # Positional args
|
|
76
|
+
invt hello -n World -c 3 # Short flags
|
|
77
|
+
invt hello --name=World --count=3 # Long flags
|
|
78
|
+
invt search users '{"query":"bob"}' # JSON params
|
|
79
|
+
invt install react vue angular # Rest params
|
|
80
|
+
invt hello -h # Task-specific help
|
|
81
|
+
```
|
|
82
|
+
|
|
66
83
|
## Namespaces
|
|
67
84
|
|
|
68
|
-
|
|
85
|
+
Group related tasks:
|
|
69
86
|
|
|
70
87
|
```typescript
|
|
71
|
-
class
|
|
88
|
+
class Db {
|
|
72
89
|
/** Run database migrations */
|
|
73
90
|
async migrate(c: Context, direction: string = "up") {
|
|
74
91
|
await c.run(`prisma migrate ${direction}`);
|
|
@@ -81,128 +98,349 @@ class DbNamespace {
|
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
export class Tasks {
|
|
84
|
-
db = new
|
|
101
|
+
db = new Db();
|
|
85
102
|
}
|
|
86
103
|
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
```bash
|
|
106
|
+
invt db:migrate up # colon separator
|
|
107
|
+
invt db.seed # dot separator also works
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Arguments
|
|
89
111
|
|
|
90
|
-
|
|
112
|
+
### Type Mapping
|
|
91
113
|
|
|
92
|
-
| TypeScript | CLI
|
|
93
|
-
|
|
94
|
-
| `name: string` | `<name>` | `hello` |
|
|
95
|
-
| `name: string = "default"` | `[name]` | `hello`
|
|
114
|
+
| TypeScript | CLI | Example |
|
|
115
|
+
|------------|-----|---------|
|
|
116
|
+
| `name: string` | `<name>` (required) | `hello` |
|
|
117
|
+
| `name: string = "default"` | `[name]` (optional) | `hello` |
|
|
96
118
|
| `count: number` | `<count>` | `42` |
|
|
97
|
-
| `force: boolean` | `<force>` | `true`
|
|
119
|
+
| `force: boolean` | `<force>` | `true`, `1`, `false`, `0` |
|
|
98
120
|
| `params: SomeInterface` | `<params>` | `'{"key": "value"}'` |
|
|
99
|
-
| `items: string[]` | `<items>` | `'["a", "b"
|
|
100
|
-
| `...args: string[]` | `[args...]` | `a b c`
|
|
121
|
+
| `items: string[]` | `<items>` | `'["a", "b"]'` |
|
|
122
|
+
| `...args: string[]` | `[args...]` (variadic) | `a b c` |
|
|
101
123
|
|
|
102
|
-
|
|
124
|
+
### Flags
|
|
103
125
|
|
|
104
|
-
|
|
105
|
-
|------|-------------|
|
|
106
|
-
| `-h`, `--help` | Show help with all tasks |
|
|
107
|
-
| `<task> -h` | Show help for a specific task |
|
|
108
|
-
| `-l`, `--list` | List available tasks |
|
|
109
|
-
| `--version` | Show version |
|
|
110
|
-
|
|
111
|
-
### Task-Specific Help
|
|
126
|
+
Every parameter automatically gets a `--long` flag. Add `@flag` annotations for short flags and aliases:
|
|
112
127
|
|
|
113
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
/**
|
|
130
|
+
* @flag env -e --environment
|
|
131
|
+
* @flag force -f
|
|
132
|
+
*/
|
|
133
|
+
async deploy(c: Context, env: string, force: boolean = false) {}
|
|
134
|
+
```
|
|
114
135
|
|
|
115
136
|
```bash
|
|
116
|
-
invt
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
# count number (required)
|
|
124
|
-
|
|
125
|
-
invt db:migrate --help
|
|
126
|
-
# Usage: invt db:migrate [direction]
|
|
127
|
-
#
|
|
128
|
-
# Run database migrations
|
|
129
|
-
#
|
|
130
|
-
# Arguments:
|
|
131
|
-
# direction string (optional)
|
|
137
|
+
invt deploy prod # positional
|
|
138
|
+
invt deploy --env=prod --force # long flags
|
|
139
|
+
invt deploy -e prod -f # short flags
|
|
140
|
+
invt deploy --environment=prod # alias
|
|
141
|
+
invt deploy --no-force # boolean negation
|
|
142
|
+
invt deploy --force=false # explicit boolean
|
|
143
|
+
invt install -- --not-a-flag # -- stops flag parsing
|
|
132
144
|
```
|
|
133
145
|
|
|
146
|
+
Flags and positional args can be freely mixed in any order.
|
|
147
|
+
|
|
148
|
+
### CLI Flags
|
|
149
|
+
|
|
150
|
+
| Flag | Description |
|
|
151
|
+
|------|-------------|
|
|
152
|
+
| `-h`, `--help` | Show all tasks |
|
|
153
|
+
| `<task> -h` | Help for a specific task |
|
|
154
|
+
| `-l`, `--list` | List tasks |
|
|
155
|
+
| `--version` | Show version |
|
|
156
|
+
|
|
134
157
|
## Context API
|
|
135
158
|
|
|
136
|
-
Every task receives a `Context`
|
|
159
|
+
Every task receives a `Context` for shell execution:
|
|
137
160
|
|
|
138
161
|
```typescript
|
|
139
162
|
async deploy(c: Context, env: string) {
|
|
140
|
-
//
|
|
141
|
-
await c.run("
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
//
|
|
147
|
-
await c.run("rm -f temp.txt", { warn: true });
|
|
148
|
-
|
|
149
|
-
// Echo command before running
|
|
150
|
-
await c.run("npm test", { echo: true });
|
|
151
|
-
|
|
152
|
-
// Change directory temporarily
|
|
153
|
-
for await (const _ of c.cd("subdir")) {
|
|
163
|
+
await c.run("npm run build"); // run command
|
|
164
|
+
const { stdout } = await c.run("git rev-parse HEAD", { hide: true }); // capture output
|
|
165
|
+
await c.run("rm -f temp.txt", { warn: true }); // ignore errors
|
|
166
|
+
await c.run("npm test", { echo: true }); // echo before running
|
|
167
|
+
await c.run("make", { stream: true }); // stream output in real-time
|
|
168
|
+
|
|
169
|
+
for await (const _ of c.cd("subdir")) { // temporary cd
|
|
154
170
|
await c.run("ls");
|
|
155
171
|
}
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
await c.
|
|
159
|
-
|
|
160
|
-
// Access config
|
|
161
|
-
console.log(c.config); // { echo: false, warn: false, ... }
|
|
162
|
-
|
|
163
|
-
// local() is alias for run()
|
|
164
|
-
await c.local("echo hello");
|
|
172
|
+
|
|
173
|
+
await c.sudo("apt update"); // sudo prefix
|
|
174
|
+
await c.local("echo hello"); // alias for run()
|
|
165
175
|
}
|
|
166
176
|
```
|
|
167
177
|
|
|
168
|
-
###
|
|
178
|
+
### Options
|
|
169
179
|
|
|
170
180
|
| Option | Type | Default | Description |
|
|
171
181
|
|--------|------|---------|-------------|
|
|
172
182
|
| `echo` | boolean | false | Print command before execution |
|
|
173
183
|
| `warn` | boolean | false | Don't throw on non-zero exit |
|
|
174
184
|
| `hide` | boolean | false | Capture output instead of printing |
|
|
175
|
-
| `
|
|
185
|
+
| `stream` | boolean | false | Stream output in real-time |
|
|
186
|
+
| `cwd` | string | `process.cwd()` | Working directory |
|
|
176
187
|
|
|
177
188
|
### RunResult
|
|
178
189
|
|
|
179
190
|
```typescript
|
|
180
191
|
interface RunResult {
|
|
181
|
-
stdout: string;
|
|
192
|
+
stdout: string; // captured output (empty when streaming)
|
|
182
193
|
stderr: string;
|
|
183
194
|
code: number;
|
|
184
|
-
ok: boolean;
|
|
185
|
-
failed: boolean;
|
|
195
|
+
ok: boolean; // code === 0
|
|
196
|
+
failed: boolean; // code !== 0
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Error Handling
|
|
201
|
+
|
|
202
|
+
Failed commands throw `CommandError`:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { CommandError } from "invoket/context";
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
await c.run("exit 1");
|
|
209
|
+
} catch (e) {
|
|
210
|
+
if (e instanceof CommandError) {
|
|
211
|
+
console.log(e.result.code); // 1
|
|
212
|
+
console.log(e.result.stderr);
|
|
213
|
+
}
|
|
186
214
|
}
|
|
187
215
|
```
|
|
188
216
|
|
|
217
|
+
Use `{ warn: true }` to suppress throws and inspect the result instead.
|
|
218
|
+
|
|
189
219
|
## Private Methods
|
|
190
220
|
|
|
191
|
-
|
|
221
|
+
Prefix with `_` to hide from CLI:
|
|
192
222
|
|
|
193
223
|
```typescript
|
|
194
224
|
export class Tasks {
|
|
195
|
-
async publicTask(c: Context) {
|
|
196
|
-
|
|
225
|
+
async publicTask(c: Context) {
|
|
226
|
+
this._helper();
|
|
227
|
+
}
|
|
228
|
+
async _helper() { } // not discoverable, not callable via CLI
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Patterns
|
|
233
|
+
|
|
234
|
+
### Project setup
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
/** Bootstrap dev environment */
|
|
238
|
+
async setup(c: Context) {
|
|
239
|
+
await c.run("bun install");
|
|
240
|
+
await c.run("cp .env.example .env", { warn: true });
|
|
241
|
+
await c.run("bun run db:migrate");
|
|
242
|
+
console.log("Ready to go!");
|
|
197
243
|
}
|
|
198
244
|
```
|
|
199
245
|
|
|
200
|
-
|
|
246
|
+
### Git workflow
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
/**
|
|
250
|
+
* Commit and push current branch
|
|
251
|
+
* @flag message -m
|
|
252
|
+
*/
|
|
253
|
+
async ship(c: Context, message: string) {
|
|
254
|
+
const { stdout } = await c.run("git branch --show-current", { hide: true });
|
|
255
|
+
await c.run("git add -A");
|
|
256
|
+
await c.run(`git commit -m "${message}"`);
|
|
257
|
+
await c.run(`git push -u origin ${stdout.trim()}`);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Run with fallback
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
/** Lint and fix */
|
|
265
|
+
async lint(c: Context) {
|
|
266
|
+
const result = await c.run("eslint . --fix", { warn: true, hide: true });
|
|
267
|
+
if (result.failed) {
|
|
268
|
+
console.log("Lint errors remain:");
|
|
269
|
+
console.log(result.stdout);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Capture and transform
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
/** Show outdated deps */
|
|
278
|
+
async deps(c: Context) {
|
|
279
|
+
const { stdout } = await c.run("bun outdated --json", { hide: true, warn: true });
|
|
280
|
+
const deps = JSON.parse(stdout || "[]");
|
|
281
|
+
for (const d of deps) console.log(`${d.name}: ${d.current} → ${d.latest}`);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Scaffold files
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
/** @flag name -n */
|
|
289
|
+
async component(c: Context, name: string) {
|
|
290
|
+
const upper = name[0].toUpperCase() + name.slice(1);
|
|
291
|
+
await c.run(`mkdir -p src/components/${name}`);
|
|
292
|
+
await c.run(`cat > src/components/${name}/index.tsx << 'EOF'
|
|
293
|
+
export function ${upper}() {
|
|
294
|
+
return <div>${upper}</div>;
|
|
295
|
+
}
|
|
296
|
+
EOF`);
|
|
297
|
+
console.log(`Created src/components/${name}/index.tsx`);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Agentic Tools
|
|
302
|
+
|
|
303
|
+
AI agents like Claude Code have built-in tools for searching files, reading code, and running commands. What they lack is **project context** — the state of migrations, the history of decisions, the shape of your API, which tests are flaky and why. That knowledge lives in developers' heads, scattered across commits, issues, and Slack threads.
|
|
304
|
+
|
|
305
|
+
invoket lets you build a **structured, queryable project knowledge base** that agents can read and write through the same CLI interface humans use. Bun's built-in SQLite makes this trivial — no external database, no setup, just a `.ctx.db` file that travels with the project.
|
|
306
|
+
|
|
307
|
+
### Project context — a SQLite-backed knowledge base
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { Context } from "invoket/context";
|
|
311
|
+
import { Database } from "bun:sqlite";
|
|
312
|
+
|
|
313
|
+
class Ctx {
|
|
314
|
+
private db: Database;
|
|
315
|
+
|
|
316
|
+
constructor() {
|
|
317
|
+
this.db = new Database(".ctx.db", { create: true });
|
|
318
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS context (
|
|
319
|
+
key TEXT PRIMARY KEY,
|
|
320
|
+
value TEXT NOT NULL,
|
|
321
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
322
|
+
)`);
|
|
323
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS decisions (
|
|
324
|
+
id INTEGER PRIMARY KEY,
|
|
325
|
+
subject TEXT NOT NULL,
|
|
326
|
+
decision TEXT NOT NULL,
|
|
327
|
+
rationale TEXT,
|
|
328
|
+
status TEXT DEFAULT 'active',
|
|
329
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
330
|
+
)`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Store a key-value fact about the project */
|
|
334
|
+
async set(c: Context, key: string, ...value: string[]) {
|
|
335
|
+
this.db.run(
|
|
336
|
+
`INSERT OR REPLACE INTO context (key, value, updated_at)
|
|
337
|
+
VALUES (?, ?, datetime('now'))`,
|
|
338
|
+
[key, value.join(" ")]
|
|
339
|
+
);
|
|
340
|
+
console.log(`Set: ${key}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Retrieve a fact */
|
|
344
|
+
async get(c: Context, key: string) {
|
|
345
|
+
const row = this.db.query("SELECT value, updated_at FROM context WHERE key = ?").get(key) as any;
|
|
346
|
+
if (!row) { console.log(`Not found: ${key}`); return; }
|
|
347
|
+
console.log(`${row.value} (${row.updated_at})`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Search facts by keyword */
|
|
351
|
+
async search(c: Context, ...terms: string[]) {
|
|
352
|
+
const pattern = `%${terms.join(" ")}%`;
|
|
353
|
+
const rows = this.db.query(
|
|
354
|
+
"SELECT key, value FROM context WHERE key LIKE ? OR value LIKE ?"
|
|
355
|
+
).all(pattern, pattern) as any[];
|
|
356
|
+
for (const r of rows) console.log(`${r.key}: ${r.value}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Record an architectural decision */
|
|
360
|
+
async decide(c: Context, subject: string, decision: string, ...rationale: string[]) {
|
|
361
|
+
this.db.run(
|
|
362
|
+
"INSERT INTO decisions (subject, decision, rationale) VALUES (?, ?, ?)",
|
|
363
|
+
[subject, decision, rationale.join(" ")]
|
|
364
|
+
);
|
|
365
|
+
console.log(`Recorded: ${subject}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/** List active decisions */
|
|
369
|
+
async decisions(c: Context) {
|
|
370
|
+
const rows = this.db.query(
|
|
371
|
+
"SELECT id, subject, decision, rationale FROM decisions WHERE status = 'active' ORDER BY created_at DESC"
|
|
372
|
+
).all() as any[];
|
|
373
|
+
for (const r of rows) {
|
|
374
|
+
console.log(`#${r.id} ${r.subject}: ${r.decision}`);
|
|
375
|
+
if (r.rationale) console.log(` ${r.rationale}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Dump all context as JSON */
|
|
380
|
+
async dump(c: Context) {
|
|
381
|
+
const facts = this.db.query("SELECT key, value FROM context ORDER BY key").all();
|
|
382
|
+
const decisions = this.db.query("SELECT * FROM decisions WHERE status = 'active'").all();
|
|
383
|
+
console.log(JSON.stringify({ facts, decisions }, null, 2));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export class Tasks {
|
|
388
|
+
ctx = new Ctx();
|
|
389
|
+
}
|
|
390
|
+
```
|
|
201
391
|
|
|
202
392
|
```bash
|
|
203
|
-
|
|
393
|
+
# Store project facts
|
|
394
|
+
invt ctx:set db "Postgres 16 on Supabase, migrations in prisma/"
|
|
395
|
+
invt ctx:set api "REST with /api/v2 prefix, auth via JWT middleware"
|
|
396
|
+
invt ctx:set deploy "Fly.io, auto-deploy on push to main"
|
|
397
|
+
|
|
398
|
+
# Record decisions with rationale
|
|
399
|
+
invt ctx:decide auth "JWT in httpOnly cookies" "Chose over localStorage for XSS protection"
|
|
400
|
+
invt ctx:decide orm "Prisma over Drizzle" "Team familiarity, existing migrations"
|
|
401
|
+
|
|
402
|
+
# Query context
|
|
403
|
+
invt ctx:get db
|
|
404
|
+
invt ctx:search auth
|
|
405
|
+
invt ctx:decisions
|
|
406
|
+
|
|
407
|
+
# Dump everything for agent context
|
|
408
|
+
invt ctx:dump
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
An agent starts a session with `invt ctx:dump` and immediately has structured project knowledge — not flat markdown, not grep results, but queryable facts and decisions with timestamps.
|
|
412
|
+
|
|
413
|
+
### Why SQLite?
|
|
414
|
+
|
|
415
|
+
Bun bundles SQLite natively — `import { Database } from "bun:sqlite"` just works. No dependencies, no server, no config. The `.ctx.db` file is a single file you can `.gitignore` or commit. You can extend the schema as the project grows: add tables for endpoints, test history, deployment logs, whatever your project needs.
|
|
416
|
+
|
|
417
|
+
### Growing the schema
|
|
418
|
+
|
|
419
|
+
The example above is a starting point. A real project might track more:
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Track API endpoints and their status
|
|
423
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS endpoints (
|
|
424
|
+
path TEXT PRIMARY KEY,
|
|
425
|
+
method TEXT,
|
|
426
|
+
handler TEXT,
|
|
427
|
+
auth TEXT DEFAULT 'required',
|
|
428
|
+
status TEXT DEFAULT 'active'
|
|
429
|
+
)`);
|
|
430
|
+
|
|
431
|
+
// Track test health
|
|
432
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS test_runs (
|
|
433
|
+
id INTEGER PRIMARY KEY,
|
|
434
|
+
suite TEXT,
|
|
435
|
+
passed INTEGER,
|
|
436
|
+
failed INTEGER,
|
|
437
|
+
skipped INTEGER,
|
|
438
|
+
ran_at TEXT DEFAULT (datetime('now'))
|
|
439
|
+
)`);
|
|
204
440
|
```
|
|
205
441
|
|
|
442
|
+
The namespace class is the interface. The schema is yours to shape around what your project actually needs to remember.
|
|
443
|
+
|
|
206
444
|
## Requirements
|
|
207
445
|
|
|
208
446
|
- Bun >= 1.0.0
|