@notis_ai/cli 0.2.0-beta.35.1 → 0.2.0-beta.41.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 +80 -75
- package/dist/scaffolds/notis-database/app/globals.css +44 -0
- package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
- package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
- package/dist/scaffolds/notis-database/index.html +12 -0
- package/dist/scaffolds/notis-database/lib/types.ts +134 -0
- package/dist/scaffolds/notis-database/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/notis.config.ts +27 -0
- package/dist/scaffolds/notis-database/package.json +31 -0
- package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +557 -0
- package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
- package/dist/scaffolds/{notes → notis-database}/tsconfig.json +2 -1
- package/dist/scaffolds/notis-database/vite.config.ts +22 -0
- package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
- package/dist/scaffolds/{notes → notis-notes}/app/page.tsx +783 -80
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-notes/components.json +20 -0
- package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-notes/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/{notes → notis-notes}/notis.config.ts +12 -7
- package/dist/scaffolds/{notes → notis-notes}/package.json +3 -3
- package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
- package/dist/scaffolds/notis-random/README.md +33 -0
- package/dist/scaffolds/notis-random/app/globals.css +11 -0
- package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
- package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
- package/dist/scaffolds/notis-random/app/page.tsx +222 -0
- package/dist/scaffolds/notis-random/index.html +12 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
- package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
- package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
- package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
- package/dist/scaffolds/notis-random/metadata/cover.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/notis.config.ts +43 -0
- package/dist/scaffolds/notis-random/package.json +32 -0
- package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
- package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +128 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
- package/dist/scaffolds/notis-random/tsconfig.json +23 -0
- package/dist/scaffolds/notis-random/vite.config.ts +11 -0
- package/dist/scaffolds.json +27 -4
- package/package.json +1 -1
- package/skills/notis-apps/SKILL.md +27 -33
- package/skills/notis-apps/cli.md +51 -51
- package/skills/notis-cli/SKILL.md +65 -70
- package/skills/notis-query/cli.md +11 -11
- package/src/cli.js +10 -1
- package/src/command-specs/apps.js +2 -2
- package/src/command-specs/helpers.js +2 -0
- package/src/command-specs/tools.js +100 -2
- package/src/runtime/app-dev-server.js +2 -2
- package/src/runtime/app-platform.js +28 -0
- package/src/runtime/transport.js +129 -17
- package/template/packages/sdk/package.json +6 -0
- package/template/packages/sdk/src/hooks/useDatabase.ts +76 -0
- package/template/packages/sdk/src/hooks/useNotis.ts +10 -6
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +50 -0
- package/template/packages/sdk/src/index.ts +9 -1
- package/template/packages/sdk/src/runtime.ts +83 -2
- /package/dist/scaffolds/{notes → notis-database}/app/layout.tsx +0 -0
- /package/dist/scaffolds/{notes → notis-database}/components/ui/badge.tsx +0 -0
- /package/dist/scaffolds/{notes → notis-database}/components/ui/button.tsx +0 -0
- /package/dist/scaffolds/{notes → notis-database}/components/ui/card.tsx +0 -0
- /package/dist/scaffolds/{notes → notis-database}/components.json +0 -0
- /package/dist/scaffolds/{notes → notis-database}/lib/utils.ts +0 -0
- /package/dist/scaffolds/{notes → notis-database}/postcss.config.mjs +0 -0
- /package/dist/scaffolds/{notes → notis-notes}/app/globals.css +0 -0
- /package/dist/scaffolds/{notes → notis-notes}/tailwind.config.ts +0 -0
- /package/dist/scaffolds/{notes → notis-notes}/vite.config.ts +0 -0
package/src/runtime/transport.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
2
3
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_PROFILE,
|
|
@@ -35,6 +36,73 @@ function canonicalizeToolName(toolName) {
|
|
|
35
36
|
return toolName;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
function escapeMultipartHeaderValue(value) {
|
|
40
|
+
return String(value ?? '')
|
|
41
|
+
.replace(/"/g, '%22')
|
|
42
|
+
.replace(/\r/g, '%0D')
|
|
43
|
+
.replace(/\n/g, '%0A');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function multipartTextPart(boundary, name, value) {
|
|
47
|
+
return [
|
|
48
|
+
`--${boundary}`,
|
|
49
|
+
`Content-Disposition: form-data; name="${escapeMultipartHeaderValue(name)}"`,
|
|
50
|
+
'',
|
|
51
|
+
value,
|
|
52
|
+
'',
|
|
53
|
+
].join('\r\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function multipartFileHeader(boundary, binding) {
|
|
57
|
+
const filename = escapeMultipartHeaderValue(binding.basename || binding.field_name);
|
|
58
|
+
return [
|
|
59
|
+
`--${boundary}`,
|
|
60
|
+
`Content-Disposition: form-data; name="${escapeMultipartHeaderValue(binding.field_name)}"; filename="${filename}"`,
|
|
61
|
+
`Content-Type: ${binding.contentType || 'application/octet-stream'}`,
|
|
62
|
+
'',
|
|
63
|
+
'',
|
|
64
|
+
].join('\r\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function* multipartFileBody(boundary, payload, bindingMetadata, fileBindings) {
|
|
68
|
+
yield Buffer.from(multipartTextPart(boundary, 'payload', JSON.stringify(payload)));
|
|
69
|
+
yield Buffer.from(multipartTextPart(boundary, 'file_bindings', JSON.stringify(bindingMetadata)));
|
|
70
|
+
for (const binding of fileBindings) {
|
|
71
|
+
yield Buffer.from(multipartFileHeader(boundary, binding));
|
|
72
|
+
for await (const chunk of createReadStream(binding.localPath)) {
|
|
73
|
+
yield chunk;
|
|
74
|
+
}
|
|
75
|
+
yield Buffer.from('\r\n');
|
|
76
|
+
}
|
|
77
|
+
yield Buffer.from(`--${boundary}--\r\n`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createMultipartFileUpload(payload, bindingMetadata, fileBindings) {
|
|
81
|
+
const boundary = `----notis-cli-${randomUUID().replace(/-/g, '')}`;
|
|
82
|
+
const textParts = [
|
|
83
|
+
multipartTextPart(boundary, 'payload', JSON.stringify(payload)),
|
|
84
|
+
multipartTextPart(boundary, 'file_bindings', JSON.stringify(bindingMetadata)),
|
|
85
|
+
];
|
|
86
|
+
const fileHeaderParts = fileBindings.map((binding) => multipartFileHeader(boundary, binding));
|
|
87
|
+
const closingPart = `--${boundary}--\r\n`;
|
|
88
|
+
const contentLength = [
|
|
89
|
+
...textParts,
|
|
90
|
+
...fileHeaderParts,
|
|
91
|
+
...fileBindings.map(() => '\r\n'),
|
|
92
|
+
closingPart,
|
|
93
|
+
].reduce((total, part) => total + Buffer.byteLength(part), 0)
|
|
94
|
+
+ fileBindings.reduce((total, binding) => total + binding.size, 0);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
body: multipartFileBody(boundary, payload, bindingMetadata, fileBindings),
|
|
98
|
+
headers: {
|
|
99
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
100
|
+
'Content-Length': String(contentLength),
|
|
101
|
+
},
|
|
102
|
+
duplex: 'half',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
38
106
|
function normalizeBackendError(status, payload) {
|
|
39
107
|
const backendError = payload?.error;
|
|
40
108
|
const message =
|
|
@@ -197,6 +265,7 @@ export async function httpRequest({
|
|
|
197
265
|
method = 'POST',
|
|
198
266
|
path,
|
|
199
267
|
body,
|
|
268
|
+
multipart = false,
|
|
200
269
|
requireAuth = true,
|
|
201
270
|
}) {
|
|
202
271
|
await maybeRefreshDevPortalAuth(runtime);
|
|
@@ -211,21 +280,41 @@ export async function httpRequest({
|
|
|
211
280
|
};
|
|
212
281
|
|
|
213
282
|
const headers = {
|
|
214
|
-
'Content-Type': 'application/json',
|
|
215
283
|
'X-Notis-CLI-Version': runtime.cliVersion,
|
|
216
284
|
'X-Notis-Request-Id': requestId,
|
|
217
285
|
};
|
|
286
|
+
if (!multipart) {
|
|
287
|
+
headers['Content-Type'] = 'application/json';
|
|
288
|
+
}
|
|
218
289
|
|
|
219
290
|
if (requireAuth && runtime.jwt) {
|
|
220
291
|
headers.Authorization = `Bearer ${runtime.jwt}`;
|
|
221
292
|
}
|
|
222
293
|
|
|
294
|
+
const resolveBody = () => {
|
|
295
|
+
if (!multipart) {
|
|
296
|
+
return {
|
|
297
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
298
|
+
headers: {},
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (typeof body === 'function') {
|
|
302
|
+
return body();
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
body,
|
|
306
|
+
headers: {},
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
|
223
310
|
try {
|
|
311
|
+
let requestBody = resolveBody();
|
|
224
312
|
let response = await fetch(`${runtime.apiBase}${path}`, {
|
|
225
313
|
method,
|
|
226
|
-
headers,
|
|
227
|
-
body:
|
|
314
|
+
headers: { ...headers, ...requestBody.headers },
|
|
315
|
+
body: requestBody.body,
|
|
228
316
|
signal: controller.signal,
|
|
317
|
+
...(requestBody.duplex ? { duplex: requestBody.duplex } : {}),
|
|
229
318
|
});
|
|
230
319
|
|
|
231
320
|
let payload = null;
|
|
@@ -243,11 +332,13 @@ export async function httpRequest({
|
|
|
243
332
|
headers.Authorization = `Bearer ${runtime.jwt}`;
|
|
244
333
|
}
|
|
245
334
|
resetTimeout();
|
|
335
|
+
requestBody = resolveBody();
|
|
246
336
|
response = await fetch(`${runtime.apiBase}${path}`, {
|
|
247
337
|
method,
|
|
248
|
-
headers,
|
|
249
|
-
body:
|
|
338
|
+
headers: { ...headers, ...requestBody.headers },
|
|
339
|
+
body: requestBody.body,
|
|
250
340
|
signal: controller.signal,
|
|
341
|
+
...(requestBody.duplex ? { duplex: requestBody.duplex } : {}),
|
|
251
342
|
});
|
|
252
343
|
try {
|
|
253
344
|
payload = await response.json();
|
|
@@ -302,22 +393,43 @@ export async function callTool({
|
|
|
302
393
|
toolName,
|
|
303
394
|
arguments_: argumentsPayload = {},
|
|
304
395
|
idempotencyKey,
|
|
396
|
+
fileBindings = [],
|
|
305
397
|
}) {
|
|
306
398
|
const requestId = idempotencyKey || (runtime.mutating ? randomUUID() : null);
|
|
399
|
+
const payload = {
|
|
400
|
+
tool_name: canonicalizeToolName(toolName),
|
|
401
|
+
arguments: argumentsPayload,
|
|
402
|
+
idempotency_key: requestId,
|
|
403
|
+
cli_context: {
|
|
404
|
+
output_mode: runtime.outputMode,
|
|
405
|
+
profile: runtime.profileName,
|
|
406
|
+
cwd: process.cwd(),
|
|
407
|
+
agent_mode: runtime.agentMode,
|
|
408
|
+
cli_version: runtime.cliVersion,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
if (Array.isArray(fileBindings) && fileBindings.length) {
|
|
413
|
+
const bindingMetadata = fileBindings.map((binding) => {
|
|
414
|
+
const {
|
|
415
|
+
contentType,
|
|
416
|
+
localPath,
|
|
417
|
+
...metadata
|
|
418
|
+
} = binding;
|
|
419
|
+
return metadata;
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
return httpRequest({
|
|
423
|
+
runtime,
|
|
424
|
+
path: '/cli_tools',
|
|
425
|
+
body: () => createMultipartFileUpload(payload, bindingMetadata, fileBindings),
|
|
426
|
+
multipart: true,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
307
430
|
return httpRequest({
|
|
308
431
|
runtime,
|
|
309
432
|
path: '/cli_tools',
|
|
310
|
-
body:
|
|
311
|
-
tool_name: canonicalizeToolName(toolName),
|
|
312
|
-
arguments: argumentsPayload,
|
|
313
|
-
idempotency_key: requestId,
|
|
314
|
-
cli_context: {
|
|
315
|
-
output_mode: runtime.outputMode,
|
|
316
|
-
profile: runtime.profileName,
|
|
317
|
-
cwd: process.cwd(),
|
|
318
|
-
agent_mode: runtime.agentMode,
|
|
319
|
-
cli_version: runtime.cliVersion,
|
|
320
|
-
},
|
|
321
|
-
},
|
|
433
|
+
body: payload,
|
|
322
434
|
});
|
|
323
435
|
}
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"src",
|
|
15
15
|
"template"
|
|
16
16
|
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"type-check": "tsc --noEmit"
|
|
19
|
+
},
|
|
17
20
|
"peerDependencies": {
|
|
18
21
|
"react": ">=18.0.0",
|
|
19
22
|
"react-dom": ">=18.0.0",
|
|
@@ -21,6 +24,9 @@
|
|
|
21
24
|
"@vitejs/plugin-react": ">=4.0.0"
|
|
22
25
|
},
|
|
23
26
|
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
|
+
"@types/react-dom": "^19.0.0",
|
|
24
30
|
"typescript": "^5.0.0"
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { DocumentRecord, QueryFilter } from '../runtime';
|
|
6
|
+
|
|
7
|
+
interface UseDatabaseOptions {
|
|
8
|
+
filter?: QueryFilter;
|
|
9
|
+
pageSize?: number;
|
|
10
|
+
offset?: number;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface UseDatabaseResult {
|
|
15
|
+
documents: DocumentRecord[];
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
refetch: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useDatabase(
|
|
22
|
+
databaseSlug: string,
|
|
23
|
+
options: UseDatabaseOptions = {},
|
|
24
|
+
): UseDatabaseResult {
|
|
25
|
+
const runtime = useNotisRuntime();
|
|
26
|
+
const [documents, setDocuments] = useState<DocumentRecord[]>([]);
|
|
27
|
+
const [loading, setLoading] = useState(true);
|
|
28
|
+
const [error, setError] = useState<Error | null>(null);
|
|
29
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
30
|
+
|
|
31
|
+
const enabled = options.enabled !== false;
|
|
32
|
+
const filterKey = JSON.stringify(options.filter ?? null);
|
|
33
|
+
|
|
34
|
+
const refetch = useCallback(() => {
|
|
35
|
+
setFetchKey((key) => key + 1);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!runtime || !enabled) {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let cancelled = false;
|
|
45
|
+
setLoading(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
|
|
48
|
+
runtime
|
|
49
|
+
.queryDatabase({
|
|
50
|
+
databaseSlug,
|
|
51
|
+
query: {
|
|
52
|
+
...options.filter,
|
|
53
|
+
page_size: options.pageSize,
|
|
54
|
+
},
|
|
55
|
+
offset: options.offset,
|
|
56
|
+
})
|
|
57
|
+
.then((result) => {
|
|
58
|
+
if (!cancelled) {
|
|
59
|
+
setDocuments(result.documents);
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
.catch((err) => {
|
|
64
|
+
if (!cancelled) {
|
|
65
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
cancelled = true;
|
|
72
|
+
};
|
|
73
|
+
}, [runtime, databaseSlug, fetchKey, enabled, filterKey, options.offset, options.pageSize]);
|
|
74
|
+
|
|
75
|
+
return { documents, loading, error, refetch };
|
|
76
|
+
}
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useNotisRuntime } from '../provider';
|
|
4
|
-
import type { AppDescriptor,
|
|
4
|
+
import type { AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
5
5
|
|
|
6
6
|
interface NotisContext {
|
|
7
7
|
/** App metadata (id, name, icon, description). Null before runtime loads. */
|
|
8
8
|
app: AppDescriptor | null;
|
|
9
9
|
/** Current route descriptor. Null before runtime loads. */
|
|
10
10
|
route: RouteDescriptor | null;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/** Databases declared by this app. Empty before runtime loads. */
|
|
12
|
+
databases: DatabaseDescriptor[];
|
|
13
|
+
/** Selected collection item for the current route, when applicable. */
|
|
14
|
+
collectionItem: CollectionItemDetail | null;
|
|
13
15
|
/** Whether the runtime is loaded and available. */
|
|
14
16
|
ready: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
* Access app-level metadata, the current route,
|
|
19
|
-
* Returns safe defaults when the portal runtime is
|
|
20
|
+
* Access app-level metadata, the current route, declared databases, and the
|
|
21
|
+
* selected collection item. Returns safe defaults when the portal runtime is
|
|
22
|
+
* not available.
|
|
20
23
|
*/
|
|
21
24
|
export function useNotis(): NotisContext {
|
|
22
25
|
const runtime = useNotisRuntime();
|
|
@@ -24,7 +27,8 @@ export function useNotis(): NotisContext {
|
|
|
24
27
|
return {
|
|
25
28
|
app: runtime?.app ?? null,
|
|
26
29
|
route: runtime?.route ?? null,
|
|
27
|
-
|
|
30
|
+
databases: runtime?.databases ?? [],
|
|
31
|
+
collectionItem: runtime?.context?.collectionItem ?? null,
|
|
28
32
|
ready: runtime !== null,
|
|
29
33
|
};
|
|
30
34
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { DocumentRecord } from '../runtime';
|
|
6
|
+
|
|
7
|
+
interface UpsertArgs {
|
|
8
|
+
databaseSlug: string;
|
|
9
|
+
documentId?: string;
|
|
10
|
+
title?: string;
|
|
11
|
+
properties?: Record<string, unknown>;
|
|
12
|
+
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface UseUpsertDocumentResult {
|
|
16
|
+
upsert: (args: UpsertArgs) => Promise<DocumentRecord>;
|
|
17
|
+
loading: boolean;
|
|
18
|
+
error: Error | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useUpsertDocument(): UseUpsertDocumentResult {
|
|
22
|
+
const runtime = useNotisRuntime();
|
|
23
|
+
const [loading, setLoading] = useState(false);
|
|
24
|
+
const [error, setError] = useState<Error | null>(null);
|
|
25
|
+
|
|
26
|
+
const upsert = useCallback(
|
|
27
|
+
async (args: UpsertArgs): Promise<DocumentRecord> => {
|
|
28
|
+
if (!runtime) {
|
|
29
|
+
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setLoading(true);
|
|
33
|
+
setError(null);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await runtime.upsertDocument(args);
|
|
37
|
+
return result.document;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
40
|
+
setError(e);
|
|
41
|
+
throw e;
|
|
42
|
+
} finally {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[runtime],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return { upsert, loading, error };
|
|
50
|
+
}
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Provider
|
|
10
|
-
export { NotisProvider } from './provider';
|
|
10
|
+
export { NotisProvider, useNotisRuntime } from './provider';
|
|
11
11
|
|
|
12
12
|
// Hooks
|
|
13
13
|
export { useNotis } from './hooks/useNotis';
|
|
14
|
+
export { useDatabase } from './hooks/useDatabase';
|
|
15
|
+
export { useUpsertDocument } from './hooks/useUpsertDocument';
|
|
14
16
|
export { useTool } from './hooks/useTool';
|
|
15
17
|
export type { ToolCallState, UseToolResult } from './hooks/useTool';
|
|
16
18
|
export { useTools } from './hooks/useTools';
|
|
@@ -38,8 +40,14 @@ export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOv
|
|
|
38
40
|
// Types (re-exported for convenience)
|
|
39
41
|
export type {
|
|
40
42
|
AppDescriptor,
|
|
43
|
+
CollectionItem,
|
|
44
|
+
CollectionItemDetail,
|
|
45
|
+
DatabaseDescriptor,
|
|
46
|
+
DatabaseProperty,
|
|
47
|
+
DocumentRecord,
|
|
41
48
|
NotisRuntime,
|
|
42
49
|
NotisRuntimeContext,
|
|
50
|
+
QueryFilter,
|
|
43
51
|
RouteDescriptor,
|
|
44
52
|
ToolDescriptor,
|
|
45
53
|
ToolInputSchema,
|
|
@@ -7,6 +7,39 @@
|
|
|
7
7
|
* (useTool, useNotis, etc.) which read from the NotisProvider context.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Database types
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export interface DatabaseProperty {
|
|
15
|
+
name: string;
|
|
16
|
+
type: 'title' | 'rich_text' | 'number' | 'checkbox' | 'date' | 'select' | 'multi_select' | 'status' | 'relation' | 'formula' | 'files';
|
|
17
|
+
description?: string;
|
|
18
|
+
options?: Array<{ name: string; id?: string }>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DatabaseDescriptor {
|
|
22
|
+
slug: string;
|
|
23
|
+
title: string;
|
|
24
|
+
description?: string | null;
|
|
25
|
+
icon?: string | null;
|
|
26
|
+
properties: DatabaseProperty[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DocumentRecord {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
properties: Record<string, unknown>;
|
|
33
|
+
icon?: string | null;
|
|
34
|
+
cover?: string | null;
|
|
35
|
+
databaseSlug?: string;
|
|
36
|
+
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
37
|
+
contentMarkdown?: string | null;
|
|
38
|
+
plainText?: string | null;
|
|
39
|
+
createdAt?: string | null;
|
|
40
|
+
lastEditedTime?: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
10
43
|
// ---------------------------------------------------------------------------
|
|
11
44
|
// Tool types
|
|
12
45
|
// ---------------------------------------------------------------------------
|
|
@@ -30,7 +63,25 @@ export interface RouteDescriptor {
|
|
|
30
63
|
icon?: string | null;
|
|
31
64
|
parentSlug?: string | null;
|
|
32
65
|
default?: boolean;
|
|
33
|
-
collection?:
|
|
66
|
+
collection?: {
|
|
67
|
+
database: string;
|
|
68
|
+
titleProperty: string;
|
|
69
|
+
parentProperty?: string | null;
|
|
70
|
+
sidebar?: {
|
|
71
|
+
mode: 'flat-list' | 'tree';
|
|
72
|
+
allowCreate: boolean;
|
|
73
|
+
} | null;
|
|
74
|
+
} | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CollectionItem {
|
|
78
|
+
id: string;
|
|
79
|
+
title: string;
|
|
80
|
+
icon?: string | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface CollectionItemDetail extends CollectionItem {
|
|
84
|
+
properties: Record<string, unknown>;
|
|
34
85
|
}
|
|
35
86
|
|
|
36
87
|
// ---------------------------------------------------------------------------
|
|
@@ -44,7 +95,22 @@ export interface AppDescriptor {
|
|
|
44
95
|
description?: string | null;
|
|
45
96
|
}
|
|
46
97
|
|
|
47
|
-
export
|
|
98
|
+
export interface QueryFilter {
|
|
99
|
+
filters?: Array<{
|
|
100
|
+
property: string;
|
|
101
|
+
operator: string;
|
|
102
|
+
value: unknown;
|
|
103
|
+
}>;
|
|
104
|
+
sorts?: Array<{
|
|
105
|
+
property: string;
|
|
106
|
+
direction: 'asc' | 'desc';
|
|
107
|
+
}>;
|
|
108
|
+
page_size?: number;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface NotisRuntimeContext {
|
|
112
|
+
collectionItem?: CollectionItemDetail | null;
|
|
113
|
+
}
|
|
48
114
|
|
|
49
115
|
// ---------------------------------------------------------------------------
|
|
50
116
|
// NotisRuntime
|
|
@@ -53,6 +119,7 @@ export type NotisRuntimeContext = Record<string, unknown>;
|
|
|
53
119
|
export interface NotisRuntime {
|
|
54
120
|
app: AppDescriptor;
|
|
55
121
|
route: RouteDescriptor;
|
|
122
|
+
databases: DatabaseDescriptor[];
|
|
56
123
|
context: NotisRuntimeContext;
|
|
57
124
|
|
|
58
125
|
navigate?: (payload: { kind: string; [key: string]: unknown }) => void;
|
|
@@ -72,6 +139,20 @@ export interface NotisRuntime {
|
|
|
72
139
|
listTools(): Promise<ToolDescriptor[]>;
|
|
73
140
|
callTool<TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult>;
|
|
74
141
|
|
|
142
|
+
queryDatabase(args: {
|
|
143
|
+
databaseSlug: string;
|
|
144
|
+
query?: QueryFilter;
|
|
145
|
+
offset?: number;
|
|
146
|
+
}): Promise<{ documents: DocumentRecord[] }>;
|
|
147
|
+
|
|
148
|
+
upsertDocument(args: {
|
|
149
|
+
databaseSlug: string;
|
|
150
|
+
documentId?: string;
|
|
151
|
+
title?: string;
|
|
152
|
+
properties?: Record<string, unknown>;
|
|
153
|
+
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
154
|
+
}): Promise<{ status: string; document: DocumentRecord }>;
|
|
155
|
+
|
|
75
156
|
request(path: string, options?: {
|
|
76
157
|
method?: string;
|
|
77
158
|
headers?: Record<string, string>;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|