@notis_ai/cli 0.2.0-beta.21.1 → 0.2.0-beta.35.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.
Files changed (79) hide show
  1. package/README.md +74 -112
  2. package/dist/scaffolds/notes/app/globals.css +3 -0
  3. package/dist/scaffolds/notes/app/layout.tsx +6 -0
  4. package/dist/scaffolds/notes/app/page.tsx +1465 -0
  5. package/dist/scaffolds/notes/components/ui/badge.tsx +28 -0
  6. package/dist/scaffolds/notes/components/ui/button.tsx +53 -0
  7. package/dist/scaffolds/notes/components/ui/card.tsx +56 -0
  8. package/dist/scaffolds/notes/components.json +20 -0
  9. package/dist/scaffolds/notes/lib/utils.ts +6 -0
  10. package/dist/scaffolds/notes/notis.config.ts +31 -0
  11. package/dist/scaffolds/notes/package.json +31 -0
  12. package/dist/scaffolds/notes/postcss.config.mjs +8 -0
  13. package/dist/scaffolds/notes/tailwind.config.ts +58 -0
  14. package/dist/scaffolds/notes/tsconfig.json +22 -0
  15. package/dist/scaffolds/notes/vite.config.ts +10 -0
  16. package/dist/scaffolds.json +13 -0
  17. package/package.json +8 -3
  18. package/skills/notis-apps/SKILL.md +168 -0
  19. package/skills/notis-apps/cli.md +208 -0
  20. package/skills/notis-cli/SKILL.md +263 -0
  21. package/skills/notis-query/cli.md +40 -0
  22. package/src/cli.js +1 -1
  23. package/src/command-specs/apps.js +962 -59
  24. package/src/command-specs/helpers.js +6 -41
  25. package/src/command-specs/index.js +1 -7
  26. package/src/command-specs/meta.js +7 -6
  27. package/src/command-specs/tools.js +29 -34
  28. package/src/runtime/agent-browser.js +192 -0
  29. package/src/runtime/app-boundary-validator.js +132 -0
  30. package/src/runtime/app-dev-server.js +605 -0
  31. package/src/runtime/app-dev-sessions.js +87 -0
  32. package/src/runtime/app-platform.js +725 -38
  33. package/src/runtime/cli-mode.generated.js +4 -0
  34. package/src/runtime/cli-mode.js +29 -0
  35. package/src/runtime/output.js +2 -2
  36. package/src/runtime/ports.js +15 -0
  37. package/src/runtime/profiles.js +36 -5
  38. package/src/runtime/transport.js +131 -6
  39. package/template/.harness/index.html.tmpl +211 -0
  40. package/template/app/layout.tsx +1 -2
  41. package/template/app/page.tsx +41 -6
  42. package/template/metadata/cover.png +0 -0
  43. package/template/metadata/screenshot-1.png +0 -0
  44. package/template/metadata/screenshot-2.png +0 -0
  45. package/template/metadata/screenshot-3.png +0 -0
  46. package/template/notis.config.ts +25 -6
  47. package/template/package.json +3 -4
  48. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +273 -0
  49. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  50. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  51. package/template/packages/{notis-sdk → sdk}/src/config.ts +25 -2
  52. package/template/packages/sdk/src/hooks/useMultiSelect.ts +503 -0
  53. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotis.ts +6 -7
  54. package/template/packages/{notis-sdk → sdk}/src/hooks/useNotisNavigation.ts +4 -4
  55. package/template/packages/{notis-sdk → sdk}/src/hooks/useTool.ts +22 -7
  56. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  57. package/template/packages/sdk/src/index.ts +46 -0
  58. package/template/packages/sdk/src/provider.tsx +43 -0
  59. package/template/packages/sdk/src/runtime.ts +80 -0
  60. package/template/packages/sdk/src/styles.css +38 -0
  61. package/src/command-specs/auth.js +0 -178
  62. package/src/command-specs/db.js +0 -163
  63. package/src/runtime/app-preview-server.js +0 -336
  64. package/template/packages/notis-sdk/src/helpers.ts +0 -131
  65. package/template/packages/notis-sdk/src/hooks/useAppState.ts +0 -50
  66. package/template/packages/notis-sdk/src/hooks/useCollectionItem.ts +0 -58
  67. package/template/packages/notis-sdk/src/hooks/useDatabase.ts +0 -87
  68. package/template/packages/notis-sdk/src/hooks/useDocument.ts +0 -61
  69. package/template/packages/notis-sdk/src/hooks/useUpsertDocument.ts +0 -58
  70. package/template/packages/notis-sdk/src/index.ts +0 -47
  71. package/template/packages/notis-sdk/src/provider.tsx +0 -44
  72. package/template/packages/notis-sdk/src/runtime.ts +0 -165
  73. package/template/packages/notis-sdk/src/styles.css +0 -123
  74. /package/template/packages/{notis-sdk → sdk}/package.json +0 -0
  75. /package/template/packages/{notis-sdk → sdk}/src/hooks/useBackend.ts +0 -0
  76. /package/template/packages/{notis-sdk → sdk}/src/hooks/useTools.ts +0 -0
  77. /package/template/packages/{notis-sdk → sdk}/src/ui.ts +0 -0
  78. /package/template/packages/{notis-sdk → sdk}/src/vite.ts +0 -0
  79. /package/template/packages/{notis-sdk → sdk}/tsconfig.json +0 -0
