@notis_ai/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -170
- package/config/notis_app_boundary_rules.json +50 -0
- package/dist/scaffolds/notis-database/app/globals.css +44 -0
- package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
- package/dist/scaffolds/notis-database/components.json +20 -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/lib/utils.ts +6 -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/postcss.config.mjs +8 -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/notis-database/tsconfig.json +23 -0
- package/dist/scaffolds/notis-database/vite.config.ts +22 -0
- package/dist/scaffolds/notis-notes/app/globals.css +3 -0
- package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +2168 -0
- 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/notis-notes/notis.config.ts +36 -0
- package/dist/scaffolds/notis-notes/package.json +31 -0
- package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
- package/dist/scaffolds/notis-notes/vite.config.ts +10 -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 +36 -0
- package/package.json +8 -3
- package/skills/notis-apps/SKILL.md +162 -0
- package/skills/notis-apps/cli.md +208 -0
- package/skills/notis-cli/SKILL.md +260 -0
- package/skills/notis-query/cli.md +39 -0
- package/src/cli.js +31 -3
- package/src/command-specs/apps.js +248 -56
- package/src/command-specs/helpers.js +72 -104
- package/src/command-specs/index.js +0 -6
- package/src/command-specs/meta.js +14 -13
- package/src/command-specs/tools.js +196 -115
- package/src/runtime/app-boundary-validator.js +65 -14
- package/src/runtime/app-dev-server.js +32 -4
- package/src/runtime/app-platform.js +404 -24
- package/src/runtime/profiles.js +12 -6
- package/src/runtime/transport.js +124 -39
- package/template/.harness/index.html.tmpl +1 -50
- package/template/app/page.tsx +41 -6
- 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 +10 -6
- package/template/package.json +2 -2
- package/template/packages/{notis-sdk → sdk}/package.json +6 -0
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectActionBar.tsx +2 -1
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectCheckbox.tsx +2 -2
- package/template/packages/{notis-sdk → sdk}/src/components/MultiSelectDragOverlay.tsx +2 -2
- package/template/packages/{notis-sdk → sdk}/src/config.ts +1 -1
- package/template/packages/{notis-sdk → sdk}/src/hooks/useDatabase.ts +1 -13
- package/template/packages/{notis-sdk → sdk}/src/hooks/useMultiSelect.ts +4 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +4 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +3 -3
- package/template/packages/{notis-sdk → sdk}/src/hooks/useTool.ts +22 -7
- package/template/packages/{notis-sdk → sdk}/src/hooks/useUpsertDocument.ts +0 -8
- package/template/packages/{notis-sdk → sdk}/src/index.ts +2 -15
- package/template/packages/{notis-sdk → sdk}/src/provider.tsx +1 -1
- package/template/packages/{notis-sdk → sdk}/src/runtime.ts +5 -26
- package/src/command-specs/auth.js +0 -178
- package/src/command-specs/db.js +0 -163
- package/template/packages/notis-sdk/src/helpers.ts +0 -131
- package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
- package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
- package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/hooks/useTopBarSearch.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/styles.css +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/src/vite.ts +0 -0
- /package/template/packages/{notis-sdk → sdk}/tsconfig.json +0 -0
|
@@ -3,27 +3,42 @@
|
|
|
3
3
|
import { useCallback, useState } from 'react';
|
|
4
4
|
import { useNotisRuntime } from '../provider';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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 {
|
|
8
13
|
loading: boolean;
|
|
9
14
|
error: Error | null;
|
|
10
15
|
}
|
|
11
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
|
+
|
|
12
24
|
/**
|
|
13
25
|
* Call a specific Notis tool by name.
|
|
14
26
|
*
|
|
15
27
|
* ```tsx
|
|
16
|
-
* const { call, loading } = useTool('
|
|
28
|
+
* const { call, loading } = useTool('LOCAL_NOTIS_WEB_SEARCH');
|
|
17
29
|
* const result = await call({ query: 'latest news' });
|
|
18
30
|
* ```
|
|
19
31
|
*/
|
|
20
|
-
export function useTool
|
|
32
|
+
export function useTool<
|
|
33
|
+
TArgs extends ToolArguments | undefined = ToolArguments | undefined,
|
|
34
|
+
TResult = unknown,
|
|
35
|
+
>(toolName: string): UseToolResult<TArgs, TResult> {
|
|
21
36
|
const runtime = useNotisRuntime();
|
|
22
37
|
const [loading, setLoading] = useState(false);
|
|
23
38
|
const [error, setError] = useState<Error | null>(null);
|
|
24
39
|
|
|
25
40
|
const call = useCallback(
|
|
26
|
-
async (args?:
|
|
41
|
+
async (args?: ToolArguments): Promise<TResult> => {
|
|
27
42
|
if (!runtime) {
|
|
28
43
|
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
29
44
|
}
|
|
@@ -32,7 +47,7 @@ export function useTool(toolName: string): UseToolResult {
|
|
|
32
47
|
setError(null);
|
|
33
48
|
|
|
34
49
|
try {
|
|
35
|
-
const result = await runtime.callTool(toolName, args);
|
|
50
|
+
const result = await runtime.callTool<TResult>(toolName, args);
|
|
36
51
|
return result;
|
|
37
52
|
} catch (err) {
|
|
38
53
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
@@ -43,7 +58,7 @@ export function useTool(toolName: string): UseToolResult {
|
|
|
43
58
|
}
|
|
44
59
|
},
|
|
45
60
|
[runtime, toolName],
|
|
46
|
-
)
|
|
61
|
+
) as ToolCall<TArgs, TResult>;
|
|
47
62
|
|
|
48
63
|
return { call, loading, error };
|
|
49
64
|
}
|
|
@@ -18,14 +18,6 @@ interface UseUpsertDocumentResult {
|
|
|
18
18
|
error: Error | null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
/**
|
|
22
|
-
* Create or update a document in a Notis database.
|
|
23
|
-
*
|
|
24
|
-
* ```tsx
|
|
25
|
-
* const { upsert, loading } = useUpsertDocument();
|
|
26
|
-
* await upsert({ databaseSlug: 'tasks', title: 'New task', properties: { status: 'Open' } });
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
21
|
export function useUpsertDocument(): UseUpsertDocumentResult {
|
|
30
22
|
const runtime = useNotisRuntime();
|
|
31
23
|
const [loading, setLoading] = useState(false);
|
|
@@ -12,15 +12,13 @@ export { NotisProvider, useNotisRuntime } from './provider';
|
|
|
12
12
|
// Hooks
|
|
13
13
|
export { useNotis } from './hooks/useNotis';
|
|
14
14
|
export { useDatabase } from './hooks/useDatabase';
|
|
15
|
-
export { useDocument } from './hooks/useDocument';
|
|
16
15
|
export { useUpsertDocument } from './hooks/useUpsertDocument';
|
|
17
16
|
export { useTool } from './hooks/useTool';
|
|
17
|
+
export type { ToolCallState, UseToolResult } from './hooks/useTool';
|
|
18
18
|
export { useTools } from './hooks/useTools';
|
|
19
|
-
export { useCollectionItems } from './hooks/useCollectionItem';
|
|
20
19
|
export { useNotisNavigation } from './hooks/useNotisNavigation';
|
|
21
20
|
export { useTopBarSearch } from './hooks/useTopBarSearch';
|
|
22
21
|
export { useBackend } from './hooks/useBackend';
|
|
23
|
-
export { useAppState } from './hooks/useAppState';
|
|
24
22
|
export { useMultiSelect } from './hooks/useMultiSelect';
|
|
25
23
|
|
|
26
24
|
// Multi-select components
|
|
@@ -39,18 +37,6 @@ export type {
|
|
|
39
37
|
export type { MultiSelectCheckboxProps } from './components/MultiSelectCheckbox';
|
|
40
38
|
export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOverlay';
|
|
41
39
|
|
|
42
|
-
// Property extraction helpers
|
|
43
|
-
export {
|
|
44
|
-
extractText,
|
|
45
|
-
extractSelect,
|
|
46
|
-
extractDate,
|
|
47
|
-
extractNumber,
|
|
48
|
-
extractCheckbox,
|
|
49
|
-
extractMultiSelect,
|
|
50
|
-
extractRelation,
|
|
51
|
-
docTitle,
|
|
52
|
-
} from './helpers';
|
|
53
|
-
|
|
54
40
|
// Types (re-exported for convenience)
|
|
55
41
|
export type {
|
|
56
42
|
AppDescriptor,
|
|
@@ -64,4 +50,5 @@ export type {
|
|
|
64
50
|
QueryFilter,
|
|
65
51
|
RouteDescriptor,
|
|
66
52
|
ToolDescriptor,
|
|
53
|
+
ToolInputSchema,
|
|
67
54
|
} from './runtime';
|
|
@@ -36,7 +36,7 @@ export function NotisProvider({ children, runtime }: { children: ReactNode; runt
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Returns the raw NotisRuntime or null if not yet available.
|
|
39
|
-
* Prefer the typed hooks (
|
|
39
|
+
* Prefer the typed hooks (useTool, useNotis, etc.) over this.
|
|
40
40
|
*/
|
|
41
41
|
export function useNotisRuntime(): NotisRuntime | null {
|
|
42
42
|
return useContext(NotisContext);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `NotisProvider` when the app is rendered.
|
|
5
5
|
*
|
|
6
6
|
* App code should never reach for globals. Use the hooks from `@notis/sdk`
|
|
7
|
-
* (
|
|
7
|
+
* (useTool, useNotis, etc.) which read from the NotisProvider context.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
@@ -44,10 +44,12 @@ export interface DocumentRecord {
|
|
|
44
44
|
// Tool types
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
|
|
47
|
+
export type ToolInputSchema = Record<string, unknown>;
|
|
48
|
+
|
|
47
49
|
export interface ToolDescriptor {
|
|
48
50
|
name: string;
|
|
49
51
|
description?: string;
|
|
50
|
-
|
|
52
|
+
inputSchema?: ToolInputSchema;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
// ---------------------------------------------------------------------------
|
|
@@ -82,11 +84,6 @@ export interface CollectionItemDetail extends CollectionItem {
|
|
|
82
84
|
properties: Record<string, unknown>;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
export interface CollectionTreeItem extends CollectionItem {
|
|
86
|
-
parent_id: string | null;
|
|
87
|
-
has_children: boolean;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
87
|
// ---------------------------------------------------------------------------
|
|
91
88
|
// App descriptor
|
|
92
89
|
// ---------------------------------------------------------------------------
|
|
@@ -98,10 +95,6 @@ export interface AppDescriptor {
|
|
|
98
95
|
description?: string | null;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Query types
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
|
|
105
98
|
export interface QueryFilter {
|
|
106
99
|
filters?: Array<{
|
|
107
100
|
property: string;
|
|
@@ -144,7 +137,7 @@ export interface NotisRuntime {
|
|
|
144
137
|
setTopBarSearchLoading?: (loading: boolean) => void;
|
|
145
138
|
|
|
146
139
|
listTools(): Promise<ToolDescriptor[]>;
|
|
147
|
-
callTool(name: string, args?: Record<string, unknown>): Promise<
|
|
140
|
+
callTool<TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult>;
|
|
148
141
|
|
|
149
142
|
queryDatabase(args: {
|
|
150
143
|
databaseSlug: string;
|
|
@@ -152,10 +145,6 @@ export interface NotisRuntime {
|
|
|
152
145
|
offset?: number;
|
|
153
146
|
}): Promise<{ documents: DocumentRecord[] }>;
|
|
154
147
|
|
|
155
|
-
getDocument(args: {
|
|
156
|
-
documentId: string;
|
|
157
|
-
}): Promise<DocumentRecord>;
|
|
158
|
-
|
|
159
148
|
upsertDocument(args: {
|
|
160
149
|
databaseSlug: string;
|
|
161
150
|
documentId?: string;
|
|
@@ -164,16 +153,6 @@ export interface NotisRuntime {
|
|
|
164
153
|
contentBlocknote?: Array<Record<string, unknown>> | null;
|
|
165
154
|
}): Promise<{ status: string; document: DocumentRecord }>;
|
|
166
155
|
|
|
167
|
-
listCollectionItems(args?: {
|
|
168
|
-
databaseSlug?: string;
|
|
169
|
-
pageSize?: number;
|
|
170
|
-
}): Promise<{ items: CollectionItem[] }>;
|
|
171
|
-
|
|
172
|
-
listCollectionTree?(args?: {
|
|
173
|
-
databaseSlug?: string;
|
|
174
|
-
pageSize?: number;
|
|
175
|
-
}): Promise<{ items: CollectionTreeItem[] }>;
|
|
176
|
-
|
|
177
156
|
request(path: string, options?: {
|
|
178
157
|
method?: string;
|
|
179
158
|
headers?: Record<string, string>;
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { CliError, EXIT_CODES } from '../runtime/errors.js';
|
|
2
|
-
import {
|
|
3
|
-
clearStoredJwt,
|
|
4
|
-
probeAuth,
|
|
5
|
-
promptForJwt,
|
|
6
|
-
updateStoredProfile,
|
|
7
|
-
} from './helpers.js';
|
|
8
|
-
|
|
9
|
-
async function authLoginHandler(ctx) {
|
|
10
|
-
let jwt = ctx.options.jwt || process.env.NOTIS_JWT;
|
|
11
|
-
if (!jwt) {
|
|
12
|
-
if (ctx.runtime.nonInteractive) {
|
|
13
|
-
throw new CliError({
|
|
14
|
-
code: 'auth_missing',
|
|
15
|
-
message: `No JWT configured for profile ${ctx.runtime.profileName}`,
|
|
16
|
-
exitCode: EXIT_CODES.auth,
|
|
17
|
-
hints: [
|
|
18
|
-
{
|
|
19
|
-
command: 'notis auth login --jwt <token>',
|
|
20
|
-
reason: 'Provide a token explicitly in non-interactive mode',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
jwt = await promptForJwt();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
updateStoredProfile({
|
|
29
|
-
profileName: ctx.runtime.profileName,
|
|
30
|
-
jwt,
|
|
31
|
-
apiBase: ctx.globalOptions.apiBase,
|
|
32
|
-
setCurrent: true,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
return ctx.output.emitSuccess({
|
|
36
|
-
command: ctx.spec.command_path.join(' '),
|
|
37
|
-
data: {
|
|
38
|
-
profile: ctx.runtime.profileName,
|
|
39
|
-
api_base: ctx.globalOptions.apiBase || ctx.runtime.apiBase,
|
|
40
|
-
},
|
|
41
|
-
humanSummary: `Stored credentials for profile ${ctx.runtime.profileName}`,
|
|
42
|
-
hints: [
|
|
43
|
-
{ command: 'notis whoami', reason: 'Verify your identity and available toolkits' },
|
|
44
|
-
],
|
|
45
|
-
meta: { mutating: true },
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function authLogoutHandler(ctx) {
|
|
50
|
-
clearStoredJwt(ctx.runtime.profileName);
|
|
51
|
-
return ctx.output.emitSuccess({
|
|
52
|
-
command: ctx.spec.command_path.join(' '),
|
|
53
|
-
data: { profile: ctx.runtime.profileName },
|
|
54
|
-
humanSummary: `Removed stored JWT for profile ${ctx.runtime.profileName}`,
|
|
55
|
-
meta: { mutating: true },
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function authStatusHandler(ctx) {
|
|
60
|
-
const hasJwt = Boolean(ctx.runtime.jwt);
|
|
61
|
-
let verified = false;
|
|
62
|
-
let toolkits = [];
|
|
63
|
-
if (hasJwt && ctx.options.verify) {
|
|
64
|
-
const payload = await probeAuth(ctx.runtime);
|
|
65
|
-
toolkits = payload.toolkits || [];
|
|
66
|
-
verified = true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return ctx.output.emitSuccess({
|
|
70
|
-
command: ctx.spec.command_path.join(' '),
|
|
71
|
-
data: {
|
|
72
|
-
profile: ctx.runtime.profileName,
|
|
73
|
-
api_base: ctx.runtime.apiBase,
|
|
74
|
-
has_jwt: hasJwt,
|
|
75
|
-
verified,
|
|
76
|
-
toolkits,
|
|
77
|
-
},
|
|
78
|
-
humanSummary: hasJwt
|
|
79
|
-
? `Profile ${ctx.runtime.profileName} is configured`
|
|
80
|
-
: `Profile ${ctx.runtime.profileName} is missing a JWT`,
|
|
81
|
-
hints: hasJwt
|
|
82
|
-
? []
|
|
83
|
-
: [{ command: 'notis auth login --jwt <token>', reason: 'Configure credentials for this profile' }],
|
|
84
|
-
meta: { mutating: false },
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export const authCommandSpecs = [
|
|
89
|
-
{
|
|
90
|
-
command_path: ['auth', 'login'],
|
|
91
|
-
summary: 'Store credentials for a named CLI profile.',
|
|
92
|
-
when_to_use: 'Use this before authenticated commands, especially in fresh environments or CI profiles.',
|
|
93
|
-
args_schema: {
|
|
94
|
-
arguments: [],
|
|
95
|
-
options: [
|
|
96
|
-
{ flags: '--jwt <token>', description: 'JWT token to store for the profile.' },
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
examples: ['notis auth login --jwt <token>', 'notis auth login --profile staging --api-base http://localhost:3001'],
|
|
100
|
-
output_schema: 'Returns the active profile name and API base.',
|
|
101
|
-
mutates: true,
|
|
102
|
-
idempotent: true,
|
|
103
|
-
require_auth: false,
|
|
104
|
-
related_commands: ['notis auth status --verify', 'notis auth logout'],
|
|
105
|
-
backend_call: { type: 'local' },
|
|
106
|
-
handler: authLoginHandler,
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
command_path: ['auth', 'logout'],
|
|
110
|
-
summary: 'Remove the stored JWT for the active profile.',
|
|
111
|
-
when_to_use: 'Use this to clear local credentials without touching other profiles.',
|
|
112
|
-
args_schema: { arguments: [], options: [] },
|
|
113
|
-
examples: ['notis auth logout', 'notis auth logout --profile staging'],
|
|
114
|
-
output_schema: 'Returns the profile that was updated.',
|
|
115
|
-
mutates: true,
|
|
116
|
-
idempotent: true,
|
|
117
|
-
require_auth: false,
|
|
118
|
-
related_commands: ['notis auth login --jwt <token>', 'notis auth status'],
|
|
119
|
-
backend_call: { type: 'local' },
|
|
120
|
-
handler: authLogoutHandler,
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
command_path: ['auth', 'status'],
|
|
124
|
-
summary: 'Inspect local auth configuration and optionally verify it against the API.',
|
|
125
|
-
when_to_use: 'Use this before automating commands to confirm the active profile and token health.',
|
|
126
|
-
args_schema: {
|
|
127
|
-
arguments: [],
|
|
128
|
-
options: [
|
|
129
|
-
{ flags: '--verify', description: 'Perform a live authenticated roundtrip to the API.' },
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
examples: ['notis auth status', 'notis auth status --verify --json'],
|
|
133
|
-
output_schema: 'Returns profile, api_base, local auth presence, and optional verification data.',
|
|
134
|
-
mutates: false,
|
|
135
|
-
idempotent: true,
|
|
136
|
-
require_auth: false,
|
|
137
|
-
related_commands: ['notis doctor', 'notis auth login --jwt <token>'],
|
|
138
|
-
backend_call: { type: 'notis_find_toolkits' },
|
|
139
|
-
handler: authStatusHandler,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
command_path: ['login'],
|
|
143
|
-
display_name: 'login',
|
|
144
|
-
summary: 'Deprecated alias for `notis auth login`.',
|
|
145
|
-
when_to_use: 'Use only for backward compatibility with older scripts.',
|
|
146
|
-
args_schema: {
|
|
147
|
-
arguments: [],
|
|
148
|
-
options: [
|
|
149
|
-
{ flags: '--jwt <token>', description: 'JWT token to store for the profile.' },
|
|
150
|
-
],
|
|
151
|
-
},
|
|
152
|
-
examples: ['notis login --jwt <token>'],
|
|
153
|
-
output_schema: 'Same output as `notis auth login`.',
|
|
154
|
-
mutates: true,
|
|
155
|
-
idempotent: true,
|
|
156
|
-
require_auth: false,
|
|
157
|
-
related_commands: ['notis auth login --jwt <token>'],
|
|
158
|
-
backend_call: { type: 'local', alias_for: 'auth login' },
|
|
159
|
-
deprecated_alias_for: 'notis auth login',
|
|
160
|
-
handler: authLoginHandler,
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
command_path: ['logout'],
|
|
164
|
-
display_name: 'logout',
|
|
165
|
-
summary: 'Deprecated alias for `notis auth logout`.',
|
|
166
|
-
when_to_use: 'Use only for backward compatibility with older scripts.',
|
|
167
|
-
args_schema: { arguments: [], options: [] },
|
|
168
|
-
examples: ['notis logout'],
|
|
169
|
-
output_schema: 'Same output as `notis auth logout`.',
|
|
170
|
-
mutates: true,
|
|
171
|
-
idempotent: true,
|
|
172
|
-
require_auth: false,
|
|
173
|
-
related_commands: ['notis auth logout'],
|
|
174
|
-
backend_call: { type: 'local', alias_for: 'auth logout' },
|
|
175
|
-
deprecated_alias_for: 'notis auth logout',
|
|
176
|
-
handler: authLogoutHandler,
|
|
177
|
-
},
|
|
178
|
-
];
|
package/src/command-specs/db.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { formatTable } from '../runtime/output.js';
|
|
2
|
-
import { nextIdempotencyKey, parseMaybeJson, runToolCommand } from './helpers.js';
|
|
3
|
-
|
|
4
|
-
async function dbListHandler(ctx) {
|
|
5
|
-
const result = await runToolCommand({
|
|
6
|
-
runtime: ctx.runtime,
|
|
7
|
-
toolName: 'notis_list_databases',
|
|
8
|
-
});
|
|
9
|
-
const databases = result.payload.databases || [];
|
|
10
|
-
const firstSlug = databases[0]?.slug;
|
|
11
|
-
return ctx.output.emitSuccess({
|
|
12
|
-
command: ctx.spec.command_path.join(' '),
|
|
13
|
-
data: { databases },
|
|
14
|
-
humanSummary: databases.length ? `Found ${databases.length} databases` : 'No databases found.',
|
|
15
|
-
hints: firstSlug
|
|
16
|
-
? [{ command: `notis db query ${firstSlug}`, reason: 'Query this database' }]
|
|
17
|
-
: [{ command: 'notis db upsert --operation create --title "My DB"', reason: 'Create a new database' }],
|
|
18
|
-
renderHuman: () =>
|
|
19
|
-
databases.length
|
|
20
|
-
? formatTable(databases, [
|
|
21
|
-
{ label: 'ID', value: (database) => database.id || '' },
|
|
22
|
-
{ label: 'Name', value: (database) => database.name || 'Untitled' },
|
|
23
|
-
{ label: 'Slug', value: (database) => database.slug || '-' },
|
|
24
|
-
])
|
|
25
|
-
: 'No databases found.',
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function dbUpsertHandler(ctx) {
|
|
30
|
-
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
31
|
-
const payload = {
|
|
32
|
-
operation: ctx.options.operation,
|
|
33
|
-
};
|
|
34
|
-
if (ctx.options.databaseId) {
|
|
35
|
-
payload.database_id = ctx.options.databaseId;
|
|
36
|
-
}
|
|
37
|
-
if (ctx.options.title) {
|
|
38
|
-
payload.title = ctx.options.title;
|
|
39
|
-
}
|
|
40
|
-
if (ctx.options.description) {
|
|
41
|
-
payload.description = ctx.options.description;
|
|
42
|
-
}
|
|
43
|
-
if (ctx.options.icon) {
|
|
44
|
-
const v = ctx.options.icon;
|
|
45
|
-
payload.icon = v.startsWith('lucide:') ? v : `lucide:${v}`;
|
|
46
|
-
}
|
|
47
|
-
if (ctx.options.properties) {
|
|
48
|
-
payload.properties = parseMaybeJson(ctx.options.properties, 'properties');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const result = await runToolCommand({
|
|
52
|
-
runtime: ctx.runtime,
|
|
53
|
-
toolName: 'notis_upsert_database',
|
|
54
|
-
arguments_: payload,
|
|
55
|
-
mutating: true,
|
|
56
|
-
idempotencyKey,
|
|
57
|
-
});
|
|
58
|
-
return ctx.output.emitSuccess({
|
|
59
|
-
command: ctx.spec.command_path.join(' '),
|
|
60
|
-
data: result.payload,
|
|
61
|
-
humanSummary: `Database ${ctx.options.operation} completed`,
|
|
62
|
-
meta: { mutating: true, idempotency_key: idempotencyKey || null },
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function dbQueryHandler(ctx) {
|
|
67
|
-
const query = {};
|
|
68
|
-
if (ctx.options.filter) {
|
|
69
|
-
query.filter = parseMaybeJson(ctx.options.filter, 'filter');
|
|
70
|
-
}
|
|
71
|
-
if (ctx.options.sort) {
|
|
72
|
-
const parsedSorts = parseMaybeJson(ctx.options.sort, 'sort');
|
|
73
|
-
query.sorts = Array.isArray(parsedSorts) ? parsedSorts : [parsedSorts];
|
|
74
|
-
}
|
|
75
|
-
if (ctx.options.pageSize) {
|
|
76
|
-
query.page_size = Number.parseInt(ctx.options.pageSize, 10);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const payload = {
|
|
80
|
-
database_slug: ctx.args.databaseSlug,
|
|
81
|
-
query,
|
|
82
|
-
};
|
|
83
|
-
if (ctx.options.offset) {
|
|
84
|
-
payload.offset = Number.parseInt(ctx.options.offset, 10);
|
|
85
|
-
}
|
|
86
|
-
if (ctx.options.cursor) {
|
|
87
|
-
payload.start_cursor = ctx.options.cursor;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const result = await runToolCommand({
|
|
91
|
-
runtime: ctx.runtime,
|
|
92
|
-
toolName: 'notis_query',
|
|
93
|
-
arguments_: payload,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
return ctx.output.emitSuccess({
|
|
97
|
-
command: ctx.spec.command_path.join(' '),
|
|
98
|
-
data: result.payload,
|
|
99
|
-
humanSummary: `Queried database ${ctx.args.databaseSlug}`,
|
|
100
|
-
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export const dbCommandSpecs = [
|
|
105
|
-
{
|
|
106
|
-
command_path: ['db', 'list'],
|
|
107
|
-
summary: 'List native Notis databases.',
|
|
108
|
-
when_to_use: 'Use this to find database ids and slugs before querying or updating schemas.',
|
|
109
|
-
args_schema: { arguments: [], options: [] },
|
|
110
|
-
examples: ['notis db list', 'notis db list --json'],
|
|
111
|
-
output_schema: 'Returns an array of native database records.',
|
|
112
|
-
mutates: false,
|
|
113
|
-
idempotent: true,
|
|
114
|
-
related_commands: ['notis db query <database-slug>', 'notis db upsert'],
|
|
115
|
-
backend_call: { type: 'tool', name: 'notis_list_databases' },
|
|
116
|
-
handler: dbListHandler,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
command_path: ['db', 'upsert'],
|
|
120
|
-
summary: 'Create or update a native database schema.',
|
|
121
|
-
when_to_use: 'Use this when you need to provision a new database or adjust an existing schema.',
|
|
122
|
-
args_schema: {
|
|
123
|
-
arguments: [],
|
|
124
|
-
options: [
|
|
125
|
-
{ flags: '--operation <create|update>', description: 'Create a new database or update an existing one.' },
|
|
126
|
-
{ flags: '--database-id <id>', description: 'Database id for update operations.' },
|
|
127
|
-
{ flags: '--title <text>', description: 'Database title.' },
|
|
128
|
-
{ flags: '--description <text>', description: 'Database description.' },
|
|
129
|
-
{ flags: '--icon <lucide-icon-name>', description: 'Database icon (Lucide icon name, e.g. database).' },
|
|
130
|
-
{ flags: '--properties <json>', description: 'JSON array of property definitions.' },
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
examples: ['notis db upsert --operation create --title "Tasks"', 'notis db upsert --operation update --database-id db_123 --title "Tasks V2"'],
|
|
134
|
-
output_schema: 'Returns the backend database upsert payload.',
|
|
135
|
-
mutates: true,
|
|
136
|
-
idempotent: true,
|
|
137
|
-
related_commands: ['notis db list', 'notis db query <database-slug>'],
|
|
138
|
-
backend_call: { type: 'tool', name: 'notis_upsert_database' },
|
|
139
|
-
handler: dbUpsertHandler,
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
command_path: ['db', 'query'],
|
|
143
|
-
summary: 'Run a structured query against a native Notis database.',
|
|
144
|
-
when_to_use: 'Use this when the database slug is known and you need direct filters, sorts, or pagination.',
|
|
145
|
-
args_schema: {
|
|
146
|
-
arguments: [{ token: '<database-slug>', description: 'Slug of the native database to query.' }],
|
|
147
|
-
options: [
|
|
148
|
-
{ flags: '--filter <json>', description: 'Structured query filter JSON.' },
|
|
149
|
-
{ flags: '--sort <json>', description: 'Sort JSON object or array.' },
|
|
150
|
-
{ flags: '--page-size <n>', description: 'Page size between 1 and 100.' },
|
|
151
|
-
{ flags: '--offset <n>', description: 'Zero-based offset.' },
|
|
152
|
-
{ flags: '--cursor <value>', description: 'Pagination cursor alias for next_offset.' },
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
examples: ['notis db query tasks --page-size 50', 'notis db query tasks --filter \'{"property":"Status"}\''],
|
|
156
|
-
output_schema: 'Returns the native database query payload from `notis_query`.',
|
|
157
|
-
mutates: false,
|
|
158
|
-
idempotent: true,
|
|
159
|
-
related_commands: ['notis db list', 'notis db upsert'],
|
|
160
|
-
backend_call: { type: 'tool', name: 'notis_query' },
|
|
161
|
-
handler: dbQueryHandler,
|
|
162
|
-
},
|
|
163
|
-
];
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Property extraction helpers for Notis DocumentRecord properties.
|
|
3
|
-
*
|
|
4
|
-
* Document properties come back in Notion-style nested shapes. These helpers
|
|
5
|
-
* normalize them into simple JS values regardless of the shape.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { DocumentRecord } from './runtime';
|
|
9
|
-
|
|
10
|
-
type Obj = Record<string, unknown>;
|
|
11
|
-
|
|
12
|
-
/** Extract a string from a title, rich_text, or plain text property. */
|
|
13
|
-
export function extractText(val: unknown): string {
|
|
14
|
-
if (typeof val === 'string') return val;
|
|
15
|
-
if (val && typeof val === 'object') {
|
|
16
|
-
const obj = val as Obj;
|
|
17
|
-
if (typeof obj.content === 'string') return obj.content;
|
|
18
|
-
if (typeof obj.plain_text === 'string') return obj.plain_text;
|
|
19
|
-
if (Array.isArray(obj.title)) {
|
|
20
|
-
return obj.title.map((t: Obj) => extractText(t?.text || t)).join('');
|
|
21
|
-
}
|
|
22
|
-
if (Array.isArray(obj.rich_text)) {
|
|
23
|
-
return obj.rich_text.map((t: Obj) => extractText(t?.text || t)).join('');
|
|
24
|
-
}
|
|
25
|
-
if (obj.text) return extractText(obj.text);
|
|
26
|
-
}
|
|
27
|
-
return '';
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Extract a string from a select or status property. */
|
|
31
|
-
export function extractSelect(val: unknown): string {
|
|
32
|
-
if (typeof val === 'string') return val;
|
|
33
|
-
if (val && typeof val === 'object') {
|
|
34
|
-
const obj = val as Obj;
|
|
35
|
-
if (obj.select && typeof obj.select === 'object') {
|
|
36
|
-
return (obj.select as Obj).name as string || '';
|
|
37
|
-
}
|
|
38
|
-
if (obj.status && typeof obj.status === 'object') {
|
|
39
|
-
return (obj.status as Obj).name as string || '';
|
|
40
|
-
}
|
|
41
|
-
if (typeof obj.name === 'string') return obj.name;
|
|
42
|
-
}
|
|
43
|
-
return '';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Extract a date string from a date property. */
|
|
47
|
-
export function extractDate(val: unknown): string {
|
|
48
|
-
if (typeof val === 'string') return val;
|
|
49
|
-
if (val && typeof val === 'object') {
|
|
50
|
-
const obj = val as Obj;
|
|
51
|
-
if (obj.date && typeof obj.date === 'object') {
|
|
52
|
-
return (obj.date as Obj).start as string || '';
|
|
53
|
-
}
|
|
54
|
-
if (typeof obj.start === 'string') return obj.start;
|
|
55
|
-
}
|
|
56
|
-
return '';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Extract a number from a number property. */
|
|
60
|
-
export function extractNumber(val: unknown): number | null {
|
|
61
|
-
if (typeof val === 'number') return val;
|
|
62
|
-
if (val && typeof val === 'object') {
|
|
63
|
-
const obj = val as Obj;
|
|
64
|
-
if (typeof obj.number === 'number') return obj.number;
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Extract a boolean from a checkbox property. */
|
|
70
|
-
export function extractCheckbox(val: unknown): boolean {
|
|
71
|
-
if (typeof val === 'boolean') return val;
|
|
72
|
-
if (val && typeof val === 'object') {
|
|
73
|
-
const obj = val as Obj;
|
|
74
|
-
if (typeof obj.checkbox === 'boolean') return obj.checkbox;
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Extract multi-select values as a string array. */
|
|
80
|
-
export function extractMultiSelect(val: unknown): string[] {
|
|
81
|
-
if (Array.isArray(val)) {
|
|
82
|
-
return val
|
|
83
|
-
.map((v) => {
|
|
84
|
-
if (typeof v === 'string') return v;
|
|
85
|
-
if (v && typeof v === 'object' && typeof (v as Obj).name === 'string') return (v as Obj).name as string;
|
|
86
|
-
return '';
|
|
87
|
-
})
|
|
88
|
-
.filter(Boolean);
|
|
89
|
-
}
|
|
90
|
-
if (val && typeof val === 'object') {
|
|
91
|
-
const obj = val as Obj;
|
|
92
|
-
if (Array.isArray(obj.multi_select)) return extractMultiSelect(obj.multi_select);
|
|
93
|
-
}
|
|
94
|
-
if (typeof val === 'string' && val) return [val];
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Extract relation IDs from a relation property. */
|
|
99
|
-
export function extractRelation(val: unknown): string[] {
|
|
100
|
-
if (Array.isArray(val)) {
|
|
101
|
-
return val
|
|
102
|
-
.map((v) => {
|
|
103
|
-
if (typeof v === 'string') return v;
|
|
104
|
-
if (v && typeof v === 'object' && typeof (v as Obj).id === 'string') return (v as Obj).id as string;
|
|
105
|
-
return '';
|
|
106
|
-
})
|
|
107
|
-
.filter(Boolean);
|
|
108
|
-
}
|
|
109
|
-
if (val && typeof val === 'object') {
|
|
110
|
-
const obj = val as Obj;
|
|
111
|
-
if (Array.isArray(obj.relation)) return extractRelation(obj.relation);
|
|
112
|
-
}
|
|
113
|
-
if (typeof val === 'string' && val) return [val];
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Extract display title from a document. Falls back through several property
|
|
119
|
-
* names and shapes to find a human-readable title.
|
|
120
|
-
*/
|
|
121
|
-
export function docTitle(doc: DocumentRecord): string {
|
|
122
|
-
const t = doc.title;
|
|
123
|
-
if (t && !/^[0-9a-f]{8}-[0-9a-f]{4}/.test(t)) return t;
|
|
124
|
-
const p = doc.properties || {};
|
|
125
|
-
const name = p.Name ?? p.name ?? p.Title ?? p.title;
|
|
126
|
-
if (name) {
|
|
127
|
-
const extracted = extractText(name);
|
|
128
|
-
if (extracted) return extracted;
|
|
129
|
-
}
|
|
130
|
-
return t || 'Untitled';
|
|
131
|
-
}
|