@notis_ai/cli 0.2.13 → 0.2.15
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 +125 -21
- package/dist/agent-hooks/notis-agent-hook.mjs +19750 -0
- package/{skills → dist/base-skills}/notis-apps/SKILL.md +93 -58
- package/{skills → dist/base-skills}/notis-cli/SKILL.md +9 -9
- package/dist/base-skills/notis-query/SKILL.md +705 -0
- package/dist/skill-sync/index.js +1528 -0
- package/dist/skill-sync/index.js.map +7 -0
- package/package.json +5 -2
- package/skills/notis-apps/cli.md +44 -18
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +39 -0
- package/skills/notis-onboarding/BRIEF.md +16 -0
- package/src/agent-hook-entry.js +5 -0
- package/src/cli.js +23 -14
- package/src/command-specs/agents.js +392 -0
- package/src/command-specs/apps.js +1090 -418
- package/src/command-specs/auth.js +63 -10
- package/src/command-specs/handover.js +374 -0
- package/src/command-specs/index.js +9 -0
- package/src/command-specs/onboarding.js +69 -5
- package/src/command-specs/skills.js +56 -0
- package/src/command-specs/tools.js +6 -0
- package/src/runtime/agent-memory-state.js +126 -0
- package/src/runtime/agent-setup.js +383 -0
- package/src/runtime/app-dev-consumers.js +154 -0
- package/src/runtime/app-dev-host-lock.js +80 -0
- package/src/runtime/app-dev-roots.js +284 -0
- package/src/runtime/app-dev-server.js +229 -30
- package/src/runtime/app-dev-sessions.js +105 -145
- package/src/runtime/app-platform.js +1499 -161
- package/src/runtime/app-registry-scaffolds.js +367 -0
- package/src/runtime/base-skills.d.ts +20 -0
- package/src/runtime/base-skills.js +167 -0
- package/src/runtime/delegated-context.js +68 -0
- package/src/runtime/git.js +233 -0
- package/src/runtime/login-listener.js +15 -0
- package/src/runtime/oauth.js +1506 -141
- package/src/runtime/output.js +7 -0
- package/src/runtime/ports.js +16 -0
- package/src/runtime/profiles.js +0 -5
- package/src/runtime/skill-sync/cloud-client.ts +96 -0
- package/src/runtime/skill-sync/index.ts +644 -0
- package/src/runtime/skill-sync/local-scanner.ts +1046 -0
- package/src/runtime/skill-sync/symlink-manager.ts +383 -0
- package/src/runtime/skill-sync/sync-plan.ts +22 -0
- package/src/runtime/skill-sync/types.ts +103 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +50 -0
- package/src/runtime/store-screenshot.js +6 -1
- package/src/runtime/sync-skills.d.ts +37 -0
- package/src/runtime/sync-skills.js +215 -0
- package/src/runtime/transport.js +19 -2
- package/template/.harness/index.html.tmpl +116 -47
- package/template/app/layout.tsx +5 -1
- package/template/notis.config.ts +1 -0
- package/template/packages/sdk/package.json +2 -1
- package/template/packages/sdk/src/components/DocumentEditor.tsx +13 -3
- package/template/packages/sdk/src/components/MarkdownEditor.tsx +121 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +37 -119
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +5 -1
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +1 -1
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +55 -0
- package/template/packages/sdk/src/components/ShortcutHints.tsx +56 -0
- package/template/packages/sdk/src/config.ts +71 -0
- package/template/packages/sdk/src/documents.ts +28 -1
- package/template/packages/sdk/src/hooks/useActiveResource.ts +19 -0
- package/template/packages/sdk/src/hooks/useCloudComputer.ts +97 -0
- package/template/packages/sdk/src/hooks/useCollectionInteractions.ts +709 -0
- package/template/packages/sdk/src/hooks/useDatabaseSubscription.ts +76 -0
- package/template/packages/sdk/src/hooks/useHandover.ts +78 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +44 -482
- package/template/packages/sdk/src/hooks/useTool.ts +5 -4
- package/template/packages/sdk/src/index.ts +64 -2
- package/template/packages/sdk/src/interactions/actions.ts +46 -0
- package/template/packages/sdk/src/interactions/shortcuts.tsx +628 -0
- package/template/packages/sdk/src/interactions.ts +41 -0
- package/template/packages/sdk/src/provider.tsx +2 -1
- package/template/packages/sdk/src/runtime.ts +219 -3
- package/dist/scaffolds/notis-database/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-database/app/globals.css +0 -44
- package/dist/scaffolds/notis-database/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-database/app/page.tsx +0 -1088
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-database/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-database/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-database/components/ui/table.tsx +0 -120
- package/dist/scaffolds/notis-database/components.json +0 -20
- package/dist/scaffolds/notis-database/index.html +0 -12
- package/dist/scaffolds/notis-database/lib/types.ts +0 -132
- package/dist/scaffolds/notis-database/lib/utils.ts +0 -6
- 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/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +0 -1839
- package/dist/scaffolds/notis-database/notis.config.ts +0 -73
- package/dist/scaffolds/notis-database/package-lock.json +0 -3935
- package/dist/scaffolds/notis-database/package.json +0 -32
- package/dist/scaffolds/notis-database/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +0 -196
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +0 -229
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +0 -83
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +0 -220
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-database/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-database/src/dev-main.tsx +0 -23
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +0 -561
- package/dist/scaffolds/notis-database/tailwind.config.ts +0 -59
- package/dist/scaffolds/notis-database/tsconfig.json +0 -23
- package/dist/scaffolds/notis-database/vite.config.ts +0 -22
- package/dist/scaffolds/notis-journal/CHANGELOG.md +0 -29
- package/dist/scaffolds/notis-journal/app/globals.css +0 -37
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +0 -513
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +0 -362
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +0 -337
- package/dist/scaffolds/notis-journal/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-journal/app/page.tsx +0 -486
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-journal/components.json +0 -20
- package/dist/scaffolds/notis-journal/index.html +0 -12
- package/dist/scaffolds/notis-journal/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +0 -132
- package/dist/scaffolds/notis-journal/notis.config.ts +0 -93
- package/dist/scaffolds/notis-journal/package-lock.json +0 -4615
- package/dist/scaffolds/notis-journal/package.json +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +0 -196
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +0 -229
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +0 -83
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +0 -220
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-journal/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +0 -120
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +0 -58
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +0 -197
- package/dist/scaffolds/notis-journal/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-journal/tsconfig.json +0 -23
- package/dist/scaffolds/notis-journal/vite.config.ts +0 -10
- package/dist/scaffolds/notis-notes/CHANGELOG.md +0 -5
- package/dist/scaffolds/notis-notes/app/globals.css +0 -3
- package/dist/scaffolds/notis-notes/app/layout.tsx +0 -6
- package/dist/scaffolds/notis-notes/app/page.tsx +0 -1943
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +0 -596
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +0 -28
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +0 -53
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +0 -56
- package/dist/scaffolds/notis-notes/components.json +0 -20
- package/dist/scaffolds/notis-notes/lib/utils.ts +0 -6
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +0 -144
- 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/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +0 -752
- package/dist/scaffolds/notis-notes/notis.config.ts +0 -80
- package/dist/scaffolds/notis-notes/package-lock.json +0 -4636
- package/dist/scaffolds/notis-notes/package.json +0 -35
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +0 -196
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +0 -229
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +0 -83
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +0 -220
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-notes/postcss.config.mjs +0 -8
- package/dist/scaffolds/notis-notes/tailwind.config.ts +0 -58
- package/dist/scaffolds/notis-notes/tsconfig.json +0 -23
- package/dist/scaffolds/notis-notes/vite.config.ts +0 -10
- package/dist/scaffolds/notis-random/CHANGELOG.md +0 -15
- package/dist/scaffolds/notis-random/README.md +0 -33
- package/dist/scaffolds/notis-random/app/globals.css +0 -11
- package/dist/scaffolds/notis-random/app/history/page.tsx +0 -67
- package/dist/scaffolds/notis-random/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-random/app/page.tsx +0 -289
- package/dist/scaffolds/notis-random/components/ui/button.tsx +0 -50
- package/dist/scaffolds/notis-random/components/ui/card.tsx +0 -16
- package/dist/scaffolds/notis-random/components/ui/input.tsx +0 -23
- package/dist/scaffolds/notis-random/components.json +0 -20
- package/dist/scaffolds/notis-random/index.html +0 -12
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +0 -128
- package/dist/scaffolds/notis-random/lib/rng.ts +0 -202
- package/dist/scaffolds/notis-random/lib/roll-record.ts +0 -189
- package/dist/scaffolds/notis-random/lib/utils.ts +0 -25
- 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/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +0 -753
- package/dist/scaffolds/notis-random/notis.config.ts +0 -86
- package/dist/scaffolds/notis-random/package-lock.json +0 -4513
- package/dist/scaffolds/notis-random/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/package.json +0 -36
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +0 -93
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +0 -60
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +0 -278
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -91
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -39
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +0 -196
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +0 -229
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +0 -41
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +0 -121
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +0 -539
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +0 -34
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +0 -49
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +0 -64
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +0 -95
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +0 -83
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +0 -43
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +0 -220
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +0 -186
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +0 -15
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +0 -56
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +0 -15
- package/dist/scaffolds/notis-random/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-random/src/dev-main.tsx +0 -70
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +0 -129
- package/dist/scaffolds/notis-random/tailwind.config.ts +0 -50
- package/dist/scaffolds/notis-random/tsconfig.json +0 -23
- package/dist/scaffolds/notis-random/vite.config.ts +0 -11
- package/dist/scaffolds.json +0 -49
- package/template/metadata/screenshot-1.png +0 -0
- package/template/metadata/screenshot-2.png +0 -0
- package/template/metadata/screenshot-3.png +0 -0
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
useEffect,
|
|
3
|
+
import React, {
|
|
5
4
|
useMemo,
|
|
6
|
-
useRef,
|
|
7
5
|
useState,
|
|
8
6
|
type CSSProperties,
|
|
9
7
|
type ReactElement,
|
|
10
|
-
type ReactNode,
|
|
11
8
|
} from 'react';
|
|
9
|
+
import type { ResolvedCollectionAction } from '../interactions/actions';
|
|
10
|
+
import { shortcutDisplay, useShortcuts, type ShortcutDefinition } from '../interactions/shortcuts';
|
|
11
|
+
import type { ShortcutScope } from '../interactions/shortcuts';
|
|
12
12
|
|
|
13
|
-
export
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
icon: ReactNode;
|
|
17
|
-
/** Single-key (e.g. "E", "#") or "Mod+K" shortcut. Bound while the bar is mounted with count > 0. */
|
|
18
|
-
shortcut?: string;
|
|
19
|
-
onRun: () => void | Promise<void>;
|
|
20
|
-
/** Hint for future destructive styling. Currently unused visually. */
|
|
21
|
-
destructive?: boolean;
|
|
22
|
-
}
|
|
13
|
+
export type MultiSelectAction = ResolvedCollectionAction;
|
|
23
14
|
|
|
24
15
|
export interface MultiSelectActionBarProps {
|
|
25
16
|
selectedCount: number;
|
|
@@ -30,73 +21,10 @@ export interface MultiSelectActionBarProps {
|
|
|
30
21
|
className?: string;
|
|
31
22
|
/** Optional override for the bar's positioning style. Defaults to bottom-center floating. */
|
|
32
23
|
style?: CSSProperties;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
needsMod: boolean;
|
|
38
|
-
needsShift: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function parseShortcut(raw: string): ParsedShortcut | null {
|
|
42
|
-
const parts = raw.split('+').map((p) => p.trim()).filter(Boolean);
|
|
43
|
-
if (parts.length === 0) return null;
|
|
44
|
-
let needsMod = false;
|
|
45
|
-
let needsShift = false;
|
|
46
|
-
let key = '';
|
|
47
|
-
for (const part of parts) {
|
|
48
|
-
const lower = part.toLowerCase();
|
|
49
|
-
if (lower === 'mod' || lower === 'cmd' || lower === 'ctrl' || lower === 'meta') {
|
|
50
|
-
needsMod = true;
|
|
51
|
-
} else if (lower === 'shift') {
|
|
52
|
-
needsShift = true;
|
|
53
|
-
} else {
|
|
54
|
-
key = lower;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!key) return null;
|
|
58
|
-
return { key, needsMod, needsShift };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function shortcutMatches(shortcut: ParsedShortcut, event: KeyboardEvent): boolean {
|
|
62
|
-
const eventKey = event.key.toLowerCase();
|
|
63
|
-
// Special-case "#" so Shift+3 matches without the consumer specifying Shift.
|
|
64
|
-
if (shortcut.key === '#') {
|
|
65
|
-
if (eventKey === '#') return true;
|
|
66
|
-
if (event.shiftKey && eventKey === '3') return true;
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
if (eventKey !== shortcut.key) return false;
|
|
70
|
-
if (shortcut.needsMod !== Boolean(event.metaKey || event.ctrlKey)) return false;
|
|
71
|
-
if (shortcut.needsShift !== event.shiftKey) return false;
|
|
72
|
-
if (event.altKey) return false;
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isEditableEvent(event: Event): boolean {
|
|
77
|
-
// Notis apps mount inside a Shadow DOM, so a document-level listener sees
|
|
78
|
-
// `event.target` retargeted to the shadow host. `composedPath()[0]` pierces
|
|
79
|
-
// the shadow boundary to the real focused element, so action-bar shortcuts
|
|
80
|
-
// don't fire while the user is typing in an app input.
|
|
81
|
-
const target = event.composedPath?.()[0] ?? event.target;
|
|
82
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
83
|
-
if (target.isContentEditable) return true;
|
|
84
|
-
const tag = target.tagName;
|
|
85
|
-
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function shortcutDisplay(raw: string): string {
|
|
89
|
-
const parts = raw.split('+').map((p) => p.trim()).filter(Boolean);
|
|
90
|
-
return parts
|
|
91
|
-
.map((part) => {
|
|
92
|
-
const lower = part.toLowerCase();
|
|
93
|
-
if (lower === 'mod' || lower === 'cmd' || lower === 'meta') return '⌘';
|
|
94
|
-
if (lower === 'ctrl') return '⌃';
|
|
95
|
-
if (lower === 'shift') return '⇧';
|
|
96
|
-
if (lower === 'alt' || lower === 'option') return '⌥';
|
|
97
|
-
return part.length === 1 ? part.toUpperCase() : part;
|
|
98
|
-
})
|
|
99
|
-
.join('');
|
|
24
|
+
/** Disable action key bindings while leaving the visible toolbar mounted. */
|
|
25
|
+
shortcutsEnabled?: boolean;
|
|
26
|
+
/** Override the default collection shortcut scope. */
|
|
27
|
+
shortcutScope?: ShortcutScope;
|
|
100
28
|
}
|
|
101
29
|
|
|
102
30
|
const containerBaseStyle: CSSProperties = {
|
|
@@ -180,43 +108,25 @@ export function MultiSelectActionBar({
|
|
|
180
108
|
itemLabel,
|
|
181
109
|
className,
|
|
182
110
|
style,
|
|
111
|
+
shortcutsEnabled = true,
|
|
112
|
+
shortcutScope = 'collection',
|
|
183
113
|
}: MultiSelectActionBarProps): ReactElement | null {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
})
|
|
194
|
-
.filter((entry): entry is { action: MultiSelectAction; parsed: ParsedShortcut } => entry !== null);
|
|
114
|
+
const actionShortcuts = useMemo<ShortcutDefinition[]>(() => {
|
|
115
|
+
return actions.flatMap((action): ShortcutDefinition[] => {
|
|
116
|
+
if (!action.shortcut || action.disabled || action.pending) return [];
|
|
117
|
+
return [{
|
|
118
|
+
id: `collection.action.${action.id}`,
|
|
119
|
+
keys: action.shortcut,
|
|
120
|
+
label: action.label,
|
|
121
|
+
onTrigger: () => action.onRun(),
|
|
122
|
+
}];
|
|
123
|
+
});
|
|
195
124
|
}, [actions]);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (isEditableEvent(event)) return;
|
|
202
|
-
for (const { action, parsed } of parsedShortcuts) {
|
|
203
|
-
if (shortcutMatches(parsed, event)) {
|
|
204
|
-
event.preventDefault();
|
|
205
|
-
event.stopPropagation();
|
|
206
|
-
if (typeof event.stopImmediatePropagation === 'function') {
|
|
207
|
-
event.stopImmediatePropagation();
|
|
208
|
-
}
|
|
209
|
-
void action.onRun();
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
document.addEventListener('keydown', handleKeyDown, true);
|
|
216
|
-
return () => {
|
|
217
|
-
document.removeEventListener('keydown', handleKeyDown, true);
|
|
218
|
-
};
|
|
219
|
-
}, [parsedShortcuts, selectedCount]);
|
|
125
|
+
useShortcuts(actionShortcuts, {
|
|
126
|
+
enabled: shortcutsEnabled && selectedCount > 0,
|
|
127
|
+
scope: shortcutScope,
|
|
128
|
+
priority: 25,
|
|
129
|
+
});
|
|
220
130
|
|
|
221
131
|
if (selectedCount === 0) return null;
|
|
222
132
|
|
|
@@ -258,21 +168,29 @@ function ActionButton({ action }: { action: MultiSelectAction }) {
|
|
|
258
168
|
color: hover
|
|
259
169
|
? 'hsl(var(--background))'
|
|
260
170
|
: baseButtonStyle.color,
|
|
171
|
+
opacity: action.disabled ? 0.45 : 1,
|
|
172
|
+
cursor: action.disabled || action.pending ? 'not-allowed' : 'pointer',
|
|
261
173
|
};
|
|
262
174
|
|
|
263
175
|
return (
|
|
264
176
|
<button
|
|
265
177
|
type="button"
|
|
266
|
-
onClick={() =>
|
|
178
|
+
onClick={() => {
|
|
179
|
+
if (!action.disabled && !action.pending) void action.onRun();
|
|
180
|
+
}}
|
|
181
|
+
disabled={action.disabled || action.pending}
|
|
182
|
+
aria-busy={action.pending || undefined}
|
|
183
|
+
aria-keyshortcuts={action.shortcut || undefined}
|
|
184
|
+
data-destructive={action.destructive ? 'true' : 'false'}
|
|
267
185
|
onMouseEnter={() => setHover(true)}
|
|
268
186
|
onMouseLeave={() => setHover(false)}
|
|
269
187
|
onFocus={() => setHover(true)}
|
|
270
188
|
onBlur={() => setHover(false)}
|
|
271
189
|
style={buttonStyle}
|
|
272
190
|
>
|
|
273
|
-
<span aria-hidden style={iconSlotStyle}>{action.icon}</span>
|
|
191
|
+
{action.icon ? <span aria-hidden style={iconSlotStyle}>{action.icon}</span> : null}
|
|
274
192
|
{display ? <kbd aria-hidden style={keycapStyle}>{display}</kbd> : null}
|
|
275
|
-
<span>{action.label}</span>
|
|
193
|
+
<span>{action.pending ? `${action.label}…` : action.label}</span>
|
|
276
194
|
</button>
|
|
277
195
|
);
|
|
278
196
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactElement } from 'react';
|
|
3
|
+
import React, { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactElement } from 'react';
|
|
4
4
|
|
|
5
5
|
export interface MultiSelectCheckboxProps {
|
|
6
6
|
isSelected: boolean;
|
|
7
7
|
onClick: (event: ReactMouseEvent) => void;
|
|
8
|
+
disabled?: boolean;
|
|
8
9
|
/** When true, the checkbox is always visible. Default false (hover/focus reveal via the parent's :hover state). */
|
|
9
10
|
alwaysVisible?: boolean;
|
|
10
11
|
/** Optional aria-label override. Defaults to "Select item" / "Deselect item". */
|
|
@@ -40,6 +41,7 @@ const checkPath = 'M3.5 7.5l2.5 2.5 6.5-6.5';
|
|
|
40
41
|
export function MultiSelectCheckbox({
|
|
41
42
|
isSelected,
|
|
42
43
|
onClick,
|
|
44
|
+
disabled = false,
|
|
43
45
|
alwaysVisible = false,
|
|
44
46
|
ariaLabel,
|
|
45
47
|
className,
|
|
@@ -49,6 +51,7 @@ export function MultiSelectCheckbox({
|
|
|
49
51
|
...baseStyle,
|
|
50
52
|
...(isSelected ? selectedStyle : null),
|
|
51
53
|
...(alwaysVisible || isSelected ? { opacity: 1 } : null),
|
|
54
|
+
...(disabled ? { opacity: 0.45, cursor: 'not-allowed' } : null),
|
|
52
55
|
...(style || null),
|
|
53
56
|
};
|
|
54
57
|
|
|
@@ -62,6 +65,7 @@ export function MultiSelectCheckbox({
|
|
|
62
65
|
type="button"
|
|
63
66
|
role="checkbox"
|
|
64
67
|
aria-checked={isSelected}
|
|
68
|
+
disabled={disabled}
|
|
65
69
|
aria-label={ariaLabel ?? (isSelected ? 'Deselect item' : 'Select item')}
|
|
66
70
|
onClick={onClick}
|
|
67
71
|
onMouseDown={(e) => e.stopPropagation()}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { CSSProperties, ClipboardEvent, ReactNode } from 'react';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
import { useNotisRuntime } from '../provider';
|
|
4
|
+
import type { ContextResource, ContextSelection } from '../runtime';
|
|
5
|
+
|
|
6
|
+
export const NOTIS_CONTEXT_CLIPBOARD_TYPE = 'application/x-notis-context+json';
|
|
7
|
+
|
|
8
|
+
export interface NotisSelectionBoundaryProps {
|
|
9
|
+
resource?: ContextResource | null;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function selectionId(): string {
|
|
16
|
+
return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
17
|
+
? crypto.randomUUID()
|
|
18
|
+
: `selection-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Adds structured provenance to ordinary copied text. Pasting still works in
|
|
23
|
+
* every app because `text/plain` is preserved; Notis chat additionally reads
|
|
24
|
+
* the custom MIME payload and renders a separate quote pill.
|
|
25
|
+
*/
|
|
26
|
+
export function NotisSelectionBoundary({
|
|
27
|
+
resource,
|
|
28
|
+
children,
|
|
29
|
+
className,
|
|
30
|
+
style,
|
|
31
|
+
}: NotisSelectionBoundaryProps) {
|
|
32
|
+
const runtime = useNotisRuntime();
|
|
33
|
+
const onCopy = useCallback((event: ClipboardEvent<HTMLDivElement>) => {
|
|
34
|
+
const selection = window.getSelection();
|
|
35
|
+
const text = selection?.toString().trim() ?? '';
|
|
36
|
+
if (
|
|
37
|
+
!text
|
|
38
|
+
|| !selection
|
|
39
|
+
|| !event.currentTarget.contains(selection.anchorNode)
|
|
40
|
+
|| !event.currentTarget.contains(selection.focusNode)
|
|
41
|
+
) return;
|
|
42
|
+
|
|
43
|
+
const payload: ContextSelection = {
|
|
44
|
+
id: selectionId(),
|
|
45
|
+
text: text.slice(0, 12_000),
|
|
46
|
+
...(resource ? { resource: { ...resource, snapshot: undefined } } : {}),
|
|
47
|
+
};
|
|
48
|
+
event.clipboardData.setData('text/plain', text);
|
|
49
|
+
event.clipboardData.setData(NOTIS_CONTEXT_CLIPBOARD_TYPE, JSON.stringify(payload));
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
runtime?.captureContextSelection?.(payload);
|
|
52
|
+
}, [resource, runtime]);
|
|
53
|
+
|
|
54
|
+
return <div className={className} style={style} onCopy={onCopy}>{children}</div>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { type CSSProperties, type ReactElement } from 'react';
|
|
4
|
+
import { shortcutDisplay } from '../interactions/shortcuts';
|
|
5
|
+
|
|
6
|
+
export interface ShortcutHint {
|
|
7
|
+
id: string;
|
|
8
|
+
keys: string;
|
|
9
|
+
label: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ShortcutHintsProps {
|
|
13
|
+
shortcuts: ShortcutHint[];
|
|
14
|
+
className?: string;
|
|
15
|
+
style?: CSSProperties;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const listStyle: CSSProperties = {
|
|
19
|
+
display: 'flex',
|
|
20
|
+
flexWrap: 'wrap',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: '0.75rem',
|
|
23
|
+
color: 'hsl(var(--muted-foreground))',
|
|
24
|
+
fontSize: '12px',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const hintStyle: CSSProperties = {
|
|
28
|
+
display: 'inline-flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
gap: '0.35rem',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const keyStyle: CSSProperties = {
|
|
34
|
+
minWidth: '20px',
|
|
35
|
+
border: '1px solid hsl(var(--border))',
|
|
36
|
+
borderRadius: '4px',
|
|
37
|
+
background: 'hsl(var(--muted))',
|
|
38
|
+
color: 'hsl(var(--foreground))',
|
|
39
|
+
padding: '1px 5px',
|
|
40
|
+
textAlign: 'center',
|
|
41
|
+
font: 'inherit',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function ShortcutHints({ shortcuts, className, style }: ShortcutHintsProps): ReactElement | null {
|
|
45
|
+
if (shortcuts.length === 0) return null;
|
|
46
|
+
return (
|
|
47
|
+
<div aria-label="Keyboard shortcuts" className={className} style={{ ...listStyle, ...style }}>
|
|
48
|
+
{shortcuts.map((shortcut) => (
|
|
49
|
+
<span key={shortcut.id} style={hintStyle}>
|
|
50
|
+
<kbd style={keyStyle}>{shortcutDisplay(shortcut.keys)}</kbd>
|
|
51
|
+
<span>{shortcut.label}</span>
|
|
52
|
+
</span>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -83,23 +83,45 @@ export interface NotisAppAuthor {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export interface NotisAppSkillConfig {
|
|
86
|
+
/** Stable source-owned key used by other app declarations. */
|
|
86
87
|
key: string;
|
|
88
|
+
/**
|
|
89
|
+
* Path to the skill, relative to notis.config.ts. Either a Markdown file
|
|
90
|
+
* (`./skills/onboarding.md`) or a directory holding SKILL.md plus its
|
|
91
|
+
* supporting files (`./skills/onboarding/`), which are packaged on deploy
|
|
92
|
+
* and materialized next to SKILL.md in the sandbox.
|
|
93
|
+
*/
|
|
87
94
|
path: string;
|
|
95
|
+
/** User-facing name used for the installed skill. */
|
|
88
96
|
name: string;
|
|
89
97
|
description?: string;
|
|
90
98
|
}
|
|
91
99
|
|
|
92
100
|
export interface NotisAppOnboardingConfig {
|
|
101
|
+
/** Key of a skill declared in `skills`. */
|
|
93
102
|
skill: string;
|
|
103
|
+
/** Editable message placed in Notis when onboarding is opened. */
|
|
94
104
|
prompt: string;
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
export interface NotisAppScreenshotConfig {
|
|
108
|
+
/** Conventional metadata/screenshot-N.png source path. */
|
|
98
109
|
path: string;
|
|
110
|
+
/** Meaningful description used by the Store gallery and assistive technology. */
|
|
99
111
|
alt: string;
|
|
112
|
+
/** Route slug captured by `notis apps screenshot`. */
|
|
100
113
|
route?: string;
|
|
114
|
+
/**
|
|
115
|
+
* Named scenario from metadata/screenshot-fixtures.json. Its `tools` and
|
|
116
|
+
* `requests` override the file-level ones key by key for this capture, and
|
|
117
|
+
* its `actions` run once the route has mounted -- so one route can be shown
|
|
118
|
+
* in several states (populated, empty, a panel opened) without the states
|
|
119
|
+
* leaking into each other.
|
|
120
|
+
*/
|
|
101
121
|
scenario?: string;
|
|
122
|
+
/** Optional CSS selector captured as the truthful focal region for this Store image. */
|
|
102
123
|
focus?: string;
|
|
124
|
+
/** Portal color scheme used while rendering this screenshot. Defaults to light. */
|
|
103
125
|
theme?: 'light' | 'dark';
|
|
104
126
|
}
|
|
105
127
|
|
|
@@ -131,11 +153,47 @@ export interface NotisAppCapabilities {
|
|
|
131
153
|
* bound to the app's own databases.
|
|
132
154
|
*/
|
|
133
155
|
workspaceDatabases?: 'read';
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Read a few facts about the user's cloud computer: whether a sandbox exists
|
|
159
|
+
* and is running, and whether the GitHub CLI is signed in there.
|
|
160
|
+
*
|
|
161
|
+
* Without this an app has to infer them — the Workspaces app treated a
|
|
162
|
+
* configured repository as proof that `gh auth login` had happened, which
|
|
163
|
+
* cannot show an account name and cannot notice a revoked credential.
|
|
164
|
+
* `'read'` never creates, resumes or commands a sandbox. Read it with
|
|
165
|
+
* `useCloudComputer()`.
|
|
166
|
+
*
|
|
167
|
+
* `'shell'` additionally asks to command the cloud computer: it unlocks
|
|
168
|
+
* `LOCAL_NOTIS_RUN_SANDBOX_SHELL` and the sandbox file tools from this app's
|
|
169
|
+
* views (they are denied to every view otherwise), and implies the read
|
|
170
|
+
* facts. This is the same authority the user's own agent has on the sandbox,
|
|
171
|
+
* so the user is asked for it explicitly at install or in the Store grant
|
|
172
|
+
* step; declare it only when the app's core actions genuinely run there.
|
|
173
|
+
*/
|
|
174
|
+
cloudComputer?: 'read' | 'shell';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Execution hint for a tool whose public name is generated by a provider.
|
|
179
|
+
*
|
|
180
|
+
* MCP public names may be truncated or deduplicated, so the upstream action
|
|
181
|
+
* cannot always be reconstructed from the final `LOCAL_MCP_*` name. Binding
|
|
182
|
+
* the two lets the host skip provider schema discovery on direct calls while
|
|
183
|
+
* the public name remains the user-approved permission boundary.
|
|
184
|
+
*/
|
|
185
|
+
export interface NotisAppToolBinding {
|
|
186
|
+
/** Exact final name also present in `tools`. */
|
|
187
|
+
name: string;
|
|
188
|
+
/** Exact upstream MCP action name, for example `execute_sql`. */
|
|
189
|
+
providerToolName: string;
|
|
134
190
|
}
|
|
135
191
|
|
|
136
192
|
export interface NotisAppConfig {
|
|
137
193
|
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
138
194
|
name: string;
|
|
195
|
+
/** Stable local-development slug. Defaults to a slug derived from `name`. */
|
|
196
|
+
devSlug?: string;
|
|
139
197
|
/** Human display title, Raycast-style. Falls back to `name`. */
|
|
140
198
|
title?: string;
|
|
141
199
|
description?: string;
|
|
@@ -154,6 +212,7 @@ export interface NotisAppConfig {
|
|
|
154
212
|
tagline?: string;
|
|
155
213
|
/** @deprecated Add release entries to the root CHANGELOG.md instead. */
|
|
156
214
|
versionNotes?: string;
|
|
215
|
+
/** Editorial screenshot order and capture scenarios for the Store listing. */
|
|
157
216
|
screenshots?: NotisAppScreenshotConfig[];
|
|
158
217
|
/**
|
|
159
218
|
* Databases this app owns. A bare string publishes structure only; use the
|
|
@@ -167,8 +226,20 @@ export interface NotisAppConfig {
|
|
|
167
226
|
*/
|
|
168
227
|
capabilities?: NotisAppCapabilities;
|
|
169
228
|
routes?: NotisRouteConfig[];
|
|
229
|
+
/**
|
|
230
|
+
* Final tool names this app can call at runtime, enforced server-side. Use
|
|
231
|
+
* names returned by shared discovery, including native `LOCAL_NOTIS_*`,
|
|
232
|
+
* connected-service names such as `GMAIL_SEND_EMAIL`,
|
|
233
|
+
* `LOCAL_POSTFORME_*`, and `LOCAL_MCP_<SERVER>_<TOOL>`. App code calls
|
|
234
|
+
* each declared name directly with `useTool`; metered calls use the shared
|
|
235
|
+
* credit-cap and usage-billing path.
|
|
236
|
+
*/
|
|
170
237
|
tools?: string[];
|
|
238
|
+
/** Optional execution bindings for provider-generated tool names. */
|
|
239
|
+
toolBindings?: NotisAppToolBinding[];
|
|
240
|
+
/** Skills shipped from this app's source tree. */
|
|
171
241
|
skills?: NotisAppSkillConfig[];
|
|
242
|
+
/** Optional onboarding entrypoint exposed from the app sidebar. */
|
|
172
243
|
onboarding?: NotisAppOnboardingConfig;
|
|
173
244
|
}
|
|
174
245
|
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
DatabasePropertyType,
|
|
13
13
|
DocumentContentType,
|
|
14
14
|
DocumentRecord,
|
|
15
|
+
SecretPropertyValue,
|
|
15
16
|
} from './runtime';
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -45,6 +46,23 @@ export function extractRichText(value: unknown): string {
|
|
|
45
46
|
.join('');
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Reads a `secret` property value. The platform only ever sends the pointer
|
|
51
|
+
* ({present, reference, status, metadata}), so this rebuilds it field by field
|
|
52
|
+
* rather than passing the payload through — an app can never surface secret
|
|
53
|
+
* material through this helper, whatever the server sent.
|
|
54
|
+
*/
|
|
55
|
+
export function getSecretValue(value: unknown): SecretPropertyValue {
|
|
56
|
+
const record = asRecord(value);
|
|
57
|
+
const metadata = asRecord(record?.metadata);
|
|
58
|
+
return {
|
|
59
|
+
present: record?.present === true,
|
|
60
|
+
reference: optionalString(record?.reference),
|
|
61
|
+
status: optionalString(record?.status),
|
|
62
|
+
metadata,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
/** Extracts the ids of a normalized relation property value. */
|
|
49
67
|
export function getRelationIds(value: unknown): string[] {
|
|
50
68
|
if (!Array.isArray(value)) return [];
|
|
@@ -81,12 +99,15 @@ export function normalizePropertyValue(value: unknown): unknown {
|
|
|
81
99
|
return items.map((item) => optionalString(asRecord(item)?.id) ?? item).filter(Boolean);
|
|
82
100
|
}
|
|
83
101
|
if (type === 'date') return optionalString(asRecord(record.date)?.start) ?? record.date ?? null;
|
|
102
|
+
// Before the `type in record` fallthrough: a secret value has no `secret`
|
|
103
|
+
// key, so passing it through would hand the caller the raw payload.
|
|
104
|
+
if (type === 'secret') return getSecretValue(record);
|
|
84
105
|
if (type in record) return record[type];
|
|
85
106
|
return value;
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
function normalizeContentType(value: unknown): DocumentContentType | null {
|
|
89
|
-
return value === 'markdown' || value === 'file' ? value : null;
|
|
110
|
+
return value === 'markdown' || value === 'file' || value === 'view' ? value : null;
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
/**
|
|
@@ -119,6 +140,12 @@ export function normalizeDocumentRecord(value: unknown): DocumentRecord {
|
|
|
119
140
|
: null,
|
|
120
141
|
contentMarkdown: optionalString(record.contentMarkdown) ?? optionalString(record.content_markdown),
|
|
121
142
|
plainText: optionalString(record.plainText) ?? optionalString(record.plain_text),
|
|
143
|
+
viewType: optionalString(record.viewType) ?? optionalString(record.view_type),
|
|
144
|
+
viewState: asRecord(record.viewState) ?? asRecord(record.view_state),
|
|
145
|
+
viewRevision:
|
|
146
|
+
typeof (record.viewRevision ?? record.view_revision) === 'number'
|
|
147
|
+
? Number(record.viewRevision ?? record.view_revision)
|
|
148
|
+
: null,
|
|
122
149
|
createdAt:
|
|
123
150
|
optionalString(record.createdAt)
|
|
124
151
|
?? optionalString(record.created_at)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
import { useNotisRuntime } from '../provider';
|
|
3
|
+
import type { ContextResource } from '../runtime';
|
|
4
|
+
|
|
5
|
+
function stableResource(resource: ContextResource | null): string {
|
|
6
|
+
return JSON.stringify(resource);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Keep Notis aware of the resource currently open inside an app view. */
|
|
10
|
+
export function useActiveResource(resource: ContextResource | null): void {
|
|
11
|
+
const runtime = useNotisRuntime();
|
|
12
|
+
const signature = stableResource(resource);
|
|
13
|
+
const stable = useMemo(() => resource, [signature]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
runtime?.publishActiveResource?.(stable);
|
|
17
|
+
return () => runtime?.publishActiveResource?.(null);
|
|
18
|
+
}, [runtime, stable]);
|
|
19
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import type { CloudComputerFacts } from '../runtime';
|
|
6
|
+
|
|
7
|
+
export interface UseCloudComputerResult {
|
|
8
|
+
/**
|
|
9
|
+
* The facts, or null while the first read is in flight. `facts.available`
|
|
10
|
+
* is false when this host cannot answer — render the app's own fallback.
|
|
11
|
+
*/
|
|
12
|
+
facts: CloudComputerFacts | null;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
/** Re-read the facts. The platform caches them for a few minutes. */
|
|
16
|
+
refresh: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const UNAVAILABLE: CloudComputerFacts = {
|
|
20
|
+
available: false,
|
|
21
|
+
reason: 'unsupported_host',
|
|
22
|
+
sandbox: null,
|
|
23
|
+
cli_auth: {
|
|
24
|
+
gh: { authenticated: null, account: null, checked_at: null, reason: 'unsupported_host' },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read-only facts about the user's cloud computer.
|
|
30
|
+
*
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const { facts } = useCloudComputer();
|
|
33
|
+
* const gh = facts?.available ? facts.cli_auth.gh : null;
|
|
34
|
+
*
|
|
35
|
+
* return gh?.authenticated
|
|
36
|
+
* ? <p>Signed in as {gh.account}</p>
|
|
37
|
+
* : <GithubConnect />;
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* Requires `capabilities.cloudComputer: 'read'` in `notis.config.ts` and the
|
|
41
|
+
* user's approval at install time. It answers with state the platform already
|
|
42
|
+
* holds: reading it never creates, resumes or commands a sandbox, and the GitHub
|
|
43
|
+
* probe only runs when the sandbox is already awake. `authenticated: null`
|
|
44
|
+
* therefore means *unknown*, not *signed out* — keep the app's own fallback for
|
|
45
|
+
* that case and for hosts that answer `{ available: false }` (the dev harness,
|
|
46
|
+
* the vite preview).
|
|
47
|
+
*/
|
|
48
|
+
export function useCloudComputer(): UseCloudComputerResult {
|
|
49
|
+
const runtime = useNotisRuntime();
|
|
50
|
+
const [facts, setFacts] = useState<CloudComputerFacts | null>(null);
|
|
51
|
+
// True from the first committed render: the initial read is already queued
|
|
52
|
+
// in an effect, and `{ loading: false, facts: null }` would flash a
|
|
53
|
+
// consumer's fallback branch before the answer arrives.
|
|
54
|
+
const [loading, setLoading] = useState(true);
|
|
55
|
+
const [error, setError] = useState<Error | null>(null);
|
|
56
|
+
const mounted = useRef(true);
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
mounted.current = true;
|
|
60
|
+
return () => {
|
|
61
|
+
mounted.current = false;
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const read = useCallback(async (options?: { refresh?: boolean }) => {
|
|
66
|
+
if (!runtime?.cloudComputerFacts) {
|
|
67
|
+
setFacts(UNAVAILABLE);
|
|
68
|
+
setLoading(false);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setLoading(true);
|
|
73
|
+
setError(null);
|
|
74
|
+
try {
|
|
75
|
+
const next = await runtime.cloudComputerFacts(options);
|
|
76
|
+
if (mounted.current) setFacts(next);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
79
|
+
if (mounted.current) {
|
|
80
|
+
setError(e);
|
|
81
|
+
// A refused or failed read is the same product state as a host that
|
|
82
|
+
// cannot answer: the app shows its fallback instead of an error.
|
|
83
|
+
setFacts(UNAVAILABLE);
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
if (mounted.current) setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
}, [runtime]);
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
void read();
|
|
92
|
+
}, [read]);
|
|
93
|
+
|
|
94
|
+
const refresh = useCallback(() => read({ refresh: true }), [read]);
|
|
95
|
+
|
|
96
|
+
return { facts, loading, error, refresh };
|
|
97
|
+
}
|