@notis_ai/cli 0.2.18 → 0.2.20
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 +542 -146
- package/bin/check-runtime.js +15 -0
- package/bin/notis.js +2 -0
- package/config/notis_app_design_rules.json +320 -0
- package/config/portal-route-roots.json +45 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +42325 -18273
- package/dist/base-skills/notis-cli/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-cli/SKILL.md +51 -280
- package/dist/base-skills/notis-cli/references/app-delivery.md +17 -0
- package/dist/base-skills/notis-cli/references/intelligence.md +94 -0
- package/dist/base-skills/notis-cli/references/native-databases.md +21 -0
- package/dist/base-skills/notis-cli/references/tool-examples.md +56 -0
- package/dist/base-skills/notis-cli/references/troubleshooting.md +67 -0
- package/dist/base-skills/notis-query/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-query/SKILL.md +35 -699
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -0
- package/dist/base-skills/notis-query/references/documents.md +74 -0
- package/dist/base-skills/notis-query/references/query.md +76 -0
- package/dist/base-skills/notis-spaces/.notis-base-skill.json +1 -0
- package/dist/base-skills/notis-spaces/SKILL.md +218 -0
- package/dist/base-skills/notis-spaces/references/architecture.md +116 -0
- package/dist/base-skills/notis-spaces/references/context.md +81 -0
- package/dist/base-skills/notis-spaces/references/design.md +141 -0
- package/dist/base-skills/notis-spaces/references/fixtures.md +29 -0
- package/dist/base-skills/notis-spaces/references/memory.md +58 -0
- package/dist/base-skills/notis-spaces/references/reading.md +73 -0
- package/dist/base-skills/notis-spaces/references/release.md +120 -0
- package/dist/base-skills/notis-spaces/references/reports.md +77 -0
- package/dist/base-skills/notis-spaces/references/sdk.md +273 -0
- package/dist/base-skills/notis-spaces/references/store.md +118 -0
- package/dist/base-skills/notis-spaces/references/troubleshooting.md +28 -0
- package/dist/base-skills/notis-spaces/references/views.md +229 -0
- package/{template/packages → dist}/sdk/package.json +9 -10
- package/dist/sdk/src/agentContext.ts +36 -0
- package/dist/sdk/src/bundleModule.ts +175 -0
- package/dist/sdk/src/components/Dialog.tsx +103 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +113 -0
- package/dist/sdk/src/components/DocumentPage.tsx +184 -0
- package/{template/packages → dist}/sdk/src/components/Markdown.tsx +9 -3
- package/{template/packages → dist}/sdk/src/components/MarkdownEditor.tsx +26 -5
- package/{template/packages → dist}/sdk/src/components/MultiSelectActionBar.tsx +95 -12
- package/{template/packages → dist}/sdk/src/components/MultiSelectCheckbox.tsx +4 -2
- package/dist/sdk/src/components/NotisCommentBoundary.tsx +172 -0
- package/{template/packages → dist}/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
- package/dist/sdk/src/components/RecordComponents.tsx +111 -0
- package/dist/sdk/src/components/Skeleton.tsx +38 -0
- package/dist/sdk/src/config.ts +31 -0
- package/{template/packages → dist}/sdk/src/documents.ts +21 -6
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useAgentContext.ts +23 -0
- package/{template/packages → dist}/sdk/src/hooks/useBackend.ts +3 -2
- package/dist/sdk/src/hooks/useCloudComputer.ts +94 -0
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/{template/packages → dist}/sdk/src/hooks/useCollectionInteractions.ts +157 -147
- package/dist/sdk/src/hooks/useDatabaseSchema.ts +50 -0
- package/{template/packages → dist}/sdk/src/hooks/useDatabaseSubscription.ts +2 -2
- package/dist/sdk/src/hooks/useDocument.ts +51 -0
- package/{template/packages → dist}/sdk/src/hooks/useDocuments.ts +25 -59
- package/{template/packages → dist}/sdk/src/hooks/useHandover.ts +7 -7
- package/dist/sdk/src/hooks/useLongPressSelection.ts +79 -0
- package/dist/sdk/src/hooks/useMarqueeSelection.ts +194 -0
- package/{template/packages → dist}/sdk/src/hooks/useMultiSelect.ts +2 -8
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +143 -0
- package/{template/packages → dist}/sdk/src/hooks/useNotis.ts +6 -1
- package/dist/sdk/src/hooks/useNotisNavigation.ts +70 -0
- package/dist/sdk/src/hooks/useQuery.ts +81 -0
- package/dist/sdk/src/hooks/useToolQuery.ts +12 -0
- package/dist/sdk/src/hooks/useTopBarSearch.ts +102 -0
- package/dist/sdk/src/hooks/useTopBarTitleLeaf.ts +75 -0
- package/{template/packages → dist}/sdk/src/hooks/useUpsertDocument.ts +1 -1
- package/dist/sdk/src/hooks/useViewParams.ts +195 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/{template/packages → dist}/sdk/src/index.ts +64 -1
- package/{template/packages → dist}/sdk/src/interactions/actions.ts +14 -1
- package/{template/packages → dist}/sdk/src/interactions/shortcuts.tsx +129 -23
- package/dist/sdk/src/interactions/visibility.ts +13 -0
- package/{template/packages → dist}/sdk/src/interactions.ts +5 -1
- package/dist/sdk/src/nativeData.ts +39 -0
- package/{template/packages → dist}/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +292 -0
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +675 -0
- package/dist/sdk/src/runtimeModules.ts +33 -0
- package/dist/sdk/src/space.ts +442 -0
- package/dist/sdk/src/standardViewParams.ts +36 -0
- package/{template/packages → dist}/sdk/src/styles.css +81 -16
- package/dist/sdk/src/tailwind.ts +66 -0
- package/dist/sdk/src/ui/charts/RenderChartData.tsx +25 -0
- package/dist/sdk/src/ui/engine/core.ts +392 -0
- package/dist/sdk/src/ui/engine/dom.ts +183 -0
- package/dist/sdk/src/ui/engine/hooks.ts +195 -0
- package/dist/sdk/src/ui/engine/instance.ts +63 -0
- package/dist/sdk/src/ui/engine/keycapContext.ts +2 -0
- package/dist/sdk/src/ui/engine/registry.ts +262 -0
- package/dist/sdk/src/ui/engine/types.ts +62 -0
- package/dist/sdk/src/ui/icons.tsx +91 -0
- package/dist/sdk/src/ui/util.ts +83 -0
- package/dist/sdk/src/viewPeriod.ts +26 -0
- package/dist/sdk/src/vite.ts +92 -0
- package/dist/sdk/src/words/actions.ts +295 -0
- package/dist/sdk/src/words/clashes.ts +158 -0
- package/dist/sdk/src/words/format.ts +136 -0
- package/dist/sdk/src/words/index.ts +13 -0
- package/dist/sdk/src/words/keymap.ts +72 -0
- package/dist/sdk/src/words/keys.ts +297 -0
- package/dist/sdk/src/words/shell.ts +46 -0
- package/dist/sdk/src/words/types.ts +65 -0
- package/dist/skill-sync/index.js +832 -151
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +3941 -0
- package/dist/space-harness/host.js +1460 -0
- package/dist/space-sdk/dist/chunk-ZSN57ATH.js +5 -0
- package/dist/space-sdk/dist/index.js +15 -0
- package/dist/space-sdk/dist/ui.js +18 -0
- package/dist/space-sdk/package.json +33 -0
- package/dist/space-sdk/src/agentContext.d.ts +36 -0
- package/dist/space-sdk/src/components/Dialog.d.ts +20 -0
- package/dist/space-sdk/src/components/DocumentEditor.d.ts +46 -0
- package/dist/space-sdk/src/components/DocumentPage.d.ts +53 -0
- package/dist/space-sdk/src/components/Markdown.d.ts +18 -0
- package/dist/space-sdk/src/components/MarkdownEditor.d.ts +11 -0
- package/dist/space-sdk/src/components/MultiSelectActionBar.d.ts +31 -0
- package/dist/space-sdk/src/components/MultiSelectCheckbox.d.ts +14 -0
- package/dist/space-sdk/src/components/MultiSelectDragOverlay.d.ts +8 -0
- package/dist/space-sdk/src/components/NotisCommentBoundary.d.ts +36 -0
- package/dist/space-sdk/src/components/NotisSelectionBoundary.d.ts +16 -0
- package/dist/space-sdk/src/components/RecordComponents.d.ts +64 -0
- package/dist/space-sdk/src/components/ShortcutHints.d.ts +12 -0
- package/dist/space-sdk/src/components/Skeleton.d.ts +23 -0
- package/dist/space-sdk/src/config.ts +31 -0
- package/dist/space-sdk/src/documents.d.ts +59 -0
- package/dist/space-sdk/src/hooks/useAction.d.ts +8 -0
- package/dist/space-sdk/src/hooks/useActiveResource.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useAgentContext.d.ts +7 -0
- package/dist/space-sdk/src/hooks/useBackend.d.ts +23 -0
- package/dist/space-sdk/src/hooks/useCloudComputer.d.ts +45 -0
- package/dist/space-sdk/src/hooks/useCloudComputerShell.d.ts +49 -0
- package/dist/space-sdk/src/hooks/useCollectionInteractions.d.ts +118 -0
- package/dist/space-sdk/src/hooks/useDatabaseSchema.d.ts +14 -0
- package/dist/space-sdk/src/hooks/useDatabaseSubscription.d.ts +26 -0
- package/dist/space-sdk/src/hooks/useDocument.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useDocuments.d.ts +31 -0
- package/dist/space-sdk/src/hooks/useHandover.d.ts +43 -0
- package/dist/space-sdk/src/hooks/useLongPressSelection.d.ts +9 -0
- package/dist/space-sdk/src/hooks/useMarqueeSelection.d.ts +57 -0
- package/dist/space-sdk/src/hooks/useMultiSelect.d.ts +51 -0
- package/dist/space-sdk/src/hooks/useNativeDocumentBody.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useNativeDocuments.d.ts +55 -0
- package/dist/space-sdk/src/hooks/useNotis.d.ts +25 -0
- package/dist/space-sdk/src/hooks/useNotisNavigation.d.ts +33 -0
- package/dist/space-sdk/src/hooks/useQuery.d.ts +22 -0
- package/dist/space-sdk/src/hooks/useTool.d.ts +20 -0
- package/dist/space-sdk/src/hooks/useToolQuery.d.ts +3 -0
- package/dist/space-sdk/src/hooks/useTools.d.ts +15 -0
- package/dist/space-sdk/src/hooks/useTopBarSearch.d.ts +48 -0
- package/dist/space-sdk/src/hooks/useTopBarTitleLeaf.d.ts +24 -0
- package/dist/space-sdk/src/hooks/useUpsertDocument.d.ts +27 -0
- package/dist/space-sdk/src/hooks/useViewParams.d.ts +76 -0
- package/dist/space-sdk/src/hooks/useViewerReads.d.ts +38 -0
- package/dist/space-sdk/src/index.d.ts +98 -0
- package/dist/space-sdk/src/interactions/actions.d.ts +33 -0
- package/dist/space-sdk/src/interactions/shortcuts.d.ts +42 -0
- package/dist/space-sdk/src/interactions/visibility.d.ts +2 -0
- package/dist/space-sdk/src/nativeData.d.ts +66 -0
- package/dist/space-sdk/src/provider.d.ts +17 -0
- package/dist/space-sdk/src/queryCache.d.ts +45 -0
- package/dist/space-sdk/src/queryErrors.ts +51 -0
- package/dist/space-sdk/src/runtime.d.ts +615 -0
- package/dist/space-sdk/src/runtimeModules.ts +33 -0
- package/dist/space-sdk/src/space.ts +442 -0
- package/dist/space-sdk/src/standardViewParams.d.ts +15 -0
- package/dist/space-sdk/src/tailwind-candidates.d.ts +3418 -0
- package/dist/space-sdk/src/tailwind.ts +66 -0
- package/dist/space-sdk/src/ui/actions/ActionToast.d.ts +15 -0
- package/dist/space-sdk/src/ui/actions/actionWords.d.ts +16 -0
- package/dist/space-sdk/src/ui/actions/index.d.ts +7 -0
- package/dist/space-sdk/src/ui/actions/useActions.d.ts +69 -0
- package/dist/space-sdk/src/ui/actions/useToast.d.ts +41 -0
- package/dist/space-sdk/src/ui/blocks/Tabs.d.ts +139 -0
- package/dist/space-sdk/src/ui/blocks/blocks.d.ts +217 -0
- package/dist/space-sdk/src/ui/blocks/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/board/BoardView.d.ts +36 -0
- package/dist/space-sdk/src/ui/board/boardModel.d.ts +46 -0
- package/dist/space-sdk/src/ui/board/boardWords.d.ts +9 -0
- package/dist/space-sdk/src/ui/board/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/calendar/CalendarView.d.ts +49 -0
- package/dist/space-sdk/src/ui/calendar/DayPicker.d.ts +23 -0
- package/dist/space-sdk/src/ui/calendar/calendarMath.d.ts +81 -0
- package/dist/space-sdk/src/ui/charts/Chart.d.ts +203 -0
- package/dist/space-sdk/src/ui/charts/ChartTable.d.ts +37 -0
- package/dist/space-sdk/src/ui/charts/RenderChartData.d.ts +16 -0
- package/dist/space-sdk/src/ui/charts/StatRow.d.ts +59 -0
- package/dist/space-sdk/src/ui/charts/chartData.d.ts +84 -0
- package/dist/space-sdk/src/ui/comments/CommentPane.d.ts +58 -0
- package/dist/space-sdk/src/ui/comments/commentModel.d.ts +157 -0
- package/dist/space-sdk/src/ui/comments/commentWords.d.ts +60 -0
- package/dist/space-sdk/src/ui/comments/index.d.ts +4 -0
- package/dist/space-sdk/src/ui/comments/useComments.d.ts +146 -0
- package/dist/space-sdk/src/ui/engine/HelpSheet.d.ts +12 -0
- package/dist/space-sdk/src/ui/engine/KeyboardEngine.d.ts +27 -0
- package/dist/space-sdk/src/ui/engine/core.d.ts +73 -0
- package/dist/space-sdk/src/ui/engine/hooks.d.ts +64 -0
- package/dist/space-sdk/src/ui/engine/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/engine/instance.d.ts +21 -0
- package/dist/space-sdk/src/ui/engine/keycapContext.d.ts +2 -0
- package/dist/space-sdk/src/ui/engine/registry.d.ts +77 -0
- package/dist/space-sdk/src/ui/engine/step.d.ts +11 -0
- package/dist/space-sdk/src/ui/engine/types.d.ts +54 -0
- package/dist/space-sdk/src/ui/fields/fieldWords.d.ts +56 -0
- package/dist/space-sdk/src/ui/fields/fields.d.ts +162 -0
- package/dist/space-sdk/src/ui/fields/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/fields/useFieldDraft.d.ts +22 -0
- package/dist/space-sdk/src/ui/gallery/AttachmentGallery.d.ts +53 -0
- package/dist/space-sdk/src/ui/gallery/ImageViewer.d.ts +27 -0
- package/dist/space-sdk/src/ui/gallery/galleryWords.d.ts +95 -0
- package/dist/space-sdk/src/ui/gallery/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/icons.d.ts +13 -0
- package/dist/space-sdk/src/ui/index.d.ts +54 -0
- package/dist/space-sdk/src/ui/keycap/Keycap.d.ts +22 -0
- package/dist/space-sdk/src/ui/keycap/index.d.ts +2 -0
- package/dist/space-sdk/src/ui/layout/Panels.d.ts +50 -0
- package/dist/space-sdk/src/ui/layout/chatPanelEdge.d.ts +77 -0
- package/dist/space-sdk/src/ui/layout/index.d.ts +5 -0
- package/dist/space-sdk/src/ui/layout/panelMath.d.ts +91 -0
- package/dist/space-sdk/src/ui/list/ListReader.d.ts +380 -0
- package/dist/space-sdk/src/ui/list/Row.d.ts +145 -0
- package/dist/space-sdk/src/ui/list/SelectionBar.d.ts +65 -0
- package/dist/space-sdk/src/ui/list/index.d.ts +14 -0
- package/dist/space-sdk/src/ui/list/rowState.d.ts +22 -0
- package/dist/space-sdk/src/ui/list/useListKeys.d.ts +89 -0
- package/dist/space-sdk/src/ui/list/useReorder.d.ts +86 -0
- package/dist/space-sdk/src/ui/list/useSelection.d.ts +90 -0
- package/dist/space-sdk/src/ui/open/OpenAsMenu.d.ts +15 -0
- package/dist/space-sdk/src/ui/open/index.d.ts +6 -0
- package/dist/space-sdk/src/ui/open/openModeStore.d.ts +39 -0
- package/dist/space-sdk/src/ui/open/useOpenMode.d.ts +17 -0
- package/dist/space-sdk/src/ui/overlay/Menu.d.ts +88 -0
- package/dist/space-sdk/src/ui/overlay/Popover.d.ts +28 -0
- package/dist/space-sdk/src/ui/overlay/Tooltip.d.ts +19 -0
- package/dist/space-sdk/src/ui/overlay/index.d.ts +9 -0
- package/dist/space-sdk/src/ui/overlay/layerRoot.d.ts +24 -0
- package/dist/space-sdk/src/ui/overlay/position.d.ts +31 -0
- package/dist/space-sdk/src/ui/period/PeriodPicker.d.ts +30 -0
- package/dist/space-sdk/src/ui/period/dateFormat.d.ts +50 -0
- package/dist/space-sdk/src/ui/period/dateWords.d.ts +88 -0
- package/dist/space-sdk/src/ui/period/periods.d.ts +70 -0
- package/dist/space-sdk/src/ui/period/usePeriod.d.ts +23 -0
- package/dist/space-sdk/src/ui/present/index.d.ts +3 -0
- package/dist/space-sdk/src/ui/present/presentWords.d.ts +27 -0
- package/dist/space-sdk/src/ui/present/screens.d.ts +36 -0
- package/dist/space-sdk/src/ui/present/useAudienceScreen.d.ts +38 -0
- package/dist/space-sdk/src/ui/styles.d.ts +21 -0
- package/dist/space-sdk/src/ui/timeline/TimelineView.d.ts +44 -0
- package/dist/space-sdk/src/ui/timeline/timelineMath.d.ts +123 -0
- package/dist/space-sdk/src/viewPeriod.d.ts +8 -0
- package/dist/space-sdk/src/vite.ts +92 -0
- package/dist/space-sdk/src/words/actions.ts +295 -0
- package/dist/space-sdk/src/words/clashes.ts +158 -0
- package/dist/space-sdk/src/words/format.ts +136 -0
- package/dist/space-sdk/src/words/index.ts +13 -0
- package/dist/space-sdk/src/words/keymap.ts +72 -0
- package/dist/space-sdk/src/words/keys.ts +297 -0
- package/dist/space-sdk/src/words/shell.ts +46 -0
- package/dist/space-sdk/src/words/types.ts +65 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +2374 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +212 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +250 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/png.js +213 -0
- package/dist/view-renderer/src/pool.js +110 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +279 -0
- package/package.json +21 -11
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +63 -12
- package/skills/notis-onboarding/BRIEF.md +14 -9
- package/skills/notis-query/cli.md +1 -1
- package/skills/notis-spaces/cli.md +540 -0
- package/src/cli.js +8 -8
- package/src/command-specs/agents.js +121 -8
- package/src/command-specs/auth.js +3 -3
- package/src/command-specs/diagnostics.js +97 -20
- package/src/command-specs/documents.js +158 -0
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/handover.js +331 -79
- package/src/command-specs/helpers.js +50 -40
- package/src/command-specs/index.js +14 -5
- package/src/command-specs/meta.js +38 -9
- package/src/command-specs/onboarding.js +1 -1
- package/src/command-specs/profile.js +42 -1
- package/src/command-specs/skills.js +314 -9
- package/src/command-specs/smoke.js +16 -4
- package/src/command-specs/spaces.js +783 -0
- package/src/command-specs/tools.js +118 -21
- package/src/command-specs/version-history.js +20 -0
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +178 -267
- package/src/runtime/agent-setup.js +172 -13
- package/src/runtime/app-boundary-validator.js +338 -2
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +750 -1570
- package/src/runtime/auth-recovery.js +17 -0
- package/src/runtime/base-skills.d.ts +11 -0
- package/src/runtime/base-skills.js +100 -3
- package/src/runtime/computer-identity.d.ts +30 -0
- package/src/runtime/computer-identity.js +151 -0
- package/src/runtime/config-lock.js +135 -0
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/delegated-context.js +5 -21
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/oauth.js +342 -31
- package/src/runtime/profiles.js +19 -112
- package/src/runtime/retired-sdk-files.js +24 -0
- package/src/runtime/retry.js +150 -0
- package/src/runtime/skill-sync/cloud-client.ts +33 -8
- package/src/runtime/skill-sync/index.ts +443 -106
- package/src/runtime/skill-sync/local-scanner.ts +248 -32
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +70 -0
- package/src/runtime/skill-sync/symlink-manager.ts +66 -16
- package/src/runtime/skill-sync/sync-plan.ts +34 -2
- package/src/runtime/skill-sync/types.ts +54 -0
- package/src/runtime/skill-sync/write-cloud-skill.ts +4 -4
- package/src/runtime/skill-sync-service.js +109 -0
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +107 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +68 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +133 -0
- package/src/runtime/space-source.js +290 -0
- package/src/runtime/space-test-server.js +354 -0
- package/src/runtime/space-verification.js +94 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +368 -0
- package/src/runtime/sync-skills.d.ts +5 -1
- package/src/runtime/sync-skills.js +11 -2
- package/src/runtime/tool-confirmation.js +138 -0
- package/src/runtime/transport.js +118 -24
- package/src/runtime/upsell.js +219 -0
- package/src/runtime/view-render.js +49 -0
- package/src/skill-sync-worker-entry.js +2 -0
- package/src/skill-sync-worker.js +114 -0
- package/src/space-harness-host.ts +122 -0
- package/template/.harness/index.html.tmpl +9 -352
- package/template/app/globals.css +29 -3
- package/template/app/layout.tsx +2 -6
- package/template/components/ui/badge.tsx +7 -4
- package/template/components/ui/button.tsx +1 -1
- package/template/components/ui/native-select.tsx +24 -0
- package/template/notis.config.ts +4 -36
- package/template/package-lock.json +1800 -1867
- package/template/package.json +16 -14
- package/template/postcss.config.mjs +1 -1
- package/template/tailwind.config.ts +1 -6
- package/template/tsconfig.json +1 -0
- package/template/vite.config.ts +2 -2
- package/dist/base-skills/notis-apps/SKILL.md +0 -613
- package/skills/notis-apps/cli.md +0 -285
- package/src/command-specs/apps.js +0 -3011
- package/src/runtime/app-changelog.js +0 -79
- package/src/runtime/app-dev-consumers.js +0 -154
- package/src/runtime/app-dev-host-lock.js +0 -80
- package/src/runtime/app-dev-process-identity.js +0 -111
- package/src/runtime/app-dev-roots.js +0 -284
- package/src/runtime/app-dev-server.js +0 -1140
- package/src/runtime/app-dev-sessions.js +0 -185
- package/src/runtime/app-registry-scaffolds.js +0 -367
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/cli-mode.generated.js +0 -5
- package/src/runtime/cli-mode.js +0 -34
- package/src/runtime/store-screenshot.js +0 -143
- package/template/CHANGELOG.md +0 -5
- package/template/app/page.tsx +0 -60
- package/template/components/ui/card.tsx +0 -56
- package/template/packages/sdk/src/components/DocumentEditor.tsx +0 -103
- package/template/packages/sdk/src/config.ts +0 -259
- package/template/packages/sdk/src/hooks/useCloudComputer.ts +0 -97
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +0 -85
- package/template/packages/sdk/src/hooks/useDocument.ts +0 -78
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +0 -52
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +0 -73
- package/template/packages/sdk/src/runtime.ts +0 -439
- package/template/packages/sdk/src/vite.ts +0 -56
- package/template/packages/sdk/tsconfig.json +0 -15
- /package/{template/packages → dist}/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/components/ShortcutHints.tsx +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useActiveResource.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTool.ts +0 -0
- /package/{template/packages → dist}/sdk/src/hooks/useTools.ts +0 -0
- /package/{template/packages → dist}/sdk/src/ui.ts +0 -0
package/src/runtime/transport.js
CHANGED
|
@@ -2,7 +2,6 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { CliError, EXIT_CODES } from './errors.js';
|
|
5
|
-
import { delegatedContextReason } from './delegated-context.js';
|
|
6
5
|
import {
|
|
7
6
|
credentialIsExpired,
|
|
8
7
|
getJwtExpiration,
|
|
@@ -14,6 +13,16 @@ import {
|
|
|
14
13
|
} from './profiles.js';
|
|
15
14
|
import { createExpiredAuthError, createInvalidAuthHints } from './auth-recovery.js';
|
|
16
15
|
import { refreshOAuthCredential } from './oauth.js';
|
|
16
|
+
import { findUpsell, renderUpsell } from './upsell.js';
|
|
17
|
+
import {
|
|
18
|
+
callCarriesToolRequestId,
|
|
19
|
+
canReplayRequest,
|
|
20
|
+
findToolOutcomeUnknown,
|
|
21
|
+
isTransientFailure,
|
|
22
|
+
retriesEnabled,
|
|
23
|
+
waitForNextAttempt,
|
|
24
|
+
withAttempts,
|
|
25
|
+
} from './retry.js';
|
|
17
26
|
|
|
18
27
|
function escapeMultipartHeaderValue(value) {
|
|
19
28
|
return String(value ?? '')
|
|
@@ -82,10 +91,30 @@ function createMultipartFileUpload(payload, bindingMetadata, fileBindings) {
|
|
|
82
91
|
};
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
94
|
+
// The Spaces client protocol this CLI speaks. The server refuses every other
|
|
95
|
+
// version with a 410 `client_update_required` answer, so an older CLI is told
|
|
96
|
+
// to update instead of half-working.
|
|
97
|
+
export const SPACES_PROTOCOL = 2;
|
|
98
|
+
export const CLI_UPDATE_COMMAND = 'npx --package @notis_ai/cli@latest -- notis';
|
|
99
|
+
|
|
100
|
+
export function normalizeBackendError(status, payload, runtime) {
|
|
86
101
|
const backendError = payload?.error;
|
|
102
|
+
if (backendError === 'client_update_required') {
|
|
103
|
+
return new CliError({
|
|
104
|
+
code: 'cli_update_required',
|
|
105
|
+
message: `This Notis CLI is too old for Spaces. Run the latest one with: ${CLI_UPDATE_COMMAND}`,
|
|
106
|
+
exitCode: EXIT_CODES.usage,
|
|
107
|
+
details: payload || {},
|
|
108
|
+
hints: [{ command: CLI_UPDATE_COMMAND, reason: 'Run your command again with the latest Notis CLI' }],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Space errors answer {error: <stable code>, message: <readable text>}: show
|
|
112
|
+
// the text (it can name the Space involved); the code stays in details.
|
|
113
|
+
const codedMessage = typeof backendError === 'string' && /^[a-z][a-z0-9_]*$/.test(backendError)
|
|
114
|
+
&& typeof payload?.message === 'string' && payload.message ? payload.message : null;
|
|
87
115
|
const message =
|
|
88
116
|
backendError?.message ||
|
|
117
|
+
codedMessage ||
|
|
89
118
|
backendError ||
|
|
90
119
|
payload?.message ||
|
|
91
120
|
payload?.error ||
|
|
@@ -115,6 +144,19 @@ function normalizeBackendError(status, payload, runtime) {
|
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
if (status === 403) {
|
|
147
|
+
const upsellBody = findUpsell(payload);
|
|
148
|
+
if (upsellBody) {
|
|
149
|
+
// Same code as every 403 so callers that treat a refusal as "not
|
|
150
|
+
// available" keep working; the message and hints are the shared upsell.
|
|
151
|
+
const rendered = renderUpsell(upsellBody, { apiBase: runtime?.apiBase });
|
|
152
|
+
return new CliError({
|
|
153
|
+
code: 'forbidden',
|
|
154
|
+
message: rendered.text,
|
|
155
|
+
exitCode: EXIT_CODES.auth,
|
|
156
|
+
details: { ...(payload || {}), upsell: rendered.upsell },
|
|
157
|
+
hints: rendered.hints,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
118
160
|
return new CliError({
|
|
119
161
|
code: 'forbidden',
|
|
120
162
|
message,
|
|
@@ -258,6 +300,9 @@ export async function httpRequest({
|
|
|
258
300
|
body,
|
|
259
301
|
multipart = false,
|
|
260
302
|
requireAuth = true,
|
|
303
|
+
spacesProtocol,
|
|
304
|
+
// This attempt's own limit; defaults to the whole --timeout-ms budget.
|
|
305
|
+
timeoutMs = runtime.timeoutMs,
|
|
261
306
|
}) {
|
|
262
307
|
// Refresh before spending the credential rather than after a rejection: the
|
|
263
308
|
// rotating refresh token is shared with every other `notis` process reading
|
|
@@ -281,18 +326,19 @@ export async function httpRequest({
|
|
|
281
326
|
}
|
|
282
327
|
|
|
283
328
|
let controller = new AbortController();
|
|
284
|
-
let timeout = setTimeout(() => controller.abort(),
|
|
329
|
+
let timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
285
330
|
const requestId = `req_${randomUUID().replace(/-/g, '')}`;
|
|
286
331
|
const resetTimeout = () => {
|
|
287
332
|
clearTimeout(timeout);
|
|
288
333
|
controller = new AbortController();
|
|
289
|
-
timeout = setTimeout(() => controller.abort(),
|
|
334
|
+
timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
290
335
|
};
|
|
291
336
|
|
|
292
337
|
const headers = {
|
|
293
338
|
'X-Notis-CLI-Version': runtime.cliVersion,
|
|
294
339
|
'X-Notis-Request-Id': requestId,
|
|
295
340
|
};
|
|
341
|
+
if (spacesProtocol) headers['X-Notis-Spaces-Protocol'] = String(SPACES_PROTOCOL);
|
|
296
342
|
if (!multipart) {
|
|
297
343
|
headers['Content-Type'] = 'application/json';
|
|
298
344
|
}
|
|
@@ -378,7 +424,10 @@ export async function httpRequest({
|
|
|
378
424
|
) {
|
|
379
425
|
throw createExpiredAuthError(runtime);
|
|
380
426
|
}
|
|
381
|
-
|
|
427
|
+
const backendError = normalizeBackendError(response.status, payload, runtime);
|
|
428
|
+
// Kept off the serialized error; the retry policy reads the HTTP status.
|
|
429
|
+
backendError.httpStatus = response.status;
|
|
430
|
+
throw backendError;
|
|
382
431
|
}
|
|
383
432
|
|
|
384
433
|
// A successful status is not a successful tool call until its response
|
|
@@ -430,48 +479,93 @@ export async function callTool({
|
|
|
430
479
|
idempotencyKey,
|
|
431
480
|
fileBindings = [],
|
|
432
481
|
}) {
|
|
433
|
-
const
|
|
434
|
-
const
|
|
482
|
+
const firstKey = idempotencyKey || (runtime.mutating ? randomUUID() : null);
|
|
483
|
+
const buildPayload = (key) => ({
|
|
435
484
|
tool_name: toolName,
|
|
436
485
|
arguments: argumentsPayload,
|
|
437
|
-
idempotency_key:
|
|
486
|
+
idempotency_key: key,
|
|
438
487
|
cli_context: {
|
|
439
488
|
output_mode: runtime.outputMode,
|
|
440
489
|
profile: runtime.profileName,
|
|
441
490
|
cwd: process.cwd(),
|
|
442
491
|
agent_mode: runtime.agentMode,
|
|
443
492
|
cli_version: runtime.cliVersion,
|
|
444
|
-
// Reported so the server can refuse work-handing tools from a run Notis
|
|
445
|
-
// is itself driving. Advisory -- a determined agent can unset the markers
|
|
446
|
-
// -- so the server treats it as one layer, not the whole guard.
|
|
447
|
-
...(delegatedContextReason() ? { delegated_context: true } : {}),
|
|
448
493
|
...(runtime.debugEntitlementOverride
|
|
449
494
|
? { debug_entitlement_override: runtime.debugEntitlementOverride }
|
|
450
495
|
: {}),
|
|
451
496
|
},
|
|
452
|
-
};
|
|
497
|
+
});
|
|
453
498
|
|
|
454
|
-
|
|
455
|
-
|
|
499
|
+
const hasFiles = Array.isArray(fileBindings) && fileBindings.length > 0;
|
|
500
|
+
const bindingMetadata = hasFiles
|
|
501
|
+
? fileBindings.map((binding) => {
|
|
456
502
|
const {
|
|
457
503
|
contentType,
|
|
458
504
|
localPath,
|
|
459
505
|
...metadata
|
|
460
506
|
} = binding;
|
|
461
507
|
return metadata;
|
|
462
|
-
})
|
|
508
|
+
})
|
|
509
|
+
: [];
|
|
463
510
|
|
|
464
|
-
|
|
511
|
+
const send = (key, timeoutMs) => (hasFiles
|
|
512
|
+
? httpRequest({
|
|
465
513
|
runtime,
|
|
466
514
|
path: '/cli_tools',
|
|
467
|
-
|
|
515
|
+
// A function, so every attempt streams the files again from the start.
|
|
516
|
+
body: () => createMultipartFileUpload(buildPayload(key), bindingMetadata, fileBindings),
|
|
468
517
|
multipart: true,
|
|
469
|
-
|
|
518
|
+
timeoutMs,
|
|
519
|
+
})
|
|
520
|
+
: httpRequest({
|
|
521
|
+
runtime,
|
|
522
|
+
path: '/cli_tools',
|
|
523
|
+
body: buildPayload(key),
|
|
524
|
+
timeoutMs,
|
|
525
|
+
}));
|
|
526
|
+
|
|
527
|
+
if (!retriesEnabled(runtime)) {
|
|
528
|
+
let result;
|
|
529
|
+
try {
|
|
530
|
+
result = await send(firstKey, runtime.timeoutMs);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
throw isTransientFailure(error) ? withAttempts(error, 1) : error;
|
|
533
|
+
}
|
|
534
|
+
return { ...result, idempotencyKey: firstKey, attempts: 1 };
|
|
470
535
|
}
|
|
471
536
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
537
|
+
// --timeout-ms bounds the whole call, every attempt included.
|
|
538
|
+
const budgetMs = runtime.timeoutMs;
|
|
539
|
+
const deadline = Date.now() + budgetMs;
|
|
540
|
+
let key = firstKey;
|
|
541
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
542
|
+
let result;
|
|
543
|
+
try {
|
|
544
|
+
result = await send(key, Math.max(1, deadline - Date.now()));
|
|
545
|
+
} catch (error) {
|
|
546
|
+
const transient = isTransientFailure(error);
|
|
547
|
+
if (
|
|
548
|
+
transient
|
|
549
|
+
&& canReplayRequest({ mutating: runtime.mutating, idempotencyKey: key })
|
|
550
|
+
&& await waitForNextAttempt({ attempt, deadline, budgetMs })
|
|
551
|
+
) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
throw transient ? withAttempts(error, attempt) : error;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// The stored answer under this key is the unknown outcome itself, so only
|
|
558
|
+
// a fresh key reaches the tool again; its request_id receipt dedupes it.
|
|
559
|
+
if (
|
|
560
|
+
runtime.mutating
|
|
561
|
+
&& key
|
|
562
|
+
&& findToolOutcomeUnknown(result.payload)
|
|
563
|
+
&& callCarriesToolRequestId(argumentsPayload)
|
|
564
|
+
&& await waitForNextAttempt({ attempt, deadline, budgetMs })
|
|
565
|
+
) {
|
|
566
|
+
key = randomUUID();
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
return { ...result, idempotencyKey: key, attempts: attempt };
|
|
570
|
+
}
|
|
477
571
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// One renderer for every upgrade_required refusal the CLI shows. The server
|
|
2
|
+
// builds the body (server/lib/tool_entitlements.py entitlement_upgrade_required_body,
|
|
3
|
+
// plus Cloud Computer overrides); this module only finds it in a payload and
|
|
4
|
+
// turns it into the lines and hints a person or agent acts on.
|
|
5
|
+
|
|
6
|
+
const UPSELL_CODES = new Set([
|
|
7
|
+
'entitlement_upgrade_required',
|
|
8
|
+
'cloud_computer_team_limit',
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const NESTED_KEYS = ['error', 'details', 'data', 'result', 'response', 'entitlement_access'];
|
|
12
|
+
|
|
13
|
+
const PORTAL_ORIGINS = {
|
|
14
|
+
'api.notis.ai': 'https://app.notis.ai',
|
|
15
|
+
'api-beta.notis.ai': 'https://beta.notis.ai',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function isUpsellBody(value) {
|
|
19
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
20
|
+
if (value.upgrade_required === true) return true;
|
|
21
|
+
return typeof value.code === 'string' && UPSELL_CODES.has(value.code)
|
|
22
|
+
&& (typeof value.message === 'string' || value.next_action);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseJsonString(value) {
|
|
26
|
+
if (typeof value !== 'string') return null;
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (!trimmed.startsWith('{')) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(trimmed);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Returns the upsell body inside a tool result, a COMPOSIO_MULTI_EXECUTE_TOOL
|
|
37
|
+
// envelope or a backend error payload, or null when there is none.
|
|
38
|
+
export function findUpsell(value, depth = 0) {
|
|
39
|
+
if (depth > 8 || value === null || value === undefined) return null;
|
|
40
|
+
const parsed = parseJsonString(value);
|
|
41
|
+
if (parsed) return findUpsell(parsed, depth + 1);
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
for (const item of value) {
|
|
44
|
+
const found = findUpsell(item, depth + 1);
|
|
45
|
+
if (found) return found;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (typeof value !== 'object') return null;
|
|
50
|
+
if (isUpsellBody(value)) {
|
|
51
|
+
// A backend error wraps the body as {code, message, details: {...body}}:
|
|
52
|
+
// prefer the inner body, which carries next_action.
|
|
53
|
+
const inner = value.details && typeof value.details === 'object' ? findUpsell(value.details, depth + 1) : null;
|
|
54
|
+
return inner && inner.next_action && !value.next_action ? { ...value, ...inner } : value;
|
|
55
|
+
}
|
|
56
|
+
for (const key of NESTED_KEYS) {
|
|
57
|
+
if (key in value) {
|
|
58
|
+
const found = findUpsell(value[key], depth + 1);
|
|
59
|
+
if (found) return found;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value.results)) return findUpsell(value.results, depth + 1);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function portalOriginFor(apiBase) {
|
|
67
|
+
try {
|
|
68
|
+
return PORTAL_ORIGINS[new URL(apiBase).hostname] || null;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function planLabel(plan) {
|
|
75
|
+
if (typeof plan !== 'string' || !plan.trim()) return null;
|
|
76
|
+
const value = plan.trim().replace(/[_-]+/g, ' ');
|
|
77
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatPrice(price) {
|
|
81
|
+
if (price === null || price === undefined || price === '') return null;
|
|
82
|
+
if (typeof price === 'string' || typeof price === 'number') return String(price);
|
|
83
|
+
if (typeof price !== 'object') return null;
|
|
84
|
+
if (typeof price.label === 'string' && price.label) return price.label;
|
|
85
|
+
const parts = [];
|
|
86
|
+
if (price.monthly_usd !== undefined && price.monthly_usd !== null) parts.push(`$${price.monthly_usd} a month`);
|
|
87
|
+
if (price.yearly_usd !== undefined && price.yearly_usd !== null) parts.push(`$${price.yearly_usd} a year`);
|
|
88
|
+
if (price.hourly_usd !== undefined && price.hourly_usd !== null) parts.push(`$${price.hourly_usd} an hour`);
|
|
89
|
+
return parts.length ? parts.join(', ') : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatUnlocks(unlocks) {
|
|
93
|
+
if (typeof unlocks === 'string' && unlocks.trim()) return [unlocks.trim()];
|
|
94
|
+
if (Array.isArray(unlocks)) return unlocks.filter((item) => typeof item === 'string' && item.trim());
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function upgradeLink(body, apiBase) {
|
|
99
|
+
const url = typeof body.upgrade_url === 'string' && body.upgrade_url ? body.upgrade_url : '/subscribe';
|
|
100
|
+
if (/^https?:\/\//.test(url)) return url;
|
|
101
|
+
const origin = portalOriginFor(apiBase);
|
|
102
|
+
return origin ? `${origin}${url.startsWith('/') ? '' : '/'}${url}` : null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Portal routes a next_action may name with `page` instead of a full url.
|
|
106
|
+
const ACTION_PAGES = {
|
|
107
|
+
billing: { path: '/settings/billing', where: 'Settings, then Billing' },
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const SUPPORT_URL = 'mailto:support@notis.ai';
|
|
111
|
+
|
|
112
|
+
function portalLink(path, apiBase) {
|
|
113
|
+
const origin = portalOriginFor(apiBase);
|
|
114
|
+
return origin ? `${origin}${path}` : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toolCommand(tool, args = {}) {
|
|
118
|
+
return `notis tools exec ${tool} --arguments '${JSON.stringify(args)}'`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Builds {message, lines, hints, upsell} for one upsell body. `lines` is the
|
|
122
|
+
// readable block for people; `hints` follow the CLI's {command, reason} shape
|
|
123
|
+
// so agents see the same next step in machine output.
|
|
124
|
+
export function renderUpsell(body, { apiBase } = {}) {
|
|
125
|
+
const plan = planLabel(body.required_plan);
|
|
126
|
+
const message = typeof body.message === 'string' && body.message
|
|
127
|
+
? body.message
|
|
128
|
+
: (plan ? `Upgrade to ${plan} to use this.` : 'This needs a paid plan.');
|
|
129
|
+
const lines = [message];
|
|
130
|
+
const hints = [];
|
|
131
|
+
|
|
132
|
+
const unlocks = formatUnlocks(body.unlocks);
|
|
133
|
+
if (unlocks.length) {
|
|
134
|
+
lines.push(plan ? `${plan} unlocks:` : 'The plan unlocks:');
|
|
135
|
+
for (const item of unlocks) lines.push(` - ${item}`);
|
|
136
|
+
}
|
|
137
|
+
const price = formatPrice(body.price);
|
|
138
|
+
if (price) lines.push(`Price: ${price}`);
|
|
139
|
+
|
|
140
|
+
const nextAction = body.next_action && typeof body.next_action === 'object' ? body.next_action : {};
|
|
141
|
+
const link = upgradeLink(body, apiBase);
|
|
142
|
+
const portalHint = {
|
|
143
|
+
command: toolCommand('LOCAL_NOTIS_GET_PORTAL_URL', { page: 'subscribe' }),
|
|
144
|
+
reason: 'Get the upgrade page link',
|
|
145
|
+
};
|
|
146
|
+
let actionUrl = null;
|
|
147
|
+
|
|
148
|
+
switch (nextAction.action) {
|
|
149
|
+
case 'contact_billing_owner': {
|
|
150
|
+
const ask = typeof nextAction.message === 'string' && nextAction.message
|
|
151
|
+
? nextAction.message
|
|
152
|
+
: 'Ask your team owner to upgrade.';
|
|
153
|
+
lines.push(`Ask your team owner: ${ask}`);
|
|
154
|
+
hints.push({ message: `Ask your team owner: ${ask}` });
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'activate_paid_billing':
|
|
158
|
+
case 'switch_trial_plan': {
|
|
159
|
+
const tool = nextAction.tool || 'LOCAL_NOTIS_ACTIVATE_PAID_BILLING';
|
|
160
|
+
const args = nextAction.action === 'switch_trial_plan' && nextAction.target_plan
|
|
161
|
+
? { target_plan: nextAction.target_plan }
|
|
162
|
+
: {};
|
|
163
|
+
const command = toolCommand(tool, args);
|
|
164
|
+
lines.push(`Start my plan now: ${command}`);
|
|
165
|
+
if (link) lines.push(`Or open the upgrade page: ${link}`);
|
|
166
|
+
hints.push({ command, reason: 'Start my plan now' });
|
|
167
|
+
if (!link) hints.push(portalHint);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'update_card': {
|
|
171
|
+
const label = nextAction.label || 'Update card';
|
|
172
|
+
const page = ACTION_PAGES[nextAction.page] || ACTION_PAGES.billing;
|
|
173
|
+
actionUrl = /^https?:\/\//.test(nextAction.url || '') ? nextAction.url : portalLink(page.path, apiBase);
|
|
174
|
+
if (actionUrl) {
|
|
175
|
+
lines.push(`${label}: ${actionUrl}`);
|
|
176
|
+
hints.push({ message: `${label}: ${actionUrl}` });
|
|
177
|
+
} else {
|
|
178
|
+
lines.push(`${label}: open ${page.where}, in the Notis app.`);
|
|
179
|
+
hints.push({ command: toolCommand('LOCAL_NOTIS_GET_PORTAL_URL', { page: page.path }), reason: 'Get the billing page link' });
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case 'contact_us': {
|
|
184
|
+
const label = nextAction.label || 'Contact us';
|
|
185
|
+
actionUrl = typeof nextAction.url === 'string' && nextAction.url ? nextAction.url : SUPPORT_URL;
|
|
186
|
+
lines.push(`${label}: ${actionUrl}`);
|
|
187
|
+
hints.push({ message: `${label}: ${actionUrl}` });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case 'retry_entitlement_check':
|
|
191
|
+
lines.push('Your plan could not be checked right now. Try again in a moment.');
|
|
192
|
+
break;
|
|
193
|
+
default: {
|
|
194
|
+
if (link) {
|
|
195
|
+
lines.push(`Open the upgrade page: ${link}`);
|
|
196
|
+
hints.push({ message: `Open the upgrade page: ${link}` });
|
|
197
|
+
} else {
|
|
198
|
+
lines.push('Open the upgrade page in the Notis app.');
|
|
199
|
+
hints.push(portalHint);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
message,
|
|
206
|
+
lines,
|
|
207
|
+
text: lines.join('\n'),
|
|
208
|
+
hints,
|
|
209
|
+
upsell: {
|
|
210
|
+
code: body.code || 'entitlement_upgrade_required',
|
|
211
|
+
entitlement: body.entitlement || null,
|
|
212
|
+
required_plan: body.required_plan || null,
|
|
213
|
+
lock: body.lock || null,
|
|
214
|
+
upgrade_url: link || body.upgrade_url || '/subscribe',
|
|
215
|
+
next_action: nextAction.action || null,
|
|
216
|
+
...(actionUrl ? { action_url: actionUrl } : {}),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, writeFile, unlink } from 'node:fs/promises';
|
|
3
|
+
import { resolve, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { httpRequest } from './transport.js';
|
|
6
|
+
import { CliError, EXIT_CODES, usageError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
async function rendererLibrary() {
|
|
9
|
+
const packaged = new URL('../../dist/view-renderer/src/index.js', import.meta.url);
|
|
10
|
+
if (existsSync(fileURLToPath(packaged))) return import(packaged.href);
|
|
11
|
+
const development = new URL('../../../view-renderer/src/index.js', import.meta.url);
|
|
12
|
+
if (existsSync(fileURLToPath(development))) return import(development.href);
|
|
13
|
+
throw usageError('The shared view renderer is missing. Rebuild or reinstall this CLI package.');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Root command registration supplies flags/output formatting; this adapter holds no browser JWT. */
|
|
17
|
+
export async function renderViewWithCliRuntime({ runtime, url, outputs = ['markdown', 'screenshot'], width = 1440,
|
|
18
|
+
directory, signal, loadLibrary = rendererLibrary, request = httpRequest }) {
|
|
19
|
+
if (typeof url !== 'string' || !url.trim() || ![390, 1440].includes(width)) throw usageError('Choose a view link and width 390 or 1440.');
|
|
20
|
+
const ticket = (await request({ runtime, method: 'POST', path: '/portal_view_render/prepare', spacesProtocol: true,
|
|
21
|
+
body: { url, outputs, width }, signal })).payload;
|
|
22
|
+
if (typeof ticket?.credential !== 'string') throw usageError('The short-lived render ticket is missing.');
|
|
23
|
+
const library = await loadLibrary();
|
|
24
|
+
const broker = library.createRemoteRenderBroker({ apiBase: runtime.apiBase, credential: ticket.credential });
|
|
25
|
+
let result;
|
|
26
|
+
try {
|
|
27
|
+
result = await library.renderView({ broker, outputs, width, signal });
|
|
28
|
+
await broker.complete(result, { signal });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof library.ViewRenderError) throw new CliError({ code: error.code,
|
|
32
|
+
message: error.message, exitCode: EXIT_CODES.backend, retryable: error.status >= 500 || error.status === 429, cause: error });
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
if (!directory) return { result, artifacts: null };
|
|
36
|
+
const root = resolve(directory), created = [], artifacts = {};
|
|
37
|
+
await mkdir(root, { recursive: true });
|
|
38
|
+
try {
|
|
39
|
+
for (const [kind, name, bytes] of [
|
|
40
|
+
['markdown', 'view.md', result.markdown === undefined ? undefined : Buffer.from(result.markdown, 'utf8')],
|
|
41
|
+
['screenshot', 'view.png', result.screenshot ? Buffer.from(result.screenshot.data_base64, 'base64') : undefined],
|
|
42
|
+
]) {
|
|
43
|
+
if (bytes === undefined) continue;
|
|
44
|
+
const path = join(root, name);
|
|
45
|
+
await writeFile(path, bytes, { flag: 'wx', mode: 0o600 }); created.push(path); artifacts[kind] = path;
|
|
46
|
+
}
|
|
47
|
+
} catch (error) { await Promise.allSettled(created.map(path => unlink(path))); throw error; }
|
|
48
|
+
return { result, artifacts };
|
|
49
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { getCurrentProfileName, getProfile, loadConfig, resolveRuntimeProfile } from './runtime/profiles.js';
|
|
6
|
+
import { ensureFreshOAuthCredential } from './runtime/oauth.js';
|
|
7
|
+
import { runSkillSync, fetchSyncSettings } from '../dist/skill-sync/index.js';
|
|
8
|
+
import { withSkillSyncLock } from './runtime/sync-skills.js';
|
|
9
|
+
|
|
10
|
+
// The server rejected the stored sign-in itself. Presenting it again can only
|
|
11
|
+
// fail the same way, so the worker waits for the sign-in to change instead of
|
|
12
|
+
// asking every minute (docs/no-dead-ends.md).
|
|
13
|
+
const REJECTED_SIGN_IN_CODES = new Set(['invalid_grant', 'invalid_client', 'unauthorized_client']);
|
|
14
|
+
|
|
15
|
+
function currentCliProfile() {
|
|
16
|
+
try {
|
|
17
|
+
return getCurrentProfileName(loadConfig());
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A fingerprint of the profile's stored sign-in that changes whenever it is
|
|
25
|
+
* renewed or replaced, by any process, and holds no credential.
|
|
26
|
+
*/
|
|
27
|
+
export function signInFingerprint(profileName, config = loadConfig()) {
|
|
28
|
+
const profile = getProfile(config, profileName);
|
|
29
|
+
if (!profile.oauth_client_id && !profile.oauth_access_expires_at) return null;
|
|
30
|
+
return createHash('sha256').update(JSON.stringify([
|
|
31
|
+
profile.oauth_issuer ?? null,
|
|
32
|
+
profile.oauth_client_id ?? null,
|
|
33
|
+
profile.oauth_user_id ?? null,
|
|
34
|
+
profile.oauth_access_expires_at ?? null,
|
|
35
|
+
profile.oauth_refresh_expires_at ?? null,
|
|
36
|
+
])).digest('hex').slice(0, 16);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runAutomaticSkillSync({ profile, apiBase, userId }, {
|
|
40
|
+
resolveRuntime = resolveRuntimeProfile, refresh = ensureFreshOAuthCredential,
|
|
41
|
+
settings = fetchSyncSettings, sync = runSkillSync, lock = withSkillSyncLock,
|
|
42
|
+
currentProfile = currentCliProfile,
|
|
43
|
+
} = {}) {
|
|
44
|
+
const runtime = resolveRuntime({ profile }, { requireAuth: true });
|
|
45
|
+
if (runtime.credentialKind !== 'oauth' || runtime.apiBase !== apiBase
|
|
46
|
+
|| runtime.oauthUserId !== userId) throw new Error('Automatic skill sync account changed; run notis skills sync to rebind');
|
|
47
|
+
await refresh(runtime);
|
|
48
|
+
const saved = await settings(runtime.apiBase, runtime.jwt);
|
|
49
|
+
if (saved.user_id !== userId) throw new Error('Automatic skill sync identity mismatch');
|
|
50
|
+
// Do not gather, unlink foreign accounts or write anything when opted out.
|
|
51
|
+
if (!saved.sync_enabled) return { status: 'disabled' };
|
|
52
|
+
// The service stays bound to its profile; after `notis profile use` moves the
|
|
53
|
+
// default away, it keeps that account's folder in sync without linking it.
|
|
54
|
+
const linkAgentFolders = (currentProfile() ?? profile) === profile;
|
|
55
|
+
const result = await lock(() => sync(runtime.apiBase, runtime.jwt, {
|
|
56
|
+
fetchSyncSettings: async () => saved,
|
|
57
|
+
}, { honorSyncEnabled: true, ...(linkAgentFolders ? {} : { linkAgentFolders: false }) }));
|
|
58
|
+
return { status: (result.failedLinks?.length || result.failedPushes?.length) ? 'partial' : 'synced', ...result };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readStatus(target) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(readFileSync(target, 'utf8'));
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function main(args = process.argv.slice(2), {
|
|
70
|
+
home = homedir(), run = runAutomaticSkillSync, fingerprint = signInFingerprint,
|
|
71
|
+
deadlineMs = 240_000,
|
|
72
|
+
} = {}) {
|
|
73
|
+
const [profile, apiBase, userId] = args;
|
|
74
|
+
const root = join(home, '.notis', 'skills', 'service');
|
|
75
|
+
const target = join(root, 'status.json');
|
|
76
|
+
const record = (value) => {
|
|
77
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
78
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
79
|
+
writeFileSync(temporary, JSON.stringify({ ...value, profile, at: new Date().toISOString() }, null, 2), { mode: 0o600 });
|
|
80
|
+
renameSync(temporary, target);
|
|
81
|
+
};
|
|
82
|
+
let signIn = null;
|
|
83
|
+
try {
|
|
84
|
+
signIn = fingerprint(profile);
|
|
85
|
+
} catch {
|
|
86
|
+
signIn = null;
|
|
87
|
+
}
|
|
88
|
+
const previous = readStatus(target);
|
|
89
|
+
if (
|
|
90
|
+
signIn
|
|
91
|
+
&& previous?.status === 'error'
|
|
92
|
+
&& previous.profile === profile
|
|
93
|
+
&& REJECTED_SIGN_IN_CODES.has(previous.code)
|
|
94
|
+
&& previous.sign_in === signIn
|
|
95
|
+
) {
|
|
96
|
+
// Same rejected sign-in: wait for `notis login`, or for any process to
|
|
97
|
+
// renew it, before presenting it again.
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const deadline = setTimeout(() => {
|
|
101
|
+
record({ status: 'error', code: 'sync_timeout' });
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}, deadlineMs);
|
|
104
|
+
try {
|
|
105
|
+
record(await run({ profile, apiBase, userId }));
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Persist only a classified error, never a bearer, signed URL or response body.
|
|
108
|
+
const code = error.code || 'sync_failed';
|
|
109
|
+
record({ status: 'error', code, ...(REJECTED_SIGN_IN_CODES.has(code) && signIn ? { sign_in: signIn } : {}) });
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
} finally {
|
|
112
|
+
clearTimeout(deadline);
|
|
113
|
+
}
|
|
114
|
+
}
|