@notis_ai/cli 0.2.0-beta.41.1 → 0.2.0-beta.48.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 CHANGED
@@ -179,9 +179,9 @@ Examples:
179
179
 
180
180
  ### `npx --package @notis_ai/cli@latest -- notis tools toolkits`
181
181
 
182
- List toolkit namespaces available to the active user.
182
+ List toolkit namespaces and connection statuses available to the active user.
183
183
 
184
- When to use: Use this before searching or executing generic tools.
184
+ When to use: Use this to inspect connection state before searching or executing generic tools.
185
185
 
186
186
  Examples:
187
187
  - `npx --package @notis_ai/cli@latest -- notis tools toolkits`
@@ -194,11 +194,11 @@ Search across toolkit namespaces using natural language.
194
194
  When to use: Use this when you need a generic capability that does not have a first-class CLI command.
195
195
 
196
196
  Options:
197
- - `--toolkits <csv-or-json>` — Optional subset of toolkit ids to search.
197
+ - `--known-fields <text>` — Optional known field hints, such as channel_name:general or user_email:a@example.com.
198
198
 
199
199
  Examples:
200
200
  - `npx --package @notis_ai/cli@latest -- notis tools search "send an email"`
201
- - `npx --package @notis_ai/cli@latest -- notis tools search "update framer page" --toolkits mcp-framer`
201
+ - `npx --package @notis_ai/cli@latest -- notis tools search "post on LinkedIn" --known-fields "platform:linkedin"`
202
202
 
203
203
  ### `npx --package @notis_ai/cli@latest -- notis tools describe <tool-name>`
204
204
 
@@ -206,12 +206,9 @@ Describe a generic tool by name.
206
206
 
207
207
  When to use: Use this when you know the tool name and want its parameter schema before execution.
208
208
 
209
- Options:
210
- - `--toolkits <csv-or-json>` — Optional subset of toolkit ids to search.
211
-
212
209
  Examples:
213
210
  - `npx --package @notis_ai/cli@latest -- notis tools describe composio-gmail-default-send_email`
214
- - `npx --package @notis_ai/cli@latest -- notis tools describe notis-default-query --toolkits notis-default`
211
+ - `npx --package @notis_ai/cli@latest -- notis tools describe notis-default-query`
215
212
 
216
213
  ### `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name>`
217
214
 
@@ -224,7 +221,6 @@ Options:
224
221
  - `--file <argument-path=local-path>` — Upload a local file into a file-uploadable tool argument. Repeatable.
225
222
  - `--get-schema` — Display the tool parameter schema without executing.
226
223
  - `--dry-run` — Validate arguments against the tool schema without executing.
227
- - `--toolkits <csv-or-json>` — Optional toolkit namespace(s) to use when resolving the tool.
228
224
 
229
225
  Examples:
230
226
  - `npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --arguments '{"database_slug":"tasks","query":{}}'`
@@ -272,7 +268,7 @@ Examples:
272
268
 
273
269
  ### `npx --package @notis_ai/cli@latest -- notis whoami`
274
270
 
275
- Display the active profile, user, and available toolkits.
271
+ Display the active profile, user, and available toolkit connection statuses.
276
272
 
277
273
  When to use: Use this to quickly confirm which account and environment a command will target.
278
274
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notis_ai/cli",
3
- "version": "0.2.0-beta.41.1",
3
+ "version": "0.2.0-beta.48.1",
4
4
  "description": "Agent-first Notis CLI for apps and generic tool execution",
5
5
  "type": "module",
