toolfunnel 0.6.0 → 0.7.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.
Files changed (72) hide show
  1. package/README.md +228 -231
  2. package/SECURITY.md +84 -0
  3. package/bin/toolfunnel.js +451 -372
  4. package/docs/KNOWN_BUGS.md +80 -99
  5. package/docs/MANUAL.pdf +0 -0
  6. package/docs/design.md +84 -70
  7. package/docs/packaging.md +115 -115
  8. package/hooks/hooks.manifest.json +27 -27
  9. package/hooks/scripts/example-audit.js +21 -21
  10. package/hooks/scripts/example-deny-dangerous.js +39 -31
  11. package/mcp/expose.example.json +30 -30
  12. package/mcp/servers/mock-modern/server.js +147 -147
  13. package/mcp/servers/mock-upstream/server.js +380 -350
  14. package/package.json +3 -1
  15. package/src/auth/config.js +174 -174
  16. package/src/auth/install.js +181 -181
  17. package/src/auth/resource-server.js +353 -344
  18. package/src/core/child-reaper.js +61 -0
  19. package/src/core/config-home.js +134 -134
  20. package/src/core/events.js +105 -105
  21. package/src/core/hook-engine.js +329 -275
  22. package/src/core/hook-loader.js +824 -791
  23. package/src/core/hook-runner.js +566 -346
  24. package/src/core/logger.js +297 -171
  25. package/src/core/matcher.js +100 -51
  26. package/src/core/metrics.js +75 -75
  27. package/src/core/rename-migration.js +163 -0
  28. package/src/core/requires.js +138 -138
  29. package/src/core/server-config.js +98 -98
  30. package/src/extend/add-hook.md +152 -152
  31. package/src/extend/add-mcp.md +119 -122
  32. package/src/extend/configure.md +88 -87
  33. package/src/extend/create-tool.md +168 -168
  34. package/src/extend/howto.js +102 -102
  35. package/src/extend/package.md +190 -187
  36. package/src/extend/wrap.md +99 -99
  37. package/src/mcp/aggregator.js +1719 -1246
  38. package/src/mcp/expose-store.js +577 -460
  39. package/src/mcp/gated-run.js +210 -201
  40. package/src/mcp/http-transport.js +1563 -1311
  41. package/src/mcp/mcp-client.js +1070 -907
  42. package/src/mcp/modern.js +406 -372
  43. package/src/mcp/protocol.js +468 -468
  44. package/src/mcp/server.js +3112 -2688
  45. package/src/tools/registry.js +661 -513
  46. package/src/tools/tool-state.js +170 -170
  47. package/src/ui/public/app.js +1187 -1174
  48. package/src/ui/public/index.html +286 -286
  49. package/src/ui/public/styles.css +535 -535
  50. package/src/ui/server.js +1616 -1348
  51. package/tools/scripts/base64.js +139 -139
  52. package/tools/scripts/danger.js +61 -61
  53. package/tools/scripts/echo.js +39 -39
  54. package/tools/scripts/hash.js +101 -101
  55. package/tools/scripts/json.js +109 -109
  56. package/tools/scripts/text-stats.js +131 -131
  57. package/tools/scripts/tf-env.js +43 -43
  58. package/tools/scripts/tf-hook-add.js +151 -151
  59. package/tools/scripts/tf-hook-set.js +120 -112
  60. package/tools/scripts/tf-list.js +192 -192
  61. package/tools/scripts/tf-log.js +86 -86
  62. package/tools/scripts/tf-mcp-add.js +117 -117
  63. package/tools/scripts/tf-mcp-set.js +121 -121
  64. package/tools/scripts/tf-pack.js +237 -237
  65. package/tools/scripts/tf-tool-add.js +134 -134
  66. package/tools/scripts/tf-tool-set.js +142 -142
  67. package/tools/scripts/tf-wrap.js +103 -103
  68. package/tools/scripts/uuid.js +102 -102
  69. package/tools/tools.register.json +227 -227
  70. package/src/core/tool-registry.js +0 -180
  71. package/src/packages/packager.js +0 -715
  72. package/src/tools/drift.js +0 -138
