@qodalis/mcp-cli 0.0.0-stage → 0.2.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +21 -0
  3. package/README.md +384 -2
  4. package/dist/src/cli.d.ts +3 -0
  5. package/dist/src/cli.d.ts.map +1 -0
  6. package/dist/src/cli.js +102 -0
  7. package/dist/src/cli.js.map +1 -0
  8. package/dist/src/config.d.ts +60 -0
  9. package/dist/src/config.d.ts.map +1 -0
  10. package/dist/src/config.js +149 -0
  11. package/dist/src/config.js.map +1 -0
  12. package/dist/src/dev/api.d.ts +94 -0
  13. package/dist/src/dev/api.d.ts.map +1 -0
  14. package/dist/src/dev/api.js +425 -0
  15. package/dist/src/dev/api.js.map +1 -0
  16. package/dist/src/dev/docs.d.ts +93 -0
  17. package/dist/src/dev/docs.d.ts.map +1 -0
  18. package/dist/src/dev/docs.js +275 -0
  19. package/dist/src/dev/docs.js.map +1 -0
  20. package/dist/src/dev/prompts.d.ts +11 -0
  21. package/dist/src/dev/prompts.d.ts.map +1 -0
  22. package/dist/src/dev/prompts.js +141 -0
  23. package/dist/src/dev/prompts.js.map +1 -0
  24. package/dist/src/dev/scaffold.d.ts +55 -0
  25. package/dist/src/dev/scaffold.d.ts.map +1 -0
  26. package/dist/src/dev/scaffold.js +213 -0
  27. package/dist/src/dev/scaffold.js.map +1 -0
  28. package/dist/src/dev/tools.d.ts +170 -0
  29. package/dist/src/dev/tools.d.ts.map +1 -0
  30. package/dist/src/dev/tools.js +258 -0
  31. package/dist/src/dev/tools.js.map +1 -0
  32. package/dist/src/env-file.d.ts +9 -0
  33. package/dist/src/env-file.d.ts.map +1 -0
  34. package/dist/src/env-file.js +54 -0
  35. package/dist/src/env-file.js.map +1 -0
  36. package/dist/src/errors.d.ts +22 -0
  37. package/dist/src/errors.d.ts.map +1 -0
  38. package/dist/src/errors.js +115 -0
  39. package/dist/src/errors.js.map +1 -0
  40. package/dist/src/filters.d.ts +13 -0
  41. package/dist/src/filters.d.ts.map +1 -0
  42. package/dist/src/filters.js +16 -0
  43. package/dist/src/filters.js.map +1 -0
  44. package/dist/src/index.d.ts +12 -0
  45. package/dist/src/index.d.ts.map +1 -0
  46. package/dist/src/index.js +12 -0
  47. package/dist/src/index.js.map +1 -0
  48. package/dist/src/remote.d.ts +64 -0
  49. package/dist/src/remote.d.ts.map +1 -0
  50. package/dist/src/remote.js +242 -0
  51. package/dist/src/remote.js.map +1 -0
  52. package/dist/src/server.d.ts +22 -0
  53. package/dist/src/server.d.ts.map +1 -0
  54. package/dist/src/server.js +156 -0
  55. package/dist/src/server.js.map +1 -0
  56. package/docs/llms-full.txt +1740 -0
  57. package/package.json +70 -5
