@notis_ai/cli 0.2.2 → 0.2.3

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 (81) hide show
  1. package/README.md +19 -18
  2. package/config/notis_app_boundary_rules.json +50 -0
  3. package/dist/scaffolds/notis-database/app/globals.css +44 -0
  4. package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
  5. package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
  6. package/dist/scaffolds/notis-database/index.html +12 -0
  7. package/dist/scaffolds/notis-database/lib/types.ts +134 -0
  8. package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
  9. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  10. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  11. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  12. package/dist/scaffolds/notis-database/notis.config.ts +27 -0
  13. package/dist/scaffolds/notis-database/package.json +31 -0
  14. package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
  15. package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
  16. package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
  17. package/dist/scaffolds/{notes → notis-database}/tsconfig.json +2 -1
  18. package/dist/scaffolds/notis-database/vite.config.ts +22 -0
  19. package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
  20. package/dist/scaffolds/{notes → notis-notes}/app/page.tsx +783 -80
  21. package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
  22. package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
  23. package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
  24. package/dist/scaffolds/notis-notes/components.json +20 -0
  25. package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
  26. package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
  27. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  28. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  29. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  30. package/dist/scaffolds/{notes → notis-notes}/notis.config.ts +12 -7
  31. package/dist/scaffolds/{notes → notis-notes}/package.json +2 -2
  32. package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
  33. package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
  34. package/dist/scaffolds/notis-random/README.md +33 -0
  35. package/dist/scaffolds/notis-random/app/globals.css +11 -0
  36. package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
  37. package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
  38. package/dist/scaffolds/notis-random/app/page.tsx +222 -0
  39. package/dist/scaffolds/notis-random/index.html +12 -0
  40. package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
  41. package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
  42. package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
  43. package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
  44. package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
  45. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  46. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  47. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  48. package/dist/scaffolds/notis-random/notis.config.ts +43 -0
  49. package/dist/scaffolds/notis-random/package.json +32 -0
  50. package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
  51. package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
  52. package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
  53. package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
  54. package/dist/scaffolds/notis-random/tsconfig.json +23 -0
  55. package/dist/scaffolds/notis-random/vite.config.ts +11 -0
  56. package/dist/scaffolds.json +27 -4
  57. package/package.json +2 -1
  58. package/skills/notis-apps/SKILL.md +2 -2
  59. package/skills/notis-cli/SKILL.md +18 -16
  60. package/skills/notis-query/cli.md +9 -10
  61. package/src/cli.js +30 -2
  62. package/src/command-specs/apps.js +42 -15
  63. package/src/command-specs/helpers.js +73 -45
  64. package/src/command-specs/meta.js +7 -7
  65. package/src/command-specs/tools.js +196 -83
  66. package/src/runtime/app-boundary-validator.js +65 -14
  67. package/src/runtime/profiles.js +10 -4
  68. package/src/runtime/transport.js +122 -37
  69. package/template/app/page.tsx +1 -1
  70. package/template/notis.config.ts +1 -1
  71. package/template/packages/sdk/src/hooks/useTool.ts +1 -1
  72. /package/dist/scaffolds/{notes → notis-database}/app/layout.tsx +0 -0
  73. /package/dist/scaffolds/{notes → notis-database}/components/ui/badge.tsx +0 -0
  74. /package/dist/scaffolds/{notes → notis-database}/components/ui/button.tsx +0 -0
  75. /package/dist/scaffolds/{notes → notis-database}/components/ui/card.tsx +0 -0
  76. /package/dist/scaffolds/{notes → notis-database}/components.json +0 -0
  77. /package/dist/scaffolds/{notes → notis-database}/lib/utils.ts +0 -0
  78. /package/dist/scaffolds/{notes → notis-database}/postcss.config.mjs +0 -0
  79. /package/dist/scaffolds/{notes → notis-notes}/app/globals.css +0 -0
  80. /package/dist/scaffolds/{notes → notis-notes}/tailwind.config.ts +0 -0
  81. /package/dist/scaffolds/{notes → notis-notes}/vite.config.ts +0 -0
@@ -2,12 +2,35 @@
2
2
  "source": "apps/*/notis.config.ts",