@@ -1,139 +1,139 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * base64-tool.js - a safe, dependency-free local Base64 codec tool.
6
- *
7
- * Purpose
8
- * -------
9
- * Encode UTF-8 text to standard Base64, or decode Base64 back to UTF-8 text.
10
- * A genuinely useful utility for the Tool Manager: inspecting / producing
11
- * Base64 blobs is a frequent chore and this gives a gated, no-side-effect way
12
- * to do it. Uses ONLY Node built-ins (Buffer) - no network, no filesystem.
13
- *
14
- * Contract with the register (the register's `defaultRunScript`)
15
- * ----------------------------------------------------------
16
- * - Structured args arrive as JSON in env TOOLFUNNEL_TOOL_ARGS.
17
- * - Args: { op: "encode" | "decode", text: <string> }.
18
- * - Prints a SINGLE JSON object to stdout and exits 0 - ALWAYS exit 0, even
19
- * on bad input. Bad input is reported as { ok:false, error } rather than a
20
- * thrown exception, because the gated run-path treats exit-0-with-parseable
21
- * stdout as a completed run.
22
- *
23
- * Output (stdout), exactly one JSON object:
24
- * success: { ok:true, op, result }
25
- * failure: { ok:false, error }
26
- *
27
- * Notes on tolerance
28
- * ------------------
29
- * - encode: any string is encodable. A non-string `text` is rejected.
30
- * - decode: Node's Buffer.from(x,'base64') is itself lenient (it skips
31
- * non-base64 chars), so to give callers a MEANINGFUL "bad base64" signal we
32
- * validate the input shape first and round-trip-check the result. If the
33
- * re-encoded form does not match a normalised version of the input, we
34
- * report ok:false rather than silently returning garbage.
35
- *
36
- * Safety invariants (mirror the isolation rule):
37
- * - NO filesystem, NO network, NO process mutation. Pure CPU on the args.
38
- * - NEVER throws for ordinary bad input - only well-shaped JSON on stdout.
39
- */
40
-
41
- /**
42
- * Parse the structured args handed in via env TOOLFUNNEL_TOOL_ARGS.
43
- * Returns the parsed value, or a sentinel describing a parse failure so the
44
- * caller can surface a clean { ok:false, error } instead of crashing.
45
- *
46
- * @returns {{ value: any } | { parseError: string }}
47
- */
48
- function parseStructuredArgs() {
49
- const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
50
- if (raw === undefined || raw === null || raw === '') {
51
- return { value: null };
52
- }
53
- try {
54
- return { value: JSON.parse(raw) };
55
- } catch (_err) {
56
- return { parseError: `TOOLFUNNEL_TOOL_ARGS was not valid JSON: ${String(raw)}` };
57
- }
58
- }
59
-
60
- /**
61
- * Decode tolerantly: validate that the input looks like Base64, decode it, and
62
- * round-trip-check so genuinely malformed input is reported rather than mangled.
63
- *
64
- * @param {string} text the candidate Base64 string
65
- * @returns {{ ok:true, result:string } | { ok:false, error:string }}
66
- */
67
- function decodeTolerant(text) {
68
- // Strip surrounding whitespace; Base64 may legitimately contain internal
69
- // newlines (MIME wrapping), so remove all ASCII whitespace before validating.
70
- const stripped = String(text).replace(/\s+/g, '');
71
- if (stripped.length === 0) {
72
- // Empty input decodes to empty string - a valid, unambiguous result.
73
- return { ok: true, result: '' };
74
- }
75
- // Standard Base64 alphabet (with optional '=' padding). Reject anything else
76
- // up front so the caller gets a clear "bad base64" rather than silent garbage.
77
- if (!/^[A-Za-z0-9+/]+={0,2}$/.test(stripped)) {
78
- return { ok: false, error: 'input is not valid Base64' };
79
- }
80
- // Length of a Base64 string (sans padding-trimming nuance) must be a multiple
81
- // of 4 once padding is included; reject obviously truncated input.
82
- if (stripped.length % 4 !== 0) {
83
- return { ok: false, error: 'input is not valid Base64 (bad length)' };
84
- }
85
- const buf = Buffer.from(stripped, 'base64');
86
- // Round-trip: re-encode and compare to the normalised input. Buffer's decoder
87
- // is lenient, so this catches inputs that "decode" but were never valid.
88
- const reencoded = buf.toString('base64');
89
- if (reencoded !== stripped) {
90
- return { ok: false, error: 'input is not valid Base64' };
91
- }
92
- return { ok: true, result: buf.toString('utf8') };
93
- }
94
-
95
- /**
96
- * Pure core: given the parsed args, produce the result object. Never throws.
97
- *
98
- * @param {any} args
99
- * @returns {object} the JSON-serialisable result
100
- */
101
- function run(args) {
102
- if (!args || typeof args !== 'object' || Array.isArray(args)) {
103
- return { ok: false, error: 'args must be an object { op, text }' };
104
- }
105
- const { op, text } = args;
106
- if (op !== 'encode' && op !== 'decode') {
107
- return { ok: false, error: 'op must be "encode" or "decode"' };
108
- }
109
- if (typeof text !== 'string') {
110
- return { ok: false, error: 'text must be a string' };
111
- }
112
- if (op === 'encode') {
113
- return { ok: true, op: 'encode', result: Buffer.from(text, 'utf8').toString('base64') };
114
- }
115
- // op === 'decode'
116
- const decoded = decodeTolerant(text);
117
- if (!decoded.ok) return { ok: false, error: decoded.error };
118
- return { ok: true, op: 'decode', result: decoded.result };
119
- }
120
-
121
- function main() {
122
- const parsed = parseStructuredArgs();
123
- let payload;
124
- if (parsed.parseError) {
125
- payload = { ok: false, error: parsed.parseError };
126
- } else {
127
- try {
128
- payload = run(parsed.value);
129
- } catch (err) {
130
- // Defensive: run() is written not to throw, but never let an unexpected
131
- // error escape as a non-zero exit / unparseable stdout.
132
- payload = { ok: false, error: `unexpected error: ${(err && err.message) || String(err)}` };
133
- }
134
- }
135
- process.stdout.write(JSON.stringify(payload) + '\n');
136
- process.exit(0);
137
- }
138
-
139
- main();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * base64-tool.js - a safe, dependency-free local Base64 codec tool.
6
+ *
7
+ * Purpose
8
+ * -------
9
+ * Encode UTF-8 text to standard Base64, or decode Base64 back to UTF-8 text.
10
+ * A genuinely useful utility for the Tool Manager: inspecting / producing
11
+ * Base64 blobs is a frequent chore and this gives a gated, no-side-effect way
12
+ * to do it. Uses ONLY Node built-ins (Buffer) - no network, no filesystem.
13
+ *
14
+ * Contract with the register (the register's `defaultRunScript`)
15
+ * ----------------------------------------------------------
16
+ * - Structured args arrive as JSON in env TOOLFUNNEL_TOOL_ARGS.
17
+ * - Args: { op: "encode" | "decode", text: <string> }.
18
+ * - Prints a SINGLE JSON object to stdout and exits 0 - ALWAYS exit 0, even
19
+ * on bad input. Bad input is reported as { ok:false, error } rather than a
20
+ * thrown exception, because the gated run-path treats exit-0-with-parseable
21
+ * stdout as a completed run.
22
+ *
23
+ * Output (stdout), exactly one JSON object:
24
+ * success: { ok:true, op, result }
25
+ * failure: { ok:false, error }
26
+ *
27
+ * Notes on tolerance
28
+ * ------------------
29
+ * - encode: any string is encodable. A non-string `text` is rejected.
30
+ * - decode: Node's Buffer.from(x,'base64') is itself lenient (it skips
31
+ * non-base64 chars), so to give callers a MEANINGFUL "bad base64" signal we
32
+ * validate the input shape first and round-trip-check the result. If the
33
+ * re-encoded form does not match a normalised version of the input, we
34
+ * report ok:false rather than silently returning garbage.
35
+ *
36
+ * Safety invariants (mirror the isolation rule):
37
+ * - NO filesystem, NO network, NO process mutation. Pure CPU on the args.
38
+ * - NEVER throws for ordinary bad input - only well-shaped JSON on stdout.
39
+ */
40
+
41
+ /**
42
+ * Parse the structured args handed in via env TOOLFUNNEL_TOOL_ARGS.
43
+ * Returns the parsed value, or a sentinel describing a parse failure so the
44
+ * caller can surface a clean { ok:false, error } instead of crashing.
45
+ *
46
+ * @returns {{ value: any } | { parseError: string }}
47
+ */
48
+ function parseStructuredArgs() {
49
+ const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
50
+ if (raw === undefined || raw === null || raw === '') {
51
+ return { value: null };
52
+ }
53
+ try {
54
+ return { value: JSON.parse(raw) };
55
+ } catch (_err) {
56
+ return { parseError: `TOOLFUNNEL_TOOL_ARGS was not valid JSON: ${String(raw)}` };
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Decode tolerantly: validate that the input looks like Base64, decode it, and
62
+ * round-trip-check so genuinely malformed input is reported rather than mangled.
63
+ *
64
+ * @param {string} text the candidate Base64 string
65
+ * @returns {{ ok:true, result:string } | { ok:false, error:string }}
66
+ */
67
+ function decodeTolerant(text) {
68
+ // Strip surrounding whitespace; Base64 may legitimately contain internal
69
+ // newlines (MIME wrapping), so remove all ASCII whitespace before validating.
70
+ const stripped = String(text).replace(/\s+/g, '');
71
+ if (stripped.length === 0) {
72
+ // Empty input decodes to empty string - a valid, unambiguous result.
73
+ return { ok: true, result: '' };
74
+ }
75
+ // Standard Base64 alphabet (with optional '=' padding). Reject anything else
76
+ // up front so the caller gets a clear "bad base64" rather than silent garbage.
77
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(stripped)) {
78
+ return { ok: false, error: 'input is not valid Base64' };
79
+ }
80
+ // Length of a Base64 string (sans padding-trimming nuance) must be a multiple
81
+ // of 4 once padding is included; reject obviously truncated input.
82
+ if (stripped.length % 4 !== 0) {
83
+ return { ok: false, error: 'input is not valid Base64 (bad length)' };
84
+ }
85
+ const buf = Buffer.from(stripped, 'base64');
86
+ // Round-trip: re-encode and compare to the normalised input. Buffer's decoder
87
+ // is lenient, so this catches inputs that "decode" but were never valid.
88
+ const reencoded = buf.toString('base64');
89
+ if (reencoded !== stripped) {
90
+ return { ok: false, error: 'input is not valid Base64' };
91
+ }
92
+ return { ok: true, result: buf.toString('utf8') };
93
+ }
94
+
95
+ /**
96
+ * Pure core: given the parsed args, produce the result object. Never throws.
97
+ *
98
+ * @param {any} args
99
+ * @returns {object} the JSON-serialisable result
100
+ */
101
+ function run(args) {
102
+ if (!args || typeof args !== 'object' || Array.isArray(args)) {
103
+ return { ok: false, error: 'args must be an object { op, text }' };
104
+ }
105
+ const { op, text } = args;
106
+ if (op !== 'encode' && op !== 'decode') {
107
+ return { ok: false, error: 'op must be "encode" or "decode"' };
108
+ }
109
+ if (typeof text !== 'string') {
110
+ return { ok: false, error: 'text must be a string' };
111
+ }
112
+ if (op === 'encode') {
113
+ return { ok: true, op: 'encode', result: Buffer.from(text, 'utf8').toString('base64') };
114
+ }
115
+ // op === 'decode'
116
+ const decoded = decodeTolerant(text);
117
+ if (!decoded.ok) return { ok: false, error: decoded.error };
118
+ return { ok: true, op: 'decode', result: decoded.result };
119
+ }
120
+
121
+ function main() {
122
+ const parsed = parseStructuredArgs();
123
+ let payload;
124
+ if (parsed.parseError) {
125
+ payload = { ok: false, error: parsed.parseError };
126
+ } else {
127
+ try {
128
+ payload = run(parsed.value);
129
+ } catch (err) {
130
+ // Defensive: run() is written not to throw, but never let an unexpected
131
+ // error escape as a non-zero exit / unparseable stdout.
132
+ payload = { ok: false, error: `unexpected error: ${(err && err.message) || String(err)}` };
133
+ }
134
+ }
135
+ process.stdout.write(JSON.stringify(payload) + '\n');
136
+ process.exit(0);
137
+ }
138
+
139
+ main();
@@ -1,61 +1,61 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * danger-tool.js - a demo "dangerous" tool used to PROVE the gate works.
6
- *
7
- * The safety case is: `toolfunnel_run_tool` must route through the hook
8
- * engine's PreToolUse gate, and a DENY there must mean the tool's side effect
9
- * never happens.
10
- *
11
- * This tool's observable side effect is: it APPENDS one line to the file named
12
- * by the env var TOOLFUNNEL_DANGER_LOG. A test arranges a PreToolUse hook that
13
- * DENIES this tool, runs the gated path, and then asserts the log file was NOT
14
- * written (or did not gain a line). If the gate had failed open, the line would
15
- * appear.
16
- *
17
- * When it DOES run (gate allows, or run-direct), it:
18
- * 1. appends `<ISO timestamp> danger-tool fired <args>\n` to TOOLFUNNEL_DANGER_LOG
19
- * 2. prints a JSON confirmation to stdout
20
- * 3. exits 0
21
- *
22
- * If TOOLFUNNEL_DANGER_LOG is unset, it still "fires" (prints, exits 0) but
23
- * writes nothing - so it is never destructive on its own; the log is the only
24
- * effect and the caller chooses the path.
25
- */
26
-
27
- const fs = require('node:fs');
28
-
29
- function structuredArgs() {
30
- const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
31
- if (!raw) return null;
32
- try {
33
- return JSON.parse(raw);
34
- } catch (_err) {
35
- return { _parseError: raw };
36
- }
37
- }
38
-
39
- function main() {
40
- const logPath = process.env.TOOLFUNNEL_DANGER_LOG;
41
- const args = structuredArgs();
42
- const argv = process.argv.slice(2);
43
- let wrote = false;
44
-
45
- if (logPath) {
46
- const line =
47
- new Date().toISOString() +
48
- ' danger-tool fired ' +
49
- JSON.stringify({ args, argv }) +
50
- '\n';
51
- fs.appendFileSync(logPath, line, 'utf8');
52
- wrote = true;
53
- }
54
-
55
- process.stdout.write(
56
- JSON.stringify({ ok: true, fired: true, wrote, logPath: logPath || null }) + '\n'
57
- );
58
- process.exit(0);
59
- }
60
-
61
- main();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * danger-tool.js - a demo "dangerous" tool used to PROVE the gate works.
6
+ *
7
+ * The safety case is: `toolfunnel_run_tool` must route through the hook
8
+ * engine's PreToolUse gate, and a DENY there must mean the tool's side effect
9
+ * never happens.
10
+ *
11
+ * This tool's observable side effect is: it APPENDS one line to the file named
12
+ * by the env var TOOLFUNNEL_DANGER_LOG. A test arranges a PreToolUse hook that
13
+ * DENIES this tool, runs the gated path, and then asserts the log file was NOT
14
+ * written (or did not gain a line). If the gate had failed open, the line would
15
+ * appear.
16
+ *
17
+ * When it DOES run (gate allows, or run-direct), it:
18
+ * 1. appends `<ISO timestamp> danger-tool fired <args>\n` to TOOLFUNNEL_DANGER_LOG
19
+ * 2. prints a JSON confirmation to stdout
20
+ * 3. exits 0
21
+ *
22
+ * If TOOLFUNNEL_DANGER_LOG is unset, it still "fires" (prints, exits 0) but
23
+ * writes nothing - so it is never destructive on its own; the log is the only
24
+ * effect and the caller chooses the path.
25
+ */
26
+
27
+ const fs = require('node:fs');
28
+
29
+ function structuredArgs() {
30
+ const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
31
+ if (!raw) return null;
32
+ try {
33
+ return JSON.parse(raw);
34
+ } catch (_err) {
35
+ return { _parseError: raw };
36
+ }
37
+ }
38
+
39
+ function main() {
40
+ const logPath = process.env.TOOLFUNNEL_DANGER_LOG;
41
+ const args = structuredArgs();
42
+ const argv = process.argv.slice(2);
43
+ let wrote = false;
44
+
45
+ if (logPath) {
46
+ const line =
47
+ new Date().toISOString() +
48
+ ' danger-tool fired ' +
49
+ JSON.stringify({ args, argv }) +
50
+ '\n';
51
+ fs.appendFileSync(logPath, line, 'utf8');
52
+ wrote = true;
53
+ }
54
+
55
+ process.stdout.write(
56
+ JSON.stringify({ ok: true, fired: true, wrote, logPath: logPath || null }) + '\n'
57
+ );
58
+ process.exit(0);
59
+ }
60
+
61
+ main();
@@ -1,39 +1,39 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * echo-tool.js - a safe local demo tool.
6
- *
7
- * Prints a JSON object of the args it was invoked with, then exits 0.
8
- * It has NO side effects: it touches no filesystem, no network, no env-driven
9
- * writes. It exists so the register / run-path / gate tests have a benign tool
10
- * to exercise (the "allowed" counterpart to danger-tool.js).
11
- *
12
- * Args are read two ways so callers can use whichever is convenient:
13
- * 1. process.argv - positional CLI args, e.g. `node echo-tool.js a b c`
14
- * 2. TOOLFUNNEL_TOOL_ARGS - a JSON-encoded object/array of structured args
15
- * (this is how the register's "script" invoke passes `args` through).
16
- *
17
- * Output (stdout): a single JSON object { ok, args, argv }.
18
- */
19
-
20
- function parseStructuredArgs() {
21
- const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
22
- if (!raw) return null;
23
- try {
24
- return JSON.parse(raw);
25
- } catch (_err) {
26
- // Malformed env - surface it rather than silently dropping.
27
- return { _parseError: `TOOLFUNNEL_TOOL_ARGS was not valid JSON: ${raw}` };
28
- }
29
- }
30
-
31
- function main() {
32
- const argv = process.argv.slice(2);
33
- const args = parseStructuredArgs();
34
- const payload = { ok: true, args, argv };
35
- process.stdout.write(JSON.stringify(payload) + '\n');
36
- process.exit(0);
37
- }
38
-
39
- main();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * echo-tool.js - a safe local demo tool.
6
+ *
7
+ * Prints a JSON object of the args it was invoked with, then exits 0.
8
+ * It has NO side effects: it touches no filesystem, no network, no env-driven
9
+ * writes. It exists so the register / run-path / gate tests have a benign tool
10
+ * to exercise (the "allowed" counterpart to danger-tool.js).
11
+ *
12
+ * Args are read two ways so callers can use whichever is convenient:
13
+ * 1. process.argv - positional CLI args, e.g. `node echo-tool.js a b c`
14
+ * 2. TOOLFUNNEL_TOOL_ARGS - a JSON-encoded object/array of structured args
15
+ * (this is how the register's "script" invoke passes `args` through).
16
+ *
17
+ * Output (stdout): a single JSON object { ok, args, argv }.
18
+ */
19
+
20
+ function parseStructuredArgs() {
21
+ const raw = process.env.TOOLFUNNEL_TOOL_ARGS;
22
+ if (!raw) return null;
23
+ try {
24
+ return JSON.parse(raw);
25
+ } catch (_err) {
26
+ // Malformed env - surface it rather than silently dropping.
27
+ return { _parseError: `TOOLFUNNEL_TOOL_ARGS was not valid JSON: ${raw}` };
28
+ }
29
+ }
30
+
31
+ function main() {
32
+ const argv = process.argv.slice(2);
33
+ const args = parseStructuredArgs();
34
+ const payload = { ok: true, args, argv };
35
+ process.stdout.write(JSON.stringify(payload) + '\n');
36
+ process.exit(0);
37
+ }
38
+
39
+ main();