asljs-rq-cli 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,63 @@
1
+ import { type Folders, Io, type Setting } from 'asljs-rq';
2
+ /**
3
+ * The environment variables `rq` reads.
4
+ */
5
+ export declare const ENVIRONMENT_VARIABLES: readonly ["RQ_AI_COMMAND", "RQ_LOG_LEVEL", "RQ_LOG_FILE", "RQ_LOG_FORMAT"];
6
+ /**
7
+ * The configuration `rq` runs with, as `rq config` prints it.
8
+ */
9
+ export interface Configuration {
10
+ version: string;
11
+ workingDir: string;
12
+ /**
13
+ * The `rq.json` in use; `null` when there is none.
14
+ */
15
+ configFile: string | null;
16
+ requirementsDir: Setting;
17
+ documentsDir: Setting;
18
+ /**
19
+ * The command `rq.json` runs on the markdown files a command wrote; `null`
20
+ * when there is none.
21
+ */
22
+ markdownPostProcessing: string | null;
23
+ /**
24
+ * The agent of instruction steps and `rq coverage` when `--ai` names none:
25
+ * `RQ_AI_COMMAND`, or the first installed of `agents`, detected when a
26
+ * command needs it.
27
+ */
28
+ agent: {
29
+ command: string | null;
30
+ agents: string[];
31
+ };
32
+ log: {
33
+ level: LogSetting;
34
+ file: LogSetting;
35
+ format: LogSetting;
36
+ };
37
+ /**
38
+ * Each variable `rq` reads, and its value; `null` when it is not set.
39
+ */
40
+ environment: Record<string, string | null>;
41
+ }
42
+ /**
43
+ * A log setting and where it comes from: its option, its `RQ_LOG_`
44
+ * variable, or the default.
45
+ */
46
+ export interface LogSetting {
47
+ value: string;
48
+ source: 'option' | 'environment' | 'default';
49
+ }
50
+ /**
51
+ * Prints the configuration: the folders and where they come from, the
52
+ * `rq.json` in use, the agent, the log settings, and the environment
53
+ * variables `rq` reads. `args` is the command line, for the log options.
54
+ */
55
+ export declare function execConfig(io: Io, options: {
56
+ folders: Folders;
57
+ args: readonly string[];
58
+ json?: boolean;
59
+ }): Promise<number>;
60
+ /**
61
+ * The configuration `rq` runs with in the working folder of `io`.
62
+ */
63
+ export declare function readConfiguration(io: Io, folders: Folders, args: readonly string[]): Promise<Configuration>;
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ import { AI_AGENTS } from 'asljs-agents';
2
+ import { readLoggerOptions } from 'asljs-logging';
3
+ import { findConfig } from 'asljs-mdcli';
4
+ import { CONFIG_FILE } from 'asljs-rq';
5
+ import { createRequire } from 'node:module';
6
+ /**
7
+ * The environment variables `rq` reads.
8
+ */
9
+ export const ENVIRONMENT_VARIABLES = ['RQ_AI_COMMAND',
10
+ 'RQ_LOG_LEVEL',
11
+ 'RQ_LOG_FILE',
12
+ 'RQ_LOG_FORMAT'];
13
+ /**
14
+ * Prints the configuration: the folders and where they come from, the
15
+ * `rq.json` in use, the agent, the log settings, and the environment
16
+ * variables `rq` reads. `args` is the command line, for the log options.
17
+ */
18
+ export async function execConfig(io, options) {
19
+ const configuration = await readConfiguration(io, options.folders, options.args);
20
+ io.stdout.write(options.json
21
+ ? `${JSON.stringify(configuration, null, 2)}\n`
22
+ : describe(configuration));
23
+ return 0;
24
+ }
25
+ /**
26
+ * The configuration `rq` runs with in the working folder of `io`.
27
+ */
28
+ export async function readConfiguration(io, folders, args) {
29
+ const found = await findConfig(io.cwd, CONFIG_FILE);
30
+ const environment = Object.fromEntries(ENVIRONMENT_VARIABLES.map(name => [name,
31
+ io.env[name] || null]));
32
+ const overrides = readLoggerOptions(args);
33
+ const pick = (option, variable, fallback) => option !== undefined
34
+ ? { value: option,
35
+ source: 'option' }
36
+ : environment[variable] !== null
37
+ ? { value: environment[variable],
38
+ source: 'environment' }
39
+ : { value: fallback,
40
+ source: 'default' };
41
+ const file = pick(overrides.file, 'RQ_LOG_FILE', 'stdout');
42
+ return { version: packageVersion(),
43
+ workingDir: io.cwd,
44
+ configFile: folders.configFile,
45
+ requirementsDir: folders.requirementsDir,
46
+ documentsDir: folders.documentsDir,
47
+ markdownPostProcessing: found?.config.markdownPostProcessing?.trim()
48
+ || null,
49
+ agent: { command: environment.RQ_AI_COMMAND,
50
+ agents: [...AI_AGENTS] },
51
+ log: { level: pick(overrides.level, 'RQ_LOG_LEVEL', file.source === 'default'
52
+ ? 'silent'
53
+ : 'information'),
54
+ file,
55
+ format: pick(overrides.format, 'RQ_LOG_FORMAT', 'auto') },
56
+ environment };
57
+ }
58
+ function describe(configuration) {
59
+ const setting = (value) => `${value.value} (${value.source})`;
60
+ const lines = [`version: ${configuration.version}`,
61
+ `workingDir: ${configuration.workingDir}`,
62
+ `configFile: ${configuration.configFile ?? '(none)'}`,
63
+ `requirementsDir: ${setting(configuration.requirementsDir)}`,
64
+ `documentsDir: ${setting(configuration.documentsDir)}`,
65
+ `markdownPostProcessing: ${configuration.markdownPostProcessing ?? '(none)'}`,
66
+ `agent: ${configuration.agent.command === null
67
+ ? `the first installed of ${configuration.agent.agents.join(', ')}, detected when needed`
68
+ : `${configuration.agent.command} (RQ_AI_COMMAND)`}`,
69
+ `logLevel: ${setting(configuration.log.level)}`,
70
+ `logFile: ${setting(configuration.log.file)}`,
71
+ `logFormat: ${setting(configuration.log.format)}`,
72
+ 'environment:',
73
+ ...Object.entries(configuration.environment).map(([name, value]) => ` ${name}=${value ?? ''}`)];
74
+ return `${lines.join('\n')}\n`;
75
+ }
76
+ function packageVersion() {
77
+ return createRequire(import.meta.url)('../package.json').version;
78
+ }
@@ -0,0 +1,3 @@
1
+ export { createCli, runCli, type CliState } from './cli.js';
2
+ export { createTools, main as runMcpMain, runMcpServer, SERVER_NAME, serverInfo } from './mcp.js';
3
+ export { execView, type ViewOptions } from './view.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createCli, runCli } from './cli.js';
2
+ export { createTools, main as runMcpMain, runMcpServer, SERVER_NAME, serverInfo } from './mcp.js';
3
+ export { execView } from './view.js';
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { type McpServerInfo, type McpTool } from 'asljs-mcp';
2
+ import { Io } from 'asljs-rq';
3
+ import { Server } from 'node:http';
4
+ import { Readable } from 'node:stream';
5
+ export declare const SERVER_NAME = "asljs-rq";
6
+ /**
7
+ * The tools of the MCP server: a tool per `rq` command, run as that command
8
+ * line in `io.cwd`, or in its `workingDir`, with its output as the result.
9
+ */
10
+ export declare function createTools(io: Io, servers?: Server[]): McpTool[];
11
+ /**
12
+ * Serves the `rq` commands as MCP tools over a line-delimited JSON-RPC
13
+ * stream, e.g. standard input and output. Resolves when the input ends,
14
+ * and closes the servers `view` started.
15
+ */
16
+ export declare function runMcpServer(io: Io, input: Readable, write: (line: string) => void): Promise<void>;
17
+ /**
18
+ * The `rq-mcp` executable: serves the tools with a logger provider made
19
+ * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
20
+ * variables, and disposes it once the input ends. Standard output carries the
21
+ * protocol, so a log level without a log file other than stdout throws here,
22
+ * before the first request is read.
23
+ */
24
+ export declare function main(args: string[], io: Io, input: Readable, write: (line: string) => void): Promise<void>;
25
+ /**
26
+ * The name and the version of the installed package.
27
+ */
28
+ export declare function serverInfo(): McpServerInfo;
package/dist/mcp.js ADDED
@@ -0,0 +1,70 @@
1
+ import { createLoggerProvider, readLoggerOptions } from 'asljs-logging';
2
+ import { commandTools, serveLines } from 'asljs-mcp';
3
+ import { getLogger } from 'asljs-rq';
4
+ import { createRequire } from 'node:module';
5
+ import { createCli, runCli } from './cli.js';
6
+ export const SERVER_NAME = 'asljs-rq';
7
+ /**
8
+ * The tools of the MCP server: a tool per `rq` command, run as that command
9
+ * line in `io.cwd`, or in its `workingDir`, with its output as the result.
10
+ */
11
+ export function createTools(io, servers = []) {
12
+ return commandTools(createCli([], io).cli, async (args) => {
13
+ let stdout = '';
14
+ let stderr = '';
15
+ const exitCode = await runCli(args, { ...io,
16
+ stdout: { write: (text) => {
17
+ stdout += text;
18
+ } },
19
+ stderr: { write: (text) => {
20
+ stderr += text;
21
+ } } }, servers);
22
+ return { exitCode,
23
+ stdout,
24
+ stderr };
25
+ });
26
+ }
27
+ /**
28
+ * Serves the `rq` commands as MCP tools over a line-delimited JSON-RPC
29
+ * stream, e.g. standard input and output. Resolves when the input ends,
30
+ * and closes the servers `view` started.
31
+ */
32
+ export async function runMcpServer(io, input, write) {
33
+ const servers = [];
34
+ try {
35
+ await serveLines(input, write, createTools(io, servers), serverInfo(), { logger: getLogger(io, 'rq.mcp') });
36
+ }
37
+ finally {
38
+ for (const server of servers) {
39
+ await new Promise((resolve) => {
40
+ server.close(resolve);
41
+ server.closeAllConnections();
42
+ });
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * The `rq-mcp` executable: serves the tools with a logger provider made
48
+ * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
49
+ * variables, and disposes it once the input ends. Standard output carries the
50
+ * protocol, so a log level without a log file other than stdout throws here,
51
+ * before the first request is read.
52
+ */
53
+ export async function main(args, io, input, write) {
54
+ const loggerProvider = createLoggerProvider('RQ_LOG_', readLoggerOptions(args), { allowStdout: false });
55
+ try {
56
+ await runMcpServer({ ...io,
57
+ loggerProvider }, input, write);
58
+ }
59
+ finally {
60
+ await loggerProvider.dispose();
61
+ }
62
+ }
63
+ /**
64
+ * The name and the version of the installed package.
65
+ */
66
+ export function serverInfo() {
67
+ const { version } = createRequire(import.meta.url)('../package.json');
68
+ return { name: SERVER_NAME,
69
+ version };
70
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The part of the diagram the window shows, in diagram coordinates: the SVG's
3
+ * `viewBox`.
4
+ */
5
+ export interface ViewBox {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }
11
+ export interface Point {
12
+ x: number;
13
+ y: number;
14
+ }
15
+ /**
16
+ * The view box zoomed by a wheel turn of `deltaY` around `point`, which stays
17
+ * where it is on the screen: a turn down, a positive `deltaY`, zooms out.
18
+ *
19
+ * Its source is sent to the browser as part of the index page, so it uses
20
+ * nothing but its arguments.
21
+ */
22
+ export declare function zoomAt(box: ViewBox, point: Point, deltaY: number): ViewBox;
23
+ /**
24
+ * The view box moved so that `grabbed`, the diagram point where a drag
25
+ * started, is under the pointer, now over `point`.
26
+ *
27
+ * Its source is sent to the browser as part of the index page, so it uses
28
+ * nothing but its arguments.
29
+ */
30
+ export declare function panTo(box: ViewBox, grabbed: Point, point: Point): ViewBox;
31
+ /**
32
+ * Whether pressing `button` on `target` starts a pan: the main button on
33
+ * empty space, not on a node or a link, so that a click on a node still opens
34
+ * its document.
35
+ *
36
+ * Its source is sent to the browser as part of the index page, so it uses
37
+ * nothing but its arguments.
38
+ */
39
+ export declare function startsPan(button: number, target: {
40
+ closest(selector: string): unknown;
41
+ }): boolean;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The view box zoomed by a wheel turn of `deltaY` around `point`, which stays
3
+ * where it is on the screen: a turn down, a positive `deltaY`, zooms out.
4
+ *
5
+ * Its source is sent to the browser as part of the index page, so it uses
6
+ * nothing but its arguments.
7
+ */
8
+ export function zoomAt(box, point, deltaY) {
9
+ const factor = Math.exp(deltaY * 0.0015);
10
+ return { x: point.x - (point.x - box.x) * factor,
11
+ y: point.y - (point.y - box.y) * factor,
12
+ width: box.width * factor,
13
+ height: box.height * factor };
14
+ }
15
+ /**
16
+ * The view box moved so that `grabbed`, the diagram point where a drag
17
+ * started, is under the pointer, now over `point`.
18
+ *
19
+ * Its source is sent to the browser as part of the index page, so it uses
20
+ * nothing but its arguments.
21
+ */
22
+ export function panTo(box, grabbed, point) {
23
+ return { x: box.x - (point.x - grabbed.x),
24
+ y: box.y - (point.y - grabbed.y),
25
+ width: box.width,
26
+ height: box.height };
27
+ }
28
+ /**
29
+ * Whether pressing `button` on `target` starts a pan: the main button on
30
+ * empty space, not on a node or a link, so that a click on a node still opens
31
+ * its document.
32
+ *
33
+ * Its source is sent to the browser as part of the index page, so it uses
34
+ * nothing but its arguments.
35
+ */
36
+ export function startsPan(button, target) {
37
+ return button === 0
38
+ && !target.closest('.node, a');
39
+ }
package/dist/view.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { type PostReply } from 'asljs-mdcli';
2
+ import { Io } from 'asljs-rq';
3
+ import { Server } from 'node:http';
4
+ export { DEFAULT_PORT } from 'asljs-mdcli';
5
+ export interface ViewOptions {
6
+ /**
7
+ * The requirement file or folder to view, relative to the working
8
+ * directory, or an id or `.md` name; the requirements folder when absent.
9
+ */
10
+ target?: string;
11
+ /**
12
+ * The port to listen on, exactly; 0 picks a free one. When absent, the
13
+ * first free port from `DEFAULT_PORT` on.
14
+ */
15
+ port?: number;
16
+ host?: string;
17
+ }
18
+ /**
19
+ * The body of a rendered document: a requirement or test starts with its
20
+ * full path, which a click copies; any other document is its text alone.
21
+ */
22
+ export declare function renderDocument(file: string): Promise<string>;
23
+ /**
24
+ * Saves what the editor of a requirement or test sends to the document at
25
+ * the URL's path in `folder`: its title and its text, unless the document
26
+ * changed since the editor opened it; the ids of the requirements folder in
27
+ * the text become links. Then the document is post-processed, as after a
28
+ * command.
29
+ */
30
+ export declare function saveDocument(io: Io, folder: string, url: URL, body: unknown): Promise<PostReply>;
31
+ /**
32
+ * Starts a web server for a requirement file or folder. It serves the
33
+ * documents folder, or the folder of the graph when none is given, which must
34
+ * contain the graph's folder. `/` shows the graph, whose nodes open the
35
+ * documents; a `.md` file of the served folder is shown rendered and any other
36
+ * file as it is, so a document a requirement links to opens wherever it is in
37
+ * the folder. The graph is reloaded on every request for `/`.
38
+ */
39
+ export declare function execView(io: Io, options: ViewOptions): Promise<Server>;