@solidactions/cli 1.13.0 → 1.15.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.
- package/README.md +18 -0
- package/dist/commands/docs-push.js +220 -0
- package/dist/commands/run-list.js +32 -4
- package/dist/index.js +16 -0
- package/dist/utils/mcp.js +19 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,6 +118,24 @@ Use `solidactions <command> --help` for full flag details on any command.
|
|
|
118
118
|
|---------|-----------|-------------|
|
|
119
119
|
| `webhook list <project>` | `-e`, `--show-secrets` | List webhook URLs |
|
|
120
120
|
|
|
121
|
+
### skill
|
|
122
|
+
|
|
123
|
+
Manage agent skills on the crews SOP surface. `push` is an idempotent upsert (create, or update on name collision).
|
|
124
|
+
|
|
125
|
+
| Command | Key Flags | Description |
|
|
126
|
+
|---------|-----------|-------------|
|
|
127
|
+
| `skill push <dir>` | `--role <name>`, `--dry-run`, `--json` | Push a skill folder, or a whole plugin dir — recursive: pushes every `skills/*/SKILL.md`, converts `commands/*.md` → skills, and ingests each skill's `references/`. `--role` scopes to a role instead of the shared library |
|
|
128
|
+
| `skill list` | `--json`, `--limit <n>` | List skills in the library |
|
|
129
|
+
| `skill view <name>` | `--json` | Show one skill |
|
|
130
|
+
| `skill pull <name> [dest]` | `--json` | Fetch a skill to a local folder for editing (inverse of push) |
|
|
131
|
+
| `skill delete <name>` | `--json` | Delete a skill (Admin only) |
|
|
132
|
+
|
|
133
|
+
### role
|
|
134
|
+
|
|
135
|
+
| Command | Key Flags | Description |
|
|
136
|
+
|---------|-----------|-------------|
|
|
137
|
+
| `role push <dir>` | `--dry-run`, `--json` | Push a role definition (create or update) |
|
|
138
|
+
|
|
121
139
|
### workspace
|
|
122
140
|
|
|
123
141
|
| Command | Description |
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* solidactions docs push <dir>
|
|
4
|
+
*
|
|
5
|
+
* Recursively uploads a local markdown tree into SA-Docs via the docs MCP
|
|
6
|
+
* server's `docs_vault bulk_create` tool, mirroring folder structure.
|
|
7
|
+
*
|
|
8
|
+
* Prints a report distinguishing fully-published docs from "properties pending"
|
|
9
|
+
* ones (docs whose frontmatter properties couldn't be validated yet).
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.docsPushWithConfig = docsPushWithConfig;
|
|
16
|
+
exports.docsPush = docsPush;
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
20
|
+
const api_1 = require("../utils/api");
|
|
21
|
+
const mcp_1 = require("../utils/mcp");
|
|
22
|
+
const CHUNK_SIZE = 50;
|
|
23
|
+
/**
|
|
24
|
+
* Recursively walk `dir` and collect all *.md files.
|
|
25
|
+
* Returns a list of absolute paths.
|
|
26
|
+
*/
|
|
27
|
+
function walkMarkdownFiles(dir) {
|
|
28
|
+
const results = [];
|
|
29
|
+
const walk = (current) => {
|
|
30
|
+
for (const entry of fs_1.default.readdirSync(current, { withFileTypes: true })) {
|
|
31
|
+
const abs = path_1.default.join(current, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
walk(abs);
|
|
34
|
+
}
|
|
35
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
36
|
+
results.push(abs);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
walk(dir);
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert an absolute file path to a bulk_create item, relative to `rootDir`.
|
|
45
|
+
*/
|
|
46
|
+
function fileToItem(absPath, rootDir) {
|
|
47
|
+
const title = path_1.default.basename(absPath, '.md');
|
|
48
|
+
const body = fs_1.default.readFileSync(absPath, 'utf8');
|
|
49
|
+
const relDir = path_1.default.relative(rootDir, path_1.default.dirname(absPath));
|
|
50
|
+
// Use POSIX separators; omit if file is in the root dir
|
|
51
|
+
const relative_folder_path = relDir === '' ? undefined : relDir.split(path_1.default.sep).join('/');
|
|
52
|
+
const item = { title, body, _filePath: absPath };
|
|
53
|
+
if (relative_folder_path !== undefined) {
|
|
54
|
+
item.relative_folder_path = relative_folder_path;
|
|
55
|
+
}
|
|
56
|
+
return item;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Merge two BulkCreateSummary objects by summing all numeric fields.
|
|
60
|
+
*/
|
|
61
|
+
function mergeSummaries(a, b) {
|
|
62
|
+
const keys = [
|
|
63
|
+
'created', 'overwritten', 'skipped', 'renamed', 'errors', 'folders_created',
|
|
64
|
+
'planned_create', 'planned_overwrite', 'planned_skip', 'planned_rename',
|
|
65
|
+
];
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const key of keys) {
|
|
68
|
+
const aVal = a[key] ?? 0;
|
|
69
|
+
const bVal = b[key] ?? 0;
|
|
70
|
+
if (aVal !== 0 || bVal !== 0) {
|
|
71
|
+
result[key] = aVal + bVal;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format the merged summary into human-readable totals line.
|
|
78
|
+
* Normal run: created/overwritten/skipped/renamed. Dry-run: planned_*.
|
|
79
|
+
*/
|
|
80
|
+
function formatSummaryLine(summary, dryRun) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (dryRun) {
|
|
83
|
+
if (summary.planned_create)
|
|
84
|
+
parts.push(`${summary.planned_create} planned create`);
|
|
85
|
+
if (summary.planned_overwrite)
|
|
86
|
+
parts.push(`${summary.planned_overwrite} planned overwrite`);
|
|
87
|
+
if (summary.planned_skip)
|
|
88
|
+
parts.push(`${summary.planned_skip} planned skip`);
|
|
89
|
+
if (summary.planned_rename)
|
|
90
|
+
parts.push(`${summary.planned_rename} planned rename`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (summary.created)
|
|
94
|
+
parts.push(`${summary.created} created`);
|
|
95
|
+
if (summary.overwritten)
|
|
96
|
+
parts.push(`${summary.overwritten} updated`);
|
|
97
|
+
if (summary.skipped)
|
|
98
|
+
parts.push(`${summary.skipped} skipped`);
|
|
99
|
+
if (summary.renamed)
|
|
100
|
+
parts.push(`${summary.renamed} renamed`);
|
|
101
|
+
if (summary.errors)
|
|
102
|
+
parts.push(`${summary.errors} errors`);
|
|
103
|
+
}
|
|
104
|
+
return parts.join(', ') || '0 docs';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Core implementation — accepts an injected config so tests can point at a
|
|
108
|
+
* stub server without touching the filesystem config.
|
|
109
|
+
*/
|
|
110
|
+
async function docsPushWithConfig(dir, options, config) {
|
|
111
|
+
const absDir = path_1.default.resolve(dir);
|
|
112
|
+
if (!fs_1.default.existsSync(absDir) || !fs_1.default.statSync(absDir).isDirectory()) {
|
|
113
|
+
process.stderr.write(chalk_1.default.red(`error: "${dir}" is not a directory.\n`));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const allFiles = walkMarkdownFiles(absDir);
|
|
117
|
+
if (allFiles.length === 0) {
|
|
118
|
+
process.stderr.write(chalk_1.default.red(`error: no .md files found under "${dir}" — nothing to push.\n`));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const items = allFiles.map((f) => fileToItem(f, absDir));
|
|
122
|
+
// Chunk into groups of CHUNK_SIZE
|
|
123
|
+
const chunks = [];
|
|
124
|
+
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
|
|
125
|
+
chunks.push(items.slice(i, i + CHUNK_SIZE));
|
|
126
|
+
}
|
|
127
|
+
const onConflict = options.onConflict ?? 'skip';
|
|
128
|
+
const allResultRows = [];
|
|
129
|
+
let mergedSummary = {};
|
|
130
|
+
for (const chunk of chunks) {
|
|
131
|
+
const callArgs = {
|
|
132
|
+
action: 'bulk_create',
|
|
133
|
+
on_conflict: onConflict,
|
|
134
|
+
items: chunk.map(({ title, body, relative_folder_path }) => {
|
|
135
|
+
const item = { title, body };
|
|
136
|
+
if (relative_folder_path !== undefined) {
|
|
137
|
+
item.relative_folder_path = relative_folder_path;
|
|
138
|
+
}
|
|
139
|
+
return item;
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
142
|
+
if (options.type) {
|
|
143
|
+
callArgs.type = options.type;
|
|
144
|
+
}
|
|
145
|
+
if (options.dryRun) {
|
|
146
|
+
callArgs.dry_run = true;
|
|
147
|
+
}
|
|
148
|
+
let mcpResult;
|
|
149
|
+
try {
|
|
150
|
+
mcpResult = await (0, mcp_1.callDocsTool)(config, 'docs_vault', callArgs);
|
|
151
|
+
}
|
|
152
|
+
catch (e) {
|
|
153
|
+
process.stderr.write(chalk_1.default.red(`error: MCP request failed — ${e.message}\n`));
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
if (!mcpResult.ok) {
|
|
157
|
+
const code = mcpResult.data?.code ?? 'unknown_error';
|
|
158
|
+
const message = mcpResult.data?.message ?? 'MCP returned an error with no message';
|
|
159
|
+
process.stderr.write(chalk_1.default.red(`error: ${code}: ${message}\n`));
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
const data = mcpResult.data;
|
|
163
|
+
const rows = data?.results ?? [];
|
|
164
|
+
const summary = data?.summary ?? {};
|
|
165
|
+
// Correlate rows back to source files using chunk index
|
|
166
|
+
for (const row of rows) {
|
|
167
|
+
const sourceFile = chunk[row.index]?._filePath ?? '(unknown)';
|
|
168
|
+
allResultRows.push({
|
|
169
|
+
...row,
|
|
170
|
+
file: path_1.default.relative(absDir, sourceFile),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
mergedSummary = mergeSummaries(mergedSummary, summary);
|
|
174
|
+
}
|
|
175
|
+
// Collect pending items (results that have property_validation)
|
|
176
|
+
const pendingItems = allResultRows
|
|
177
|
+
.filter((r) => r.property_validation != null)
|
|
178
|
+
.map((r) => ({
|
|
179
|
+
file: r.file,
|
|
180
|
+
missing: r.property_validation.missing ?? [],
|
|
181
|
+
invalid: r.property_validation.invalid ?? [],
|
|
182
|
+
}));
|
|
183
|
+
// --json: output single JSON object
|
|
184
|
+
if (options.json) {
|
|
185
|
+
console.log(JSON.stringify({
|
|
186
|
+
summary: mergedSummary,
|
|
187
|
+
pending: pendingItems,
|
|
188
|
+
results: allResultRows,
|
|
189
|
+
}));
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
// Human-readable output
|
|
193
|
+
const summaryLine = formatSummaryLine(mergedSummary, !!options.dryRun);
|
|
194
|
+
if (options.dryRun) {
|
|
195
|
+
console.log(chalk_1.default.cyan(`[dry-run preview] ${summaryLine}`));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.log(chalk_1.default.green(`done: ${summaryLine}`));
|
|
199
|
+
}
|
|
200
|
+
if (pendingItems.length > 0) {
|
|
201
|
+
console.log(chalk_1.default.yellow(`\nProperties pending (${pendingItems.length} doc${pendingItems.length === 1 ? '' : 's'}):`));
|
|
202
|
+
for (const item of pendingItems) {
|
|
203
|
+
console.log(chalk_1.default.yellow(` ${item.file}`));
|
|
204
|
+
if (item.missing.length > 0) {
|
|
205
|
+
console.log(chalk_1.default.yellow(` missing: ${item.missing.join(', ')}`));
|
|
206
|
+
}
|
|
207
|
+
for (const inv of item.invalid) {
|
|
208
|
+
console.log(chalk_1.default.yellow(` invalid: ${inv.key} — ${inv.reason}`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Entry point called from index.ts.
|
|
216
|
+
*/
|
|
217
|
+
async function docsPush(dir, options) {
|
|
218
|
+
const config = await (0, api_1.requireConfigWithWorkspace)();
|
|
219
|
+
await docsPushWithConfig(dir, options, config);
|
|
220
|
+
}
|
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runs = runs;
|
|
7
|
+
exports.summaryStatusLabel = summaryStatusLabel;
|
|
8
|
+
exports.detailedOutcomeTag = detailedOutcomeTag;
|
|
7
9
|
const axios_1 = __importDefault(require("axios"));
|
|
8
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
11
|
const api_1 = require("../utils/api");
|
|
@@ -70,6 +72,32 @@ async function runs(projectName, options = {}) {
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
// ─── Display modes ─────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Summary-table status label for a run. Recovered/degraded runs share SUCCESS's
|
|
77
|
+
* execution_status, so without this they'd be indistinguishable from a clean success in
|
|
78
|
+
* the summary view. Returns the label to display and whether it's an "attention" outcome
|
|
79
|
+
* (rendered yellow). Falls back to the raw status for servers that don't send `outcome`.
|
|
80
|
+
*/
|
|
81
|
+
function summaryStatusLabel(run) {
|
|
82
|
+
if (run.outcome === 'recovered')
|
|
83
|
+
return { label: 'recovered', attention: true };
|
|
84
|
+
if (run.outcome === 'degraded')
|
|
85
|
+
return { label: 'degraded', attention: true };
|
|
86
|
+
return { label: run.execution_status || run.status || '?', attention: false };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Detailed-view tag appended to a run header ('' when none). Prefers the API `outcome`;
|
|
90
|
+
* falls back to the local hasRunErrors heuristic for servers that don't send `outcome`.
|
|
91
|
+
*/
|
|
92
|
+
function detailedOutcomeTag(run) {
|
|
93
|
+
if (run.outcome === 'recovered')
|
|
94
|
+
return ' [RECOVERED]';
|
|
95
|
+
if (run.outcome === 'degraded')
|
|
96
|
+
return ' [DEGRADED]';
|
|
97
|
+
if (hasRunErrors(run) && isSuccessStatus(run.execution_status || run.status || ''))
|
|
98
|
+
return ' [DEGRADED]';
|
|
99
|
+
return '';
|
|
100
|
+
}
|
|
73
101
|
function displaySummaryTable(runsList, projectName) {
|
|
74
102
|
const header = projectName ? `Recent runs for "${projectName}":` : 'Recent runs:';
|
|
75
103
|
console.log(chalk_1.default.blue(header));
|
|
@@ -79,8 +107,8 @@ function displaySummaryTable(runsList, projectName) {
|
|
|
79
107
|
for (const run of runsList) {
|
|
80
108
|
const id = String(run.id || '?').padEnd(8);
|
|
81
109
|
const workflow = truncate(run.workflow_name || '?', 24).padEnd(25);
|
|
82
|
-
const status
|
|
83
|
-
const statusColor = getStatusColor(status);
|
|
110
|
+
const { label: status, attention } = summaryStatusLabel(run);
|
|
111
|
+
const statusColor = attention ? chalk_1.default.yellow : getStatusColor(status);
|
|
84
112
|
const triggeredAt = run.triggered_at ? new Date(run.triggered_at).toLocaleString() : '-';
|
|
85
113
|
const triggeredBy = run.triggered_by || '-';
|
|
86
114
|
console.log(chalk_1.default.gray(id) +
|
|
@@ -99,9 +127,9 @@ function displayDetailedList(runsList, projectName) {
|
|
|
99
127
|
const status = run.execution_status || run.status || '?';
|
|
100
128
|
const statusColor = getStatusColor(status);
|
|
101
129
|
const exitStr = run.exit_code !== null && run.exit_code !== undefined ? ` (exit ${run.exit_code})` : '';
|
|
102
|
-
const isSilentFailure = hasRunErrors(run) && isSuccessStatus(status);
|
|
103
130
|
console.log('');
|
|
104
|
-
const
|
|
131
|
+
const outcomeTag = detailedOutcomeTag(run);
|
|
132
|
+
const silentTag = outcomeTag ? chalk_1.default.yellow(outcomeTag) : '';
|
|
105
133
|
console.log(chalk_1.default.bold(` Run #${run.id}`) + chalk_1.default.gray(` — ${run.workflow_name || '?'} (${run.project_name || '?'})`) + silentTag);
|
|
106
134
|
console.log(` Status: ${statusColor(status)}${chalk_1.default.gray(exitStr)}`);
|
|
107
135
|
console.log(` Trigger: ${chalk_1.default.gray(run.triggered_by || '-')}`);
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const skill_list_1 = require("./commands/skill-list");
|
|
|
38
38
|
const skill_view_1 = require("./commands/skill-view");
|
|
39
39
|
const skill_delete_1 = require("./commands/skill-delete");
|
|
40
40
|
const role_push_1 = require("./commands/role-push");
|
|
41
|
+
const docs_push_1 = require("./commands/docs-push");
|
|
41
42
|
const config_1 = require("./utils/config");
|
|
42
43
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
43
44
|
const pkg = require('../package.json');
|
|
@@ -454,6 +455,21 @@ role
|
|
|
454
455
|
.action(async (dir, options) => {
|
|
455
456
|
await (0, role_push_1.rolePush)(dir, options);
|
|
456
457
|
});
|
|
458
|
+
// =============================================================================
|
|
459
|
+
// docs <subcommand> (SA-Docs surface)
|
|
460
|
+
// =============================================================================
|
|
461
|
+
const docs = program.command('docs').description('Manage docs in SA-Docs');
|
|
462
|
+
docs
|
|
463
|
+
.command('push')
|
|
464
|
+
.description('Recursively upload a local markdown tree into SA-Docs')
|
|
465
|
+
.argument('<dir>', 'Path to the directory containing markdown files')
|
|
466
|
+
.option('--on-conflict <mode>', 'Conflict resolution: skip|overwrite|rename (default: skip)', 'skip')
|
|
467
|
+
.option('--type <slug>', 'Doc-type slug to apply to all uploaded docs')
|
|
468
|
+
.option('--dry-run', 'Preview what would be created without writing')
|
|
469
|
+
.option('--json', 'Output result as JSON')
|
|
470
|
+
.action(async (dir, options) => {
|
|
471
|
+
await (0, docs_push_1.docsPush)(dir, { onConflict: options.onConflict, type: options.type, dryRun: options.dryRun, json: options.json });
|
|
472
|
+
});
|
|
457
473
|
program.parseAsync().catch((err) => {
|
|
458
474
|
console.error(chalk_1.default.red(err.message ?? String(err)));
|
|
459
475
|
process.exit(1);
|
package/dist/utils/mcp.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Minimal MCP client for the SolidActions
|
|
3
|
+
* Minimal MCP client for the SolidActions in-app MCP servers.
|
|
4
4
|
*
|
|
5
5
|
* Sends a single stateless JSON-RPC tools/call POST. No initialize handshake
|
|
6
|
-
* required — the streamable-HTTP transport
|
|
7
|
-
*
|
|
6
|
+
* required — the streamable-HTTP transport accepts single stateless POSTs
|
|
7
|
+
* (confirmed live).
|
|
8
8
|
*/
|
|
9
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -41,16 +41,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
})();
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.callCrewsTool = callCrewsTool;
|
|
44
|
+
exports.callDocsTool = callDocsTool;
|
|
44
45
|
const http = __importStar(require("http"));
|
|
45
46
|
const https = __importStar(require("https"));
|
|
46
47
|
const url_1 = require("url");
|
|
47
48
|
const api_1 = require("./api");
|
|
48
49
|
/**
|
|
49
|
-
*
|
|
50
|
+
* Internal: call a single MCP tool on the given endpoint path.
|
|
50
51
|
*
|
|
51
52
|
* Returns { ok: true, data: <success shape> } or { ok: false, data: { code, message } }.
|
|
52
53
|
*/
|
|
53
|
-
async function
|
|
54
|
+
async function callMcpTool(config, endpointPath, toolName, args) {
|
|
54
55
|
const baseHeaders = (0, api_1.getApiHeaders)(config, 'application/json');
|
|
55
56
|
// Override Accept to include text/event-stream for streamable-HTTP transport
|
|
56
57
|
const headers = {
|
|
@@ -66,7 +67,7 @@ async function callCrewsTool(config, toolName, args) {
|
|
|
66
67
|
arguments: args,
|
|
67
68
|
},
|
|
68
69
|
});
|
|
69
|
-
const parsed = new url_1.URL(`${config.host}
|
|
70
|
+
const parsed = new url_1.URL(`${config.host}${endpointPath}`);
|
|
70
71
|
const isHttps = parsed.protocol === 'https:';
|
|
71
72
|
const transport = isHttps ? https : http;
|
|
72
73
|
const responseData = await new Promise((resolve, reject) => {
|
|
@@ -115,3 +116,15 @@ async function callCrewsTool(config, toolName, args) {
|
|
|
115
116
|
}
|
|
116
117
|
return { ok: !isError, data: toolData };
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Call a single MCP tool on /mcp/crews.
|
|
121
|
+
*/
|
|
122
|
+
async function callCrewsTool(config, toolName, args) {
|
|
123
|
+
return callMcpTool(config, '/mcp/crews', toolName, args);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Call a single MCP tool on /mcp/docs.
|
|
127
|
+
*/
|
|
128
|
+
async function callDocsTool(config, toolName, args) {
|
|
129
|
+
return callMcpTool(config, '/mcp/docs', toolName, args);
|
|
130
|
+
}
|