@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.
Files changed (81) hide show
  1. package/README.md +122 -158
  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 +12 -2
  18. package/skills/notis-apps/SKILL.md +162 -0
  19. package/skills/notis-apps/cli.md +208 -0
  20. package/skills/notis-cli/SKILL.md +258 -0
  21. package/skills/notis-query/cli.md +40 -0
  22. package/src/cli.js +1 -1
  23. package/src/command-specs/apps.js +1029 -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 +1037 -82
  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/globals.css +3 -0
  41. package/template/app/layout.tsx +6 -0
  42. package/template/app/page.tsx +90 -0
  43. package/template/components/ui/badge.tsx +28 -0
  44. package/template/components/ui/button.tsx +53 -0
  45. package/template/components/ui/card.tsx +56 -0
  46. package/template/components.json +20 -0
  47. package/template/lib/utils.ts +6 -0
  48. package/template/metadata/cover.png +0 -0
  49. package/template/metadata/screenshot-1.png +0 -0
  50. package/template/metadata/screenshot-2.png +0 -0
  51. package/template/metadata/screenshot-3.png +0 -0
  52. package/template/notis.config.ts +37 -0
  53. package/template/package.json +31 -0
  54. package/template/packages/sdk/package.json +32 -0
  55. package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +273 -0
  56. package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  57. package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  58. package/template/packages/sdk/src/config.ts +71 -0
  59. package/template/packages/sdk/src/hooks/useBackend.ts +41 -0
  60. package/template/packages/sdk/src/hooks/useDatabase.ts +76 -0
  61. package/template/packages/sdk/src/hooks/useMultiSelect.ts +503 -0
  62. package/template/packages/sdk/src/hooks/useNotis.ts +34 -0
  63. package/template/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  64. package/template/packages/sdk/src/hooks/useTool.ts +64 -0
  65. package/template/packages/sdk/src/hooks/useTools.ts +56 -0
  66. package/template/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  67. package/template/packages/sdk/src/hooks/useUpsertDocument.ts +50 -0
  68. package/template/packages/sdk/src/index.ts +54 -0
  69. package/template/packages/sdk/src/provider.tsx +43 -0
  70. package/template/packages/sdk/src/runtime.ts +161 -0
  71. package/template/packages/sdk/src/styles.css +38 -0
  72. package/template/packages/sdk/src/ui.ts +15 -0
  73. package/template/packages/sdk/src/vite.ts +56 -0
  74. package/template/packages/sdk/tsconfig.json +15 -0
  75. package/template/postcss.config.mjs +8 -0
  76. package/template/tailwind.config.ts +58 -0
  77. package/template/tsconfig.json +22 -0
  78. package/template/vite.config.ts +10 -0
  79. package/src/command-specs/auth.js +0 -178
  80. package/src/command-specs/db.js +0 -163
  81. package/src/runtime/app-preview-server.js +0 -272
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const badgeVariants = cva(
7
+ 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-transparent bg-primary/10 text-primary',
12
+ secondary: 'border-transparent bg-secondary text-secondary-foreground',
13
+ outline: 'text-foreground',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: 'default',
18
+ },
19
+ },
20
+ );
21
+
22
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
23
+
24
+ function Badge({ className, variant, ...props }: BadgeProps) {
25
+ return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
26
+ }
27
+
28
+ export { Badge, badgeVariants };
@@ -0,0 +1,53 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '@/lib/utils';
6
+
7
+ const buttonVariants = cva(
8
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
14
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
15
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
16
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
17
+ link: 'text-primary underline-offset-4 hover:underline',
18
+ },
19
+ size: {
20
+ default: 'h-10 px-4 py-2',
21
+ sm: 'h-9 rounded-md px-3',
22
+ lg: 'h-11 rounded-md px-8',
23
+ icon: 'h-10 w-10',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ size: 'default',
29
+ },
30
+ },
31
+ );
32
+
33
+ export interface ButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof buttonVariants> {
36
+ asChild?: boolean;
37
+ }
38
+
39
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
40
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
41
+ const Comp = asChild ? Slot : 'button';
42
+ return (
43
+ <Comp
44
+ className={cn(buttonVariants({ variant, size, className }))}
45
+ ref={ref}
46
+ {...props}
47
+ />
48
+ );
49
+ },
50
+ );
51
+ Button.displayName = 'Button';
52
+
53
+ export { Button, buttonVariants };
@@ -0,0 +1,56 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn('rounded-2xl border bg-card text-card-foreground shadow-sm', className)}
10
+ {...props}
11
+ />
12
+ ),
13
+ );
14
+ Card.displayName = 'Card';
15
+
16
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
+ ({ className, ...props }, ref) => (
18
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
19
+ ),
20
+ );
21
+ CardHeader.displayName = 'CardHeader';
22
+
23
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
24
+ ({ className, ...props }, ref) => (
25
+ <h3
26
+ ref={ref}
27
+ className={cn('text-xl font-semibold leading-none tracking-tight', className)}
28
+ {...props}
29
+ />
30
+ ),
31
+ );
32
+ CardTitle.displayName = 'CardTitle';
33
+
34
+ const CardDescription = React.forwardRef<
35
+ HTMLParagraphElement,
36
+ React.HTMLAttributes<HTMLParagraphElement>
37
+ >(({ className, ...props }, ref) => (
38
+ <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
39
+ ));
40
+ CardDescription.displayName = 'CardDescription';
41
+
42
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
43
+ ({ className, ...props }, ref) => (
44
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
45
+ ),
46
+ );
47
+ CardContent.displayName = 'CardContent';
48
+
49
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
50
+ ({ className, ...props }, ref) => (
51
+ <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
52
+ ),
53
+ );
54
+ CardFooter.displayName = 'CardFooter';
55
+
56
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ }
20
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,31 @@
1
+ import { defineNotisApp } from '@notis/sdk/config';
2
+
3
+ export default defineNotisApp({
4
+ name: 'Notis Notes',
5
+ description: 'Browse notes by documents, table, or calendar with folders in the sidebar.',
6
+ icon: 'lucide:notebook-pen',
7
+ databases: ['note_folders', 'notes'],
8
+ routes: [
9
+ {
10
+ path: '/',
11
+ slug: 'notes',
12
+ name: 'Notes',
13
+ icon: 'lucide:notebook-pen',
14
+ default: true,
15
+ collection: {
16
+ database: 'note_folders',
17
+ titleProperty: 'Name',
18
+ parentProperty: 'Parent',
19
+ sidebar: {
20
+ mode: 'tree',
21
+ allowCreate: true,
22
+ },
23
+ },
24
+ },
25
+ ],
26
+ tools: [
27
+ 'notis_query_database',
28
+ 'notis_get_document',
29
+ 'notis_upsert_document',
30
+ ],
31
+ });
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "notis-notes-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build"
9
+ },
10
+ "dependencies": {
11
+ "@notis/sdk": "file:../../packages/sdk",
12
+ "@radix-ui/react-slot": "^1.1.0",
13
+ "class-variance-authority": "^0.7.0",
14
+ "clsx": "^2.1.1",
15
+ "lucide-react": "^0.474.0",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0",
18
+ "tailwind-merge": "^2.6.0",
19
+ "tailwindcss-animate": "^1.0.7"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^22.0.0",
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@vitejs/plugin-react": "^4.0.0",
26
+ "postcss": "^8.0.0",
27
+ "tailwindcss": "^3.4.0",
28
+ "typescript": "^5.0.0",
29
+ "vite": "^6.0.0"
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
@@ -0,0 +1,58 @@
1
+ import type { Config } from 'tailwindcss';
2
+ import tailwindAnimate from 'tailwindcss-animate';
3
+
4
+ const config: Config = {
5
+ darkMode: ['class'],
6
+ content: [
7
+ './app/**/*.{ts,tsx}',
8
+ './components/**/*.{ts,tsx}',
9
+ './lib/**/*.{ts,tsx}',
10
+ ],
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ border: 'hsl(var(--border))',
15
+ input: 'hsl(var(--input))',
16
+ ring: 'hsl(var(--ring))',
17
+ background: 'hsl(var(--background))',
18
+ foreground: 'hsl(var(--foreground))',
19
+ primary: {
20
+ DEFAULT: 'hsl(var(--primary))',
21
+ foreground: 'hsl(var(--primary-foreground))',
22
+ },
23
+ secondary: {
24
+ DEFAULT: 'hsl(var(--secondary))',
25
+ foreground: 'hsl(var(--secondary-foreground))',
26
+ },
27
+ destructive: {
28
+ DEFAULT: 'hsl(var(--destructive))',
29
+ foreground: 'hsl(var(--destructive-foreground))',
30
+ },
31
+ muted: {
32
+ DEFAULT: 'hsl(var(--muted))',
33
+ foreground: 'hsl(var(--muted-foreground))',
34
+ },
35
+ accent: {
36
+ DEFAULT: 'hsl(var(--accent))',
37
+ foreground: 'hsl(var(--accent-foreground))',
38
+ },
39
+ popover: {
40
+ DEFAULT: 'hsl(var(--popover))',
41
+ foreground: 'hsl(var(--popover-foreground))',
42
+ },
43
+ card: {
44
+ DEFAULT: 'hsl(var(--card))',
45
+ foreground: 'hsl(var(--card-foreground))',
46
+ },
47
+ },
48
+ borderRadius: {
49
+ lg: 'var(--radius)',
50
+ md: 'calc(var(--radius) - 2px)',
51
+ sm: 'calc(var(--radius) - 4px)',
52
+ },
53
+ },
54
+ },
55
+ plugins: [tailwindAnimate],
56
+ };
57
+
58
+ export default config;
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "types": ["vite/client"],
16
+ "paths": {
17
+ "@/*": ["./*"]
18
+ }
19
+ },
20
+ "include": ["**/*.ts", "**/*.tsx"],
21
+ "exclude": ["node_modules"]
22
+ }
@@ -0,0 +1,10 @@
1
+ import { notisViteConfig } from '@notis/sdk/vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import appConfig from './notis.config';
4
+
5
+ const config = notisViteConfig(appConfig);
6
+
7
+ export default {
8
+ ...config,
9
+ plugins: [react(), ...(config.plugins || [])],
10
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "source": "apps/*/notis.config.ts",
3
+ "scaffolds": [
4
+ {
5
+ "slug": "notes",
6
+ "name": "Notis Notes",
7
+ "description": "Browse notes by documents, table, or calendar with folders in the sidebar.",
8
+ "icon": "lucide:notebook-pen",
9
+ "categories": [],
10
+ "tagline": "Browse notes by documents, table, or calendar with folders in the sidebar."
11
+ }
12
+ ]
13
+ }
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@notis_ai/cli",
3
- "version": "0.2.0",
4
- "description": "Agent-first Notis CLI for apps, databases, and generic tool execution",
3
+ "version": "0.2.2",
4
+ "description": "Agent-first Notis CLI for apps and generic tool execution",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "notis": "bin/notis.js"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
+ "dist/",
11
12
  "src/",
