From 80454f9a2afebd4560dc547e3f57603dca9250e4 Mon Sep 17 00:00:00 2001 From: Shivering Ocelot Date: Fri, 2 Oct 2026 08:28:51 -0500 Subject: [PATCH] libs directory, default main function --- readme.md | 4 +++- src/loader.js | 26 ++++++++++++++++++++++++++ src/main.js | 32 +++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/loader.js mode change 100644 => 100755 src/main.js diff --git a/readme.md b/readme.md index d3668a5..8851ea9 100644 --- a/readme.md +++ b/readme.md @@ -2,12 +2,14 @@ Clone the repository, then install with `npm install -g .`. -Usage: `gral -p `, or pass a file as a prompt instead with the `-f ` option. +Usage: `gral -p `, or pass a file as a prompt instead with the `-f ` option. 'main' is the default function if it exists, otherwise a function name is required. Gral uses the `.gral` directory from the working path for its configuration. The horde connection uses the 'host' and 'token' values stored in `.gral/config.json`, or loads them from the environment if not present. Every javascript module in `.gral/functions` exports a default function, it receives a single prompt string argument. These are the functions invoked by the gral command, you can list them with `gral -l`. +Shared libraries live in `.gral/lib`. Any module there can be imported from a function module using the `@lib/` prefix. + The `Gral` global is available in the function module scopes, and exposes: - `path`: the working directory - `run`: the run command for sending prompts to agents diff --git a/src/loader.js b/src/loader.js new file mode 100644 index 0000000..a498817 --- /dev/null +++ b/src/loader.js @@ -0,0 +1,26 @@ +'use strict'; + +/* + * ESM loader hook registered from 'main.js' via `module.register()`. + * It resolves '@lib/' specifiers against the project's '.gral/lib' + * directory, supplied through the `data` option of `register()`. + */ + +import * as path from 'path'; +import { pathToFileURL } from 'url'; + +const LIB_PREFIX = '@lib/'; + +let libDir = null; + +export function initialize(data) { + libDir = data && data.libDir ? data.libDir : null; +} + +export async function resolve(specifier, context, nextResolve) { + if (libDir && specifier.startsWith(LIB_PREFIX)) { + const target = path.join(libDir, specifier.slice(LIB_PREFIX.length)); + return { url: pathToFileURL(target).href, shortCircuit: true }; + } + return nextResolve(specifier, context); +} \ No newline at end of file diff --git a/src/main.js b/src/main.js old mode 100644 new mode 100755 index 4fd84e2..87747da --- a/src/main.js +++ b/src/main.js @@ -19,12 +19,14 @@ import * as fs from 'fs'; import * as path from 'path'; +import { register } from 'node:module'; import Horde from './horde.js'; import run from './run.js'; const CONFIG_DIR = '.gral'; const CONFIG_FILE = 'config.json'; const FUNCTIONS_DIR = 'functions'; +const LIB_DIR = 'lib'; const DEFAULT_CONFIG = { host: 'wss://hub.hordelab.com', @@ -211,12 +213,12 @@ async function main(argv) { let config = loadConfig(root); Gral.path = root; + // Make '.gral/lib/*' importable from function modules via '@lib/'. + const libDir = path.join(root, CONFIG_DIR, LIB_DIR); + await register('./loader.js', import.meta.url, { data: { libDir } }); + const functions = await loadFunctions(root); - - let horde = new Horde({host: config.host, token: config.token}); - await horde.connect(); - - Gral.horde = horde; + Gral.horde = new Horde({host: config.host, token: config.token}); if (command.help) { console.log(HELP); @@ -234,15 +236,20 @@ async function main(argv) { return 0; } - if (!command.functionName) { - console.error('Error: a function name is required.'); - console.error(HELP); - return 1; + let fname = command.functionName; + if (!fname) { + if (functions.has('main')) { + fname = 'main'; + } else { + console.error('Error: a function name is required.'); + console.error(HELP); + return 1; + } } - const fn = functions.get(command.functionName); + const fn = functions.get(fname); if (!fn) { - console.error(`Error: unknown function '${command.functionName}'.`); + console.error(`Error: unknown function '${fname}'.`); return 1; } @@ -253,6 +260,9 @@ async function main(argv) { const input = { prompt: command.prompt }; + // Connect before executing + await Gral.horde.connect(); + try { await fn(input); } catch (err) {