3
3
  "scaffolds": [
4
4
  {
5
- "slug": "notes",
5
+ "slug": "notis-database",
6
+ "name": "Databases",
7
+ "description": "Read-only catalog and schema explorer for every database in your Notis workspace.",
8
+ "icon": "phosphor:database",
9
+ "categories": [
10
+ "Product & Engineering",
11
+ "Operations"
12
+ ],
13
+ "tagline": "Explore every database and schema in your Notis workspace."
14
+ },
15
+ {
16
+ "slug": "notis-notes",
6
17
  "name": "Notis Notes",
7
18
  "description": "Browse notes by documents, table, or calendar with folders in the sidebar.",
8
- "icon": "lucide:notebook-pen",
9
- "categories": [],
10
- "tagline": "Browse notes by documents, table, or calendar with folders in the sidebar."
19
+ "icon": "phosphor:note-pencil",
20
+ "categories": [
21
+ "Productivity"
22
+ ],
23
+ "tagline": "Folder-based notes with gallery, table, and calendar views."
24
+ },
25
+ {
26
+ "slug": "notis-random",
27
+ "name": "Random Number Generator",
28
+ "description": "Generate random numbers with configurable bounds, and keep a history of everything you rolled.",
29
+ "icon": "phosphor:dice-five",
30
+ "categories": [
31
+ "Personal"
32
+ ],
33
+ "tagline": "Roll dice, keep history."
11
34
  }
12
35
  ]
13
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notis_ai/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Agent-first Notis CLI for apps and generic tool execution",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
+ "config/",
11
12
  "dist/",
12
13
  "src/",
13
14
  "skills/",
@@ -138,7 +138,7 @@ export default defineNotisApp({
138
138
  default: true,
139
139
  },
140
140
  ],
141
- tools: ['notis-default-query', 'notis-default-upsert_tasks'],
141
+ tools: ['LOCAL_NOTIS_DATABASE_QUERY', 'LOCAL_NOTIS_DATABASE_UPSERT_TASKS'],
142
142
  });
143
143
  ```
144
144
 
@@ -149,7 +149,7 @@ Use SDK hooks for data and tool access:
149
149
  ```tsx
150
150
  import { useTool } from '@notis/sdk';
151
151
 
152
- const query = useTool('notis-default-query');
152
+ const query = useTool('LOCAL_NOTIS_DATABASE_QUERY');
153
153
  ```
154
154
 
155
155
  Do not fetch Notis backend endpoints directly from app code unless the SDK explicitly requires it. The Portal provides authentication, runtime context, and route rendering.
@@ -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`
@@ -155,7 +155,7 @@ This is the main escape hatch for:
155
155
 
156
156
  Typical toolkit namespaces include:
157
157
 
158
- - `notis-default` for native Notis tools
158
+ - `notis` for native Notis tools
159
159
  - `composio-*` for Composio-backed integrations
160
160
  - `mcp-*` for MCP-backed tools
161
161
 
@@ -178,22 +178,22 @@ npx --package @notis_ai/cli@latest -- notis tools search "list today's calendar
178
178
  Inspect a tool before execution:
179
179
 
180
180
  ```bash
181
- npx --package @notis_ai/cli@latest -- notis tools describe composio-googlecalendar-default-list_events
182
- npx --package @notis_ai/cli@latest -- notis tools exec composio-googlecalendar-default-list_events --get-schema
181
+ npx --package @notis_ai/cli@latest -- notis tools describe composio-googlecalendar-list_events
182
+ npx --package @notis_ai/cli@latest -- notis tools exec composio-googlecalendar-list_events --get-schema
183
183
  ```
184
184
 
185
185
  Dry-run a tool call:
186
186
 
187
187
  ```bash
188
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --dry-run --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
189
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --dry-run --arguments '{"database_slug":"tasks"}'
188
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --dry-run --arguments '{"database_slug":"tasks"}'
189
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --dry-run --arguments '{"database_id":"tasks-db-id","query":{"page_size":10}}'
190
190
  ```
191
191
 
192
192
  Execute a tool call:
193
193
 
194
194
  ```bash
195
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
196
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --arguments '{"database_slug":"tasks"}'
195
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"tasks"}'
196
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments '{"database_id":"tasks-db-id","query":{"page_size":10}}'
197
197
  ```
198
198
 
199
199
  Connect a missing toolkit:
@@ -206,21 +206,23 @@ npx --package @notis_ai/cli@latest -- notis tools link github
206
206
 