@@ -0,0 +1,213 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ /**
5
+ * `qcli_scaffold_plugin`: runs `@qodalis/create-cli-plugin` non-interactively. Inside a
6
+ * web-cli checkout it creates `packages/plugins/<name>`; anywhere else a standalone
7
+ * `qodalis-cli-<name>` project (and installs its dependencies).
8
+ */
9
+ export const SCAFFOLD_PACKAGE = '@qodalis/create-cli-plugin@latest';
10
+ export const SCAFFOLD_TIMEOUT_MS = 5 * 60_000;
11
+ const OUTPUT_LIMIT = 6000;
12
+ export class ScaffoldError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'ScaffoldError';
16
+ }
17
+ }
18
+ const toPascalCase = (name) => name
19
+ .split(/[-_\s]+/)
20
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
21
+ .join('');
22
+ /** The web-cli monorepo root above `dir` (same rule as create-cli-plugin), if any. */
23
+ export const detectMonorepo = (dir) => {
24
+ let current = path.resolve(dir);
25
+ for (;;) {
26
+ const workspace = path.join(current, 'pnpm-workspace.yaml');
27
+ if (existsSync(workspace) &&
28
+ existsSync(path.join(current, 'packages', 'plugins')) &&
29
+ readFileSync(workspace, 'utf8').includes('packages/plugins')) {
30
+ return current;
31
+ }
32
+ const parent = path.dirname(current);
33
+ if (parent === current) {
34
+ return undefined;
35
+ }
36
+ current = parent;
37
+ }
38
+ };
39
+ /** Validates the arguments and works out where the plugin goes; throws {@link ScaffoldError}. */
40
+ export const planScaffold = (input, cwd = process.cwd()) => {
41
+ const name = (input.name ?? '').trim();
42
+ if (!/^[a-z][a-z0-9-]*$/.test(name) || name.endsWith('-') || name.includes('--')) {
43
+ throw new ScaffoldError(`Invalid plugin name "${name}": use lowercase letters, digits and single hyphens, starting with a letter (e.g. "weather" or "json-tools").`);
44
+ }
45
+ if (name.startsWith('cli-')) {
46
+ throw new ScaffoldError(`Invalid plugin name "${name}": leave out the "cli-" prefix, it is added to the package name (@qodalis/cli-${name.slice(4)}).`);
47
+ }
48
+ if (name.length > 50) {
49
+ throw new ScaffoldError('Invalid plugin name: at most 50 characters.');
50
+ }
51
+ const processorName = input.processorName?.trim() || toPascalCase(name);
52
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(processorName)) {
53
+ throw new ScaffoldError(`Invalid processorName "${processorName}": PascalCase letters and digits (e.g. "Weather"; the class becomes Cli${processorName}CommandProcessor).`);
54
+ }
55
+ const description = input.description?.trim();
56
+ if (description !== undefined &&
57
+ (description.length > 200 || /[\0-\x1f"`$\\]/.test(description))) {
58
+ throw new ScaffoldError('Invalid description: one line of at most 200 characters, without quotes, backticks, $ or backslashes.');
59
+ }
60
+ const dir = path.resolve(cwd, input.directory?.trim() || '.');
61
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
62
+ throw new ScaffoldError(`Directory ${dir} does not exist.`);
63
+ }
64
+ const monorepo = detectMonorepo(dir);
65
+ const target = monorepo
66
+ ? path.join(monorepo, 'packages', 'plugins', name)
67
+ : path.join(dir, `qodalis-cli-${name}`);
68
+ if (existsSync(target)) {
69
+ throw new ScaffoldError(`${target} already exists; pick another name or directory.`);
70
+ }
71
+ const args = ['-y', SCAFFOLD_PACKAGE, '--name', name, '--processor-name', processorName];
72
+ if (description) {
73
+ args.push('--description', description);
74
+ }
75
+ return { args, cwd: dir, monorepo, name, processorName, target };
76
+ };
77
+ /** Runs a command without a TTY, stdin closed, capturing stdout and stderr together. */
78
+ export const runCommand = (command, args, options) => new Promise((resolve, reject) => {
79
+ const windows = process.platform === 'win32';
80
+ // npx is a .cmd on Windows, which needs a shell; the arguments are validated
81
+ // (no quotes or shell metacharacters) and quoted.
82
+ const viaShell = windows && command === 'npx';
83
+ const child = spawn(viaShell ? 'npx.cmd' : command, viaShell ? args.map((a) => `"${a}"`) : args, {
84
+ cwd: options.cwd,
85
+ // Own process group on POSIX, so a timeout also stops npm install below npx.
86
+ detached: !windows,
87
+ env: { ...process.env, CI: 'true', npm_config_yes: 'true' },
88
+ shell: viaShell,
89
+ stdio: ['ignore', 'pipe', 'pipe'],
90
+ });
91
+ let output = '';
92
+ let timedOut = false;
93
+ const append = (chunk) => {
94
+ output += chunk.toString();
95
+ if (output.length > OUTPUT_LIMIT * 4) {
96
+ output = output.slice(-OUTPUT_LIMIT * 2);
97
+ }
98
+ };
99
+ child.stdout.on('data', append);
100
+ child.stderr.on('data', append);
101
+ const signalTree = (signal) => {
102
+ try {
103
+ if (!windows && child.pid) {
104
+ process.kill(-child.pid, signal);
105
+ }
106
+ else {
107
+ child.kill(signal);
108
+ }
109
+ }
110
+ catch {
111
+ // already gone
112
+ }
113
+ };
114
+ const kill = () => {
115
+ signalTree('SIGTERM');
116
+ setTimeout(() => signalTree('SIGKILL'), 3000).unref();
117
+ };
118
+ const timer = setTimeout(() => {
119
+ timedOut = true;
120
+ kill();
121
+ }, options.timeoutMs);
122
+ options.signal?.addEventListener('abort', kill, { once: true });
123
+ child.on('error', (error) => {
124
+ clearTimeout(timer);
125
+ reject(error);
126
+ });
127
+ child.on('close', (code) => {
128
+ clearTimeout(timer);
129
+ options.signal?.removeEventListener('abort', kill);
130
+ resolve({ code, output, timedOut });
131
+ });
132
+ });
133
+ /** Files under `dir`, relative, without node_modules / dist / .git. */
134
+ export const listFiles = (dir, limit = 200) => {
135
+ const files = [];
136
+ const walk = (current) => {
137
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
138
+ if (files.length >= limit) {
139
+ return;
140
+ }
141
+ if (['node_modules', 'dist', '.git'].includes(entry.name)) {
142
+ continue;
143
+ }
144
+ const full = path.join(current, entry.name);
145
+ if (entry.isDirectory()) {
146
+ walk(full);
147
+ }
148
+ else {
149
+ files.push(path.relative(dir, full).split(path.sep).join('/'));
150
+ }
151
+ }
152
+ };
153
+ if (existsSync(dir)) {
154
+ walk(dir);
155
+ }
156
+ return files.sort();
157
+ };
158
+ const stripAnsi = (text) => text.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
159
+ const tail = (text) => {
160
+ const clean = stripAnsi(text).trim();
161
+ return clean.length > OUTPUT_LIMIT ? `…${clean.slice(-OUTPUT_LIMIT)}` : clean;
162
+ };
163
+ /** Runs create-cli-plugin for a validated plan and describes what it created. */
164
+ export const scaffoldPlugin = async (plan, options = {}) => {
165
+ const timeoutMs = options.timeoutMs ?? SCAFFOLD_TIMEOUT_MS;
166
+ const result = await (options.runner ?? runCommand)('npx', plan.args, {
167
+ cwd: plan.cwd,
168
+ signal: options.signal,
169
+ timeoutMs,
170
+ });
171
+ const command = `npx ${plan.args.join(' ')}`;
172
+ if (result.timedOut || result.code !== 0) {
173
+ const why = result.timedOut
174
+ ? `timed out after ${Math.round(timeoutMs / 1000)} s`
175
+ : `exited with code ${result.code}`;
176
+ return {
177
+ isError: true,
178
+ text: `\`${command}\` (in ${plan.cwd}) ${why}.\n\nOutput:\n${tail(result.output)}`,
179
+ };
180
+ }
181
+ const files = listFiles(plan.target);
182
+ const className = `Cli${plan.processorName}CommandProcessor`;
183
+ const steps = plan.monorepo
184
+ ? [
185
+ `Implement the command in ${plan.target}/src/lib/processors/ (class ${className}).`,
186
+ `Build: \`pnpm nx build ${plan.name}\`; test: \`pnpm nx test ${plan.name}\`.`,
187
+ 'Register the module in a demo app (e.g. apps/demo-angular) to try it.',
188
+ ]
189
+ : [
190
+ `Implement the command in ${plan.target}/src/lib/processors/ (class ${className}).`,
191
+ `Build with \`npx tsup\` in ${plan.target}.`,
192
+ 'Register the exported module in your host app (`modules` input/prop, or engine.registerModule()).',
193
+ 'Publish with `npm publish --access public` when ready.',
194
+ ];
195
+ return {
196
+ isError: false,
197
+ text: [
198
+ `Created the ${plan.name} plugin in ${plan.target}${plan.monorepo ? ` (web-cli monorepo at ${plan.monorepo})` : ''}.`,
199
+ '',
200
+ `Files (${files.length}):`,
201
+ ...files.map((f) => `- ${f}`),
202
+ '',
203
+ 'Next steps:',
204
+ ...steps.map((s, i) => `${i + 1}. ${s}`),
205
+ '',
206
+ 'Look up the APIs with qcli_api (ICliCommandProcessor, ICliExecutionContext, ICliModule) and the guide with qcli_docs_search.',
207
+ '',
208
+ `Output of \`${command}\`:`,
209
+ tail(result.output),
210
+ ].join('\n'),
211
+ };
212
+ };
213
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/dev/scaffold.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ import type { CallToolResult, Tool } from '@modelcontextprotocol/server';
2
+ import type { Config } from '../config.js';
3
+ import { ApiSource, type ApiSourceOptions } from './api.js';
4
+ import { DocsSource, type DocsSourceOptions } from './docs.js';
5
+ import { type Runner } from './scaffold.js';
6
+ export declare const DOCS_RESOURCE_SCHEME = "qcli-docs://";
7
+ /** The local plugin-development tools, served without a Qodalis CLI server. */
8
+ export declare const DEV_TOOLS: Tool[];
9
+ export declare const DEV_TOOL_NAMES: Set<string>;
10
+ export type DevToolsOptions = {
11
+ api?: ApiSourceOptions;
12
+ docs?: Partial<DocsSourceOptions>;
13
+ log?: (message: string) => void;
14
+ /** Runs npx (tests) */
15
+ runner?: Runner;
16
+ scaffoldTimeoutMs?: number;
17
+ };
18
+ /** Docs, API and scaffold tools plus the prompts and docs resources, filtered by the config. */
19
+ export declare class DevTools {
20
+ private readonly config;
21
+ private readonly options;
22
+ readonly docs: DocsSource;
23
+ readonly api: ApiSource;
24
+ constructor(config: Config, options?: DevToolsOptions);
25
+ /** The dev tools that pass QCLI_READ_ONLY / QCLI_TOOLS / QCLI_EXCLUDE. */
26
+ tools(): Tool[];
27
+ has(name: string): boolean;
28
+ /** Why a dev tool is hidden, or undefined when it is available. */
29
+ hiddenReason(name: string): string | undefined;
30
+ call(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<CallToolResult>;
31
+ private search;
32
+ private read;
33
+ private lookup;
34
+ private scaffold;
35
+ prompts(): {
36
+ name: string;
37
+ description?: string | undefined;
38
+ arguments?: {
39
+ name: string;
40
+ description?: string | undefined;
41
+ required?: boolean | undefined;
42
+ }[] | undefined;
43
+ _meta?: {
44
+ [x: string]: unknown;
45
+ } | undefined;
46
+ icons?: {
47
+ src: string;
48
+ mimeType?: string | undefined;
49
+ sizes?: string[] | undefined;
50
+ theme?: "light" | "dark" | undefined;
51
+ }[] | undefined;
52
+ title?: string | undefined;
53
+ }[];
54
+ getPrompt(name: string, args: Record<string, string> | undefined): {
55
+ [x: string]: unknown;
56
+ messages: {
57
+ role: "user" | "assistant";
58
+ content: {
59
+ type: "text";
60
+ text: string;
61
+ annotations?: {
62
+ audience?: ("user" | "assistant")[] | undefined;
63
+ priority?: number | undefined;
64
+ lastModified?: string | undefined;
65
+ } | undefined;
66
+ _meta?: {
67
+ [x: string]: unknown;
68
+ } | undefined;
69
+ } | {
70
+ type: "image";
71
+ data: string;
72
+ mimeType: string;
73
+ annotations?: {
74
+ audience?: ("user" | "assistant")[] | undefined;
75
+ priority?: number | undefined;
76
+ lastModified?: string | undefined;
77
+ } | undefined;
78
+ _meta?: {
79
+ [x: string]: unknown;
80
+ } | undefined;
81
+ } | {
82
+ type: "audio";
83
+ data: string;
84
+ mimeType: string;
85
+ annotations?: {
86
+ audience?: ("user" | "assistant")[] | undefined;
87
+ priority?: number | undefined;
88
+ lastModified?: string | undefined;
89
+ } | undefined;
90
+ _meta?: {
91
+ [x: string]: unknown;
92
+ } | undefined;
93
+ } | {
94
+ uri: string;
95
+ name: string;
96
+ type: "resource_link";
97
+ description?: string | undefined;
98
+ mimeType?: string | undefined;
99
+ size?: number | undefined;
100
+ annotations?: {
101
+ audience?: ("user" | "assistant")[] | undefined;
102
+ priority?: number | undefined;
103
+ lastModified?: string | undefined;
104
+ } | undefined;
105
+ _meta?: {
106
+ [x: string]: unknown;
107
+ } | undefined;
108
+ icons?: {
109
+ src: string;
110
+ mimeType?: string | undefined;
111
+ sizes?: string[] | undefined;
112
+ theme?: "light" | "dark" | undefined;
113
+ }[] | undefined;
114
+ title?: string | undefined;
115
+ } | {
116
+ type: "resource";
117
+ resource: {
118
+ uri: string;
119
+ text: string;
120
+ mimeType?: string | undefined;
121
+ _meta?: {
122
+ [x: string]: unknown;
123
+ } | undefined;
124
+ } | {
125
+ uri: string;
126
+ blob: string;
127
+ mimeType?: string | undefined;
128
+ _meta?: {
129
+ [x: string]: unknown;
130
+ } | undefined;
131
+ };
132
+ annotations?: {
133
+ audience?: ("user" | "assistant")[] | undefined;
134
+ priority?: number | undefined;
135
+ lastModified?: string | undefined;
136
+ } | undefined;
137
+ _meta?: {
138
+ [x: string]: unknown;
139
+ } | undefined;
140
+ };
141
+ }[];
142
+ _meta?: {
143
+ [x: string]: unknown;
144
+ "io.modelcontextprotocol/serverInfo"?: {
145
+ version: string;
146
+ name: string;
147
+ websiteUrl?: string | undefined;
148
+ description?: string | undefined;
149
+ icons?: {
150
+ src: string;
151
+ mimeType?: string | undefined;
152
+ sizes?: string[] | undefined;
153
+ theme?: "light" | "dark" | undefined;
154
+ }[] | undefined;
155
+ title?: string | undefined;
156
+ } | undefined;
157
+ } | undefined;
158
+ description?: string | undefined;
159
+ };
160
+ resources(): Promise<{
161
+ description: string;
162
+ mimeType: string;
163
+ name: string;
164
+ title: string;
165
+ uri: string;
166
+ }[]>;
167
+ /** The markdown of a `qcli-docs://<section>/<topic>` resource, or undefined. */
168
+ readResource(uri: string): Promise<string | undefined>;
169
+ }
170
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/dev/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EACH,SAAS,EAIT,KAAK,gBAAgB,EACxB,MAAM,UAAU,CAAC;AAClB,OAAO,EACH,UAAU,EAKV,KAAK,iBAAiB,EACzB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAE1E,eAAO,MAAM,oBAAoB,iBAAiB,CAAC;AAEnD,+EAA+E;AAC/E,eAAO,MAAM,SAAS,EAAE,IAAI,EA4F3B,CAAC;AAEF,eAAO,MAAM,cAAc,aAAwC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC1B,GAAG,CAAC,EAAE,gBAAgB,CAAC;IACvB,IAAI,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAyBF,gGAAgG;AAChG,qBAAa,QAAQ;IAKb,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;gBAGH,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB;IAUlD,0EAA0E;IAC1E,KAAK,IAAI,IAAI,EAAE;IAIf,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,mEAAmE;IACnE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAUxC,IAAI,CACN,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,cAAc,CAAC;YAmBZ,MAAM;YAuBN,IAAI;YAwBJ,MAAM;YAmBN,QAAQ;IA2BtB,OAAO;;;;;;;;;;;;;;;;;;;IAIP,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAO1D,SAAS;;;;;;;IAWf,gFAAgF;IAC1E,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAQ/D"}
@@ -0,0 +1,258 @@
1
+ import { isToolAllowed } from '../filters.js';
2
+ import { ApiSource, formatLookup, lookupSymbol, normalizePackageName, } from './api.js';
3
+ import { DocsSource, listTopics, readDocs, searchDocs, } from './docs.js';
4
+ import { getPrompt, PROMPTS } from './prompts.js';
5
+ import { planScaffold, scaffoldPlugin } from './scaffold.js';
6
+ export const DOCS_RESOURCE_SCHEME = 'qcli-docs://';
7
+ /** The local plugin-development tools, served without a Qodalis CLI server. */
8
+ export const DEV_TOOLS = [
9
+ {
10
+ annotations: { openWorldHint: true, readOnlyHint: true, title: 'Search the plugin docs' },
11
+ description: 'Search the Qodalis CLI plugin-development guide (web plugins, server plugins, recipes). Returns the best matching topics as "Section › Topic" with their summary and an excerpt; read one in full with qcli_docs_read.',
12
+ inputSchema: {
13
+ properties: {
14
+ limit: {
15
+ description: 'Maximum number of topics (default 5, at most 20)',
16
+ maximum: 20,
17
+ minimum: 1,
18
+ type: 'integer',
19
+ },
20
+ query: {
21
+ description: 'Keywords, e.g. "read user input", "register module", "ICliExecutionContext"',
22
+ type: 'string',
23
+ },
24
+ },
25
+ required: ['query'],
26
+ type: 'object',
27
+ },
28
+ name: 'qcli_docs_search',
29
+ },
30
+ {
31
+ annotations: { openWorldHint: true, readOnlyHint: true, title: 'Read a plugin docs topic' },
32
+ description: 'Read a topic of the Qodalis CLI plugin-development guide in full (markdown with code samples). Pass a topic heading (case-insensitive), "Section › Topic" when a heading exists in several sections, or a section name ("Web plugins") for the whole section. Lists the topics when nothing matches.',
33
+ inputSchema: {
34
+ properties: {
35
+ topic: {
36
+ description: 'Topic heading, "Section › Topic", or a section name',
37
+ type: 'string',
38
+ },
39
+ },
40
+ required: ['topic'],
41
+ type: 'object',
42
+ },
43
+ name: 'qcli_docs_read',
44
+ },
45
+ {
46
+ annotations: { openWorldHint: true, readOnlyHint: true, title: 'Look up a Qodalis API' },
47
+ description: "TypeScript declaration (with JSDoc) of an interface, type, class, function, enum or constant from the published @qodalis packages: the project's node_modules when installed, else the latest version on npm. Use it for exact member names and signatures, e.g. ICliCommandProcessor, ICliExecutionContext, ICliModule, ICliInputReader, CliProcessCommand.",
48
+ inputSchema: {
49
+ properties: {
50
+ package: {
51
+ description: 'Package to read (default @qodalis/cli-core; also @qodalis/cli, @qodalis/angular-cli, ...). "cli-core" is short for @qodalis/cli-core.',
52
+ type: 'string',
53
+ },
54
+ symbol: { description: 'Exported name, case-sensitive', type: 'string' },
55
+ },
56
+ required: ['symbol'],
57
+ type: 'object',
58
+ },
59
+ name: 'qcli_api',
60
+ },
61
+ {
62
+ annotations: {
63
+ destructiveHint: false,
64
+ idempotentHint: false,
65
+ openWorldHint: true,
66
+ readOnlyHint: false,
67
+ title: 'Scaffold a Qodalis CLI plugin',
68
+ },
69
+ description: 'Create a new web CLI plugin with @qodalis/create-cli-plugin (npx): processor, module, build config and tests. Inside a web-cli checkout it goes to packages/plugins/<name>; elsewhere to a standalone qodalis-cli-<name> project with its dependencies installed. Returns the created files and next steps.',
70
+ inputSchema: {
71
+ properties: {
72
+ description: { description: 'One-line description of the plugin', type: 'string' },
73
+ directory: {
74
+ description: 'Directory to run in (default: the current working directory of the MCP server)',
75
+ type: 'string',
76
+ },
77
+ name: {
78
+ description: 'Plugin name: lowercase letters, digits and hyphens, without a "cli-" prefix (package @qodalis/cli-<name>)',
79
+ type: 'string',
80
+ },
81
+ processorName: {
82
+ description: 'PascalCase processor name (class Cli<processorName>CommandProcessor); default derived from name',
83
+ type: 'string',
84
+ },
85
+ },
86
+ required: ['name'],
87
+ type: 'object',
88
+ },
89
+ name: 'qcli_scaffold_plugin',
90
+ },
91
+ ];
92
+ export const DEV_TOOL_NAMES = new Set(DEV_TOOLS.map((t) => t.name));
93
+ const text = (value, isError = false) => ({
94
+ content: [{ text: value, type: 'text' }],
95
+ ...(isError ? { isError: true } : {}),
96
+ });
97
+ const stringArg = (args, name, required = false) => {
98
+ const value = args[name];
99
+ if (value === undefined || value === null || value === '') {
100
+ if (required) {
101
+ throw new Error(`Missing required argument "${name}".`);
102
+ }
103
+ return undefined;
104
+ }
105
+ if (typeof value !== 'string') {
106
+ throw new Error(`Argument "${name}" must be a string.`);
107
+ }
108
+ return value;
109
+ };
110
+ /** Docs, API and scaffold tools plus the prompts and docs resources, filtered by the config. */
111
+ export class DevTools {
112
+ config;
113
+ options;
114
+ docs;
115
+ api;
116
+ constructor(config, options = {}) {
117
+ this.config = config;
118
+ this.options = options;
119
+ this.docs = new DocsSource({
120
+ url: config.docsUrl,
121
+ ...(options.log ? { log: options.log } : {}),
122
+ ...options.docs,
123
+ });
124
+ this.api = new ApiSource({ ...(options.log ? { log: options.log } : {}), ...options.api });
125
+ }
126
+ /** The dev tools that pass QCLI_READ_ONLY / QCLI_TOOLS / QCLI_EXCLUDE. */
127
+ tools() {
128
+ return DEV_TOOLS.filter((tool) => isToolAllowed(tool, this.config));
129
+ }
130
+ has(name) {
131
+ return DEV_TOOL_NAMES.has(name);
132
+ }
133
+ /** Why a dev tool is hidden, or undefined when it is available. */
134
+ hiddenReason(name) {
135
+ const tool = DEV_TOOLS.find((t) => t.name === name);
136
+ if (!tool || isToolAllowed(tool, this.config)) {
137
+ return undefined;
138
+ }
139
+ return this.config.readOnly && tool.annotations?.readOnlyHint !== true
140
+ ? `Tool ${name} is not available: QCLI_READ_ONLY allows only read-only tools.`
141
+ : `Tool ${name} is not available: excluded by QCLI_TOOLS / QCLI_EXCLUDE.`;
142
+ }
143
+ async call(name, args, signal) {
144
+ try {
145
+ switch (name) {
146
+ case 'qcli_docs_search':
147
+ return await this.search(args);
148
+ case 'qcli_docs_read':
149
+ return await this.read(args);
150
+ case 'qcli_api':
151
+ return await this.lookup(args);
152
+ case 'qcli_scaffold_plugin':
153
+ return await this.scaffold(args, signal);
154
+ default:
155
+ return text(`Unknown tool: ${name}`, true);
156
+ }
157
+ }
158
+ catch (error) {
159
+ return text(error instanceof Error ? error.message : String(error), true);
160
+ }
161
+ }
162
+ async search(args) {
163
+ const query = stringArg(args, 'query', true);
164
+ const rawLimit = args['limit'];
165
+ const limit = typeof rawLimit === 'number' && Number.isFinite(rawLimit)
166
+ ? Math.min(20, Math.max(1, Math.floor(rawLimit)))
167
+ : 5;
168
+ const docs = await this.docs.get();
169
+ const hits = searchDocs(docs, query, limit);
170
+ if (hits.length === 0) {
171
+ return text(`No topic matches "${query}". Topics in the guide (${docs.source}):\n\n${listTopics(docs)}`);
172
+ }
173
+ const lines = hits.map((hit, i) => `${i + 1}. **${hit.topic.section} › ${hit.topic.title}** (score ${hit.score})\n ${hit.topic.summary}${hit.excerpt && hit.excerpt !== hit.topic.summary ? `\n > ${hit.excerpt}` : ''}`);
174
+ return text(`${hits.length} topic(s) for "${query}" (source: ${docs.source}). Read one with qcli_docs_read and its "Section › Topic".\n\n${lines.join('\n')}`);
175
+ }
176
+ async read(args) {
177
+ const topic = stringArg(args, 'topic', true);
178
+ const docs = await this.docs.get();
179
+ const result = readDocs(docs, topic);
180
+ if (result.kind === 'section') {
181
+ return text(result.section.content);
182
+ }
183
+ if (result.kind === 'topics') {
184
+ return text(result.topics
185
+ .map((t) => result.topics.length > 1
186
+ ? `<!-- ${t.section} › ${t.title} -->\n${t.content}`
187
+ : t.content)
188
+ .join('\n\n---\n\n'));
189
+ }
190
+ return text(`No topic or section "${topic}" in the guide (${docs.source}). Available topics:\n\n${listTopics(docs)}`, true);
191
+ }
192
+ async lookup(args) {
193
+ const symbol = stringArg(args, 'symbol', true).trim();
194
+ if (!/^[A-Za-z_$][\w$]*$/.test(symbol)) {
195
+ return text(`"${symbol}" is not an identifier (e.g. ICliCommandProcessor).`, true);
196
+ }
197
+ const pkg = normalizePackageName(stringArg(args, 'package'));
198
+ let types;
199
+ try {
200
+ types = await this.api.get(pkg);
201
+ }
202
+ catch (error) {
203
+ return text(`Could not load the declarations of ${pkg}: ${error.message}. Install the package in the project (npm install ${pkg}) to work offline.`, true);
204
+ }
205
+ const lookup = lookupSymbol(types, symbol);
206
+ return text(formatLookup(symbol, lookup), lookup.declarations.length === 0);
207
+ }
208
+ async scaffold(args, signal) {
209
+ const reason = this.hiddenReason('qcli_scaffold_plugin');
210
+ if (reason) {
211
+ return text(reason, true);
212
+ }
213
+ const plan = planScaffold({
214
+ description: stringArg(args, 'description'),
215
+ directory: stringArg(args, 'directory'),
216
+ name: stringArg(args, 'name', true),
217
+ processorName: stringArg(args, 'processorName'),
218
+ });
219
+ this.options.log?.(`scaffold: npx ${plan.args.join(' ')} in ${plan.cwd}`);
220
+ const result = await scaffoldPlugin(plan, {
221
+ ...(this.options.runner ? { runner: this.options.runner } : {}),
222
+ ...(this.options.scaffoldTimeoutMs
223
+ ? { timeoutMs: this.options.scaffoldTimeoutMs }
224
+ : {}),
225
+ signal,
226
+ });
227
+ return text(result.text, result.isError);
228
+ }
229
+ // ------------------------------------------------------------ prompts
230
+ prompts() {
231
+ return PROMPTS;
232
+ }
233
+ getPrompt(name, args) {
234
+ const ctx = { canScaffold: !this.hiddenReason('qcli_scaffold_plugin') };
235
+ return getPrompt(name, args, ctx);
236
+ }
237
+ // ------------------------------------------------------------ resources
238
+ async resources() {
239
+ const docs = await this.docs.get();
240
+ return docs.topics.map((t) => ({
241
+ description: t.summary,
242
+ mimeType: 'text/markdown',
243
+ name: `${t.section} › ${t.title}`,
244
+ title: t.title,
245
+ uri: `${DOCS_RESOURCE_SCHEME}${t.uri}`,
246
+ }));
247
+ }
248
+ /** The markdown of a `qcli-docs://<section>/<topic>` resource, or undefined. */
249
+ async readResource(uri) {
250
+ if (!uri.startsWith(DOCS_RESOURCE_SCHEME)) {
251
+ return undefined;
252
+ }
253
+ const docs = await this.docs.get();
254
+ const path = uri.slice(DOCS_RESOURCE_SCHEME.length);
255
+ return docs.topics.find((t) => t.uri === path)?.content;
256
+ }
257
+ }
258
+ //# sourceMappingURL=tools.js.map