6
6
  "bin": {
@@ -130,7 +130,7 @@ This is the main escape hatch for:
130
130
  - `npx --package @notis_ai/cli@latest -- notis tools toolkits`
131
131
  2. Search for the capability you need using natural language:
132
132
  - `npx --package @notis_ai/cli@latest -- notis tools search "<query>"`
133
- - optionally narrow with `--toolkits <csv-or-json>`
133
+ - optionally add known field hints with `--known-fields "<key:value>"`
134
134
  3. If needed, inspect the exact tool and parameter schema:
135
135
  - `npx --package @notis_ai/cli@latest -- notis tools describe <tool-name>`
136
136
  - `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name> --get-schema`
@@ -447,7 +447,7 @@ function databaseMaterializationWarnings(apps) {
447
447
  async function assertDirectDeployAccess(runtime, appId) {
448
448
  const result = await runToolCommand({
449
449
  runtime,
450
- toolName: 'notis_list_apps',
450
+ toolName: 'notis-default-list_apps',
451
451
  });
452
452
  const apps = result.payload.apps || [];
453
453
  const hasAccess = apps.some((app) => (app.app_id || app.id) === appId);
@@ -463,7 +463,7 @@ async function assertDirectDeployAccess(runtime, appId) {
463
463
  async function appsListHandler(ctx) {
464
464
  const result = await runToolCommand({
465
465
  runtime: ctx.runtime,
466
- toolName: 'notis_list_apps',
466
+ toolName: 'notis-default-list_apps',
467
467
  });
468
468
  const apps = result.payload.apps || [];
469
469
  return ctx.output.emitSuccess({
@@ -511,7 +511,7 @@ async function appsCreateHandler(ctx) {
511
511
  const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
512
512
  const result = await runToolCommand({
513
513
  runtime: ctx.runtime,
514
- toolName: 'notis_create_app',
514
+ toolName: 'notis-default-create_app',
515
515
  arguments_: {
516
516
  name: ctx.args.name,
517
517
  description: appConfig?.description || undefined,
@@ -1073,7 +1073,7 @@ async function appsDeployHandler(ctx) {
1073
1073
  try {
1074
1074
  result = await runToolCommand({
1075
1075
  runtime: ctx.runtime,
1076
- toolName: 'notis_save_app_files',
1076
+ toolName: 'notis-default-save_app_files',
1077
1077
  arguments_: {
1078
1078
  app_id: appId,
1079
1079
  files,
@@ -1185,7 +1185,7 @@ export const appsCommandSpecs = [
1185
1185
  examples: ['notis apps list', 'notis apps list --json'],
1186
1186
  mutates: false,
1187
1187
  idempotent: true,
1188
- backend_call: { type: 'tool', name: 'notis_list_apps' },
1188
+ backend_call: { type: 'tool', name: 'notis-default-list_apps' },
1189
1189
  handler: appsListHandler,
1190
1190
  },
1191
1191
  {
@@ -1242,7 +1242,7 @@ export const appsCommandSpecs = [
1242
1242
  ],
1243
1243
  mutates: true,
1244
1244
  idempotent: false,
1245
- backend_call: { type: 'tool', name: 'notis_create_app' },
1245
+ backend_call: { type: 'tool', name: 'notis-default-create_app' },
1246
1246
  handler: appsCreateHandler,
1247
1247
  },
1248
1248
  {
@@ -1376,7 +1376,7 @@ export const appsCommandSpecs = [
1376
1376
  examples: ['notis apps deploy', 'notis apps deploy --skip-build', 'notis apps deploy --app-id abc123', 'notis apps deploy --direct'],
1377
1377
  mutates: true,
1378
1378
  idempotent: true,
1379
- backend_call: { type: 'tool', name: 'notis_save_app_files' },
1379
+ backend_call: { type: 'tool', name: 'notis-default-save_app_files' },
1380
1380
  handler: appsDeployHandler,
1381
1381
  },
1382
1382
  {
@@ -17,23 +17,6 @@ export function parseMaybeJson(value, label) {
17
17
  return parseJson(value, label);
18
18
  }
19
19
 
20
- export function normalizeToolkits(value) {
21
- if (!value) {
22
- return [];
23
- }
24
- if (value.startsWith('[')) {
25
- const parsed = parseJson(value, 'toolkits');
26
- if (!Array.isArray(parsed)) {
27
- throw usageError('toolkits JSON must be an array of toolkit strings');
28
- }
29
- return parsed;
30
- }
31
- return value
32
- .split(',')
33
- .map((entry) => entry.trim())
34
- .filter(Boolean);
35
- }
36
-
37
20
  export function nextIdempotencyKey(globalOptions) {
38
21
  return globalOptions.idempotencyKey || randomUUID();
39
22
  }
@@ -57,28 +40,19 @@ export async function runToolCommand({
57
40
  }
58
41
 
59
42
  export async function fetchToolkits(runtime) {
60
- const result = await runToolCommand({
61
- runtime,
62
- toolName: 'notis_find_toolkits',
63
- });
64
- return result.payload.toolkits || [];
65
- }
66
-
67
- export async function resolveSearchToolkits(runtime, rawToolkits) {
68
- const toolkits = normalizeToolkits(rawToolkits);
69
- if (toolkits.length) {
70
- return toolkits;
71
- }
72
- const availableToolkits = await fetchToolkits(runtime);
73
- return availableToolkits.map((entry) => entry.id);
43
+ const payload = await fetchToolDiscovery(runtime, 'List available toolkit namespaces and connection statuses');
44
+ return (payload.toolkit_connection_statuses || []).map((entry) => ({
45
+ id: entry.toolkit,
46
+ provider: typeof entry.toolkit === 'string' ? entry.toolkit.split('-', 1)[0] : undefined,
47
+ description: entry.description || entry.status_message || entry.toolkit,
48
+ has_active_connection: Boolean(entry.has_active_connection),
49
+ status_message: entry.status_message || '',
50
+ connection_details: entry.connection_details || {},
51
+ }));
74
52
  }
75
53
 
76
54
  export async function probeAuth(runtime) {
77
- const result = await runToolCommand({
78
- runtime,
79
- toolName: 'notis_find_toolkits',
80
- });
81
- return result.payload;
55
+ return fetchToolDiscovery(runtime, 'List available toolkit namespaces and connection statuses');
82
56
  }
83
57
 
84
58
  export async function healthCheck(runtime) {
@@ -90,22 +64,69 @@ export async function healthCheck(runtime) {
90
64
  });
91
65
  }
92
66
 
93
- export async function fetchToolSchema(runtime, toolName, rawToolkits) {
94
- const toolkits = normalizeToolkits(rawToolkits);
67
+ export async function fetchToolSchema(runtime, toolName) {
68
+ const canonicalHints = canonicalToolHints(toolName);
69
+ const payload = await fetchToolDiscovery(
70
+ runtime,
71
+ canonicalHints.useCase,
72
+ canonicalHints.knownFields,
73
+ );
74
+ const schema = payload.tool_schemas?.[toolName];
75
+ if (!schema) {
76
+ throw usageError(`Tool "${toolName}" not found.`);
77
+ }
78
+ return {
79
+ name: toolName,
80
+ toolkit_id: schema.toolkit,
81
+ description: schema.description || '',
82
+ parameters: schema.input_schema || { type: 'object', properties: {} },
83
+ output_schema: schema.output_schema || {},
84
+ schema_available: Boolean(schema.hasFullSchema),
85
+ };
86
+ }
87
+
88
+ function canonicalToolHints(toolName) {
89
+ const rawName = String(toolName || '').trim();
90
+ const parts = rawName.split('-').filter(Boolean);
91
+ if (parts.length < 3) {
92
+ return {
93
+ useCase: `Describe the ${rawName} tool schema`,
94
+ knownFields: `tool_name:${rawName}`,
95
+ };
96
+ }
97
+
98
+ const provider = parts[0];
99
+ const action = parts[parts.length - 1];
100
+ const toolkitParts = parts.slice(1, -1);
101
+ const toolkit = [provider, ...toolkitParts].join('-');
102
+ const providerToolkit = toolkitParts[0] || '';
103
+ const knownFields = [
104
+ `tool_name:${rawName}`,
105
+ `provider:${provider}`,
106
+ `toolkit:${toolkit}`,
107
+ providerToolkit ? `toolkit_slug:${providerToolkit}` : null,
108
+ `action:${action}`,
109
+ ].filter(Boolean).join(' ');
110
+
111
+ return {
112
+ useCase: `Describe the ${action} schema for ${toolkit}`,
113
+ knownFields,
114
+ };
115
+ }
116
+
117
+ export async function fetchToolDiscovery(runtime, useCase, knownFields = '') {
118
+ const query = { use_case: useCase };
119
+ if (knownFields) {
120
+ query.known_fields = knownFields;
121
+ }
95
122
  const result = await runToolCommand({
96
123
  runtime,
97
124
  toolName: 'notis_find_tools',
98
125
  arguments_: {
99
- query: toolName,
100
- ...(toolkits.length ? { toolkits } : {}),
126
+ queries: [query],
101
127
  },
102
128
  });
103
- const tools = result.payload.tools || [];
104
- const match = tools.find((t) => t.name === toolName);
105
- if (!match) {
106
- throw usageError(`Tool "${toolName}" not found.`);
107
- }
108
- return match;
129
+ return result.payload || {};
109
130
  }
110
131
 
111
132
  export function validateArguments(schema, args) {
@@ -21,7 +21,7 @@ async function doctorHandler(ctx) {
21
21
  if (ctx.runtime.jwt) {
22
22
  try {
23
23
  const payload = await probeAuth(ctx.runtime);
24
- checks.tool_roundtrip = Array.isArray(payload.toolkits) ? 'ok' : 'error';
24
+ checks.tool_roundtrip = Array.isArray(payload.toolkit_connection_statuses) ? 'ok' : 'error';
25
25
  } catch {
26
26
  checks.tool_roundtrip = 'error';
27
27
  }
@@ -68,7 +68,7 @@ function decodeJwtUserId(jwt) {
68
68
 
69
69
  async function whoamiHandler(ctx) {
70
70
  const payload = await probeAuth(ctx.runtime);
71
- const toolkits = payload.toolkits || [];
71
+ const toolkits = payload.toolkit_connection_statuses || [];
72
72
  const userId = decodeJwtUserId(ctx.runtime.jwt);
73
73
 
74
74
  return ctx.output.emitSuccess({
@@ -78,7 +78,7 @@ async function whoamiHandler(ctx) {
78
78
  api_base: ctx.runtime.apiBase,
79
79
  user_id: userId,
80
80
  toolkit_count: toolkits.length,
81
- toolkits: toolkits.map((t) => t.id),
81
+ toolkits: toolkits.map((t) => t.toolkit),
82
82
  cli_version: ctx.runtime.cliVersion,
83
83
  },
84
84
  humanSummary: `Logged in as ${userId || 'unknown'} via profile "${ctx.runtime.profileName}"`,
@@ -91,7 +91,7 @@ async function whoamiHandler(ctx) {
91
91
  `Version: ${ctx.runtime.cliVersion}`,
92
92
  ].join('\n'),
93
93
  hints: [
94
- { command: 'notis tools toolkits', reason: 'List available toolkit namespaces' },
94
+ { command: 'notis tools toolkits', reason: 'List available toolkit namespaces and connection statuses' },
95
95
  { command: 'notis doctor', reason: 'Run a full health check' },
96
96
  ],
97
97
  });
@@ -110,7 +110,7 @@ async function describeHandler(ctx) {
110
110
  export const metaCommandSpecs = [
111
111
  {
112
112
  command_path: ['whoami'],
113
- summary: 'Display the active profile, user, and available toolkits.',
113
+ summary: 'Display the active profile, user, and available toolkit connection statuses.',
114
114
  when_to_use: 'Use this to quickly confirm which account and environment a command will target.',
115
115
  args_schema: { arguments: [], options: [] },
116
116
  examples: ['notis whoami', 'notis whoami --json'],
@@ -118,7 +118,7 @@ export const metaCommandSpecs = [
118
118
  mutates: false,
119
119
  idempotent: true,
120
120
  related_commands: ['notis doctor'],
121
- backend_call: { type: 'tool', name: 'notis_find_toolkits' },
121
+ backend_call: { type: 'tool', name: 'notis_find_tools' },
122
122
  handler: whoamiHandler,
123
123
  },
124
124
  {
@@ -3,13 +3,12 @@ import { createHash } from 'node:crypto';
3
3
  import { basename } from 'node:path';
4
4
  import { usageError } from '../runtime/errors.js';
5
5
  import {
6
+ fetchToolDiscovery,
6
7
  fetchToolkits,
7
8
  fetchToolSchema,
8
9
  nextIdempotencyKey,
9
10
  parseJson,
10
11
  parseMaybeJson,
11
- normalizeToolkits,
12
- resolveSearchToolkits,
13
12
  runToolCommand,
14
13
  validateArguments,
15
14
  } from './helpers.js';
@@ -142,27 +141,55 @@ async function toolsToolkitsHandler(ctx) {
142
141
  data: { toolkits },
143
142
  humanSummary: `Found ${toolkits.length} toolkits`,
144
143
  hints: [
145
- { command: 'notis tools search <query>', reason: 'Search for tools in these toolkits' },
144
+ { command: 'notis tools search <query>', reason: 'Search canonical tools and schemas' },
146
145
  ],
147
- renderHuman: () => toolkits.map((entry) => `${entry.id} ${entry.description}`).join('\n'),
146
+ renderHuman: () =>
147
+ toolkits
148
+ .map((entry) => {
149
+ const status = entry.has_active_connection ? 'connected' : 'needs auth';
150
+ return `${entry.id} ${status} ${entry.description}`;
151
+ })
152
+ .join('\n'),
148
153
  });
149
154
  }
150
155
 
156
+ function discoveryToolRows(payload) {
157
+ const schemas = payload.tool_schemas || {};
158
+ const statuses = new Map(
159
+ (payload.toolkit_connection_statuses || [])
160
+ .filter((entry) => entry && typeof entry.toolkit === 'string')
161
+ .map((entry) => [entry.toolkit, entry])
162
+ );
163
+ const seen = new Set();
164
+ const rows = [];
165
+ for (const result of payload.results || []) {
166
+ for (const name of [...(result.primary_tool_slugs || []), ...(result.related_tool_slugs || [])]) {
167
+ if (!name || seen.has(name)) continue;
168
+ seen.add(name);
169
+ const schema = schemas[name] || {};
170
+ const toolkit = schema.toolkit || name.split('-').slice(0, -1).join('-');
171
+ const status = statuses.get(toolkit);
172
+ rows.push({
173
+ name,
174
+ toolkit,
175
+ description: schema.description || '',
176
+ schema_available: Boolean(schemas[name]),
177
+ connection_status: status
178
+ ? (status.has_active_connection ? 'connected' : 'needs auth')
179
+ : 'unknown',
180
+ });
181
+ }
182
+ }
183
+ return rows;
184
+ }
185
+
151
186
  async function toolsSearchHandler(ctx) {
152
- const toolkits = await resolveSearchToolkits(ctx.runtime, ctx.options.toolkits);
153
- const result = await runToolCommand({
154
- runtime: ctx.runtime,
155
- toolName: 'notis_find_tools',
156
- arguments_: {
157
- query: ctx.args.query || undefined,
158
- toolkits,
159
- },
160
- });
161
- const tools = result.payload.tools || [];
187
+ const payload = await fetchToolDiscovery(ctx.runtime, ctx.args.query || 'Find tools', ctx.options.knownFields || '');
188
+ const tools = discoveryToolRows(payload);
162
189
  const firstTool = tools[0];
163
190
  return ctx.output.emitSuccess({
164
191
  command: ctx.spec.command_path.join(' '),
165
- data: { toolkits, tools },
192
+ data: payload,
166
193
  humanSummary: `Found ${tools.length} tools`,
167
194
  hints: firstTool
168
195
  ? [
@@ -172,31 +199,18 @@ async function toolsSearchHandler(ctx) {
172
199
  : [],
173
200
  renderHuman: () =>
174
201
  tools
175
- .map((tool) => `${tool.name}\n ${tool.toolkit_id || 'unknown toolkit'}\n ${tool.description || 'No description'}\n`)
202
+ .map((tool) => `${tool.name}\n toolkit: ${tool.toolkit || 'unknown'} (${tool.connection_status})\n schema: ${tool.schema_available ? 'available' : 'missing'}\n ${tool.description || 'No description'}\n`)
176
203
  .join('\n')
177
204
  .trim(),
178
205
  });
179
206
  }
180
207
 
181
208
  async function toolsDescribeHandler(ctx) {
182
- const toolkits = await resolveSearchToolkits(ctx.runtime, ctx.options.toolkits);
183
- const result = await runToolCommand({
184
- runtime: ctx.runtime,
185
- toolName: 'notis_find_tools',
186
- arguments_: {
187
- query: ctx.args.toolName,
188
- toolkits,
189
- },
190
- });
191
- const tools = result.payload.tools || [];
192
- const match = tools.find((tool) => tool.name === ctx.args.toolName);
193
- if (!match) {
194
- throw usageError(`Tool ${ctx.args.toolName} was not found in the selected toolkits.`);
195
- }
209
+ const match = await fetchToolSchema(ctx.runtime, ctx.args.toolName);
196
210
 
197
211
  return ctx.output.emitSuccess({
198
212
  command: ctx.spec.command_path.join(' '),
199
- data: { tool: match, toolkits },
213
+ data: { tool: match },
200
214
  humanSummary: `Described tool ${ctx.args.toolName}`,
201
215
  hints: [
202
216
  { command: `notis tools exec ${ctx.args.toolName} --dry-run --arguments '{}'`, reason: 'Validate arguments first' },
@@ -208,14 +222,13 @@ async function toolsDescribeHandler(ctx) {
208
222
 
209
223
  async function toolsExecHandler(ctx) {
210
224
  ensureLongRunningToolTimeout(ctx, ctx.args.toolName);
211
- const toolkits = normalizeToolkits(ctx.options.toolkits);
212
225
  const fileBindings = await parseFileBindings(ctx.options.file);
213
226
 
214
227
  if (ctx.options.getSchema) {
215
- const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName, ctx.options.toolkits);
228
+ const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName);
216
229
  return ctx.output.emitSuccess({
217
230
  command: ctx.spec.command_path.join(' '),
218
- data: { tool, toolkits },
231
+ data: { tool },
219
232
  humanSummary: `Schema for ${ctx.args.toolName}`,
220
233
  hints: [
221
234
  { command: `notis tools exec ${ctx.args.toolName} --dry-run --arguments '{}'`, reason: 'Validate arguments before executing' },
@@ -233,13 +246,13 @@ async function toolsExecHandler(ctx) {
233
246
  if (fileBindings.length) {
234
247
  throw usageError('--file is only supported when executing a tool, not with --dry-run');
235
248
  }
236
- const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName, ctx.options.toolkits);
249
+ const tool = await fetchToolSchema(ctx.runtime, ctx.args.toolName);
237
250
  const errors = validateArguments(tool.parameters, args);
238
251
 
239
252
  if (errors.length) {
240
253
  return ctx.output.emitSuccess({
241
254
  command: ctx.spec.command_path.join(' '),
242
- data: { valid: false, errors, tool: ctx.args.toolName, toolkits },
255
+ data: { valid: false, errors, tool: ctx.args.toolName },
243
256
  humanSummary: `Dry run failed: ${errors.length} validation error(s)`,
244
257
  hints: [
245
258
  { command: `notis tools exec ${ctx.args.toolName} --get-schema`, reason: 'Review the full parameter schema' },
@@ -250,7 +263,7 @@ async function toolsExecHandler(ctx) {
250
263
 
251
264
  return ctx.output.emitSuccess({
252
265
  command: ctx.spec.command_path.join(' '),
253
- data: { valid: true, tool: ctx.args.toolName, arguments: args, toolkits },
266
+ data: { valid: true, tool: ctx.args.toolName, arguments: args },
254
267
  humanSummary: `Dry run passed for ${ctx.args.toolName}`,
255
268
  hints: [
256
269
  { command: `notis tools exec ${ctx.args.toolName} --arguments '${JSON.stringify(args)}'`, reason: 'Execute with these arguments' },
@@ -266,7 +279,6 @@ async function toolsExecHandler(ctx) {
266
279
  arguments_: {
267
280
  tool_name: ctx.args.toolName,
268
281
  arguments: args,
269
- ...(toolkits.length ? { toolkits } : {}),
270
282
  },
271
283
  mutating: true,
272
284
  idempotencyKey,
@@ -369,15 +381,15 @@ async function toolsLinkHandler(ctx) {
369
381
  export const toolsCommandSpecs = [
370
382
  {
371
383
  command_path: ['tools', 'toolkits'],
372
- summary: 'List toolkit namespaces available to the active user.',
373
- when_to_use: 'Use this before searching or executing generic tools.',
384
+ summary: 'List toolkit namespaces and connection statuses available to the active user.',
385
+ when_to_use: 'Use this to inspect connection state before searching or executing generic tools.',
374
386
  args_schema: { arguments: [], options: [] },
375
387
  examples: ['notis tools toolkits', 'notis tools toolkits --json'],
376
- output_schema: 'Returns available toolkit namespaces.',
388
+ output_schema: 'Returns toolkit_connection_statuses from notis_find_tools, rendered as toolkit rows.',
377
389
  mutates: false,
378
390
  idempotent: true,
379
391
  related_commands: ['notis tools search <query>', 'notis tools describe <tool-name>'],
380
- backend_call: { type: 'tool', name: 'notis_find_toolkits' },
392
+ backend_call: { type: 'tool', name: 'notis_find_tools' },
381
393
  handler: toolsToolkitsHandler,
382
394
  },
383
395
  {
@@ -386,10 +398,10 @@ export const toolsCommandSpecs = [
386
398
  when_to_use: 'Use this when you need a generic capability that does not have a first-class CLI command.',
387
399
  args_schema: {
388
400
  arguments: [{ token: '<query>', description: 'Natural language description of the tool you need.' }],
389
- options: [{ flags: '--toolkits <csv-or-json>', description: 'Optional subset of toolkit ids to search.' }],
401
+ options: [{ flags: '--known-fields <text>', key: 'knownFields', description: 'Optional known field hints, such as channel_name:general or user_email:a@example.com.' }],
390
402
  },
391
- examples: ['notis tools search "send an email"', 'notis tools search "update framer page" --toolkits mcp-framer'],
392
- output_schema: 'Returns tool matches with descriptions and parameter schemas.',
403
+ examples: ['notis tools search "send an email"', 'notis tools search "post on LinkedIn" --known-fields "platform:linkedin"'],
404
+ output_schema: 'Returns the full notis_find_tools response. Human output renders canonical names, connection status, and schema availability.',
393
405
  mutates: false,
394
406
  idempotent: true,
395
407
  related_commands: ['notis tools toolkits', 'notis tools exec <tool-name>'],
@@ -402,9 +414,9 @@ export const toolsCommandSpecs = [
402
414
  when_to_use: 'Use this when you know the tool name and want its parameter schema before execution.',
403
415
  args_schema: {
404
416
  arguments: [{ token: '<tool-name>', description: 'Exact tool name to locate and describe.' }],
405
- options: [{ flags: '--toolkits <csv-or-json>', description: 'Optional subset of toolkit ids to search.' }],
417
+ options: [],
406
418
  },
407
- examples: ['notis tools describe composio-gmail-default-send_email', 'notis tools describe notis-default-query --toolkits notis-default'],
419
+ examples: ['notis tools describe composio-gmail-default-send_email', 'notis tools describe notis-default-query'],
408
420
  output_schema: 'Returns one tool descriptor with name, description, and parameters.',
409
421
  mutates: false,
410
422
  idempotent: true,
@@ -423,7 +435,6 @@ export const toolsCommandSpecs = [
423
435
  { flags: '--file <argument-path=local-path>', description: 'Upload a local file into a file-uploadable tool argument. Repeatable.', collect: true },
424
436
  { flags: '--get-schema', description: 'Display the tool parameter schema without executing.' },
425
437
  { flags: '--dry-run', description: 'Validate arguments against the tool schema without executing.' },
426
- { flags: '--toolkits <csv-or-json>', description: 'Optional toolkit namespace(s) to use when resolving the tool.' },
427
438
  ],
428
439
  },
429
440
  examples: [
@@ -9,33 +9,6 @@ import {
9
9
  saveConfig,
10
10
  } from './profiles.js';
11
11
 
12
- const META_TOOL_NAMES = new Set([
13
- 'notis_find_toolkits',
14
- 'notis_find_tools',
15
- 'notis_execute_tool',
16
- 'notis_find_additional_tools',
17
- ]);
18
-
19
- function canonicalizeToolName(toolName) {
20
- if (typeof toolName !== 'string' || !toolName.length) {
21
- return toolName;
22
- }
23
-
24
- if (META_TOOL_NAMES.has(toolName) || toolName.startsWith('notis-default-')) {
25
- return toolName;
26
- }
27
-
28
- if (toolName.startsWith('notis_')) {
29
- return `notis-default-${toolName.slice('notis_'.length)}`;
30
- }
31
-
32
- if (toolName.includes('_notis_')) {
33
- return `notis-default-${toolName.replace(/_notis_/g, '_')}`;
34
- }
35
-
36
- return toolName;
37
- }
38
-
39
12
  function escapeMultipartHeaderValue(value) {
40
13
  return String(value ?? '')
41
14
  .replace(/"/g, '%22')
@@ -397,7 +370,7 @@ export async function callTool({
397
370
  }) {
398
371
  const requestId = idempotencyKey || (runtime.mutating ? randomUUID() : null);
399
372
  const payload = {
400
- tool_name: canonicalizeToolName(toolName),
373
+ tool_name: toolName,
401
374
  arguments: argumentsPayload,
402
375
  idempotency_key: requestId,
403
376
  cli_context: {