207
207
  Native Notis databases are accessed through the generic tool workflow, not a first-class database command group. Use these canonical tool names:
208
208
 
209
- - `notis-default-list_databases` -- list databases accessible to the current profile
210
- - `notis-default-get_database` -- inspect read-only metadata and schema detail
211
- - `notis-default-query` -- query documents from a database
212
- - `notis-default-upsert_database` -- create or update a database schema
209
+ - `LOCAL_NOTIS_DATABASE_LIST_DATABASES` -- list databases accessible to the current profile
210
+ - `LOCAL_NOTIS_DATABASE_GET_DATABASE` -- inspect read-only metadata and schema detail
211
+ - `LOCAL_NOTIS_DATABASE_QUERY` -- query documents from a database
212
+ - `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` -- create or update a database schema
213
213
 
214
214
  Example workflow before building an app:
215
215
 
216
216
  ```bash
217
217
  npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
218
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-list_databases --arguments '{}'
219
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --get-schema
220
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --arguments '{"database_slug":"social_media_calendar"}'
221
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --arguments '{"database_slug":"social_media_calendar","query":{"page_size":1}}'
218
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'
219
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --get-schema
220
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"social_media_calendar"}'
221
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments '{"database_id":"social-media-calendar-db-id","query":{"page_size":1}}'
222
222
  ```
223
223
 
224
+ When `LOCAL_NOTIS_DATABASE_LIST_DATABASES` or `LOCAL_NOTIS_DATABASE_GET_DATABASE` returns a database ID, prefer `database_id` for `LOCAL_NOTIS_DATABASE_QUERY`; `database_slug` remains supported as a fallback.
225
+
224
226
  ## Supporting commands
225
227
 
226
228
  - `npx --package @notis_ai/cli@latest -- notis doctor` — verify CLI config, auth, and API reachability before relying on the CLI
@@ -10,20 +10,20 @@ For CI, hosted agents, or internal scripts, pass a non-persisted token with `NOT
10
10
 
11
11
  ## Canonical database tools
12
12
 
13
- - `notis-default-list_databases` — list native databases.
14
- - `notis-default-get_database` — inspect one database schema.
15
- - `notis-default-query` — query native database documents.
16
- - `notis-default-upsert_database` — create or update database schema.
13
+ - `LOCAL_NOTIS_DATABASE_LIST_DATABASES` — list native databases.
14
+ - `LOCAL_NOTIS_DATABASE_GET_DATABASE` — inspect one database schema.
15
+ - `LOCAL_NOTIS_DATABASE_QUERY` — query native database documents.
16
+ - `LOCAL_NOTIS_DATABASE_UPSERT_DATABASE` — create or update database schema.
17
17
 
18
18
  ## Workflow
19
19
 
20
20
  ```bash
21
21
  npx --package @notis_ai/cli@latest -- notis tools search "list Notis databases"