13
+ "skills/",
14
+ "template/",
12
15
  "README.md"
13
16
  ],
14
17
  "scripts": {
18
+ "build": "node ./scripts/build-scaffolds.js",
15
19
  "docs:generate": "node ./scripts/generate-docs.js",
16
20
  "docs:check": "node ./scripts/generate-docs.js --check",
21
+ "prepack": "npm run build",
22
+ "release:prepare": "npm run build && node ./scripts/prepare-publish.js --apply",
23
+ "cli:set-mode": "node ./scripts/set-cli-mode.js",
17
24
  "test": "node --test"
18
25
  },
19
26
  "engines": {
@@ -29,5 +36,8 @@
29
36
  "ai",
30
37
  "agent-skills"
31
38
  ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
32
42
  "license": "MIT"
33
43
  }
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: notis-apps
3
+ description: Design and package Notis apps. Use when users want an app that groups databases, routes, documents, automations, and skills into one installable Notis product.
4
+ ---
5
+
6
+ # Notis Apps Skill
7
+
8
+ Use this skill when the user wants to create, edit, debug, or package a Notis app. Notis apps are Vite + React projects that deploy into the Notis Portal as installed apps for the current user or team.
9
+
10
+ Run the Notis CLI through NPX, for example `npx --package @notis_ai/cli@latest -- notis apps list`. Notis Desktop keeps the CLI auth profile current. This `notis-apps` skill is delivered through normal Notis skill sync for the signed-in user, alongside other curated skills.
11
+
12
+ ## Platform Model
13
+
14
+ A Notis app is:
15
+
16
+ - a Vite + React project
17
+ - built with `@notis/sdk`
18
+ - configured through `notis.config.ts`
19
+ - packaged as an ES module bundle with `app.js`, `app.css`, and `manifest.json`
20
+ - rendered by the Notis Portal as a React component, not an iframe
21
+ - connected to Notis data and tools through the Portal-provided runtime and SDK hooks
22
+
23
+ Use the Notis CLI for the app lifecycle:
24
+
25
+ ```bash
26
+ npx --package @notis_ai/cli@latest -- notis apps init
27
+ npx --package @notis_ai/cli@latest -- notis apps dev
28
+ npx --package @notis_ai/cli@latest -- notis apps build
29
+ npx --package @notis_ai/cli@latest -- notis apps verify
30
+ npx --package @notis_ai/cli@latest -- notis apps create
31
+ npx --package @notis_ai/cli@latest -- notis apps link
32
+ npx --package @notis_ai/cli@latest -- notis apps pull
33
+ npx --package @notis_ai/cli@latest -- notis apps deploy
34
+ npx --package @notis_ai/cli@latest -- notis apps doctor
35
+ ```
36
+
37
+ Use this command form everywhere:
38
+
39
+ ```bash
40
+ npx --package @notis_ai/cli@latest -- notis apps ...
41
+ ```
42
+
43
+ ## Hard Rules
44
+
45
+ - Use React + Vite only. Do not use Next.js or a custom server for app projects.
46
+ - Build standard pages in `app/`. Do not write raw `views/<slug>/index.js` files.
47
+ - Keep navigation in `notis.config.ts` `routes`; each route needs a stable `slug`.
48
+ - Reference existing databases by slug in `notis.config.ts`; do not assume deploy creates databases automatically.
49
+ - Declare runtime tool access in `notis.config.ts` `tools`.
50
+ - Use `@notis/sdk` hooks instead of direct runtime access.
51
+ - Do not rely on `window.__NOTIS_RUNTIME__` or portal-owned DOM hooks.
52
+ - `npx --package @notis_ai/cli@latest -- notis apps deploy` updates an installed app. It does not publish to the public App Store.
53
+ - Public or team App Store publishing is a Portal review flow.
54
+ - Use Phosphor icon names with the `phosphor:` prefix. Do not use emoji icons.
55
+ - Prefer scaffolded shadcn components and portal theme tokens over custom visual systems.
56
+
57
+ ## App Workflow
58
+
59
+ 1. Discover existing apps if needed:
60
+
61
+ ```bash
62
+ npx --package @notis_ai/cli@latest -- notis apps list
63
+ ```
64
+
65
+ 2. Start from a scaffold or pull an existing app:
66
+
67
+ ```bash
68
+ npx --package @notis_ai/cli@latest -- notis apps scaffolds list
69
+ npx --package @notis_ai/cli@latest -- notis apps init "My App"
70
+ npx --package @notis_ai/cli@latest -- notis apps pull <app-id> ./my-app
71
+ ```
72
+
73
+ 3. Install dependencies and develop:
74
+
75
+ ```bash
76
+ npm install
77
+ npx --package @notis_ai/cli@latest -- notis apps dev
78
+ ```
79
+
80
+ 4. Build and verify:
81
+
82
+ ```bash
83
+ npx --package @notis_ai/cli@latest -- notis apps build
84
+ npx --package @notis_ai/cli@latest -- notis apps verify
85
+ ```
86
+
87
+ 5. Create or link the remote app, then deploy:
88
+
89
+ ```bash
90
+ npx --package @notis_ai/cli@latest -- notis apps create "My App" .
91
+ npx --package @notis_ai/cli@latest -- notis apps deploy
92
+ ```
93
+
94
+ or:
95
+
96
+ ```bash
97
+ npx --package @notis_ai/cli@latest -- notis apps link <app-id>
98
+ npx --package @notis_ai/cli@latest -- notis apps deploy
99
+ ```
100
+
101
+ ## App Structure
102
+
103
+ Expected project shape:
104
+
105
+ ```text
106
+ notis.config.ts
107
+ vite.config.ts
108
+ app/
109
+ components/
110
+ metadata/
111
+ .notis/output/
112
+ ```
113
+
114
+ `notis.config.ts` owns:
115
+
116
+ - app metadata: name, title, description, icon, categories, tagline
117
+ - database slug references
118
+ - route definitions
119
+ - tool access
120
+
121
+ Example:
122
+
123
+ ```ts
124
+ import { defineNotisApp } from '@notis/sdk/config';
125
+
126
+ export default defineNotisApp({
127
+ name: 'my-app',
128
+ title: 'My App',
129
+ description: 'A focused Notis app.',
130
+ icon: 'phosphor:squares-four',
131
+ databases: ['tasks'],
132
+ routes: [
133
+ {
134
+ path: '/',
135
+ slug: 'tasks',
136
+ name: 'Tasks',
137
+ icon: 'phosphor:check-square',
138
+ default: true,
139
+ },
140
+ ],
141
+ tools: ['notis-default-query', 'notis-default-upsert_tasks'],
142
+ });
143
+ ```
144
+
145
+ ## Runtime Usage
146
+
147
+ Use SDK hooks for data and tool access:
148
+
149
+ ```tsx
150
+ import { useTool } from '@notis/sdk';
151
+
152
+ const query = useTool('notis-default-query');
153
+ ```
154
+
155
+ Do not fetch Notis backend endpoints directly from app code unless the SDK explicitly requires it. The Portal provides authentication, runtime context, and route rendering.
156
+
157
+ ## Troubleshooting
158
+
159
+ - If deploy fails with backend connectivity errors, run `npx --package @notis_ai/cli@latest -- notis apps doctor` and check the configured API base.
160
+ - If editing an existing app, use `npx --package @notis_ai/cli@latest -- notis apps pull <app-id>` when source snapshots are available.
161
+ - If the Portal shows a stale bundle, hard refresh the Portal or deploy a new version.
162
+ - If an app route fails to load, run `npx --package @notis_ai/cli@latest -- notis apps verify` and inspect `.notis/output/manifest.json`.