@rendotdev/rig 0.0.16 → 0.0.18

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/cli-0p4wnh3p.js +226 -0
  3. package/dist/{cli-mswdavqx.js → cli-96n231pn.js} +3 -3
  4. package/dist/{cli-7zqnqrah.js → cli-c6y9p8v9.js} +106 -42
  5. package/dist/{cli-dshh1cp9.js → cli-f4k31gtc.js} +1 -1
  6. package/dist/{cli-vx11bmzr.js → cli-ggk7t6dg.js} +2 -2
  7. package/dist/{cli-xv4m20sx.js → cli-jbscjyqq.js} +93 -65
  8. package/dist/{cli-ne7ed594.js → cli-vkw9rcke.js} +8 -2
  9. package/dist/{run-y66hvyxg.js → cli-yahk2r84.js} +158 -16
  10. package/dist/{config-24gwe08t.js → config-8mr59m0q.js} +2 -3
  11. package/dist/{create-59myk91d.js → create-9frp1c7g.js} +7 -8
  12. package/dist/cron-e6whgdf7.js +18 -0
  13. package/dist/{dev-link-jpte1gq9.js → dev-link-x3hjyy3j.js} +7 -4
  14. package/dist/{discover-fd9e6j9j.js → discover-9n7b9ra2.js} +3 -4
  15. package/dist/{help-6k8m07yv.js → help-17a9ywd9.js} +5 -6
  16. package/dist/{inspect-ks3x7s4n.js → inspect-k2g3dt1g.js} +5 -6
  17. package/dist/list-pahq60sp.js +13 -0
  18. package/dist/{paths-hq1vy0wh.js → paths-cxq6fvhp.js} +1 -1
  19. package/dist/{registry-gng9br0x.js → registry-e68a7b4v.js} +2 -3
  20. package/dist/rig.js +80 -35
  21. package/dist/run-apb4mx5d.js +13 -0
  22. package/dist/{runtime-comment-gvmkkcyh.js → runtime-comment-3yq1pmaf.js} +15 -9
  23. package/dist/{sync-zsc35m3f.js → sync-3362wne1.js} +33 -16
  24. package/dist/{typecheck-c2k87ppw.js → typecheck-5325aspe.js} +5 -7
  25. package/dist/{update-check-87p71vrc.js → update-check-a02ydxdb.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
 
@@ -0,0 +1,226 @@
1
+ import {
2
+ ToolRunner
3
+ } from "./cli-yahk2r84.js";
4
+ import {
5
+ RigConfigStore
6
+ } from "./cli-jbscjyqq.js";
7
+ import {
8
+ RigError
9
+ } from "./cli-1c7te5cg.js";
10
+ import {
11
+ RigPaths
12
+ } from "./cli-vkw9rcke.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 };
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  SchemaRenderer
3
- } from "./cli-dshh1cp9.js";
3
+ } from "./cli-f4k31gtc.js";
4
4
  import {
5
5
  CommandIds,
6
6
  ToolLoader
7
- } from "./cli-7zqnqrah.js";
7
+ } from "./cli-c6y9p8v9.js";
8
8
  import {
9
9
  ToolDiscoveryService
10
- } from "./cli-vx11bmzr.js";
10
+ } from "./cli-ggk7t6dg.js";
11
11
 
12
12
  // src/tools/list.ts
13
13
  class CommandRunExampleRenderer {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ToolDiscoveryService
3
- } from "./cli-vx11bmzr.js";
3
+ } from "./cli-ggk7t6dg.js";
4
4
  import {
5
5
  exports_external
6
- } from "./cli-xv4m20sx.js";
6
+ } from "./cli-jbscjyqq.js";
7
7
  import {
8
8
  RigError
9
9
  } from "./cli-1c7te5cg.js";
@@ -12,8 +12,6 @@ import {
12
12
  } from "./cli-b7jgjgy7.js";
13
13
 
14
14
  // src/tools/types.ts