22
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-list_databases --arguments '{}'
23
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --get-schema
24
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-get_database --arguments '{"database_slug":"tasks"}'
25
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --dry-run --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
26
- npx --package @notis_ai/cli@latest -- notis tools exec notis-default-query --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
22
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_LIST_DATABASES --arguments '{}'
23
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --get-schema
24
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"tasks"}'
25
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --dry-run --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
26
+ npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments '{"database_slug":"tasks","query":{"page_size":10}}'
27
27
  ```
28
28
 
29
29
  ## Supporting command
@@ -37,4 +37,3 @@ When to use: Use this before relying on the CLI in automation or after changing
37
37
  Examples:
38
38
  - `npx --package @notis_ai/cli@latest -- notis doctor`
39
39
  - `npx --package @notis_ai/cli@latest -- notis doctor --json`
40
-
package/src/cli.js CHANGED
@@ -1,10 +1,29 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import { Command } from 'commander';
2
5
  import { COMMAND_SPECS, GROUP_SUMMARIES } from './command-specs/index.js';
3
6
  import { OutputManager } from './runtime/output.js';
4
7
  import { asCliError } from './runtime/errors.js';
5
8
  import { resolveRuntimeProfile, workspacePath } from './runtime/profiles.js';
6
9
 
7
- const CLI_VERSION = '0.2.0';
10
+ // Read the version from package.json: the publish pipeline bumps the manifest
11
+ // (scripts/release-utils.js applyVersion), so a hardcoded string here goes
12
+ // stale on every release.
13
+ function readCliVersion() {
14
+ try {
15
+ const manifestPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
16
+ const version = JSON.parse(readFileSync(manifestPath, 'utf-8')).version;
17
+ if (typeof version === 'string' && version.trim()) {
18
+ return version.trim();
19
+ }
20
+ } catch {
21
+ // Fall through to the placeholder below.
22
+ }
23
+ return '0.0.0';
24
+ }
25
+
26
+ const CLI_VERSION = readCliVersion();
8
27
 
9
28
  function buildHelpFooter(spec) {
10
29
  const lines = [
@@ -64,7 +83,16 @@ function attachSpec(program, parentMap, spec, specs) {
64
83
  }
65
84
 
66
85
  for (const option of spec.args_schema?.options || []) {
67
- command.option(option.flags, option.description);
86
+ if (option.collect) {
87
+ command.option(
88
+ option.flags,
89
+ option.description,
90
+ (value, previous) => [...(previous || []), value],
91
+ [],
92
+ );
93
+ } else {
94
+ command.option(option.flags, option.description);
95
+ }
68
96
  }
69
97
 
70
98
  command.action(async (...raw) => {
@@ -46,6 +46,7 @@ import {
46
46
  import { getAvailablePort } from '../runtime/ports.js';
47
47
  import { getCliMode } from '../runtime/cli-mode.js';
48
48
  import {
49
+ localNotisToolSlug,
49
50
  nextIdempotencyKey,
50
51
  runToolCommand,
51
52
  toolConflictToError,
@@ -54,8 +55,11 @@ import {
54
55
  const DEFAULT_DEV_PORT = 5173;
55
56
  const DEV_HEARTBEAT_INTERVAL_MS = 10_000;
56
57
  const CONFIG_FILENAMES = ['notis.config.ts', 'notis.config.js', 'notis.config.mjs'];
57
- const ENSURE_DEV_APP_INSTALLATION_TOOL = 'notis-default-ensure_dev_app_installation';
58
- const GET_APP_TOOL = 'notis-default-get_app';
58
+ const ENSURE_DEV_APP_INSTALLATION_TOOL = 'LOCAL_NOTIS_ENSURE_DEV_APP_INSTALLATION';
59
+ const GET_APP_TOOL = 'LOCAL_NOTIS_GET_APP';
60
+ const LIST_APPS_TOOL = 'LOCAL_NOTIS_LIST_APPS';
61
+ const CREATE_APP_TOOL = 'LOCAL_NOTIS_CREATE_APP';
62
+ const SAVE_APP_FILES_TOOL = 'LOCAL_NOTIS_SAVE_APP_FILES';
59
63
 
60
64
  // ---------------------------------------------------------------------------
61
65
  // Formatters
@@ -192,9 +196,29 @@ function pickDefaultRouteSlug(manifest) {
192
196
  return firstWithSlug ? firstWithSlug.slug : null;
193
197
  }
194
198
 
195
- export function buildDevelopmentDesktopUrl(appHref = null) {
199
+ const DESKTOP_DEEP_LINK_SCHEME_PATTERN = /^[a-z][a-z0-9-]*$/;
200
+
201
+ export function resolveDevelopmentDesktopScheme(env = process.env) {
202
+ // Mirror the desktop app's own scheme resolution (electron main + forge config):
203
+ // local dev launches register `notis-dev`, while installed prod/beta builds claim
204
+ // `notis`. Hardcoding `notis` here is what made `apps dev` open the installed
205
+ // prod/beta app instead of the local dev app.
206
+ const scheme = (env.NOTIS_DESKTOP_DEEP_LINK_SCHEME || '').trim();
207
+ return DESKTOP_DEEP_LINK_SCHEME_PATTERN.test(scheme) ? scheme : 'notis';
208
+ }
209
+
210
+ export function buildDevelopmentDesktopUrl(appHref = null, scheme = 'notis') {
196
211
  const route = String(appHref || '/store').replace(/^\/+/, '');
197
- return `notis://${route || 'store'}`;
212
+ const normalizedScheme = DESKTOP_DEEP_LINK_SCHEME_PATTERN.test(scheme) ? scheme : 'notis';
213
+ return `${normalizedScheme}://${route || 'store'}`;
214
+ }
215
+
216
+ export function shouldOpenDevelopmentTab(options = {}) {
217
+ // Commander stores the negatable `--no-open` flag as `options.open === false`;
218
+ // it never sets `options.noOpen`. Reading the non-existent `noOpen` key meant
219
+ // `--no-open` was silently ignored and `apps dev` always ran `open notis://…`,
220
+ // which macOS routes to the installed prod/beta desktop app.
221
+ return options.open !== false;
198
222
  }
