@notis_ai/cli 0.2.0 → 0.2.2
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 +122 -158
- package/dist/scaffolds/notes/app/globals.css +3 -0
- package/dist/scaffolds/notes/app/layout.tsx +6 -0
- package/dist/scaffolds/notes/app/page.tsx +1465 -0
- package/dist/scaffolds/notes/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notes/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notes/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notes/components.json +20 -0
- package/dist/scaffolds/notes/lib/utils.ts +6 -0
- package/dist/scaffolds/notes/notis.config.ts +31 -0
- package/dist/scaffolds/notes/package.json +31 -0
- package/dist/scaffolds/notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notes/tailwind.config.ts +58 -0
- package/dist/scaffolds/notes/tsconfig.json +22 -0
- package/dist/scaffolds/notes/vite.config.ts +10 -0
- package/dist/scaffolds.json +13 -0
- package/package.json +12 -2
- package/skills/notis-apps/SKILL.md +162 -0
- package/skills/notis-apps/cli.md +208 -0
- package/skills/notis-cli/SKILL.md +258 -0
- package/skills/notis-query/cli.md +40 -0
- package/src/cli.js +1 -1
- package/src/command-specs/apps.js +1029 -59
- package/src/command-specs/helpers.js +6 -41
- package/src/command-specs/index.js +1 -7
- package/src/command-specs/meta.js +7 -6
- package/src/command-specs/tools.js +29 -34
- package/src/runtime/agent-browser.js +192 -0
- package/src/runtime/app-boundary-validator.js +132 -0
- package/src/runtime/app-dev-server.js +605 -0
- package/src/runtime/app-dev-sessions.js +87 -0
- package/src/runtime/app-platform.js +1037 -82
- package/src/runtime/cli-mode.generated.js +4 -0
- package/src/runtime/cli-mode.js +29 -0
- package/src/runtime/output.js +2 -2
- package/src/runtime/ports.js +15 -0
- package/src/runtime/profiles.js +36 -5
- package/src/runtime/transport.js +131 -6
- package/template/.harness/index.html.tmpl +211 -0
- package/template/app/globals.css +3 -0
- package/template/app/layout.tsx +6 -0
- package/template/app/page.tsx +90 -0
- package/template/components/ui/badge.tsx +28 -0
- package/template/components/ui/button.tsx +53 -0
- package/template/components/ui/card.tsx +56 -0
- package/template/components.json +20 -0
- package/template/lib/utils.ts +6 -0
- package/template/metadata/cover.png +0 -0
- package/template/metadata/screenshot-1.png +0 -0
- package/template/metadata/screenshot-2.png +0 -0
- package/template/metadata/screenshot-3.png +0 -0
- package/template/notis.config.ts +37 -0
- package/template/package.json +31 -0
- package/template/packages/sdk/package.json +32 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +273 -0
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/template/packages/sdk/src/config.ts +71 -0
- package/template/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/template/packages/sdk/src/hooks/useDatabase.ts +76 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +503 -0
- package/template/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/template/packages/sdk/src/hooks/useTool.ts +64 -0
- package/template/packages/sdk/src/hooks/useTools.ts +56 -0
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +50 -0
- package/template/packages/sdk/src/index.ts +54 -0
- package/template/packages/sdk/src/provider.tsx +43 -0
- package/template/packages/sdk/src/runtime.ts +161 -0
- package/template/packages/sdk/src/styles.css +38 -0
- package/template/packages/sdk/src/ui.ts +15 -0
- package/template/packages/sdk/src/vite.ts +56 -0
- package/template/packages/sdk/tsconfig.json +15 -0
- package/template/postcss.config.mjs +8 -0
- package/template/tailwind.config.ts +58 -0
- package/template/tsconfig.json +22 -0
- package/template/vite.config.ts +10 -0
- package/src/command-specs/auth.js +0 -178
- package/src/command-specs/db.js +0 -163
- package/src/runtime/app-preview-server.js +0 -272
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useNotisRuntime } from '../provider';
|
|
4
|
+
import type { AppDescriptor, CollectionItemDetail, DatabaseDescriptor, RouteDescriptor } from '../runtime';
|
|
5
|
+
|
|
6
|
+
interface NotisContext {
|
|
7
|
+
/** App metadata (id, name, icon, description). Null before runtime loads. */
|
|
8
|
+
app: AppDescriptor | null;
|
|
9
|
+
/** Current route descriptor. Null before runtime loads. */
|
|
10
|
+
route: RouteDescriptor | null;
|
|
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;
|
|
15
|
+
/** Whether the runtime is loaded and available. */
|
|
16
|
+
ready: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
export function useNotis(): NotisContext {
|
|
25
|
+
const runtime = useNotisRuntime();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
app: runtime?.app ?? null,
|
|
29
|
+
route: runtime?.route ?? null,
|
|
30
|
+
databases: runtime?.databases ?? [],
|
|
31
|
+
collectionItem: runtime?.context?.collectionItem ?? null,
|
|
32
|
+
ready: runtime !== null,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
interface NavigationActions {
|
|
7
|
+
/** Navigate to a route within the app by its path. */
|
|
8
|
+
toRoute: (path: string) => void;
|
|
9
|
+
/** Navigate to a document detail view. */
|
|
10
|
+
toDocument: (documentId: string, title?: string | null) => void;
|
|
11
|
+
/** Navigate to the app's default route. */
|
|
12
|
+
toApp: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Navigation helpers for moving between routes and documents within the app.
|
|
17
|
+
* When rendered inside the portal, uses the runtime.navigate() bridge.
|
|
18
|
+
* Without a portal runtime, falls back to window.location for route navigation.
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const nav = useNotisNavigation();
|
|
22
|
+
* nav.toRoute('/settings');
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function useNotisNavigation(): NavigationActions {
|
|
26
|
+
const runtime = useNotisRuntime();
|
|
27
|
+
|
|
28
|
+
const toRoute = useCallback((path: string) => {
|
|
29
|
+
if (runtime?.navigate) {
|
|
30
|
+
runtime.navigate({ kind: 'route', path });
|
|
31
|
+
} else if (typeof window !== 'undefined') {
|
|
32
|
+
window.location.href = path;
|
|
33
|
+
}
|
|
34
|
+
}, [runtime]);
|
|
35
|
+
|
|
36
|
+
const toDocument = useCallback((documentId: string, title?: string | null) => {
|
|
37
|
+
if (runtime?.navigate) {
|
|
38
|
+
runtime.navigate({ kind: 'document', documentId, title: title ?? undefined });
|
|
39
|
+
}
|
|
40
|
+
}, [runtime]);
|
|
41
|
+
|
|
42
|
+
const toApp = useCallback(() => {
|
|
43
|
+
if (runtime?.navigate) {
|
|
44
|
+
runtime.navigate({ kind: 'app' });
|
|
45
|
+
}
|
|
46
|
+
}, [runtime]);
|
|
47
|
+
|
|
48
|
+
return { toRoute, toDocument, toApp };
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
type ToolArguments = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
type ToolCall<TArgs extends ToolArguments | undefined, TResult> = undefined extends TArgs
|
|
9
|
+
? (args?: Exclude<TArgs, undefined>) => Promise<TResult>
|
|
10
|
+
: (args: TArgs) => Promise<TResult>;
|
|
11
|
+
|
|
12
|
+
export interface ToolCallState {
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseToolResult<
|
|
18
|
+
TArgs extends ToolArguments | undefined = ToolArguments | undefined,
|
|
19
|
+
TResult = unknown,
|
|
20
|
+
> extends ToolCallState {
|
|
21
|
+
call: ToolCall<TArgs, TResult>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Call a specific Notis tool by name.
|
|
26
|
+
*
|
|
27
|
+
* ```tsx
|
|
28
|
+
* const { call, loading } = useTool('notis-default-web_search');
|
|
29
|
+
* const result = await call({ query: 'latest news' });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function useTool<
|
|
33
|
+
TArgs extends ToolArguments | undefined = ToolArguments | undefined,
|
|
34
|
+
TResult = unknown,
|
|
35
|
+
>(toolName: string): UseToolResult<TArgs, TResult> {
|
|
36
|
+
const runtime = useNotisRuntime();
|
|
37
|
+
const [loading, setLoading] = useState(false);
|
|
38
|
+
const [error, setError] = useState<Error | null>(null);
|
|
39
|
+
|
|
40
|
+
const call = useCallback(
|
|
41
|
+
async (args?: ToolArguments): Promise<TResult> => {
|
|
42
|
+
if (!runtime) {
|
|
43
|
+
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setLoading(true);
|
|
47
|
+
setError(null);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await runtime.callTool<TResult>(toolName, args);
|
|
51
|
+
return result;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
setError(e);
|
|
55
|
+
throw e;
|
|
56
|
+
} finally {
|
|
57
|
+
setLoading(false);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
[runtime, toolName],
|
|
61
|
+
) as ToolCall<TArgs, TResult>;
|
|
62
|
+
|
|
63
|
+
return { call, loading, error };
|
|
64
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { ToolDescriptor } from '../runtime';
|
|
6
|
+
|
|
7
|
+
interface UseToolsResult {
|
|
8
|
+
tools: ToolDescriptor[];
|
|
9
|
+
loading: boolean;
|
|
10
|
+
error: Error | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* List all tools available to this app.
|
|
15
|
+
*
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const { tools, loading } = useTools();
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function useTools(): UseToolsResult {
|
|
21
|
+
const runtime = useNotisRuntime();
|
|
22
|
+
const [tools, setTools] = useState<ToolDescriptor[]>([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [error, setError] = useState<Error | null>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!runtime) {
|
|
28
|
+
setLoading(false);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
setLoading(true);
|
|
34
|
+
|
|
35
|
+
runtime
|
|
36
|
+
.listTools()
|
|
37
|
+
.then((result) => {
|
|
38
|
+
if (!cancelled) {
|
|
39
|
+
setTools(result);
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
.catch((err) => {
|
|
44
|
+
if (!cancelled) {
|
|
45
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
};
|
|
53
|
+
}, [runtime]);
|
|
54
|
+
|
|
55
|
+
return { tools, loading, error };
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
interface TopBarSearchOptions {
|
|
7
|
+
/** Current value shown in the top bar search input. */
|
|
8
|
+
value: string;
|
|
9
|
+
/** Called when the user types in the top bar. */
|
|
10
|
+
onChange: (value: string) => void;
|
|
11
|
+
/** Placeholder text for the input. Defaults to 'Search…'. */
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
/** Called when the user presses Enter. May return a Promise. */
|
|
14
|
+
onSubmit?: () => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TopBarSearchActions {
|
|
18
|
+
/** Toggle the loader that replaces the search icon in the top bar. */
|
|
19
|
+
setLoading: (loading: boolean) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Registers this view's search binding with the portal's top bar search input.
|
|
24
|
+
* While the component is mounted, typing in the top bar calls `onChange`,
|
|
25
|
+
* pressing Enter calls `onSubmit`, and the `value` prop drives the input.
|
|
26
|
+
*
|
|
27
|
+
* Call `setLoading(true)` to show the standard spinner in the top bar while a
|
|
28
|
+
* query is running; `setLoading(false)` restores the search icon.
|
|
29
|
+
*
|
|
30
|
+
* Without a portal runtime, this hook is a safe no-op.
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const [q, setQ] = useState('');
|
|
34
|
+
* const { setLoading } = useTopBarSearch({
|
|
35
|
+
* value: q,
|
|
36
|
+
* onChange: setQ,
|
|
37
|
+
* placeholder: 'Search notes…',
|
|
38
|
+
* onSubmit: async () => {
|
|
39
|
+
* setLoading(true);
|
|
40
|
+
* try { await runSearch(q); } finally { setLoading(false); }
|
|
41
|
+
* },
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Stabilize `onChange` and `onSubmit` with `useCallback` to avoid re-registering
|
|
46
|
+
* on every render.
|
|
47
|
+
*/
|
|
48
|
+
export function useTopBarSearch(opts: TopBarSearchOptions): TopBarSearchActions {
|
|
49
|
+
const runtime = useNotisRuntime();
|
|
50
|
+
const { value, onChange, placeholder, onSubmit } = opts;
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const register = runtime?.registerTopBarSearch;
|
|
54
|
+
if (!register) return;
|
|
55
|
+
register({ onChange, placeholder, onSubmit });
|
|
56
|
+
return () => {
|
|
57
|
+
register(null);
|
|
58
|
+
};
|
|
59
|
+
}, [runtime, onChange, placeholder, onSubmit]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
runtime?.setTopBarSearchValue?.(value);
|
|
63
|
+
}, [runtime, value]);
|
|
64
|
+
|
|
65
|
+
const setLoading = useCallback(
|
|
66
|
+
(loading: boolean) => {
|
|
67
|
+
runtime?.setTopBarSearchLoading?.(loading);
|
|
68
|
+
},
|
|
69
|
+
[runtime],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return { setLoading };
|
|
73
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @notis/sdk - The Notis App SDK
|
|
3
|
+
*
|
|
4
|
+
* Public API for building Notis apps. Import hooks and the provider from
|
|
5
|
+
* this entrypoint. For configuration, use `@notis/sdk/config`. For the
|
|
6
|
+
* Vite config builder, use `@notis/sdk/vite`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Provider
|
|
10
|
+
export { NotisProvider, useNotisRuntime } from './provider';
|
|
11
|
+
|
|
12
|
+
// Hooks
|
|
13
|
+
export { useNotis } from './hooks/useNotis';
|
|
14
|
+
export { useDatabase } from './hooks/useDatabase';
|
|
15
|
+
export { useUpsertDocument } from './hooks/useUpsertDocument';
|
|
16
|
+
export { useTool } from './hooks/useTool';
|
|
17
|
+
export type { ToolCallState, UseToolResult } from './hooks/useTool';
|
|
18
|
+
export { useTools } from './hooks/useTools';
|
|
19
|
+
export { useNotisNavigation } from './hooks/useNotisNavigation';
|
|
20
|
+
export { useTopBarSearch } from './hooks/useTopBarSearch';
|
|
21
|
+
export { useBackend } from './hooks/useBackend';
|
|
22
|
+
export { useMultiSelect } from './hooks/useMultiSelect';
|
|
23
|
+
|
|
24
|
+
// Multi-select components
|
|
25
|
+
export { MultiSelectActionBar } from './components/MultiSelectActionBar';
|
|
26
|
+
export { MultiSelectCheckbox } from './components/MultiSelectCheckbox';
|
|
27
|
+
export { MultiSelectDragOverlay } from './components/MultiSelectDragOverlay';
|
|
28
|
+
export type {
|
|
29
|
+
DragRect,
|
|
30
|
+
MultiSelectController,
|
|
31
|
+
UseMultiSelectOptions,
|
|
32
|
+
} from './hooks/useMultiSelect';
|
|
33
|
+
export type {
|
|
34
|
+
MultiSelectAction,
|
|
35
|
+
MultiSelectActionBarProps,
|
|
36
|
+
} from './components/MultiSelectActionBar';
|
|
37
|
+
export type { MultiSelectCheckboxProps } from './components/MultiSelectCheckbox';
|
|
38
|
+
export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOverlay';
|
|
39
|
+
|
|
40
|
+
// Types (re-exported for convenience)
|
|
41
|
+
export type {
|
|
42
|
+
AppDescriptor,
|
|
43
|
+
CollectionItem,
|
|
44
|
+
CollectionItemDetail,
|
|
45
|
+
DatabaseDescriptor,
|
|
46
|
+
DatabaseProperty,
|
|
47
|
+
DocumentRecord,
|
|
48
|
+
NotisRuntime,
|
|
49
|
+
NotisRuntimeContext,
|
|
50
|
+
QueryFilter,
|
|
51
|
+
RouteDescriptor,
|
|
52
|
+
ToolDescriptor,
|
|
53
|
+
ToolInputSchema,
|
|
54
|
+
} from './runtime';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, type Context, type ReactNode } from 'react';
|
|
4
|
+
import type { NotisRuntime } from './runtime';
|
|
5
|
+
|
|
6
|
+
const NOTIS_CONTEXT_SYMBOL = Symbol.for('notis.sdk.runtime_context');
|
|
7
|
+
|
|
8
|
+
type NotisContextGlobal = typeof globalThis & {
|
|
9
|
+
[NOTIS_CONTEXT_SYMBOL]?: Context<NotisRuntime | null>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function getNotisContext(): Context<NotisRuntime | null> {
|
|
13
|
+
const scope = globalThis as NotisContextGlobal;
|
|
14
|
+
if (!scope[NOTIS_CONTEXT_SYMBOL]) {
|
|
15
|
+
scope[NOTIS_CONTEXT_SYMBOL] = createContext<NotisRuntime | null>(null);
|
|
16
|
+
}
|
|
17
|
+
return scope[NOTIS_CONTEXT_SYMBOL]!;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const NotisContext = getNotisContext();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Provides the Notis runtime context to all child components. The portal owns
|
|
24
|
+
* the runtime and injects it into the rendered app tree.
|
|
25
|
+
*/
|
|
26
|
+
export function NotisProvider({ children, runtime }: { children: ReactNode; runtime: NotisRuntime | null }) {
|
|
27
|
+
if (runtime === undefined) {
|
|
28
|
+
throw new Error('NotisProvider requires an explicit runtime prop.');
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
<NotisContext.Provider value={runtime}>
|
|
32
|
+
{children}
|
|
33
|
+
</NotisContext.Provider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns the raw NotisRuntime or null if not yet available.
|
|
39
|
+
* Prefer the typed hooks (useTool, useNotis, etc.) over this.
|
|
40
|
+
*/
|
|
41
|
+
export function useNotisRuntime(): NotisRuntime | null {
|
|
42
|
+
return useContext(NotisContext);
|
|
43
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotisRuntime is the bridge between app code running in the browser and the
|
|
3
|
+
* Notis platform. The portal owns the runtime and injects it through
|
|
4
|
+
* `NotisProvider` when the app is rendered.
|
|
5
|
+
*
|
|
6
|
+
* App code should never reach for globals. Use the hooks from `@notis/sdk`
|
|
7
|
+
* (useTool, useNotis, etc.) which read from the NotisProvider context.
|
|
8
|
+
*/
|
|
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
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Tool types
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export type ToolInputSchema = Record<string, unknown>;
|
|
48
|
+
|
|
49
|
+
export interface ToolDescriptor {
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
inputSchema?: ToolInputSchema;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Route types
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
export interface RouteDescriptor {
|
|
60
|
+
slug: string;
|
|
61
|
+
path: string;
|
|
62
|
+
name: string;
|
|
63
|
+
icon?: string | null;
|
|
64
|
+
parentSlug?: string | null;
|
|
65
|
+
default?: boolean;
|
|
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>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// App descriptor
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
export interface AppDescriptor {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
icon?: string | null;
|
|
95
|
+
description?: string | null;
|
|
96
|
+
}
|
|
97
|
+
|
|
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
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// NotisRuntime
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
export interface NotisRuntime {
|
|
120
|
+
app: AppDescriptor;
|
|
121
|
+
route: RouteDescriptor;
|
|
122
|
+
databases: DatabaseDescriptor[];
|
|
123
|
+
context: NotisRuntimeContext;
|
|
124
|
+
|
|
125
|
+
navigate?: (payload: { kind: string; [key: string]: unknown }) => void;
|
|
126
|
+
|
|
127
|
+
registerTopBarSearch?: (
|
|
128
|
+
config:
|
|
129
|
+
| {
|
|
130
|
+
onChange: (value: string) => void;
|
|
131
|
+
placeholder?: string;
|
|
132
|
+
onSubmit?: () => void | Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
| null,
|
|
135
|
+
) => void;
|
|
136
|
+
setTopBarSearchValue?: (value: string) => void;
|
|
137
|
+
setTopBarSearchLoading?: (loading: boolean) => void;
|
|
138
|
+
|
|
139
|
+
listTools(): Promise<ToolDescriptor[]>;
|
|
140
|
+
callTool<TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult>;
|
|
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
|
+
|
|
156
|
+
request(path: string, options?: {
|
|
157
|
+
method?: string;
|
|
158
|
+
headers?: Record<string, string>;
|
|
159
|
+
body?: unknown;
|
|
160
|
+
}): Promise<unknown>;
|
|
161
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notis theme CSS variables.
|
|
3
|
+
*
|
|
4
|
+
* Import this in your app layout:
|
|
5
|
+
*
|
|
6
|
+
* import '@notis/sdk/styles.css';
|
|
7
|
+
*
|
|
8
|
+
* The portal injects the live Notis theme variables at render time.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
@tailwind base;
|
|
12
|
+
@tailwind components;
|
|
13
|
+
@tailwind utilities;
|
|
14
|
+
|
|
15
|
+
@layer base {
|
|
16
|
+
* {
|
|
17
|
+
@apply border-border;
|
|
18
|
+
}
|
|
19
|
+
[data-notis-app-root] {
|
|
20
|
+
color: hsl(var(--foreground));
|
|
21
|
+
display: block;
|
|
22
|
+
min-height: 100%;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@layer components {
|
|
27
|
+
.notis-app-shell {
|
|
28
|
+
@apply mx-auto w-full max-w-[var(--portal-page-max-width)] px-4 py-6 md:px-4 md:py-8;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.notis-app-surface {
|
|
32
|
+
@apply rounded-2xl border border-border bg-card text-card-foreground shadow-sm;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.notis-app-section {
|
|
36
|
+
@apply p-5 md:p-6;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @notis/sdk/ui - Re-exported shadcn components for Notis apps.
|
|
3
|
+
*
|
|
4
|
+
* Apps include shadcn via their own `components.json` and `@/components/ui/*`
|
|
5
|
+
* imports. This entrypoint is provided as a convenience for apps that want to
|
|
6
|
+
* import directly from the SDK without setting up shadcn locally.
|
|
7
|
+
*
|
|
8
|
+
* For now, this is a placeholder. The scaffold template sets up shadcn
|
|
9
|
+
* directly in the app project, so components come from `@/components/ui/*`.
|
|
10
|
+
* This file can be expanded later to re-export a curated set of components
|
|
11
|
+
* pre-themed for Notis.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Placeholder -- apps use shadcn directly via their project's components/ui/
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite config builder for Notis apps.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```ts
|
|
6
|
+
* // vite.config.ts
|
|
7
|
+
* import { notisViteConfig } from '@notis/sdk/vite';
|
|
8
|
+
* import appConfig from './notis.config';
|
|
9
|
+
* export default notisViteConfig(appConfig);
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* Produces a library-mode ES module bundle with React externalized.
|
|
13
|
+
* Output: .notis/output/bundle/app.js + app.css
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { NotisAppConfig } from './config';
|
|
17
|
+
|
|
18
|
+
export function notisViteConfig(appConfig: NotisAppConfig) {
|
|
19
|
+
return {
|
|
20
|
+
plugins: [
|
|
21
|
+
// @vitejs/plugin-react is added by the consumer's vite.config.ts
|
|
22
|
+
// or auto-detected. We provide the config shape only.
|
|
23
|
+
],
|
|
24
|
+
build: {
|
|
25
|
+
lib: {
|
|
26
|
+
entry: '.notis/_entry.tsx',
|
|
27
|
+
formats: ['es'] as const,
|
|
28
|
+
fileName: () => 'app.js',
|
|
29
|
+
},
|
|
30
|
+
rollupOptions: {
|
|
31
|
+
external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime', 'react/jsx-dev-runtime'],
|
|
32
|
+
output: {
|
|
33
|
+
globals: {
|
|
34
|
+
react: 'window.React',
|
|
35
|
+
'react-dom': 'window.ReactDOM',
|
|
36
|
+
'react-dom/client': 'window.ReactDOMClient',
|
|
37
|
+
'react/jsx-runtime': 'window.React',
|
|
38
|
+
},
|
|
39
|
+
assetFileNames: 'app[extname]',
|
|
40
|
+
inlineDynamicImports: true,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
outDir: '.notis/output/bundle',
|
|
44
|
+
emptyOutDir: true,
|
|
45
|
+
cssCodeSplit: false,
|
|
46
|
+
},
|
|
47
|
+
resolve: {
|
|
48
|
+
alias: {
|
|
49
|
+
'@': process.cwd(),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
define: {
|
|
53
|
+
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|