@rendotdev/rig 0.0.17 → 0.0.19

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.
Files changed (28) hide show
  1. package/README.md +4 -0
  2. package/dist/{run-y66hvyxg.js → cli-113n7c3t.js} +158 -16
  3. package/dist/{cli-ne7ed594.js → cli-13q90bjr.js} +12 -0
  4. package/dist/{cli-vx11bmzr.js → cli-1hm5dxfw.js} +2 -2
  5. package/dist/{cli-dshh1cp9.js → cli-884hpkjc.js} +1 -1
  6. package/dist/cli-es8j6mma.js +226 -0
  7. package/dist/{cli-7zqnqrah.js → cli-f7p31fzm.js} +106 -42
  8. package/dist/{cli-xv4m20sx.js → cli-nk194xv0.js} +198 -71
  9. package/dist/{cli-mswdavqx.js → cli-zgw1jbeg.js} +3 -3
  10. package/dist/{config-24gwe08t.js → config-20f90shh.js} +2 -3
  11. package/dist/{create-59myk91d.js → create-pvym2abv.js} +7 -8
  12. package/dist/cron-aw908dzn.js +18 -0
  13. package/dist/{dev-link-jpte1gq9.js → dev-link-e1mj8w17.js} +7 -4
  14. package/dist/{discover-fd9e6j9j.js → discover-95bpzy46.js} +3 -4
  15. package/dist/{help-6k8m07yv.js → help-qj75r0x3.js} +5 -6
  16. package/dist/{inspect-ks3x7s4n.js → inspect-0ckw7bs5.js} +5 -6
  17. package/dist/list-reekhy3q.js +13 -0
  18. package/dist/{paths-hq1vy0wh.js → paths-3vxw7dek.js} +1 -1
  19. package/dist/{registry-gng9br0x.js → registry-fzc3aeb8.js} +2 -3
  20. package/dist/rig.js +101 -32
  21. package/dist/run-thx15p83.js +13 -0
  22. package/dist/{runtime-comment-gvmkkcyh.js → runtime-comment-07cpchsr.js} +15 -9
  23. package/dist/{sync-zsc35m3f.js → sync-kkqh3axx.js} +33 -16
  24. package/dist/{typecheck-c2k87ppw.js → typecheck-971tmqbp.js} +5 -7
  25. package/dist/{update-check-87p71vrc.js → update-check-dpkh7hc6.js} +8 -5
  26. package/package.json +2 -2
  27. package/dist/cli-aj56a1ja.js +0 -48
  28. package/dist/list-f7r354tq.js +0 -14
package/README.md CHANGED
@@ -68,11 +68,15 @@ The `rig` CLI is installed on this machine. It allows you to write, run and own
68
68
  - To discover available tools, run `rig list`.
69
69
  - To learn about a tool's usage, run `rig help <tool>`.
70
70
  - To run a tool, use `rig run <tool>.<command> [args]`.
71
+ - Tools run under Bun with fallback auto-install enabled, so tool files can import npm packages; add explicit package versions when reproducibility matters.
72
+ - To schedule a tool command, use `rig cron add <name> <tool>.<command> <schedule> --input '<json>'`; use `rig cron list`, `rig cron run <name>`, and `rig cron remove <name>` to manage scheduled runs.
73
+ - If a tool needs local secrets or settings, put them in the tool folder's `.env`, add an `env` Zod schema to the tool definition, and read validated values from `context.env`.
71
74
  - To create a new tool, run `rig create <tool>`.
72
75
  - To edit an existing tool, run `rig edit <tool>` and open the printed file path.
73
76
  - To remove an existing tool, run `rig remove <tool>`.
74
77
  - To list tool registries, run `rig registry list`.
75
78
  - To add a registry, run `rig registry create [path]` (defaults to current directory).
79
+ - If a tool needs persistent state, define `setupDb` and use `context.db`; Rig stores that SQLite database beside the tool entry file as `index.sqlite`.
76
80
 
77
81
  When rig runs, it keeps detected `AGENTS.md` and `CLAUDE.md` files updated with these instructions and the current `rig list` output.
78
82
 
