@notis_ai/cli 0.2.0-beta.16.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.
- package/README.md +335 -0
- package/bin/notis.js +2 -0
- package/package.json +38 -0
- package/src/cli.js +147 -0
- package/src/command-specs/apps.js +496 -0
- package/src/command-specs/auth.js +178 -0
- package/src/command-specs/db.js +163 -0
- package/src/command-specs/helpers.js +193 -0
- package/src/command-specs/index.js +20 -0
- package/src/command-specs/meta.js +154 -0
- package/src/command-specs/tools.js +391 -0
- package/src/runtime/app-platform.js +624 -0
- package/src/runtime/app-preview-server.js +312 -0
- package/src/runtime/errors.js +55 -0
- package/src/runtime/help.js +60 -0
- package/src/runtime/output.js +180 -0
- package/src/runtime/profiles.js +202 -0
- package/src/runtime/transport.js +198 -0
- package/template/app/globals.css +3 -0
- package/template/app/layout.tsx +7 -0
- package/template/app/page.tsx +55 -0
- package/template/components/ui/badge.tsx +28 -0
- package/template/components/ui/button.tsx +53 -0
- package/template/components/ui/card.tsx +56 -0
- package/template/components.json +20 -0
- package/template/lib/utils.ts +6 -0
- package/template/notis.config.ts +18 -0
- package/template/package.json +32 -0
- package/template/packages/notis-sdk/package.json +26 -0
- package/template/packages/notis-sdk/src/config.ts +48 -0
- package/template/packages/notis-sdk/src/helpers.ts +131 -0
- package/template/packages/notis-sdk/src/hooks/useAppState.ts +50 -0
- package/template/packages/notis-sdk/src/hooks/useBackend.ts +41 -0
- package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +58 -0
- package/template/packages/notis-sdk/src/hooks/useDatabase.ts +87 -0
- package/template/packages/notis-sdk/src/hooks/useDocument.ts +61 -0
- package/template/packages/notis-sdk/src/hooks/useNotis.ts +31 -0
- package/template/packages/notis-sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/template/packages/notis-sdk/src/hooks/useTool.ts +49 -0
- package/template/packages/notis-sdk/src/hooks/useTools.ts +56 -0
- package/template/packages/notis-sdk/src/hooks/useUpsertDocument.ts +57 -0
- package/template/packages/notis-sdk/src/index.ts +47 -0
- package/template/packages/notis-sdk/src/provider.tsx +44 -0
- package/template/packages/notis-sdk/src/runtime.ts +159 -0
- package/template/packages/notis-sdk/src/styles.css +123 -0
- package/template/packages/notis-sdk/src/ui.ts +15 -0
- package/template/packages/notis-sdk/src/vite.ts +54 -0
- package/template/packages/notis-sdk/tsconfig.json +15 -0
- package/template/postcss.config.mjs +8 -0
- package/template/tailwind.config.ts +58 -0
- package/template/tsconfig.json +22 -0
- package/template/vite.config.ts +10 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { usageError } from '../runtime/errors.js';
|
|
3
|
+
import {
|
|
4
|
+
fetchToolkits,
|
|
5
|
+
fetchToolSchema,
|
|
6
|
+
nextIdempotencyKey,
|
|
7
|
+
parseJson,
|
|
8
|
+
parseMaybeJson,
|
|
9
|
+
resolveSearchToolkits,
|
|
10
|
+
runToolCommand,
|
|
11
|
+
validateArguments,
|
|
12
|
+
} from './helpers.js';
|
|
13
|
+
|
|
14
|
+
async function resolveJsonInput(value, label) {
|
|
15
|
+
if (!value) return undefined;
|
|
16
|
+
|
|
17
|
+
if (value === '-') {
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for await (const chunk of process.stdin) {
|
|
20
|
+
chunks.push(chunk);
|
|
21
|
+
}
|
|
22
|
+
return parseJson(Buffer.concat(chunks).toString('utf-8'), label);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (value.startsWith('@')) {
|
|
26
|
+
const filePath = value.slice(1);
|
|
27
|
+
if (!existsSync(filePath)) {
|
|
28
|
+
throw usageError(`File not found: ${filePath}`);
|
|
29
|
+
}
|
|
30
|
+
return parseJson(readFileSync(filePath, 'utf-8'), label);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return parseJson(value, label);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function toolsToolkitsHandler(ctx) {
|
|
37
|
+
const toolkits = await fetchToolkits(ctx.runtime);
|
|
38
|
+
return ctx.output.emitSuccess({
|
|
39
|
+
command: ctx.spec.command_path.join(' '),
|
|
40
|
+
data: { toolkits },
|
|
41
|
+
humanSummary: `Found ${toolkits.length} toolkits`,
|
|
42
|
+
hints: [
|
|
43
|
+
{ command: 'notis tools search <query>', reason: 'Search for tools in these toolkits' },
|
|
44
|
+
],
|
|
45
|
+
renderHuman: () => toolkits.map((entry) => `${entry.id} ${entry.description}`).join('\n'),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function toolsSearchHandler(ctx) {
|
|
50
|
+
const toolkits = await resolveSearchToolkits(ctx.runtime, ctx.options.toolkits);
|
|
51
|
+
const result = await runToolCommand({
|
|
52
|
+
runtime: ctx.runtime,
|
|
53
|
+
toolName: 'notis_find_tools',
|
|
54
|
+
arguments_: {
|
|
55
|
+
query: ctx.args.query || undefined,
|
|
56
|
+
toolkits,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const tools = result.payload.tools || [];
|
|
60
|
+
const firstTool = tools[0];
|
|
61
|
+
return ctx.output.emitSuccess({
|
|
62
|
+
command: ctx.spec.command_path.join(' '),
|
|
63
|
+
data: { toolkits, tools },
|
|
64
|
+
humanSummary: `Found ${tools.length} tools`,
|
|
65
|
+
hints: firstTool
|
|
66
|
+
? [
|
|
67
|
+
{ command: `notis tools exec ${firstTool.name} --get-schema`, reason: 'Inspect the parameter schema' },
|
|
68
|
+
{ command: `notis tools exec ${firstTool.name} --arguments '{}'`, reason: 'Execute the tool' },
|
|
69
|
+
]
|
|
70
|
+
: [],
|
|
71
|
+
renderHuman: () =>
|
|
72
|
+
tools
|
|
73
|
+
.map((tool) => `${tool.name}\n ${tool.toolkit_id || 'unknown toolkit'}\n ${tool.description || 'No description'}\n`)
|
|
74
|
+
.join('\n')
|
|
75
|
+
.trim(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function toolsDescribeHandler(ctx) {
|
|
80
|
+
const toolkits = await resolveSearchToolkits(ctx.runtime, ctx.options.toolkits);
|
|
81
|
+
const result = await runToolCommand({
|
|
82
|
+
runtime: ctx.runtime,
|
|
83
|
+
toolName: 'notis_find_tools',
|
|
84
|
+
arguments_: {
|
|
85
|
+
query: ctx.args.toolName,
|
|
86
|
+
toolkits,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const tools = result.payload.tools || [];
|
|
90
|
+
const match = tools.find((tool) => tool.name === ctx.args.toolName);
|
|
91
|
+
if (!match) {
|
|
92
|
+
throw usageError(`Tool ${ctx.args.toolName} was not found in the selected toolkits.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return ctx.output.emitSuccess({
|
|
96
|
+
command: ctx.spec.command_path.join(' '),
|
|
97
|
+
data: { tool: match, toolkits },
|
|
98
|
+
humanSummary: `Described tool ${ctx.args.toolName}`,
|
|
99
|
+
hints: [
|
|
100
|
+
{ command: `notis tools exec ${ctx.args.toolName} --dry-run --arguments '{}'`, reason: 'Validate arguments first' },
|
|
101
|
+
{ command: `notis tools exec ${ctx.args.toolName} --arguments '{}'`, reason: 'Execute this tool' },
|
|
102
|
+
],
|
|
103
|
+
renderHuman: () => JSON.stringify(match, null, 2),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function toolsExecHandler(ctx) {
|
|
108
|
+
if (ctx.options.getSchema) {
|
|
109
|
+
const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName);
|
|
110
|
+
return ctx.output.emitSuccess({
|
|
111
|
+
command: ctx.spec.command_path.join(' '),
|
|
112
|
+
data: { tool },
|
|
113
|
+
humanSummary: `Schema for ${ctx.args.toolName}`,
|
|
114
|
+
hints: [
|
|
115
|
+
{ command: `notis tools exec ${ctx.args.toolName} --dry-run --arguments '{}'`, reason: 'Validate arguments before executing' },
|
|
116
|
+
],
|
|
117
|
+
renderHuman: () => JSON.stringify(tool, null, 2),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const rawArguments = ctx.options.arguments || '{}';
|
|
122
|
+
const args = rawArguments === '-' || rawArguments.startsWith('@')
|
|
123
|
+
? await resolveJsonInput(rawArguments, 'arguments') || {}
|
|
124
|
+
: parseMaybeJson(rawArguments, 'arguments') || {};
|
|
125
|
+
|
|
126
|
+
if (ctx.options.dryRun) {
|
|
127
|
+
const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName);
|
|
128
|
+
const errors = validateArguments(tool.parameters, args);
|
|
129
|
+
|
|
130
|
+
if (errors.length) {
|
|
131
|
+
return ctx.output.emitSuccess({
|
|
132
|
+
command: ctx.spec.command_path.join(' '),
|
|
133
|
+
data: { valid: false, errors, tool: ctx.args.toolName },
|
|
134
|
+
humanSummary: `Dry run failed: ${errors.length} validation error(s)`,
|
|
135
|
+
hints: [
|
|
136
|
+
{ command: `notis tools exec ${ctx.args.toolName} --get-schema`, reason: 'Review the full parameter schema' },
|
|
137
|
+
],
|
|
138
|
+
renderHuman: () => errors.map((e) => ` - ${e}`).join('\n'),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return ctx.output.emitSuccess({
|
|
143
|
+
command: ctx.spec.command_path.join(' '),
|
|
144
|
+
data: { valid: true, tool: ctx.args.toolName, arguments: args },
|
|
145
|
+
humanSummary: `Dry run passed for ${ctx.args.toolName}`,
|
|
146
|
+
hints: [
|
|
147
|
+
{ command: `notis tools exec ${ctx.args.toolName} --arguments '${JSON.stringify(args)}'`, reason: 'Execute with these arguments' },
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
153
|
+
|
|
154
|
+
if (ctx.options.watch) {
|
|
155
|
+
const intervalSec = Math.max(1, parseInt(ctx.options.watch, 10));
|
|
156
|
+
const intervalMs = intervalSec * 1000;
|
|
157
|
+
process.on('SIGINT', () => process.exit(0));
|
|
158
|
+
|
|
159
|
+
while (true) {
|
|
160
|
+
try {
|
|
161
|
+
const result = await runToolCommand({
|
|
162
|
+
runtime: ctx.runtime,
|
|
163
|
+
toolName: 'notis_execute_tool',
|
|
164
|
+
arguments_: { tool_name: ctx.args.toolName, arguments: args },
|
|
165
|
+
mutating: true,
|
|
166
|
+
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
167
|
+
});
|
|
168
|
+
ctx.output.emitSuccess({
|
|
169
|
+
command: ctx.spec.command_path.join(' '),
|
|
170
|
+
data: result.payload,
|
|
171
|
+
humanSummary: `[${new Date().toISOString()}] ${ctx.args.toolName}`,
|
|
172
|
+
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
process.stderr.write(`[${new Date().toISOString()}] Error: ${error.message}\n`);
|
|
176
|
+
}
|
|
177
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = await runToolCommand({
|
|
182
|
+
runtime: ctx.runtime,
|
|
183
|
+
toolName: 'notis_execute_tool',
|
|
184
|
+
arguments_: {
|
|
185
|
+
tool_name: ctx.args.toolName,
|
|
186
|
+
arguments: args,
|
|
187
|
+
},
|
|
188
|
+
mutating: true,
|
|
189
|
+
idempotencyKey,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return ctx.output.emitSuccess({
|
|
193
|
+
command: ctx.spec.command_path.join(' '),
|
|
194
|
+
data: result.payload,
|
|
195
|
+
humanSummary: `Executed tool ${ctx.args.toolName}`,
|
|
196
|
+
meta: { mutating: true, idempotency_key: idempotencyKey },
|
|
197
|
+
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function toolsExecParallelHandler(ctx) {
|
|
202
|
+
const calls = parseMaybeJson(ctx.args.calls, 'calls');
|
|
203
|
+
if (!Array.isArray(calls) || !calls.length) {
|
|
204
|
+
throw usageError('calls must be a non-empty JSON array of {tool_name, arguments} objects');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const [i, call] of calls.entries()) {
|
|
208
|
+
if (!call.tool_name || typeof call.tool_name !== 'string') {
|
|
209
|
+
throw usageError(`calls[${i}] missing required "tool_name" string`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const data = await Promise.all(
|
|
214
|
+
calls.map((call) =>
|
|
215
|
+
runToolCommand({
|
|
216
|
+
runtime: ctx.runtime,
|
|
217
|
+
toolName: 'notis_execute_tool',
|
|
218
|
+
arguments_: {
|
|
219
|
+
tool_name: call.tool_name,
|
|
220
|
+
arguments: call.arguments || {},
|
|
221
|
+
},
|
|
222
|
+
mutating: true,
|
|
223
|
+
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
224
|
+
})
|
|
225
|
+
.then((r) => ({ tool_name: call.tool_name, status: 'ok', payload: r.payload }))
|
|
226
|
+
.catch((e) => ({ tool_name: call.tool_name, status: 'error', error: e.message }))
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return ctx.output.emitSuccess({
|
|
231
|
+
command: ctx.spec.command_path.join(' '),
|
|
232
|
+
data: { results: data },
|
|
233
|
+
humanSummary: `Executed ${calls.length} tools in parallel`,
|
|
234
|
+
meta: { mutating: true },
|
|
235
|
+
renderHuman: () => data.map((r) => `${r.tool_name}: ${r.status}`).join('\n'),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function toolsLinkHandler(ctx) {
|
|
240
|
+
const apiBase = ctx.runtime.apiBase.replace(/\/+$/, '');
|
|
241
|
+
let portalBase;
|
|
242
|
+
try {
|
|
243
|
+
const parsed = new URL(apiBase);
|
|
244
|
+
if (parsed.hostname.startsWith('api.')) {
|
|
245
|
+
parsed.hostname = parsed.hostname.replace(/^api\./, 'app.');
|
|
246
|
+
portalBase = parsed.origin;
|
|
247
|
+
} else if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
|
|
248
|
+
const apiPort = parseInt(parsed.port, 10) || 80;
|
|
249
|
+
parsed.port = String(apiPort === 3001 ? 3000 : apiPort);
|
|
250
|
+
portalBase = parsed.origin;
|
|
251
|
+
} else {
|
|
252
|
+
portalBase = apiBase;
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
portalBase = apiBase.replace('api.', 'app.');
|
|
256
|
+
}
|
|
257
|
+
const portalUrl = `${portalBase}/integrations?connect=${encodeURIComponent(ctx.args.toolkit)}`;
|
|
258
|
+
|
|
259
|
+
return ctx.output.emitSuccess({
|
|
260
|
+
command: ctx.spec.command_path.join(' '),
|
|
261
|
+
data: {
|
|
262
|
+
toolkit: ctx.args.toolkit,
|
|
263
|
+
auth_url: portalUrl,
|
|
264
|
+
},
|
|
265
|
+
humanSummary: `Open this URL to connect ${ctx.args.toolkit}`,
|
|
266
|
+
hints: [
|
|
267
|
+
{ command: 'notis tools toolkits', reason: 'Verify the toolkit is connected after setup' },
|
|
268
|
+
],
|
|
269
|
+
renderHuman: () =>
|
|
270
|
+
[
|
|
271
|
+
`Connect ${ctx.args.toolkit}:`,
|
|
272
|
+
` ${portalUrl}`,
|
|
273
|
+
'',
|
|
274
|
+
'After connecting, verify with:',
|
|
275
|
+
' notis tools toolkits',
|
|
276
|
+
].join('\n'),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export const toolsCommandSpecs = [
|
|
281
|
+
{
|
|
282
|
+
command_path: ['tools', 'toolkits'],
|
|
283
|
+
summary: 'List toolkit namespaces available to the active user.',
|
|
284
|
+
when_to_use: 'Use this before searching or executing generic tools.',
|
|
285
|
+
args_schema: { arguments: [], options: [] },
|
|
286
|
+
examples: ['notis tools toolkits', 'notis tools toolkits --json'],
|
|
287
|
+
output_schema: 'Returns available toolkit namespaces.',
|
|
288
|
+
mutates: false,
|
|
289
|
+
idempotent: true,
|
|
290
|
+
related_commands: ['notis tools search <query>', 'notis tools describe <tool-name>'],
|
|
291
|
+
backend_call: { type: 'tool', name: 'notis_find_toolkits' },
|
|
292
|
+
handler: toolsToolkitsHandler,
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
command_path: ['tools', 'search'],
|
|
296
|
+
summary: 'Search across toolkit namespaces using natural language.',
|
|
297
|
+
when_to_use: 'Use this when you need a generic capability that does not have a first-class CLI command.',
|
|
298
|
+
args_schema: {
|
|
299
|
+
arguments: [{ token: '<query>', description: 'Natural language description of the tool you need.' }],
|
|
300
|
+
options: [{ flags: '--toolkits <csv-or-json>', description: 'Optional subset of toolkit ids to search.' }],
|
|
301
|
+
},
|
|
302
|
+
examples: ['notis tools search "send an email"', 'notis tools search "update framer page" --toolkits mcp-framer'],
|
|
303
|
+
output_schema: 'Returns tool matches with descriptions and parameter schemas.',
|
|
304
|
+
mutates: false,
|
|
305
|
+
idempotent: true,
|
|
306
|
+
related_commands: ['notis tools toolkits', 'notis tools exec <tool-name>'],
|
|
307
|
+
backend_call: { type: 'tool', name: 'notis_find_tools' },
|
|
308
|
+
handler: toolsSearchHandler,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
command_path: ['tools', 'describe'],
|
|
312
|
+
summary: 'Describe a generic tool by name.',
|
|
313
|
+
when_to_use: 'Use this when you know the tool name and want its parameter schema before execution.',
|
|
314
|
+
args_schema: {
|
|
315
|
+
arguments: [{ token: '<tool-name>', description: 'Exact tool name to locate and describe.' }],
|
|
316
|
+
options: [{ flags: '--toolkits <csv-or-json>', description: 'Optional subset of toolkit ids to search.' }],
|
|
317
|
+
},
|
|
318
|
+
examples: ['notis tools describe composio-gmail-default-send_email', 'notis tools describe notis-default-query --toolkits notis-default'],
|
|
319
|
+
output_schema: 'Returns one tool descriptor with name, description, and parameters.',
|
|
320
|
+
mutates: false,
|
|
321
|
+
idempotent: true,
|
|
322
|
+
related_commands: ['notis tools search <query>', 'notis tools exec <tool-name>'],
|
|
323
|
+
backend_call: { type: 'tool', name: 'notis_find_tools' },
|
|
324
|
+
handler: toolsDescribeHandler,
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
command_path: ['tools', 'exec'],
|
|
328
|
+
summary: 'Execute a generic tool by canonical tool name.',
|
|
329
|
+
when_to_use: 'Use this as the escape hatch for integrations or Notis tools without a first-class CLI wrapper.',
|
|
330
|
+
args_schema: {
|
|
331
|
+
arguments: [{ token: '<tool-name>', description: 'Tool name returned by `notis tools search`.' }],
|
|
332
|
+
options: [
|
|
333
|
+
{ flags: '--arguments <json>', description: 'JSON object, @file path, or - for stdin.' },
|
|
334
|
+
{ flags: '--get-schema', description: 'Display the tool parameter schema without executing.' },
|
|
335
|
+
{ flags: '--dry-run', description: 'Validate arguments against the tool schema without executing.' },
|
|
336
|
+
{ flags: '--watch <seconds>', description: 'Re-execute on an interval and stream results.' },
|
|
337
|
+
],
|
|
338
|
+
},
|
|
339
|
+
examples: [
|
|
340
|
+
'notis tools exec notis-default-query --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
341
|
+
'notis tools exec notis-default-query --get-schema',
|
|
342
|
+
'notis tools exec notis-default-query --dry-run --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
343
|
+
'notis tools exec notis-default-query --arguments @query.json',
|
|
344
|
+
'notis tools exec notis-default-query --arguments - < query.json',
|
|
345
|
+
'notis tools exec notis-default-query --watch 10 --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
346
|
+
],
|
|
347
|
+
output_schema: 'Returns the raw tool execution payload.',
|
|
348
|
+
mutates: true,
|
|
349
|
+
idempotent: true,
|
|
350
|
+
related_commands: ['notis tools search <query>', 'notis tools describe <tool-name>', 'notis tools exec-parallel <calls>'],
|
|
351
|
+
backend_call: { type: 'tool', name: 'notis_execute_tool' },
|
|
352
|
+
handler: toolsExecHandler,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
command_path: ['tools', 'exec-parallel'],
|
|
356
|
+
summary: 'Execute multiple tools concurrently.',
|
|
357
|
+
when_to_use: 'Use this when you need to run independent tool calls simultaneously for speed.',
|
|
358
|
+
args_schema: {
|
|
359
|
+
arguments: [
|
|
360
|
+
{ token: '<calls>', description: 'JSON array of {tool_name, arguments} objects.' },
|
|
361
|
+
],
|
|
362
|
+
options: [],
|
|
363
|
+
},
|
|
364
|
+
examples: [
|
|
365
|
+
'notis tools exec-parallel \'[{"tool_name":"notis-default-query","arguments":{"database_slug":"tasks","query":{}}},{"tool_name":"notis-default-list_databases","arguments":{}}]\'',
|
|
366
|
+
],
|
|
367
|
+
output_schema: 'Returns an array of results, one per tool call.',
|
|
368
|
+
mutates: true,
|
|
369
|
+
idempotent: true,
|
|
370
|
+
related_commands: ['notis tools exec <tool-name>', 'notis tools search <query>'],
|
|
371
|
+
backend_call: { type: 'tool', name: 'notis_execute_tool' },
|
|
372
|
+
handler: toolsExecParallelHandler,
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
command_path: ['tools', 'link'],
|
|
376
|
+
summary: 'Get the URL to connect an integration toolkit.',
|
|
377
|
+
when_to_use: 'Use this when a tool requires authentication with an external service.',
|
|
378
|
+
args_schema: {
|
|
379
|
+
arguments: [{ token: '<toolkit>', description: 'Toolkit name to connect (e.g. github, gmail, slack).' }],
|
|
380
|
+
options: [],
|
|
381
|
+
},
|
|
382
|
+
examples: ['notis tools link github', 'notis tools link gmail --json'],
|
|
383
|
+
output_schema: 'Returns the authentication URL for the specified toolkit.',
|
|
384
|
+
mutates: false,
|
|
385
|
+
idempotent: true,
|
|
386
|
+
require_auth: false,
|
|
387
|
+
related_commands: ['notis tools toolkits', 'notis tools search <query>'],
|
|
388
|
+
backend_call: { type: 'local' },
|
|
389
|
+
handler: toolsLinkHandler,
|
|
390
|
+
},
|
|
391
|
+
];
|