@@ -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,46 @@
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 } from './provider';
11
+
12
+ // Hooks
13
+ export { useNotis } from './hooks/useNotis';
14
+ export { useTool } from './hooks/useTool';
15
+ export type { ToolCallState, UseToolResult } from './hooks/useTool';
16
+ export { useTools } from './hooks/useTools';
17
+ export { useNotisNavigation } from './hooks/useNotisNavigation';
18
+ export { useTopBarSearch } from './hooks/useTopBarSearch';
19
+ export { useBackend } from './hooks/useBackend';
20
+ export { useMultiSelect } from './hooks/useMultiSelect';
21
+
22
+ // Multi-select components
23
+ export { MultiSelectActionBar } from './components/MultiSelectActionBar';
24
+ export { MultiSelectCheckbox } from './components/MultiSelectCheckbox';
25
+ export { MultiSelectDragOverlay } from './components/MultiSelectDragOverlay';
26
+ export type {
27
+ DragRect,
28
+ MultiSelectController,
29
+ UseMultiSelectOptions,
30
+ } from './hooks/useMultiSelect';
31
+ export type {
32
+ MultiSelectAction,
33
+ MultiSelectActionBarProps,
34
+ } from './components/MultiSelectActionBar';
35
+ export type { MultiSelectCheckboxProps } from './components/MultiSelectCheckbox';
36
+ export type { MultiSelectDragOverlayProps } from './components/MultiSelectDragOverlay';
37
+
38
+ // Types (re-exported for convenience)
39
+ export type {
40
+ AppDescriptor,
41
+ NotisRuntime,
42
+ NotisRuntimeContext,
43
+ RouteDescriptor,
44
+ ToolDescriptor,
45
+ ToolInputSchema,
46
+ } 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,80 @@
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
+ // Tool types
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export type ToolInputSchema = Record<string, unknown>;
15
+
16
+ export interface ToolDescriptor {
17
+ name: string;
18
+ description?: string;
19
+ inputSchema?: ToolInputSchema;
20
+ }
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Route types
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export interface RouteDescriptor {
27
+ slug: string;
28
+ path: string;
29
+ name: string;
30
+ icon?: string | null;
31
+ parentSlug?: string | null;
32
+ default?: boolean;
33
+ collection?: Record<string, unknown> | null;
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // App descriptor
38
+ // ---------------------------------------------------------------------------
39
+
40
+ export interface AppDescriptor {
41
+ id: string;
42
+ name: string;
43
+ icon?: string | null;
44
+ description?: string | null;
45
+ }
46
+
47
+ export type NotisRuntimeContext = Record<string, unknown>;
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // NotisRuntime
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export interface NotisRuntime {
54
+ app: AppDescriptor;
55
+ route: RouteDescriptor;
56
+ context: NotisRuntimeContext;
57
+
58
+ navigate?: (payload: { kind: string; [key: string]: unknown }) => void;
59
+
60
+ registerTopBarSearch?: (
61
+ config:
62
+ | {
63
+ onChange: (value: string) => void;
64
+ placeholder?: string;
65
+ onSubmit?: () => void | Promise<void>;
66
+ }
67
+ | null,
68
+ ) => void;
69
+ setTopBarSearchValue?: (value: string) => void;
70
+ setTopBarSearchLoading?: (loading: boolean) => void;
71
+
72
+ listTools(): Promise<ToolDescriptor[]>;
73
+ callTool<TResult = unknown>(name: string, args?: Record<string, unknown>): Promise<TResult>;
74
+
75
+ request(path: string, options?: {
76
+ method?: string;
77
+ headers?: Record<string, string>;
78
+ body?: unknown;
79
+ }): Promise<unknown>;
80
+ }
@@ -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
+ }
@@ -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
- ];
@@ -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
- ];