@notis_ai/cli 0.2.0-beta.48.1 → 0.2.0-beta.76.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 +10 -10
- package/config/notis_app_boundary_rules.json +50 -0
- package/dist/scaffolds/notis-database/app/page.tsx +3 -3
- package/dist/scaffolds/notis-database/notis.config.ts +3 -3
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +6 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +3 -3
- package/dist/scaffolds/notis-notes/notis.config.ts +3 -3
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +2 -2
- package/dist/scaffolds/notis-random/notis.config.ts +2 -2
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +4 -4
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +2 -2
- package/skills/notis-cli/SKILL.md +17 -15
- package/skills/notis-query/cli.md +9 -10
- package/src/command-specs/apps.js +42 -15
- package/src/command-specs/helpers.js +40 -35
- package/src/command-specs/meta.js +2 -2
- package/src/command-specs/tools.js +47 -43
- package/src/runtime/app-boundary-validator.js +25 -5
- package/src/runtime/profiles.js +10 -4
- package/template/app/page.tsx +1 -1
- package/template/notis.config.ts +1 -1
- package/template/packages/sdk/src/hooks/useTool.ts +1 -1
package/README.md
CHANGED
|
@@ -207,8 +207,8 @@ Describe a generic tool by name.
|
|
|
207
207
|
When to use: Use this when you know the tool name and want its parameter schema before execution.
|
|
208
208
|
|
|
209
209
|
Examples:
|
|
210
|
-
- `npx --package @notis_ai/cli@latest -- notis tools describe composio-gmail-
|
|
211
|
-
- `npx --package @notis_ai/cli@latest -- notis tools describe
|
|
210
|
+
- `npx --package @notis_ai/cli@latest -- notis tools describe composio-gmail-send_email`
|
|
211
|
+
- `npx --package @notis_ai/cli@latest -- notis tools describe LOCAL_NOTIS_DATABASE_QUERY`
|
|
212
212
|
|
|
213
213
|
### `npx --package @notis_ai/cli@latest -- notis tools exec <tool-name>`
|
|
214
214
|
|
|
@@ -223,13 +223,13 @@ Options:
|
|
|
223
223
|
- `--dry-run` — Validate arguments against the tool schema without executing.
|
|
224
224
|
|
|
225
225
|
Examples:
|
|
226
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
227
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
228
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
229
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
230
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
231
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec
|
|
232
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec composio-dropbox-
|
|
226
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments '{"database_slug":"tasks","query":{}}'`
|
|
227
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments '{"database_slug":"tasks"}'`
|
|
228
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --get-schema`
|
|
229
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --dry-run --arguments '{"database_slug":"tasks","query":{}}'`
|
|
230
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments @query.json`
|
|
231
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments - < query.json`
|
|
232
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec composio-dropbox-upload_file --arguments '{"path":"/target/in/dropbox.pdf"}' --file content=./Invoice.pdf`
|
|
233
233
|
|
|
234
234
|
### `npx --package @notis_ai/cli@latest -- notis tools exec-parallel <calls>`
|
|
235
235
|
|
|
@@ -241,7 +241,7 @@ Options:
|
|
|
241
241
|
- `--file <argument-path=local-path>` — Unsupported for exec-parallel; use tools exec for file uploads.
|
|
242
242
|
|
|
243
243
|
Examples:
|
|
244
|
-
- `npx --package @notis_ai/cli@latest -- notis tools exec-parallel '[{"tool_name":"
|
|
244
|
+
- `npx --package @notis_ai/cli@latest -- notis tools exec-parallel '[{"tool_name":"LOCAL_NOTIS_DATABASE_QUERY","arguments":{"database_slug":"tasks","query":{}}},{"tool_name":"LOCAL_NOTIS_DATABASE_LIST_DATABASES","arguments":{}}]'`
|
|
245
245
|
|
|
246
246
|
### `npx --package @notis_ai/cli@latest -- notis tools link <toolkit>`
|
|
247
247
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"javascript": [
|
|
3
|
+
{
|
|
4
|
+
"pattern": "window\\.__NOTIS_RUNTIME__",
|
|
5
|
+
"message": "window.__NOTIS_RUNTIME__ is forbidden; rely on the portal-injected NotisProvider runtime."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"pattern": "document\\.(?:body|head|documentElement)\\b",
|
|
9
|
+
"message": "document.body, document.head, and document.documentElement are forbidden; apps must stay inside the app surface."
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"pattern": "document\\.(?:querySelector|querySelectorAll|getElementById|getElementsByClassName|getElementsByTagName)\\s*\\(\\s*(['\"`])[^\\n]*\\[data-notis-",
|
|
13
|
+
"message": "querying portal-owned [data-notis-*] hooks is forbidden."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"pattern": "createPortal\\s*\\(",
|
|
17
|
+
"message": "React portals are unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"pattern": "@radix-ui/react-portal",
|
|
21
|
+
"message": "@radix-ui/react-portal is unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"pattern": "PortalPrimitive",
|
|
25
|
+
"message": "Radix portal primitives are unsupported in Notis apps until a shadow-safe overlay contract exists."
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"css": [
|
|
29
|
+
{
|
|
30
|
+
"pattern": ":root\\b",
|
|
31
|
+
"message": ":root selectors are forbidden; styles must stay inside the app surface."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"pattern": "(^|[\\s,>+~])html(?=[\\s.#[:{>+~,]|$)",
|
|
35
|
+
"message": "html selectors are forbidden; styles must stay inside the app surface."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"pattern": "(^|[\\s,>+~])body(?=[\\s.#[:{>+~,]|$)",
|
|
39
|
+
"message": "body selectors are forbidden; styles must stay inside the app surface."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"pattern": "\\[data-notis-(?!app-root\\b)[^\\]]*\\]",
|
|
43
|
+
"message": "portal-owned [data-notis-*] selectors are forbidden. Use [data-notis-app-root] only for the app surface itself."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"pattern": "\\[data-sidebar(?:=[^\\]]+)?\\]",
|
|
47
|
+
"message": "portal sidebar selectors are forbidden."
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -224,13 +224,13 @@ type ActiveTab = 'properties' | 'relations' | 'documents';
|
|
|
224
224
|
|
|
225
225
|
export default function CatalogPage() {
|
|
226
226
|
const listDatabases = useTool<ListDatabasesArgs, ListDatabasesResult>(
|
|
227
|
-
'
|
|
227
|
+
'LOCAL_NOTIS_DATABASE_LIST_DATABASES',
|
|
228
228
|
);
|
|
229
229
|
const getDatabase = useTool<GetDatabaseArgs, GetDatabaseResult>(
|
|
230
|
-
'
|
|
230
|
+
'LOCAL_NOTIS_DATABASE_GET_DATABASE',
|
|
231
231
|
);
|
|
232
232
|
const queryDatabase = useTool<QueryDatabaseArgs, QueryDatabaseResult>(
|
|
233
|
-
'
|
|
233
|
+
'LOCAL_NOTIS_DATABASE_QUERY',
|
|
234
234
|
);
|
|
235
235
|
|
|
236
236
|
const [rows, setRows] = useState<DatabaseCatalogRow[]>([]);
|
|
@@ -20,8 +20,8 @@ export default defineNotisApp({
|
|
|
20
20
|
},
|
|
21
21
|
],
|
|
22
22
|
tools: [
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
23
|
+
'LOCAL_NOTIS_DATABASE_LIST_DATABASES',
|
|
24
|
+
'LOCAL_NOTIS_DATABASE_GET_DATABASE',
|
|
25
|
+
'LOCAL_NOTIS_DATABASE_QUERY',
|
|
26
26
|
],
|
|
27
27
|
});
|
|
@@ -505,21 +505,21 @@ export function installMockRuntime(): NotisRuntime {
|
|
|
505
505
|
},
|
|
506
506
|
context: {},
|
|
507
507
|
listTools: async () => [
|
|
508
|
-
{ name: '
|
|
509
|
-
{ name: '
|
|
510
|
-
{ name: '
|
|
508
|
+
{ name: 'LOCAL_NOTIS_DATABASE_LIST_DATABASES', inputSchema: { type: 'object', properties: {} } },
|
|
509
|
+
{ name: 'LOCAL_NOTIS_DATABASE_GET_DATABASE', inputSchema: { type: 'object', properties: {} } },
|
|
510
|
+
{ name: 'LOCAL_NOTIS_DATABASE_QUERY', inputSchema: { type: 'object', properties: {} } },
|
|
511
511
|
],
|
|
512
512
|
callTool: async <TResult = unknown>(
|
|
513
513
|
name: string,
|
|
514
514
|
args?: Record<string, unknown>,
|
|
515
515
|
): Promise<TResult> => {
|
|
516
|
-
if (name === '
|
|
516
|
+
if (name === 'LOCAL_NOTIS_DATABASE_LIST_DATABASES') {
|
|
517
517
|
return {
|
|
518
518
|
databases: SEED.map((s) => s.catalog),
|
|
519
519
|
total_count: SEED.length,
|
|
520
520
|
} as TResult;
|
|
521
521
|
}
|
|
522
|
-
if (name === '
|
|
522
|
+
if (name === 'LOCAL_NOTIS_DATABASE_GET_DATABASE') {
|
|
523
523
|
const request = args ?? {};
|
|
524
524
|
const id = typeof request.database_id === 'string' ? request.database_id : null;
|
|
525
525
|
const slug = typeof request.database_slug === 'string' ? request.database_slug : null;
|
|
@@ -531,7 +531,7 @@ export function installMockRuntime(): NotisRuntime {
|
|
|
531
531
|
}
|
|
532
532
|
return { database: match.detail } as TResult;
|
|
533
533
|
}
|
|
534
|
-
if (name === '
|
|
534
|
+
if (name === 'LOCAL_NOTIS_DATABASE_QUERY') {
|
|
535
535
|
const request = args ?? {};
|
|
536
536
|
const slug = typeof request.database_slug === 'string' ? request.database_slug : null;
|
|
537
537
|
const match = SEED.find((s) => slug && s.detail.slug === slug);
|
|
@@ -358,7 +358,7 @@ function normalizeCollectionItem(value: unknown): CollectionItemContext | null {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
function useNoteSchema(databaseSlug: string) {
|
|
361
|
-
const getDatabase = useTool<GetDatabaseArgs, GetDatabaseResult>('
|
|
361
|
+
const getDatabase = useTool<GetDatabaseArgs, GetDatabaseResult>('LOCAL_NOTIS_DATABASE_GET_DATABASE');
|
|
362
362
|
const [properties, setProperties] = useState<DatabaseProperty[]>([]);
|
|
363
363
|
const [loading, setLoading] = useState(true);
|
|
364
364
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -399,7 +399,7 @@ function useNoteSchema(databaseSlug: string) {
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
function useQueryDocuments(databaseSlug: string, options: { filter?: QueryFilter; pageSize?: number; offset?: number } = {}) {
|
|
402
|
-
const queryTool = useTool<QueryDatabaseArgs, QueryDatabaseResult>('
|
|
402
|
+
const queryTool = useTool<QueryDatabaseArgs, QueryDatabaseResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
403
403
|
const [documents, setDocuments] = useState<DocumentRecord[]>([]);
|
|
404
404
|
const [loading, setLoading] = useState(true);
|
|
405
405
|
const [error, setError] = useState<Error | null>(null);
|
|
@@ -1061,7 +1061,7 @@ export default function NotesPage() {
|
|
|
1061
1061
|
const { app, route, context } = useNotis();
|
|
1062
1062
|
const navigation = useNotisNavigation();
|
|
1063
1063
|
const { request } = useBackend();
|
|
1064
|
-
const upsertNote = useTool<UpsertNoteArgs, UpsertNoteResult>('
|
|
1064
|
+
const upsertNote = useTool<UpsertNoteArgs, UpsertNoteResult>('LOCAL_NOTIS_DATABASE_UPSERT_NOTES');
|
|
1065
1065
|
|
|
1066
1066
|
const [activeTab, setActiveTab] = useState<TabKey>('gallery');
|
|
1067
1067
|
const [activeDateProperty, setActiveDateProperty] = useState('');
|
|
@@ -29,8 +29,8 @@ export default defineNotisApp({
|
|
|
29
29
|
},
|
|
30
30
|
],
|
|
31
31
|
tools: [
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
32
|
+
'LOCAL_NOTIS_DATABASE_QUERY',
|
|
33
|
+
'LOCAL_NOTIS_DATABASE_GET_DATABASE',
|
|
34
|
+
'LOCAL_NOTIS_DATABASE_UPSERT_NOTES',
|
|
35
35
|
],
|
|
36
36
|
});
|
|
@@ -37,7 +37,7 @@ type UpsertRollResult = {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
export function useRollDocuments(pageSize?: number) {
|
|
40
|
-
const queryRolls = useTool<QueryRollsArgs, QueryRollsResult>('
|
|
40
|
+
const queryRolls = useTool<QueryRollsArgs, QueryRollsResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
41
41
|
const [documents, setDocuments] = useState<RollDocument[]>([]);
|
|
42
42
|
const [error, setError] = useState<Error | null>(null);
|
|
43
43
|
const [fetchKey, setFetchKey] = useState(0);
|
|
@@ -80,7 +80,7 @@ export function useRollDocuments(pageSize?: number) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function usePersistRoll() {
|
|
83
|
-
const upsertRoll = useTool<UpsertRollArgs, UpsertRollResult>('
|
|
83
|
+
const upsertRoll = useTool<UpsertRollArgs, UpsertRollResult>('LOCAL_NOTIS_DATABASE_UPSERT_ROLLS');
|
|
84
84
|
|
|
85
85
|
const persist = useCallback(
|
|
86
86
|
async (roll: Roll) => {
|
|
@@ -34,16 +34,16 @@ export function installMockRuntime(initialRoute: 'home' | 'history' = 'home'): N
|
|
|
34
34
|
|
|
35
35
|
listTools: async () => [
|
|
36
36
|
{
|
|
37
|
-
name: '
|
|
37
|
+
name: 'LOCAL_NOTIS_DATABASE_QUERY',
|
|
38
38
|
inputSchema: { type: 'object', properties: { database_slug: { type: 'string' } } },
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
|
-
name: '
|
|
41
|
+
name: 'LOCAL_NOTIS_DATABASE_UPSERT_ROLLS',
|
|
42
42
|
inputSchema: { type: 'object', properties: {} },
|
|
43
43
|
},
|
|
44
44
|
],
|
|
45
45
|
callTool: async <TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult> => {
|
|
46
|
-
if (name === '
|
|
46
|
+
if (name === 'LOCAL_NOTIS_DATABASE_QUERY') {
|
|
47
47
|
const request = args ?? {};
|
|
48
48
|
const databaseSlug = typeof request.database_slug === 'string' ? request.database_slug : 'rolls';
|
|
49
49
|
const query = request.query && typeof request.query === 'object'
|
|
@@ -56,7 +56,7 @@ export function installMockRuntime(initialRoute: 'home' | 'history' = 'home'): N
|
|
|
56
56
|
return { documents: typeof pageSize === 'number' ? sliced.slice(0, pageSize) : sliced } as TResult;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
if (name === '
|
|
59
|
+
if (name === 'LOCAL_NOTIS_DATABASE_UPSERT_ROLLS') {
|
|
60
60
|
const request = args ?? {};
|
|
61
61
|
const docs = STATE.rolls ?? [];
|
|
62
62
|
const now = new Date().toISOString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notis_ai/cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.76.1",
|
|
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: ['
|
|
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('
|
|
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.
|
|
@@ -155,7 +155,7 @@ This is the main escape hatch for:
|
|
|
155
155
|
|
|
156
156
|
Typical toolkit namespaces include:
|
|
157
157
|
|
|
158
|
-
- `notis
|
|
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-
|
|
182
|
-
npx --package @notis_ai/cli@latest -- notis tools exec composio-googlecalendar-
|
|
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
|
|
189
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
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
|
|
196
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
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
|
-
- `
|
|
210
|
-
- `
|
|
211
|
-
- `
|
|
212
|
-
- `
|
|
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
|
|
219
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
220
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
221
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
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
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
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
|
|
23
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
24
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
25
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
26
|
-
npx --package @notis_ai/cli@latest -- notis tools exec
|
|
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
|
-
|
|
@@ -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 = '
|
|
58
|
-
const GET_APP_TOOL = '
|
|
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
|
-
|
|
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
|
-
|
|
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 === '
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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 (
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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);
|
|
@@ -65,13 +93,19 @@ export async function healthCheck(runtime) {
|
|
|
65
93
|
}
|
|
66
94
|
|
|
67
95
|
export async function fetchToolSchema(runtime, toolName) {
|
|
68
|
-
const
|
|
69
|
-
const payload = await fetchToolDiscovery(
|
|
96
|
+
const result = await runToolCommand({
|
|
70
97
|
runtime,
|
|
71
|
-
|
|
72
|
-
|
|
98
|
+
toolName: COMPOSIO_GET_TOOL_SCHEMAS,
|
|
99
|
+
arguments_: {
|
|
100
|
+
tool_slugs: [toolName],
|
|
101
|
+
},
|
|
102
|
+
});
|
|
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)
|
|
73
108
|
);
|
|
74
|
-
const schema = payload.tool_schemas?.[toolName];
|
|
75
109
|
if (!schema) {
|
|
76
110
|
throw usageError(`Tool "${toolName}" not found.`);
|
|
77
111
|
}
|
|
@@ -85,35 +119,6 @@ export async function fetchToolSchema(runtime, toolName) {
|
|
|
85
119
|
};
|
|
86
120
|
}
|
|
87
121
|
|
|
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
122
|
export async function fetchToolDiscovery(runtime, useCase, knownFields = '') {
|
|
118
123
|
const query = { use_case: useCase };
|
|
119
124
|
if (knownFields) {
|
|
@@ -121,7 +126,7 @@ export async function fetchToolDiscovery(runtime, useCase, knownFields = '') {
|
|
|
121
126
|
}
|
|
122
127
|
const result = await runToolCommand({
|
|
123
128
|
runtime,
|
|
124
|
-
toolName:
|
|
129
|
+
toolName: COMPOSIO_SEARCH_TOOLS,
|
|
125
130
|
arguments_: {
|
|
126
131
|
queries: [query],
|
|
127
132
|
},
|
|
@@ -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) {
|
|
@@ -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:
|
|
121
|
+
backend_call: { type: 'tool', name: COMPOSIO_SEARCH_TOOLS },
|
|
122
122
|
handler: whoamiHandler,
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -3,9 +3,13 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { basename } from 'node:path';
|
|
4
4
|
import { usageError } from '../runtime/errors.js';
|
|
5
5
|
import {
|
|
6
|
+
COMPOSIO_GET_TOOL_SCHEMAS,
|
|
7
|
+
COMPOSIO_MULTI_EXECUTE_TOOL,
|
|
8
|
+
COMPOSIO_SEARCH_TOOLS,
|
|
6
9
|
fetchToolDiscovery,
|
|
7
10
|
fetchToolkits,
|
|
8
11
|
fetchToolSchema,
|
|
12
|
+
localNotisToolSlug,
|
|
9
13
|
nextIdempotencyKey,
|
|
10
14
|
parseJson,
|
|
11
15
|
parseMaybeJson,
|
|
@@ -37,10 +41,10 @@ async function resolveJsonInput(value, label) {
|
|
|
37
41
|
|
|
38
42
|
const LONG_RUNNING_TOOL_TIMEOUT_MS = 600000;
|
|
39
43
|
const LONG_RUNNING_TOOL_NAMES = new Set([
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
+
'LOCAL_NOTIS_GENERATE_IMAGE_OPENAI',
|
|
45
|
+
'LOCAL_NOTIS_EDIT_IMAGE_OPENAI',
|
|
46
|
+
'LOCAL_NOTIS_GENERATE_IMAGE_GEMINI',
|
|
47
|
+
'LOCAL_NOTIS_EDIT_IMAGE_GEMINI',
|
|
44
48
|
]);
|
|
45
49
|
|
|
46
50
|
const EXTENSION_CONTENT_TYPES = new Map([
|
|
@@ -128,7 +132,7 @@ async function parseFileBindings(rawFileOptions) {
|
|
|
128
132
|
|
|
129
133
|
function ensureLongRunningToolTimeout(ctx, toolNames) {
|
|
130
134
|
const names = Array.isArray(toolNames) ? toolNames : [toolNames];
|
|
131
|
-
if (!names.some((name) => LONG_RUNNING_TOOL_NAMES.has(name))) {
|
|
135
|
+
if (!names.some((name) => LONG_RUNNING_TOOL_NAMES.has(localNotisToolSlug(name)))) {
|
|
132
136
|
return;
|
|
133
137
|
}
|
|
134
138
|
ctx.runtime.timeoutMs = Math.max(ctx.runtime.timeoutMs || 0, LONG_RUNNING_TOOL_TIMEOUT_MS);
|
|
@@ -206,32 +210,34 @@ async function toolsSearchHandler(ctx) {
|
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
async function toolsDescribeHandler(ctx) {
|
|
209
|
-
const
|
|
213
|
+
const requestedToolName = localNotisToolSlug(ctx.args.toolName);
|
|
214
|
+
const match = await fetchToolSchema(ctx.runtime, requestedToolName);
|
|
210
215
|
|
|
211
216
|
return ctx.output.emitSuccess({
|
|
212
217
|
command: ctx.spec.command_path.join(' '),
|
|
213
218
|
data: { tool: match },
|
|
214
|
-
humanSummary: `Described tool ${
|
|
219
|
+
humanSummary: `Described tool ${requestedToolName}`,
|
|
215
220
|
hints: [
|
|
216
|
-
{ command: `notis tools exec ${
|
|
217
|
-
{ command: `notis tools exec ${
|
|
221
|
+
{ command: `notis tools exec ${requestedToolName} --dry-run --arguments '{}'`, reason: 'Validate arguments first' },
|
|
222
|
+
{ command: `notis tools exec ${requestedToolName} --arguments '{}'`, reason: 'Execute this tool' },
|
|
218
223
|
],
|
|
219
224
|
renderHuman: () => JSON.stringify(match, null, 2),
|
|
220
225
|
});
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
async function toolsExecHandler(ctx) {
|
|
224
|
-
|
|
229
|
+
const requestedToolName = localNotisToolSlug(ctx.args.toolName);
|
|
230
|
+
ensureLongRunningToolTimeout(ctx, requestedToolName);
|
|
225
231
|
const fileBindings = await parseFileBindings(ctx.options.file);
|
|
226
232
|
|
|
227
233
|
if (ctx.options.getSchema) {
|
|
228
|
-
const tool = await fetchToolSchema(ctx.runtime,
|
|
234
|
+
const tool = await fetchToolSchema(ctx.runtime, requestedToolName);
|
|
229
235
|
return ctx.output.emitSuccess({
|
|
230
236
|
command: ctx.spec.command_path.join(' '),
|
|
231
237
|
data: { tool },
|
|
232
|
-
humanSummary: `Schema for ${
|
|
238
|
+
humanSummary: `Schema for ${requestedToolName}`,
|
|
233
239
|
hints: [
|
|
234
|
-
{ command: `notis tools exec ${
|
|
240
|
+
{ command: `notis tools exec ${requestedToolName} --dry-run --arguments '{}'`, reason: 'Validate arguments before executing' },
|
|
235
241
|
],
|
|
236
242
|
renderHuman: () => JSON.stringify(tool, null, 2),
|
|
237
243
|
});
|
|
@@ -246,16 +252,16 @@ async function toolsExecHandler(ctx) {
|
|
|
246
252
|
if (fileBindings.length) {
|
|
247
253
|
throw usageError('--file is only supported when executing a tool, not with --dry-run');
|
|
248
254
|
}
|
|
249
|
-
const tool = await fetchToolSchema(ctx.runtime,
|
|
255
|
+
const tool = await fetchToolSchema(ctx.runtime, requestedToolName);
|
|
250
256
|
const errors = validateArguments(tool.parameters, args);
|
|
251
257
|
|
|
252
258
|
if (errors.length) {
|
|
253
259
|
return ctx.output.emitSuccess({
|
|
254
260
|
command: ctx.spec.command_path.join(' '),
|
|
255
|
-
data: { valid: false, errors, tool:
|
|
261
|
+
data: { valid: false, errors, tool: requestedToolName },
|
|
256
262
|
humanSummary: `Dry run failed: ${errors.length} validation error(s)`,
|
|
257
263
|
hints: [
|
|
258
|
-
{ command: `notis tools exec ${
|
|
264
|
+
{ command: `notis tools exec ${requestedToolName} --get-schema`, reason: 'Review the full parameter schema' },
|
|
259
265
|
],
|
|
260
266
|
renderHuman: () => errors.map((e) => ` - ${e}`).join('\n'),
|
|
261
267
|
});
|
|
@@ -263,10 +269,10 @@ async function toolsExecHandler(ctx) {
|
|
|
263
269
|
|
|
264
270
|
return ctx.output.emitSuccess({
|
|
265
271
|
command: ctx.spec.command_path.join(' '),
|
|
266
|
-
data: { valid: true, tool:
|
|
267
|
-
humanSummary: `Dry run passed for ${
|
|
272
|
+
data: { valid: true, tool: requestedToolName, arguments: args },
|
|
273
|
+
humanSummary: `Dry run passed for ${requestedToolName}`,
|
|
268
274
|
hints: [
|
|
269
|
-
{ command: `notis tools exec ${
|
|
275
|
+
{ command: `notis tools exec ${requestedToolName} --arguments '${JSON.stringify(args)}'`, reason: 'Execute with these arguments' },
|
|
270
276
|
],
|
|
271
277
|
});
|
|
272
278
|
}
|
|
@@ -275,10 +281,9 @@ async function toolsExecHandler(ctx) {
|
|
|
275
281
|
|
|
276
282
|
const result = await runToolCommand({
|
|
277
283
|
runtime: ctx.runtime,
|
|
278
|
-
toolName:
|
|
284
|
+
toolName: COMPOSIO_MULTI_EXECUTE_TOOL,
|
|
279
285
|
arguments_: {
|
|
280
|
-
|
|
281
|
-
arguments: args,
|
|
286
|
+
tools: [{ tool_slug: requestedToolName, arguments: args }],
|
|
282
287
|
},
|
|
283
288
|
mutating: true,
|
|
284
289
|
idempotencyKey,
|
|
@@ -288,7 +293,7 @@ async function toolsExecHandler(ctx) {
|
|
|
288
293
|
return ctx.output.emitSuccess({
|
|
289
294
|
command: ctx.spec.command_path.join(' '),
|
|
290
295
|
data: result.payload,
|
|
291
|
-
humanSummary: `Executed tool ${
|
|
296
|
+
humanSummary: `Executed tool ${requestedToolName}`,
|
|
292
297
|
meta: { mutating: true, idempotency_key: idempotencyKey },
|
|
293
298
|
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
294
299
|
});
|
|
@@ -309,16 +314,15 @@ async function toolsExecParallelHandler(ctx) {
|
|
|
309
314
|
throw usageError(`calls[${i}] missing required "tool_name" string`);
|
|
310
315
|
}
|
|
311
316
|
}
|
|
312
|
-
ensureLongRunningToolTimeout(ctx, calls.map((call) => call.tool_name));
|
|
317
|
+
ensureLongRunningToolTimeout(ctx, calls.map((call) => localNotisToolSlug(call.tool_name)));
|
|
313
318
|
|
|
314
319
|
const data = await Promise.all(
|
|
315
320
|
calls.map((call) =>
|
|
316
321
|
runToolCommand({
|
|
317
322
|
runtime: ctx.runtime,
|
|
318
|
-
toolName:
|
|
323
|
+
toolName: COMPOSIO_MULTI_EXECUTE_TOOL,
|
|
319
324
|
arguments_: {
|
|
320
|
-
|
|
321
|
-
arguments: call.arguments || {},
|
|
325
|
+
tools: [{ tool_slug: localNotisToolSlug(call.tool_name), arguments: call.arguments || {} }],
|
|
322
326
|
},
|
|
323
327
|
mutating: true,
|
|
324
328
|
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
@@ -385,11 +389,11 @@ export const toolsCommandSpecs = [
|
|
|
385
389
|
when_to_use: 'Use this to inspect connection state before searching or executing generic tools.',
|
|
386
390
|
args_schema: { arguments: [], options: [] },
|
|
387
391
|
examples: ['notis tools toolkits', 'notis tools toolkits --json'],
|
|
388
|
-
output_schema: 'Returns toolkit_connection_statuses from
|
|
392
|
+
output_schema: 'Returns toolkit_connection_statuses from COMPOSIO_SEARCH_TOOLS, rendered as toolkit rows.',
|
|
389
393
|
mutates: false,
|
|
390
394
|
idempotent: true,
|
|
391
395
|
related_commands: ['notis tools search <query>', 'notis tools describe <tool-name>'],
|
|
392
|
-
backend_call: { type: 'tool', name:
|
|
396
|
+
backend_call: { type: 'tool', name: COMPOSIO_SEARCH_TOOLS },
|
|
393
397
|
handler: toolsToolkitsHandler,
|
|
394
398
|
},
|
|
395
399
|
{
|
|
@@ -401,11 +405,11 @@ export const toolsCommandSpecs = [
|
|
|
401
405
|
options: [{ flags: '--known-fields <text>', key: 'knownFields', description: 'Optional known field hints, such as channel_name:general or user_email:a@example.com.' }],
|
|
402
406
|
},
|
|
403
407
|
examples: ['notis tools search "send an email"', 'notis tools search "post on LinkedIn" --known-fields "platform:linkedin"'],
|
|
404
|
-
output_schema: 'Returns the full
|
|
408
|
+
output_schema: 'Returns the full COMPOSIO_SEARCH_TOOLS response. Human output renders canonical names, connection status, and schema availability.',
|
|
405
409
|
mutates: false,
|
|
406
410
|
idempotent: true,
|
|
407
411
|
related_commands: ['notis tools toolkits', 'notis tools exec <tool-name>'],
|
|
408
|
-
backend_call: { type: 'tool', name:
|
|
412
|
+
backend_call: { type: 'tool', name: COMPOSIO_SEARCH_TOOLS },
|
|
409
413
|
handler: toolsSearchHandler,
|
|
410
414
|
},
|
|
411
415
|
{
|
|
@@ -416,12 +420,12 @@ export const toolsCommandSpecs = [
|
|
|
416
420
|
arguments: [{ token: '<tool-name>', description: 'Exact tool name to locate and describe.' }],
|
|
417
421
|
options: [],
|
|
418
422
|
},
|
|
419
|
-
examples: ['notis tools describe composio-gmail-
|
|
423
|
+
examples: ['notis tools describe composio-gmail-send_email', 'notis tools describe LOCAL_NOTIS_DATABASE_QUERY'],
|
|
420
424
|
output_schema: 'Returns one tool descriptor with name, description, and parameters.',
|
|
421
425
|
mutates: false,
|
|
422
426
|
idempotent: true,
|
|
423
427
|
related_commands: ['notis tools search <query>', 'notis tools exec <tool-name>'],
|
|
424
|
-
backend_call: { type: 'tool', name:
|
|
428
|
+
backend_call: { type: 'tool', name: COMPOSIO_GET_TOOL_SCHEMAS },
|
|
425
429
|
handler: toolsDescribeHandler,
|
|
426
430
|
},
|
|
427
431
|
{
|
|
@@ -438,19 +442,19 @@ export const toolsCommandSpecs = [
|
|
|
438
442
|
],
|
|
439
443
|
},
|
|
440
444
|
examples: [
|
|
441
|
-
'notis tools exec
|
|
442
|
-
'notis tools exec
|
|
443
|
-
'notis tools exec
|
|
444
|
-
'notis tools exec
|
|
445
|
-
'notis tools exec
|
|
446
|
-
'notis tools exec
|
|
447
|
-
'notis tools exec composio-dropbox-
|
|
445
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
446
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_GET_DATABASE --arguments \'{"database_slug":"tasks"}\'',
|
|
447
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --get-schema',
|
|
448
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --dry-run --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
449
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments @query.json',
|
|
450
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments - < query.json',
|
|
451
|
+
'notis tools exec composio-dropbox-upload_file --arguments \'{"path":"/target/in/dropbox.pdf"}\' --file content=./Invoice.pdf',
|
|
448
452
|
],
|
|
449
453
|
output_schema: 'Returns the raw tool execution payload.',
|
|
450
454
|
mutates: true,
|
|
451
455
|
idempotent: true,
|
|
452
456
|
related_commands: ['notis tools search <query>', 'notis tools describe <tool-name>', 'notis tools exec-parallel <calls>'],
|
|
453
|
-
backend_call: { type: 'tool', name:
|
|
457
|
+
backend_call: { type: 'tool', name: COMPOSIO_MULTI_EXECUTE_TOOL },
|
|
454
458
|
handler: toolsExecHandler,
|
|
455
459
|
},
|
|
456
460
|
{
|
|
@@ -466,13 +470,13 @@ export const toolsCommandSpecs = [
|
|
|
466
470
|
],
|
|
467
471
|
},
|
|
468
472
|
examples: [
|
|
469
|
-
'notis tools exec-parallel \'[{"tool_name":"
|
|
473
|
+
'notis tools exec-parallel \'[{"tool_name":"LOCAL_NOTIS_DATABASE_QUERY","arguments":{"database_slug":"tasks","query":{}}},{"tool_name":"LOCAL_NOTIS_DATABASE_LIST_DATABASES","arguments":{}}]\'',
|
|
470
474
|
],
|
|
471
475
|
output_schema: 'Returns an array of results, one per tool call.',
|
|
472
476
|
mutates: true,
|
|
473
477
|
idempotent: true,
|
|
474
478
|
related_commands: ['notis tools exec <tool-name>', 'notis tools search <query>'],
|
|
475
|
-
backend_call: { type: 'tool', name:
|
|
479
|
+
backend_call: { type: 'tool', name: COMPOSIO_MULTI_EXECUTE_TOOL },
|
|
476
480
|
handler: toolsExecParallelHandler,
|
|
477
481
|
},
|
|
478
482
|
{
|
|
@@ -4,10 +4,30 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
import { usageError } from './errors.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
)
|
|
7
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
// Candidate locations for the app boundary rules, in priority order:
|
|
10
|
+
// 1. The monorepo server source (always fresh during local development).
|
|
11
|
+
// 2. The copy bundled into the published package at build time (see
|
|
12
|
+
// scripts/copy-boundary-rules.js + package.json "files": config/). When the
|
|
13
|
+
// CLI is installed via npm, candidate 1 escapes the package and is absent,
|
|
14
|
+
// so the bundled copy is used. This is what was missing before: the package
|
|
15
|
+
// shipped only the escaping path and crashed with ENOENT on any command.
|
|
16
|
+
export const RULES_PATH_CANDIDATES = [
|
|
17
|
+
resolve(moduleDir, '../../../../server/config/notis_app_boundary_rules.json'),
|
|
18
|
+
resolve(moduleDir, '../../config/notis_app_boundary_rules.json'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function resolveRulesPath() {
|
|
22
|
+
for (const candidate of RULES_PATH_CANDIDATES) {
|
|
23
|
+
if (existsSync(candidate)) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Fall back to the bundled-copy path so any error message points inside the
|
|
28
|
+
// package rather than at an escaping monorepo path.
|
|
29
|
+
return RULES_PATH_CANDIDATES[RULES_PATH_CANDIDATES.length - 1];
|
|
30
|
+
}
|
|
11
31
|
|
|
12
32
|
const SOURCE_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.css', '.scss', '.sass', '.pcss']);
|
|
13
33
|
const IGNORED_SOURCE_DIRS = new Set([
|
|
@@ -21,7 +41,7 @@ const IGNORED_SOURCE_DIRS = new Set([
|
|
|
21
41
|
]);
|
|
22
42
|
|
|
23
43
|
function loadBoundaryRules() {
|
|
24
|
-
return JSON.parse(readFileSync(
|
|
44
|
+
return JSON.parse(readFileSync(resolveRulesPath(), 'utf-8'));
|
|
25
45
|
}
|
|
26
46
|
|
|
27
47
|
function compileRules(entries) {
|
package/src/runtime/profiles.js
CHANGED
|
@@ -212,15 +212,21 @@ export function resolveRuntimeProfile(globalOptions = {}, { requireAuth = true }
|
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// An explicit NOTIS_JWT is a complete credential override: use it verbatim and do
|
|
216
|
+
// NOT attempt a stored-profile dev_portal token refresh (whose refresh_token may be
|
|
217
|
+
// stale or belong to a different session). Without this, a long-lived shell with an
|
|
218
|
+
// expired profile refresh_token fails every command with invalid_grant even though
|
|
219
|
+
// the supplied NOTIS_JWT is valid.
|
|
220
|
+
const usingEnvJwt = Boolean(process.env.NOTIS_JWT);
|
|
215
221
|
return {
|
|
216
222
|
config,
|
|
217
223
|
profileName,
|
|
218
224
|
apiBase,
|
|
219
225
|
jwt,
|
|
220
|
-
authMode: profile.auth_mode,
|
|
221
|
-
refreshToken: profile.refresh_token,
|
|
222
|
-
accessExpiresAt: profile.access_expires_at,
|
|
223
|
-
refreshExpiresAt: profile.refresh_expires_at,
|
|
226
|
+
authMode: usingEnvJwt ? 'jwt' : profile.auth_mode,
|
|
227
|
+
refreshToken: usingEnvJwt ? undefined : profile.refresh_token,
|
|
228
|
+
accessExpiresAt: usingEnvJwt ? undefined : profile.access_expires_at,
|
|
229
|
+
refreshExpiresAt: usingEnvJwt ? undefined : profile.refresh_expires_at,
|
|
224
230
|
agentMode,
|
|
225
231
|
nonInteractive,
|
|
226
232
|
outputMode,
|
package/template/app/page.tsx
CHANGED
|
@@ -25,7 +25,7 @@ type QueryItemsResult = {
|
|
|
25
25
|
|
|
26
26
|
export default function HomePage() {
|
|
27
27
|
const { app, ready } = useNotis();
|
|
28
|
-
const queryItems = useTool<QueryItemsArgs, QueryItemsResult>('
|
|
28
|
+
const queryItems = useTool<QueryItemsArgs, QueryItemsResult>('LOCAL_NOTIS_DATABASE_QUERY');
|
|
29
29
|
const [documents, setDocuments] = useState<ItemDocument[]>([]);
|
|
30
30
|
|
|
31
31
|
useEffect(() => {
|
package/template/notis.config.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface UseToolResult<
|
|
|
25
25
|
* Call a specific Notis tool by name.
|
|
26
26
|
*
|
|
27
27
|
* ```tsx
|
|
28
|
-
* const { call, loading } = useTool('
|
|
28
|
+
* const { call, loading } = useTool('LOCAL_NOTIS_WEB_SEARCH');
|
|
29
29
|
* const result = await call({ query: 'latest news' });
|
|
30
30
|
* ```
|
|
31
31
|
*/
|