@@ -1,20 +1,15 @@
1
1
  import {
2
2
  SchemaRenderer
3
- } from "./cli-dshh1cp9.js";
3
+ } from "./cli-884hpkjc.js";
4
4
  import {
5
5
  CommandIds,
6
6
  ToolLoader,
7
7
  createRigToolKit
8
- } from "./cli-7zqnqrah.js";
9
- import"./cli-vx11bmzr.js";
10
- import"./cli-xv4m20sx.js";
11
- import"./cli-aj56a1ja.js";
8
+ } from "./cli-f7p31fzm.js";
12
9
  import {
13
10
  RigError,
14
11
  RigErrors
15
12
  } from "./cli-1c7te5cg.js";
16
- import"./cli-ne7ed594.js";
17
- import"./cli-b7jgjgy7.js";
18
13
 
19
14
  // src/tools/run.ts
20
15
  import { readFile } from "node:fs/promises";
@@ -94,7 +89,11 @@ class OutputTempFileStore {
94
89
  async writeJson(content) {
95
90
  const dir = await mkdtemp(join(tmpdir(), "rig-output-"));
96
91
  const path = join(dir, "data.json");
97
- await writeFile(path, `${content}
92
+ if (typeof Bun !== "undefined")
93
+ await Bun.write(path, `${content}
94
+ `);
95
+ else
96
+ await writeFile(path, `${content}
98
97
  `, "utf8");
99
98
  return path;
100
99
  }
@@ -156,6 +155,138 @@ class RigOutputTruncator {
156
155
  }
157
156
  }
158
157
 
158
+ // src/tools/db.ts
159
+ import { createHash } from "node:crypto";
160
+ import { mkdir } from "node:fs/promises";
161
+ import { dirname, join as join2 } from "node:path";
162
+ class MigrationChecksum {
163
+ digest(version, name, sql) {
164
+ return createHash("sha256").update(`${version}\x00${name}\x00${sql}`).digest("hex");
165
+ }
166
+ }
167
+
168
+ class RigDatabaseMigrator {
169
+ db;
170
+ lastVersion = 0;
171
+ checksums = new MigrationChecksum;
172
+ constructor(db) {
173
+ this.db = db;
174
+ }
175
+ ensureMetadata() {
176
+ this.db.run(`
177
+ create table if not exists _rig_migrations (
178
+ version integer primary key,
179
+ name text not null,
180
+ checksum text not null,
181
+ applied_at text not null
182
+ );
183
+ `);
184
+ }
185
+ migrate(version, name, sql) {
186
+ this.validate(version, name, sql);
187
+ const checksum = this.checksums.digest(version, name, sql);
188
+ const existing = this.existingMigration(version);
189
+ if (existing) {
190
+ this.assertExistingMigrationMatches(version, name, checksum, existing);
191
+ return;
192
+ }
193
+ const apply = this.db.transaction(() => {
194
+ this.db.run(sql);
195
+ this.db.query(`insert into _rig_migrations (version, name, checksum, applied_at)
196
+ values ($version, $name, $checksum, $appliedAt)`).run({ version, name, checksum, appliedAt: new Date().toISOString() });
197
+ });
198
+ apply();
199
+ }
200
+ validate(version, name, sql) {
201
+ if (!Number.isInteger(version) || version <= 0) {
202
+ throw new RigError("TOOL_INVALID", `Migration version must be a positive integer: ${version}`);
203
+ }
204
+ if (version <= this.lastVersion) {
205
+ throw new RigError("TOOL_INVALID", `Migration versions must be declared in ascending order: ${version} after ${this.lastVersion}`);
206
+ }
207
+ if (!name.trim())
208
+ throw new RigError("TOOL_INVALID", "Migration name must not be empty.");
209
+ if (!sql.trim())
210
+ throw new RigError("TOOL_INVALID", `Migration ${version} SQL must not be empty.`);
211
+ this.lastVersion = version;
212
+ }
213
+ existingMigration(version) {
214
+ const row = this.db.query("select name, checksum from _rig_migrations where version = ?").get(version);
215
+ return row ?? undefined;
216
+ }
217
+ assertExistingMigrationMatches(version, name, checksum, existing) {
218
+ if (existing.name === name && existing.checksum === checksum)
219
+ return;
220
+ throw new RigError("TOOL_INVALID", `Migration ${version} has changed since it was applied.`, {
221
+ version,
222
+ expected: existing,
223
+ actual: { name, checksum }
224
+ });
225
+ }
226
+ }
227
+
228
+ class BunSqliteModuleLoader {
229
+ async database() {
230
+ const database = globalThis.rigSqliteDatabaseForTests;
231
+ if (!database)
232
+ return this.nativeDatabase();
233
+ return database;
234
+ }
235
+ async nativeDatabase() {
236
+ const specifier = "bun:sqlite";
237
+ const moduleValue = await import(specifier);
238
+ return moduleValue.Database;
239
+ }
240
+ }
241
+
242
+ class RigToolDatabaseFactory {
243
+ sqlite = new BunSqliteModuleLoader;
244
+ async create(toolPath) {
245
+ const dbPath = this.dbPathForToolPath(toolPath);
246
+ await mkdir(dirname(dbPath), { recursive: true });
247
+ const Database = await this.sqlite.database();
248
+ const db = new Database(dbPath, { create: true, strict: true });
249
+ const migrator = new RigDatabaseMigrator(db);
250
+ Object.defineProperty(db, "path", { value: dbPath, enumerable: true });
251
+ Object.defineProperty(db, "migrate", {
252
+ value: (version, name, sql) => migrator.migrate(version, name, sql),
253
+ enumerable: true
254
+ });
255
+ db.run("PRAGMA journal_mode = WAL;");
256
+ migrator.ensureMetadata();
257
+ return db;
258
+ }
259
+ dbPathForToolPath(toolPath) {
260
+ return join2(dirname(toolPath), "index.sqlite");
261
+ }
262
+ }
263
+
264
+ class UnavailableToolDatabaseFactory {
265
+ create(toolName) {
266
+ return new Proxy({}, {
267
+ get() {
268
+ throw new RigError("TOOL_INVALID", `Tool ${toolName} must define setupDb before using context.db.`, {
269
+ tool: toolName
270
+ });
271
+ }
272
+ });
273
+ }
274
+ }
275
+
276
+ class ToolDatabaseService {
277
+ factory = new RigToolDatabaseFactory;
278
+ async setup(tool) {
279
+ if (!tool.definition.setupDb)
280
+ return;
281
+ const db = await this.factory.create(tool.path);
282
+ await tool.definition.setupDb(db);
283
+ return db;
284
+ }
285
+ dbPathForToolPath(toolPath) {
286
+ return this.factory.dbPathForToolPath(toolPath);
287
+ }
288
+ }
289
+
159
290
  // src/tools/run.ts
160
291
  class RunInputReader {
161
292
  async read(command, options) {
@@ -169,8 +300,8 @@ class RunInputReader {
169
300
  throw new RigError("INPUT_ERROR", "Use args, --input, or --input-file, not more than one.");
170
301
  }
171
302
  if (options.inputFile) {
172
- const raw = await readFile(options.inputFile, "utf8");
173
- return { value: JSON.parse(raw), source: `--input-file ${options.inputFile}` };
303
+ const value = typeof Bun !== "undefined" ? await Bun.file(options.inputFile).json() : JSON.parse(await readFile(options.inputFile, "utf8"));
304
+ return { value, source: `--input-file ${options.inputFile}` };
174
305
  }
175
306
  if (options.input) {
176
307
  return { value: JSON.parse(options.input), source: `--input '${options.input}'` };
@@ -300,16 +431,19 @@ class DryRunPresenter {
300
431
 
301
432
  class ToolRunner {
302
433
  options;
434
+ databases = new ToolDatabaseService;
303
435
  loader;
304
436
  inputReader = new RunInputReader;
305
437
  outputTruncator = new RigOutputTruncator;
438
+ unavailableDatabases = new UnavailableToolDatabaseFactory;
306
439
  constructor(options = {}) {
307
440
  this.options = options;
308
441
  this.loader = new ToolLoader(options);
309
442
  }
310
443
  async run(toolName, commandName, options = {}) {
444
+ let db;
311
445
  try {
312
- const { command } = await this.loader.loadCommand(toolName, commandName);
446
+ const { tool, command } = await this.loader.loadCommand(toolName, commandName);
313
447
  const input = await this.inputReader.read(command, options);
314
448
  const inputResult = command.input.safeParse(input.value);
315
449
  if (!inputResult.success) {
@@ -330,11 +464,13 @@ class ToolRunner {
330
464
  };
331
465
  }
332
466
  const rig = createRigToolKit(this.options);
467
+ db = await this.databases.setup(tool);
333
468
  const data = await command.run({
334
469
  input: inputResult.data,
335
- env: process.env,
470
+ env: tool.env,
471
+ processEnv: process.env,
336
472
  cwd: process.cwd(),
337
- shell: rig.shell,
473
+ db: db ?? this.unavailableDatabases.create(toolName),
338
474
  rig
339
475
  });
340
476
  const outputResult = command.output.safeParse(data);
@@ -357,8 +493,15 @@ class ToolRunner {
357
493
  }),
358
494
  exitCode: 1
359
495
  };
496
+ } finally {
497
+ this.closeDatabase(db);
360
498
  }
361
499
  }
500
+ closeDatabase(db) {
501
+ try {
502
+ db?.close(false);
503
+ } catch {}
504
+ }
362
505
  asInputAwareRigError(error) {
363
506
  if (error instanceof SyntaxError) {
364
507
  return new RigError("INPUT_ERROR", "Input JSON is invalid.", { message: error.message });
@@ -366,6 +509,5 @@ class ToolRunner {
366
509
  return RigErrors.from(error);
367
510
  }
368
511
  }
369
- export {
370
- ToolRunner
371
- };
512
+
513
+ export { ToolRunner };
@@ -22,6 +22,9 @@ class RigPaths {
22
22
  return isAbsolute(expanded) ? resolve(expanded) : resolve(process.cwd(), expanded);
23
23
  }
24
24
  get rigDir() {
25
+ return join(this.homeDir, "rig");
26
+ }
27
+ get legacyRigDir() {
25
28
  return join(this.homeDir, ".rig");
26
29
  }
27
30
  get configPath() {
@@ -42,10 +45,19 @@ class RigPaths {
42
45
  get runtimeToolTsconfigPath() {
43
46
  return join(this.runtimeDir, "tsconfig.tools.json");
44
47
  }
48
+ get cronDir() {
49
+ return join(this.rigDir, "cron");
50
+ }
51
+ cronWorkerPath(name) {
52
+ return join(this.cronDir, `${name}.ts`);
53
+ }
45
54
  get updateCheckCachePath() {
46
55
  return join(this.rigDir, "update-check.json");
47
56
  }
48
57
  get defaultBaseRegistryDir() {
58
+ return "~/rig/tools";
59
+ }
60
+ get legacyDefaultBaseRegistryDir() {
49
61
  return "~/.rig/tools";
50
62
  }
51
63
  parentDir(pathValue) {
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  RigConfigStore
3
- } from "./cli-xv4m20sx.js";
3
+ } from "./cli-nk194xv0.js";
4
4
  import {
5
5
  RigError
6
6
  } from "./cli-1c7te5cg.js";
7
7
  import {
8
8
  RigPaths
9
- } from "./cli-ne7ed594.js";
9
+ } from "./cli-13q90bjr.js";
10
10
 
11
11
  // src/registry/discover.ts
12
12
  import { existsSync, statSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  exports_external
3
- } from "./cli-xv4m20sx.js";
3
+ } from "./cli-nk194xv0.js";
4
4
 
5
5
  // src/tools/schema.ts
6
6
  class SchemaRenderer {
@@ -0,0 +1,226 @@
1
+ import {
2
+ ToolRunner
3
+ } from "./cli-113n7c3t.js";
4
+ import {
5
+ RigConfigStore
6
+ } from "./cli-nk194xv0.js";
7
+ import {
8
+ RigError
9
+ } from "./cli-1c7te5cg.js";
10
+ import {
11
+ RigPaths
12
+ } from "./cli-13q90bjr.js";
13
+
14
+ // src/tools/cron.ts
15
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
16
+ import { dirname } from "node:path";
17
+ import { fileURLToPath, pathToFileURL } from "node:url";
18
+ class BunCronRegistrar {
19
+ register(path, schedule, title) {
20
+ return this.cron()(path, schedule, title);
21
+ }
22
+ remove(title) {
23
+ return this.cron().remove(title);
24
+ }
25
+ validate(schedule) {
26
+ const next = this.cron().parse(schedule);
27
+ if (!next)
28
+ throw new RigError("CRON_ERROR", `Cron schedule has no future runs: ${schedule}`);
29
+ }
30
+ cron() {
31
+ const cron = globalThis.Bun?.cron;
32
+ if (typeof cron !== "function") {
33
+ throw new RigError("CRON_ERROR", "Bun cron is unavailable. Run rig with Bun.");
34
+ }
35
+ return cron;
36
+ }
37
+ }
38
+
39
+ class CronJobName {
40
+ value;
41
+ constructor(value) {
42
+ this.value = value;
43
+ if (!/^[A-Za-z0-9_-]+$/.test(value)) {
44
+ throw new RigError("INPUT_ERROR", "Cron job names may only contain letters, numbers, hyphens, and underscores.", { name: value });
45
+ }
46
+ }
47
+ }
48
+
49
+ class CronInputReader {
50
+ async read(options) {
51
+ if (options.input && options.inputFile) {
52
+ throw new RigError("INPUT_ERROR", "Use --input or --input-file, not both.");
53
+ }
54
+ if (options.inputFile) {
55
+ return typeof Bun !== "undefined" ? Bun.file(options.inputFile).json() : JSON.parse(await readFile(options.inputFile, "utf8"));
56
+ }
57
+ return options.input === undefined ? undefined : JSON.parse(options.input);
58
+ }
59
+ }
60
+
61
+ class CronCommandTarget {
62
+ id;
63
+ tool;
64
+ command;
65
+ constructor(id) {
66
+ this.id = id;
67
+ const parts = id.split(".");
68
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
69
+ throw new RigError("INPUT_ERROR", `Command id must use <tool>.<command>: ${id}`);
70
+ }
71
+ this.tool = parts[0];
72
+ this.command = parts[1];
73
+ }
74
+ }
75
+
76
+ class CronWorkerScript {
77
+ render(params) {
78
+ const entrypoint = fileURLToPath(params.moduleUrl);
79
+ const env = params.homeDir ? { RIG_HOME: params.homeDir } : {};
80
+ return `export default {
81
+ async scheduled() {
82
+ const proc = Bun.spawn([
83
+ process.execPath,
84
+ "--install=fallback",
85
+ ${JSON.stringify(entrypoint)},
86
+ "cron",
87
+ "run",
88
+ ${JSON.stringify(params.name)},
89
+ ], {
90
+ stdout: "pipe",
91
+ stderr: "pipe",
92
+ env: { ...process.env, ...${JSON.stringify(env)} },
93
+ });
94
+
95
+ const [stdout, stderr, exitCode] = await Promise.all([
96
+ new Response(proc.stdout).text(),
97
+ new Response(proc.stderr).text(),
98
+ proc.exited,
99
+ ]);
100
+ if (stdout) console.log(stdout.trimEnd());
101
+ if (stderr) console.error(stderr.trimEnd());
102
+ if (exitCode !== 0) throw new Error("Rig cron job failed: " + ${JSON.stringify(params.name)});
103
+ },
104
+ };
105
+ `;
106
+ }
107
+ }
108
+
109
+ class RigCronService {
110
+ options;
111
+ registrar;
112
+ paths;
113
+ configStore;
114
+ inputReader = new CronInputReader;
115
+ workerScript = new CronWorkerScript;
116
+ constructor(options = {}, registrar = new BunCronRegistrar) {
117
+ this.options = options;
118
+ this.registrar = registrar;
119
+ this.paths = new RigPaths(options);
120
+ this.configStore = new RigConfigStore(options);
121
+ }
122
+ async list() {
123
+ const config = await this.configStore.ensure();
124
+ return {
125
+ cronJobs: [...config.cronJobs].toSorted((left, right) => left.name.localeCompare(right.name))
126
+ };
127
+ }
128
+ async add(options) {
129
+ const name = new CronJobName(options.name);
130
+ const target = new CronCommandTarget(options.command);
131
+ const input = await this.inputReader.read(options);
132
+ this.registrar.validate(options.schedule);
133
+ await this.validateCommand(target, input);
134
+ const config = await this.configStore.ensure();
135
+ const job = {
136
+ name: name.value,
137
+ command: target.id,
138
+ schedule: options.schedule,
139
+ ...input === undefined ? {} : { input }
140
+ };
141
+ const workerPath = this.paths.cronWorkerPath(name.value);
142
+ await this.writeWorker(workerPath, {
143
+ name: name.value,
144
+ homeDir: this.options.homeDir,
145
+ moduleUrl: options.moduleUrl
146
+ });
147
+ await this.configStore.write({
148
+ ...config,
149
+ cronJobs: [...config.cronJobs.filter((existing) => existing.name !== name.value), job]
150
+ });
151
+ try {
152
+ await this.registrar.register(workerPath, options.schedule, name.value);
153
+ } catch (error) {
154
+ await this.configStore.write(config);
155
+ await rm(workerPath, { force: true });
156
+ throw error;
157
+ }
158
+ return { job, workerPath };
159
+ }
160
+ async remove(nameValue) {
161
+ const name = new CronJobName(nameValue);
162
+ const config = await this.configStore.ensure();
163
+ const workerPath = this.paths.cronWorkerPath(name.value);
164
+ const nextJobs = config.cronJobs.filter((job) => job.name !== name.value);
165
+ await this.configStore.write({ ...config, cronJobs: nextJobs });
166
+ await this.registrar.remove(name.value);
167
+ await rm(workerPath, { force: true });
168
+ return { name: name.value, removed: nextJobs.length !== config.cronJobs.length, workerPath };
169
+ }
170
+ async run(nameValue) {
171
+ const name = new CronJobName(nameValue);
172
+ const config = await this.configStore.ensure();
173
+ const job = config.cronJobs.find((candidate) => candidate.name === name.value);
174
+ if (!job)
175
+ throw new RigError("CRON_ERROR", `Cron job not found: ${name.value}`, { name });
176
+ const target = new CronCommandTarget(job.command);
177
+ const result = await new ToolRunner(this.options).run(target.tool, target.command, {
178
+ ...this.options,
179
+ input: job.input === undefined ? undefined : JSON.stringify(job.input)
180
+ });
181
+ return { job, envelope: result.envelope, exitCode: result.exitCode };
182
+ }
183
+ async validateCommand(target, input) {
184
+ const result = await new ToolRunner(this.options).run(target.tool, target.command, {
185
+ ...this.options,
186
+ input: input === undefined ? undefined : JSON.stringify(input),
187
+ dryRun: true
188
+ });
189
+ if (result.exitCode !== 0) {
190
+ throw new RigError("CRON_ERROR", `Cron command validation failed: ${target.id}`, {
191
+ envelope: result.envelope
192
+ });
193
+ }
194
+ }
195
+ async writeWorker(workerPath, params) {
196
+ await mkdir(dirname(workerPath), { recursive: true });
197
+ await writeFile(workerPath, this.workerScript.render(params), "utf8");
198
+ }
199
+ }
200
+
201
+ class RigCronWorker {
202
+ options;
203
+ constructor(options = {}) {
204
+ this.options = options;
205
+ }
206
+ async scheduled(name, controller) {
207
+ const result = await new RigCronService(this.options).run(name);
208
+ const envelope = result.envelope;
209
+ console.log(JSON.stringify({
210
+ cron: {
211
+ name: result.job.name,
212
+ command: result.job.command,
213
+ schedule: controller?.cron ?? result.job.schedule,
214
+ scheduledTime: controller?.scheduledTime,
215
+ type: controller?.type
216
+ },
217
+ result: envelope
218
+ }, null, 2));
219
+ process.exitCode = result.exitCode;
220
+ }
221
+ }
222
+ function cronModuleUrl(metaUrl) {
223
+ return pathToFileURL(fileURLToPath(metaUrl)).href;
224
+ }
225
+
226
+ export { RigCronService, RigCronWorker, cronModuleUrl };