199
223
 
200
224
  function buildAppHref({ appSlug, appId, manifest }) {
@@ -311,14 +335,14 @@ function assertHarnessResult(result, route, databaseSlugs) {
311
335
  const seen = (result.runtimeCalls || []).some(
312
336
  (call) =>
313
337
  call?.op === 'callTool' &&
314
- call?.args?.name === 'notis-default-query' &&
338
+ localNotisToolSlug(call?.args?.name) === 'LOCAL_NOTIS_DATABASE_QUERY' &&
315
339
  call?.args?.arguments?.database_slug === databaseSlug,
316
340
  );
317
341
  if (!seen) {
318
342
  assertions.push({
319
343
  ok: false,
320
344
  code: 'missing_database_query',
321
- message: `Route "${route.slug}" did not call notis-default-query for "${databaseSlug}".`,
345
+ message: `Route "${route.slug}" did not call LOCAL_NOTIS_DATABASE_QUERY for "${databaseSlug}".`,
322
346
  details: { databaseSlug },
323
347
  });
324
348
  }
@@ -447,7 +471,7 @@ function databaseMaterializationWarnings(apps) {
447
471
  async function assertDirectDeployAccess(runtime, appId) {
448
472
  const result = await runToolCommand({
449
473
  runtime,
450
- toolName: 'notis_list_apps',
474
+ toolName: LIST_APPS_TOOL,
451
475
  });
452
476
  const apps = result.payload.apps || [];
453
477
  const hasAccess = apps.some((app) => (app.app_id || app.id) === appId);
@@ -463,7 +487,7 @@ async function assertDirectDeployAccess(runtime, appId) {
463
487
  async function appsListHandler(ctx) {
464
488
  const result = await runToolCommand({
465
489
  runtime: ctx.runtime,
466
- toolName: 'notis_list_apps',
490
+ toolName: LIST_APPS_TOOL,
467
491
  });
468
492
  const apps = result.payload.apps || [];
469
493
  return ctx.output.emitSuccess({
@@ -511,7 +535,7 @@ async function appsCreateHandler(ctx) {
511
535
  const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
512
536
  const result = await runToolCommand({
513
537
  runtime: ctx.runtime,
514
- toolName: 'notis_create_app',
538
+ toolName: CREATE_APP_TOOL,
515
539
  arguments_: {
516
540
  name: ctx.args.name,
517
541
  description: appConfig?.description || undefined,
@@ -634,7 +658,10 @@ async function appsDevHandler(ctx) {
634
658
  }),
635
659
  };
636
660
  });
637
- const developmentTabUrl = buildDevelopmentDesktopUrl(apps[0]?.appHref);
661
+ const developmentTabUrl = buildDevelopmentDesktopUrl(
662
+ apps[0]?.appHref,
663
+ resolveDevelopmentDesktopScheme(),
664
+ );
638
665
  const warnings = databaseMaterializationWarnings(apps);
639
666
 
640
667
  const devServer = await startAppDevServer({
@@ -710,7 +737,7 @@ async function appsDevHandler(ctx) {
710
737
  ].join('\n'),
711
738
  });
712
739
 
713
- if (!ctx.options.noOpen) {
740
+ if (shouldOpenDevelopmentTab(ctx.options)) {
714
741
  openInBrowser(developmentTabUrl);
715
742
  }
716
743
 
@@ -1073,7 +1100,7 @@ async function appsDeployHandler(ctx) {
1073
1100
  try {
1074
1101
  result = await runToolCommand({
1075
1102
  runtime: ctx.runtime,
1076
- toolName: 'notis_save_app_files',
1103
+ toolName: SAVE_APP_FILES_TOOL,
1077
1104
  arguments_: {
1078
1105
  app_id: appId,
1079
1106
  files,
@@ -1185,7 +1212,7 @@ export const appsCommandSpecs = [
1185
1212
  examples: ['notis apps list', 'notis apps list --json'],
1186
1213
  mutates: false,
1187
1214
  idempotent: true,
1188
- backend_call: { type: 'tool', name: 'notis_list_apps' },
1215
+ backend_call: { type: 'tool', name: LIST_APPS_TOOL },
1189
1216
  handler: appsListHandler,
1190
1217
  },
1191
1218
  {
@@ -1242,7 +1269,7 @@ export const appsCommandSpecs = [
1242
1269
  ],
1243
1270
  mutates: true,
1244
1271
  idempotent: false,
1245
- backend_call: { type: 'tool', name: 'notis_create_app' },
1272
+ backend_call: { type: 'tool', name: CREATE_APP_TOOL },
1246
1273
  handler: appsCreateHandler,
1247
1274
  },
1248
1275
  {
@@ -1376,7 +1403,7 @@ export const appsCommandSpecs = [
1376
1403
  examples: ['notis apps deploy', 'notis apps deploy --skip-build', 'notis apps deploy --app-id abc123', 'notis apps deploy --direct'],
1377
1404
  mutates: true,
1378
1405
  idempotent: true,
1379
- backend_call: { type: 'tool', name: 'notis_save_app_files' },
1406
+ backend_call: { type: 'tool', name: SAVE_APP_FILES_TOOL },
1380
1407
  handler: appsDeployHandler,
1381
1408
  },
1382
1409
  {
@@ -2,6 +2,34 @@ import { randomUUID } from 'node:crypto';
2
2
  import { CliError, EXIT_CODES, usageError } from '../runtime/errors.js';
3
3
  import { callTool, httpRequest } from '../runtime/transport.js';
4
4
 
5
+ export const COMPOSIO_SEARCH_TOOLS = 'COMPOSIO_SEARCH_TOOLS';
6
+ export const COMPOSIO_GET_TOOL_SCHEMAS = 'COMPOSIO_GET_TOOL_SCHEMAS';
7
+ export const COMPOSIO_MULTI_EXECUTE_TOOL = 'COMPOSIO_MULTI_EXECUTE_TOOL';
8
+
9
+ const NOTIS_DATABASE_CORE_NAMES = new Set([
10
+ 'query',
11
+ 'get_database',
12
+ 'get_document',
13
+ 'list_databases',
14
+ 'upsert_database',
15
+ ]);
16
+
17
+ function isNotisDatabaseCoreName(coreName) {
18
+ // Mirrors server is_database_tool_name: canonical database ops plus generated
19
+ // upsert_<db> tools. Database tools live in the NOTIS_DATABASE toolkit so their
20
+ // public slug carries the DATABASE segment.
21
+ return NOTIS_DATABASE_CORE_NAMES.has(coreName) || coreName.startsWith('upsert_');
22
+ }
23
+
24
+ export function localNotisToolSlug(toolName) {
25
+ if (typeof toolName !== 'string' || !toolName.startsWith('notis-')) {
26
+ return toolName;
27
+ }
28
+ const coreName = toolName.slice('notis-'.length);
29
+ const prefix = isNotisDatabaseCoreName(coreName) ? 'LOCAL_NOTIS_DATABASE_' : 'LOCAL_NOTIS_';
30
+ return `${prefix}${coreName.replace(/-/g, '_').toUpperCase()}`;
31
+ }
32
+
5
33
  export function parseJson(value, label) {
6
34
  try {
7
35
  return JSON.parse(value);
@@ -17,23 +45,6 @@ export function parseMaybeJson(value, label) {
17
45
  return parseJson(value, label);
18
46
  }
19
47
 
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
48
  export function nextIdempotencyKey(globalOptions) {
38
49
  return globalOptions.idempotencyKey || randomUUID();
39
50
  }
@@ -44,39 +55,32 @@ export async function runToolCommand({
44
55
  arguments_ = {},
45
56
  mutating = false,
46
57
  idempotencyKey,
58
+ fileBindings = [],
47
59
  }) {
48
60
  const result = await callTool({
49
61
  runtime: { ...runtime, mutating },
50
62
  toolName,
51
63
  arguments_,
52
64
  idempotencyKey: mutating ? idempotencyKey : null,
65
+ fileBindings,
53
66
  });
54
67
  return result;
55
68
  }
56
69
 
57
70
  export async function fetchToolkits(runtime) {
58
- const result = await runToolCommand({
59
- runtime,
60
- toolName: 'notis_find_toolkits',
61
- });
62
- return result.payload.toolkits || [];
63
- }
64
-
65
- export async function resolveSearchToolkits(runtime, rawToolkits) {
66
- const toolkits = normalizeToolkits(rawToolkits);
67
- if (toolkits.length) {
68
- return toolkits;
69
- }
70
- const availableToolkits = await fetchToolkits(runtime);
71
- return availableToolkits.map((entry) => entry.id);
71
+ const payload = await fetchToolDiscovery(runtime, 'List available toolkit namespaces and connection statuses');
72
+ return (payload.toolkit_connection_statuses || []).map((entry) => ({
73
+ id: entry.toolkit,
74
+ provider: typeof entry.toolkit === 'string' ? entry.toolkit.split('-', 1)[0] : undefined,
75
+ description: entry.description || entry.status_message || entry.toolkit,
76
+ has_active_connection: Boolean(entry.has_active_connection),
77
+ status_message: entry.status_message || '',
78
+ connection_details: entry.connection_details || {},
79
+ }));
72
80
  }
73
81
 
74
82
  export async function probeAuth(runtime) {
75
- const result = await runToolCommand({
76
- runtime,
77
- toolName: 'notis_find_toolkits',
78
- });
79
- return result.payload;
83
+ return fetchToolDiscovery(runtime, 'List available toolkit namespaces and connection statuses');
80
84
  }
81
85
 
82
86
  export async function healthCheck(runtime) {
@@ -88,22 +92,46 @@ export async function healthCheck(runtime) {
88
92
  });
89
93
  }
90
94
 
91
- export async function fetchToolSchema(runtime, toolName, rawToolkits) {
92
- const toolkits = normalizeToolkits(rawToolkits);
95
+ export async function fetchToolSchema(runtime, toolName) {
93
96
  const result = await runToolCommand({
94
97
  runtime,
95
- toolName: 'notis_find_tools',
98
+ toolName: COMPOSIO_GET_TOOL_SCHEMAS,
96
99
  arguments_: {
97
- query: toolName,
98
- ...(toolkits.length ? { toolkits } : {}),
100
+ tool_slugs: [toolName],
99
101
  },
100
102
  });
101
- const tools = result.payload.tools || [];
102
- const match = tools.find((t) => t.name === toolName);
103
- if (!match) {
103
+ const payload = result.payload || {};
104
+ const schema = (
105
+ payload.tool_schemas?.[toolName] ||
106
+ payload.schemas?.[toolName] ||
107
+ payload.tools?.find?.((tool) => tool?.tool_slug === toolName || tool?.name === toolName)
108
+ );
109
+ if (!schema) {
104
110
  throw usageError(`Tool "${toolName}" not found.`);
105
111
  }
106
- return match;
112
+ return {
113
+ name: toolName,
114
+ toolkit_id: schema.toolkit,
115
+ description: schema.description || '',
116
+ parameters: schema.input_schema || { type: 'object', properties: {} },
117
+ output_schema: schema.output_schema || {},
118
+ schema_available: Boolean(schema.hasFullSchema),
119
+ };
120
+ }
121
+
122
+ export async function fetchToolDiscovery(runtime, useCase, knownFields = '') {
123
+ const query = { use_case: useCase };
124
+ if (knownFields) {
125
+ query.known_fields = knownFields;
126
+ }
127
+ const result = await runToolCommand({
128
+ runtime,
129
+ toolName: COMPOSIO_SEARCH_TOOLS,
130
+ arguments_: {
131
+ queries: [query],
132
+ },
133
+ });
134
+ return result.payload || {};
107
135
  }
108
136
 
109
137
  export function validateArguments(schema, args) {
@@ -1,4 +1,4 @@
1
- import { healthCheck, probeAuth } from './helpers.js';
1
+ import { COMPOSIO_SEARCH_TOOLS, healthCheck, probeAuth } from './helpers.js';
2
2
  import { findCommandSpec, formatDescribe } from '../runtime/help.js';
3
3
 
4
4
  async function doctorHandler(ctx) {
@@ -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: COMPOSIO_SEARCH_TOOLS },
122
122
  handler: whoamiHandler,
123
123
  },
124
124
  {