15
- var RigSchemaRoleSymbol = Symbol.for("rig.schemaRole");
16
-
17
15
  class CommandIds {
18
16
  static from(tool, command) {
19
17
  return `${tool}.${command}`;
@@ -238,7 +236,7 @@ class RigCommandRunnerRuntime {
238
236
  }
239
237
  async run(options) {
240
238
  const target = this.commandTarget(options);
241
- const { ToolRunner } = await import("./run-y66hvyxg.js");
239
+ const { ToolRunner } = await import("./run-apb4mx5d.js");
242
240
  const result = await new ToolRunner(this.options).run(target.tool, target.command, {
243
241
  ...this.options,
244
242
  args: options.args,
@@ -272,9 +270,7 @@ class RigToolKitFactory {
272
270
  return {
273
271
  z: exports_external,
274
272
  defineTool: (definition) => definition,
275
- command: (definition) => definition,
276
- input: (value) => this.schema(value, "input"),
277
- output: (value) => this.schema(value, "output"),
273
+ defineCommand: (definition) => definition,
278
274
  run: (options) => new RigCommandRunnerRuntime(this.options).run(options),
279
275
  $: (strings, ...values) => shell.$(strings, ...values),
280
276
  args: () => new RigArgsRuntime,
@@ -282,27 +278,9 @@ class RigToolKitFactory {
282
278
  shell
283
279
  };
284
280
  }
285
- schema(value, role) {
286
- const schema = this.isZodSchema(value) ? value : exports_external.object(value);
287
- const existingRole = schema[RigSchemaRoleSymbol];
288
- if (existingRole === role)
289
- return schema;
290
- Object.defineProperty(schema, RigSchemaRoleSymbol, {
291
- value: role,
292
- configurable: false,
293
- enumerable: false,
294
- writable: false
295
- });
296
- return schema;
297
- }
298
- isZodSchema(value) {
299
- return typeof value === "object" && value !== null && typeof value.safeParse === "function";
300
- }
301
281
  }
302
282
  var rig = new RigToolKitFactory().create();
303
- var command = rig.command;
304
- var input = rig.input;
305
- var output = rig.output;
283
+ var defineCommand = rig.defineCommand;
306
284
  var run = rig.run;
307
285
  var args = rig.args;
308
286
  var paths = rig.paths;
@@ -311,6 +289,9 @@ function createRigToolKit(options = {}) {
311
289
  }
312
290
 
313
291
  // src/tools/loader.ts
292
+ import { existsSync as existsSync2 } from "node:fs";
293
+ import { readFile } from "node:fs/promises";
294
+ import { dirname as dirname2, join as join2 } from "node:path";
314
295
  import { pathToFileURL } from "node:url";
315
296
  class ToolDefinitionValidator {
316
297
  validateToolName(name) {
@@ -346,6 +327,14 @@ class ToolDefinitionValidator {
346
327
  expected: "non-empty string"
347
328
  });
348
329
  }
330
+ if (value.setupDb !== undefined && typeof value.setupDb !== "function") {
331
+ throw new RigError("TOOL_INVALID", `Tool ${value.name} setupDb must be a function.`, {
332
+ expected: "function"
333
+ });
334
+ }
335
+ if (value.env !== undefined) {
336
+ this.validateSchema(value.env, "env", value.name);
337
+ }
349
338
  if (!this.isRecord(value.commands)) {
350
339
  throw new RigError("TOOL_INVALID", `Tool ${value.name} needs a commands object.`, {
351
340
  expected: "object"
@@ -355,8 +344,8 @@ class ToolDefinitionValidator {
355
344
  if (entries.length === 0) {
356
345
  throw new RigError("TOOL_INVALID", `Tool ${value.name} must define at least one command.`);
357
346
  }
358
- for (const [commandName, command2] of entries) {
359
- this.validateCommand(commandName, command2, value.name);
347
+ for (const [commandName, command] of entries) {
348
+ this.validateCommand(commandName, command, value.name);
360
349
  }
361
350
  return value;
362
351
  }
@@ -406,15 +395,8 @@ class ToolDefinitionValidator {
406
395
  }
407
396
  validateSchema(value, role, id) {
408
397
  if (!this.hasSafeParse(value)) {
409
- throw new RigError("TOOL_INVALID", `Command ${id} needs a Rig ${role} schema.`, {
410
- expected: `schema created with rig.${role}(...)`
411
- });
412
- }
413
- const actualRole = value[RigSchemaRoleSymbol];
414
- if (actualRole !== role) {
415
- throw new RigError("TOOL_INVALID", `Command ${id} needs a Rig ${role} schema.`, {
416
- expected: `rig.${role}(...)`,
417
- actual: actualRole ?? "unbranded Zod schema"
398
+ throw new RigError("TOOL_INVALID", `Command ${id} needs a Zod schema for ${role}.`, {
399
+ expected: `rig.z.object({ ... })`
418
400
  });
419
401
  }
420
402
  }
@@ -426,9 +408,90 @@ class ToolDefinitionValidator {
426
408
  }
427
409
  }
428
410
 
411
+ class ToolEnvFileParser {
412
+ parse(source, path) {
413
+ const env = {};
414
+ for (const [index, line] of source.split(/\r?\n/).entries()) {
415
+ const parsed = this.parseLine(line, path, index + 1);
416
+ if (parsed)
417
+ env[parsed.key] = parsed.value;
418
+ }
419
+ return env;
420
+ }
421
+ parseLine(line, path, lineNumber) {
422
+ const trimmed = line.trim();
423
+ if (!trimmed || trimmed.startsWith("#"))
424
+ return;
425
+ const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(trimmed);
426
+ if (!match) {
427
+ throw new RigError("TOOL_INVALID", "Invalid .env line.", {
428
+ path,
429
+ line: lineNumber
430
+ });
431
+ }
432
+ return { key: match[1], value: this.parseValue(match[2]) };
433
+ }
434
+ parseValue(value) {
435
+ const trimmed = value.trim();
436
+ if (trimmed.length < 2)
437
+ return trimmed;
438
+ const quote = trimmed[0];
439
+ if (quote !== '"' && quote !== "'" || trimmed.at(-1) !== quote)
440
+ return trimmed;
441
+ const inner = trimmed.slice(1, -1);
442
+ if (quote === "'")
443
+ return inner;
444
+ return inner.replaceAll("\\n", `
445
+ `).replaceAll("\\r", "\r").replaceAll("\\t", "\t").replaceAll("\\\"", '"').replaceAll("\\\\", "\\");
446
+ }
447
+ }
448
+
449
+ class ToolEnvLoader {
450
+ parser = new ToolEnvFileParser;
451
+ async load(tool, definition) {
452
+ const envPath = join2(dirname2(tool.toolPath), ".env");
453
+ const fileExists = await this.exists(envPath);
454
+ if (!definition.env) {
455
+ if (fileExists) {
456
+ throw new RigError("TOOL_INVALID", `Tool ${definition.name} has .env but no env schema.`, {
457
+ path: envPath
458
+ });
459
+ }
460
+ return {};
461
+ }
462
+ const rawEnv = fileExists ? this.parser.parse(await this.readText(envPath), envPath) : {};
463
+ const result = definition.env.safeParse(rawEnv);
464
+ if (!result.success) {
465
+ throw new RigError("TOOL_INVALID", `Tool ${definition.name} env is invalid.`, {
466
+ path: envPath,
467
+ errors: result.error.flatten()
468
+ });
469
+ }
470
+ return result.data;
471
+ }
472
+ async exists(path) {
473
+ const bunFile = this.bunFile();
474
+ if (bunFile) {
475
+ return bunFile(path).exists();
476
+ }
477
+ return existsSync2(path);
478
+ }
479
+ async readText(path) {
480
+ const bunFile = this.bunFile();
481
+ if (bunFile)
482
+ return bunFile(path).text();
483
+ return readFile(path, "utf8");
484
+ }
485
+ bunFile() {
486
+ const candidate = globalThis.Bun?.file;
487
+ return typeof candidate === "function" ? candidate : undefined;
488
+ }
489
+ }
490
+
429
491
  class ToolLoader {
430
492
  discovery;
431
493
  validator;
494
+ envLoader = new ToolEnvLoader;
432
495
  constructor(options = {}) {
433
496
  this.discovery = new ToolDiscoveryService(options);
434
497
  this.validator = new ToolDefinitionValidator;
@@ -453,7 +516,8 @@ class ToolLoader {
453
516
  const moduleRecord = moduleValue;
454
517
  const definitionValue = await this.evaluateModuleDefault(moduleRecord.default, tool.name);
455
518
  const definition = this.validator.validateToolDefinition(definitionValue, tool.name);
456
- return { name: definition.name, path: tool.toolPath, definition };
519
+ const env = await this.envLoader.load(tool, definition);
520
+ return { name: definition.name, path: tool.toolPath, env, definition };
457
521
  }
458
522
  async evaluateModuleDefault(value, toolName) {
459
523
  const awaitedValue = await Promise.resolve(value);
@@ -475,15 +539,15 @@ class ToolLoader {
475
539
  async loadCommand(toolName, commandName) {
476
540
  const tool = await this.load(toolName);
477
541
  this.validator.validateCommandName(commandName);
478
- const command2 = tool.definition.commands[commandName];
479
- if (!command2) {
542
+ const command = tool.definition.commands[commandName];
543
+ if (!command) {
480
544
  throw new RigError("COMMAND_NOT_FOUND", `Command not found: ${CommandIds.from(toolName, commandName)}`, {
481
545
  tool: toolName,
482
546
  command: commandName,
483
547
  available: Object.keys(tool.definition.commands)
484
548
  });
485
549
  }
486
- return { tool, commandName, command: command2 };
550
+ return { tool, commandName, command };
487
551
  }
488
552
  }
489
553
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  exports_external
3
- } from "./cli-xv4m20sx.js";
3
+ } from "./cli-jbscjyqq.js";
4
4
 
5
5
  // src/tools/schema.ts
6
6
  class SchemaRenderer {
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  RigConfigStore
3
- } from "./cli-xv4m20sx.js";
3
+ } from "./cli-jbscjyqq.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-vkw9rcke.js";
10
10
 
11
11
  // src/registry/discover.ts
12
12
  import { existsSync, statSync } from "node:fs";