driggsby 0.2.4 → 0.3.0

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,78 @@
1
+ // driggsby query: run one read-only tool once with the saved sign-in and
2
+ // print its result as JSON — exactly what driggsby.watch hands an app's
3
+ // callback for the same tool and params — so an agent can read a result's
4
+ // shape before writing render code. stdout carries only the JSON; every
5
+ // message is a CliError on stderr, so the output can be piped or saved.
6
+ import { apiBaseUrl } from "../api/base-url.js";
7
+ import { CliError } from "../cli-error.js";
8
+ import { defaultCredentialEnvironment, } from "../credentials/store.js";
9
+ import { McpBroker } from "../dev/mcp-broker.js";
10
+ import { SQL_TOOLS } from "../args-query.js";
11
+ import { APP_TOOL_ALLOWLIST } from "../dev/tool-allowlist.js";
12
+ import { deployFailure, requireDeploySession, SIGN_IN_AGAIN_MESSAGE } from "../deploy/api-session.js";
13
+ import { quotedForTerminal, wrapProse } from "../terminal-text.js";
14
+ // The Driggsby endpoint requires a short reason on most read tools; when
15
+ // the caller gives none, this names the CLI rather than the dev preview.
16
+ const QUERY_REASON = "Checking this tool's result shape from the driggsby CLI.";
17
+ function defaultQueryIo() {
18
+ return {
19
+ out: (text) => {
20
+ process.stdout.write(text);
21
+ },
22
+ };
23
+ }
24
+ export async function runQuery(options, environment = defaultCredentialEnvironment(), io = defaultQueryIo()) {
25
+ // The parser already rejects a tool apps cannot call; re-checking here keeps
26
+ // the read-only guarantee (and the echoed retry command) from hanging on
27
+ // one distant caller.
28
+ if (!APP_TOOL_ALLOWLIST.has(options.tool)) {
29
+ throw new CliError(`${quotedForTerminal(options.tool, 60)} isn't a tool a Driggsby app can call.`, 2);
30
+ }
31
+ const baseUrl = apiBaseUrl(environment.env);
32
+ const retryCommand = retryCommandFor(options);
33
+ try {
34
+ const session = await requireDeploySession(environment);
35
+ const broker = new McpBroker({
36
+ baseUrl: session.baseUrl,
37
+ token: session.token,
38
+ defaultReason: QUERY_REASON,
39
+ signInAgainMessage: SIGN_IN_AGAIN_MESSAGE,
40
+ // A connection failure here should read as one (a blocked network
41
+ // names the host to allow), not as a tool that "couldn't run".
42
+ transportErrors: "throw",
43
+ });
44
+ const outcome = await broker.runToolCall(options.tool, options.params);
45
+ if (!outcome.ok) {
46
+ // The tool's own message (a SQL error, a refused param) is the
47
+ // answer the caller needs. The broker sanitized and capped it to one
48
+ // line; wrapping bounds its width. The sign-in-again message is ours
49
+ // and keeps its own line breaks.
50
+ const message = outcome.error.message === SIGN_IN_AGAIN_MESSAGE
51
+ ? outcome.error.message
52
+ : wrapProse(outcome.error.message);
53
+ throw new CliError(message, 1);
54
+ }
55
+ io.out(`${JSON.stringify(outcome.result, null, 2)}\n`);
56
+ return 0;
57
+ }
58
+ catch (error) {
59
+ throw deployFailure(error, baseUrl, retryCommand);
60
+ }
61
+ }
62
+ // The command to run again after a network blip. User values are never
63
+ // rebuilt into a shell line: no quoting is right for sh, cmd.exe, and
64
+ // PowerShell at once, so the flags to repeat are named in a plain
65
+ // sentence under the command instead.
66
+ function retryCommandFor(options) {
67
+ const command = `npx driggsby@latest query ${options.tool}`;
68
+ // Only the SQL tools take --sql; for every other tool a sql key is just
69
+ // another param, and naming --sql would describe a command the parser
70
+ // refuses.
71
+ const sqlAsFlag = SQL_TOOLS.has(options.tool) && options.params.sql !== undefined;
72
+ const otherKeys = Object.keys(options.params).filter((key) => !(sqlAsFlag && key === "sql"));
73
+ const flags = [sqlAsFlag ? "--sql" : null, otherKeys.length > 0 ? "--params" : null].filter((flag) => flag !== null);
74
+ if (flags.length === 0) {
75
+ return command;
76
+ }
77
+ return `${command}\n with the same ${flags.join(" and ")} as before`;
78
+ }
@@ -17,3 +17,18 @@ const TRAILING_RUST_WHITESPACE = new RegExp(`${RUST_WHITESPACE_CLASS}+$`);
17
17
  export function trimLikeRust(value) {
18
18
  return value.replace(LEADING_RUST_WHITESPACE, "").replace(TRAILING_RUST_WHITESPACE, "");
19
19
  }
20
+ // Comma-separated names on indented lines that stay inside 80 columns.
21
+ export function wrapNames(names, indent) {
22
+ const lines = [];
23
+ let line = indent;
24
+ for (const [index, name] of names.entries()) {
25
+ const piece = index === names.length - 1 ? `${name}.` : `${name},`;
26
+ if (line !== indent && line.length + 1 + piece.length > 80) {
27
+ lines.push(line);
28
+ line = indent;
29
+ }
30
+ line += line === indent ? piece : ` ${piece}`;
31
+ }
32
+ lines.push(line);
33
+ return lines.join("\n");
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "driggsby",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Set up and build on Driggsby, the MCP server for personal finance.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "node": ">=18"
29
29
  },
30
30
  "dependencies": {
31
- "@driggsby/deploy": "0.2.4",
32
- "@driggsby/sdk": "0.2.4"
31
+ "@driggsby/deploy": "0.3.0",
32
+ "@driggsby/sdk": "0.3.0"
33
33
  }
34
34
  }