@notis_ai/cli 0.2.17 → 0.2.19
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 +42323 -18258
- 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 +271 -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 -1568
- 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 -3000
- 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
|
@@ -49,6 +49,23 @@ export function getAuthRecovery(runtime = {}, { mode = 'expired' } = {}) {
|
|
|
49
49
|
return { hints };
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* How to recover when renewing a sign-in failed but the sign-in itself is
|
|
54
|
+
* kept: try again. Never point at login, which would replace a working grant.
|
|
55
|
+
*/
|
|
56
|
+
export function getRetryRecovery(runtime = {}) {
|
|
57
|
+
const CLI_NPX = cliNpx(runtime);
|
|
58
|
+
const suffix = profileSuffix(runtime.profileName);
|
|
59
|
+
return {
|
|
60
|
+
hints: [
|
|
61
|
+
{
|
|
62
|
+
command: `${CLI_NPX} doctor${suffix}`,
|
|
63
|
+
reason: 'Check that Notis is reachable from this computer, then run the same command again',
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
52
69
|
export function createExpiredAuthError(runtime) {
|
|
53
70
|
const CLI_NPX = cliNpx(runtime || {});
|
|
54
71
|
if (runtime?.credentialKind === 'worktree') {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export interface BaseSkillReconcileResult {
|
|
2
2
|
installed: number;
|
|
3
|
+
/** Skills left as installed because that copy is newer than this one. */
|
|
4
|
+
kept: number;
|
|
3
5
|
linked: number;
|
|
4
6
|
unchanged: number;
|
|
5
7
|
backups: string[];
|
|
@@ -7,6 +9,15 @@ export interface BaseSkillReconcileResult {
|
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export const BASE_SKILL_NAMES: readonly string[];
|
|
12
|
+
export const RETIRED_BASE_SKILL_NAMES: readonly string[];
|
|
13
|
+
|
|
14
|
+
export function isBaseSkillLink(linkPath: string, name: string, options?: {
|
|
15
|
+
home?: string;
|
|
16
|
+
}): boolean;
|
|
17
|
+
|
|
18
|
+
export const BASE_SKILL_STAMP_FILE: string;
|
|
19
|
+
|
|
20
|
+
export function readBaseSkillStamp(skillDir: string): number | null;
|
|
10
21
|
|
|
11
22
|
export function resolveBundledBaseSkillsRoot(options?: {
|
|
12
23
|
resourcesPath?: string;
|
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import {
|
|
2
3
|
cpSync,
|
|
3
4
|
existsSync,
|
|
4
5
|
lstatSync,
|
|
5
6
|
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
6
9
|
readlinkSync,
|
|
7
10
|
renameSync,
|
|
8
11
|
rmSync,
|
|
9
12
|
symlinkSync,
|
|
13
|
+
unlinkSync,
|
|
10
14
|
} from 'node:fs';
|
|
11
15
|
import { homedir } from 'node:os';
|
|
12
16
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
13
17
|
import { fileURLToPath } from 'node:url';
|
|
14
18
|
|
|
15
19
|
export const BASE_SKILL_NAMES = Object.freeze([
|
|
16
|
-
'notis-
|
|
20
|
+
'notis-spaces',
|
|
17
21
|
'notis-query',
|
|
18
22
|
'notis-cli',
|
|
19
23
|
]);
|
|
24
|
+
// Earlier CLIs linked these names. Only links this installer created are retired.
|
|
25
|
+
export const RETIRED_BASE_SKILL_NAMES = Object.freeze(['notis-apps', 'notis-reports']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The npm package and Desktop build write this file into each skill folder
|
|
29
|
+
* they ship: `stamp` is the commit time (Unix seconds) of the newest commit
|
|
30
|
+
* that changed the skill. A source checkout has none.
|
|
31
|
+
*/
|
|
32
|
+
export const BASE_SKILL_STAMP_FILE = '.notis-base-skill.json';
|
|
20
33
|
|
|
21
34
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
22
35
|
|
|
@@ -65,6 +78,11 @@ function sameLinkTarget(linkPath, expectedTarget) {
|
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
80
|
|
|
81
|
+
/** True only for a link into this installer's base copy of `name`. */
|
|
82
|
+
export function isBaseSkillLink(linkPath, name, { home = homedir() } = {}) {
|
|
83
|
+
return sameLinkTarget(linkPath, join(getBaseSkillPaths({ home }).baseRoot, name));
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
function backupConflict(linkPath, backupRoot, targetLabel, skillName, now) {
|
|
69
87
|
const backupPath = join(backupRoot, String(now), targetLabel, skillName);
|
|
70
88
|
mkdirSync(dirname(backupPath), { recursive: true, mode: 0o700 });
|
|
@@ -72,6 +90,60 @@ function backupConflict(linkPath, backupRoot, targetLabel, skillName, now) {
|
|
|
72
90
|
return backupPath;
|
|
73
91
|
}
|
|
74
92
|
|
|
93
|
+
/** The version stamp of one skill folder, or null when it has none. */
|
|
94
|
+
export function readBaseSkillStamp(skillDir) {
|
|
95
|
+
try {
|
|
96
|
+
const { stamp } = JSON.parse(readFileSync(join(skillDir, BASE_SKILL_STAMP_FILE), 'utf8'));
|
|
97
|
+
return Number.isSafeInteger(stamp) && stamp > 0 ? stamp : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function byName(left, right) {
|
|
104
|
+
if (left.name === right.name) return 0;
|
|
105
|
+
return left.name < right.name ? -1 : 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Digest of a skill folder's files, leaving out its version stamp. */
|
|
109
|
+
function contentDigest(root) {
|
|
110
|
+
const hash = createHash('sha256');
|
|
111
|
+
const walk = (directory, prefix) => {
|
|
112
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort(byName)) {
|
|
113
|
+
const path = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
114
|
+
const full = join(directory, entry.name);
|
|
115
|
+
if (path === BASE_SKILL_STAMP_FILE) continue;
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
hash.update(`dir\0${path}\0`);
|
|
118
|
+
walk(full, path);
|
|
119
|
+
} else if (entry.isSymbolicLink()) {
|
|
120
|
+
hash.update(`link\0${path}\0${readlinkSync(full)}\0`);
|
|
121
|
+
} else {
|
|
122
|
+
const bytes = readFileSync(full);
|
|
123
|
+
hash.update(`file\0${path}\0${bytes.length}\0`);
|
|
124
|
+
hash.update(bytes);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
walk(root, '');
|
|
129
|
+
return hash.digest('hex');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Newest wins. Desktop and the CLI both run this with the copy they ship, so
|
|
134
|
+
* a copy replaces the installed one only when it is newer, or when both are
|
|
135
|
+
* the same version and the installed files changed since. A source checkout
|
|
136
|
+
* has no stamp and replaces the installed copy whenever their files differ.
|
|
137
|
+
*/
|
|
138
|
+
function baseSkillAction(source, destination) {
|
|
139
|
+
if (!existsSync(join(destination, 'SKILL.md'))) return 'install';
|
|
140
|
+
const bundled = readBaseSkillStamp(source);
|
|
141
|
+
const installed = readBaseSkillStamp(destination);
|
|
142
|
+
if (bundled !== null && installed !== null && installed > bundled) return 'keep';
|
|
143
|
+
if (bundled !== null && (installed === null || installed < bundled)) return 'install';
|
|
144
|
+
return contentDigest(source) === contentDigest(destination) ? 'current' : 'install';
|
|
145
|
+
}
|
|
146
|
+
|
|
75
147
|
function replaceDirectory(source, destination) {
|
|
76
148
|
mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
|
|
77
149
|
const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
|
|
@@ -109,6 +181,7 @@ export function reconcileBaseSkills({
|
|
|
109
181
|
const paths = getBaseSkillPaths({ home, userId });
|
|
110
182
|
const result = {
|
|
111
183
|
installed: 0,
|
|
184
|
+
kept: 0,
|
|
112
185
|
linked: 0,
|
|
113
186
|
unchanged: 0,
|
|
114
187
|
backups: [],
|
|
@@ -120,13 +193,31 @@ export function reconcileBaseSkills({
|
|
|
120
193
|
if (!existsSync(join(source, 'SKILL.md'))) {
|
|
121
194
|
throw new Error(`Bundled base skill is missing: ${name}`);
|
|
122
195
|
}
|
|
123
|
-
|
|
124
|
-
|
|
196
|
+
const destination = join(paths.baseRoot, name);
|
|
197
|
+
let action;
|
|
198
|
+
try {
|
|
199
|
+
action = baseSkillAction(source, destination);
|
|
200
|
+
} catch {
|
|
201
|
+
// An unreadable installed copy is repaired, as before stamps existed.
|
|
202
|
+
action = 'install';
|
|
203
|
+
}
|
|
204
|
+
if (action === 'install') {
|
|
205
|
+
replaceDirectory(source, destination);
|
|
206
|
+
result.installed += 1;
|
|
207
|
+
} else if (action === 'keep') {
|
|
208
|
+
result.kept += 1;
|
|
209
|
+
}
|
|
125
210
|
}
|
|
126
211
|
|
|
127
212
|
for (const targetRoot of paths.targetRoots) {
|
|
128
213
|
mkdirSync(targetRoot, { recursive: true, mode: 0o700 });
|
|
129
214
|
const targetLabel = relative(home, targetRoot).replaceAll('/', '_') || 'home';
|
|
215
|
+
// Retire only links this installer owns. A personal skill with an old
|
|
216
|
+
// product name remains untouched; name alone is never deletion authority.
|
|
217
|
+
for (const retired of RETIRED_BASE_SKILL_NAMES) {
|
|
218
|
+
const oldLink = join(targetRoot, retired);
|
|
219
|
+
if (sameLinkTarget(oldLink, join(paths.baseRoot, retired))) unlinkSync(oldLink);
|
|
220
|
+
}
|
|
130
221
|
for (const name of BASE_SKILL_NAMES) {
|
|
131
222
|
const skillTarget = join(paths.baseRoot, name);
|
|
132
223
|
const linkPath = join(targetRoot, name);
|
|
@@ -152,6 +243,12 @@ export function reconcileBaseSkills({
|
|
|
152
243
|
}
|
|
153
244
|
}
|
|
154
245
|
|
|
246
|
+
// Keep the old managed bytes recoverable, outside every agent's skill scan.
|
|
247
|
+
for (const retired of RETIRED_BASE_SKILL_NAMES) {
|
|
248
|
+
const oldRoot = join(paths.baseRoot, retired);
|
|
249
|
+
if (existsSync(oldRoot)) result.backups.push(backupConflict(oldRoot, paths.backupRoot, 'base', retired, now));
|
|
250
|
+
}
|
|
251
|
+
|
|
155
252
|
return result;
|
|
156
253
|
}
|
|
157
254
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ComputerIdentity {
|
|
2
|
+
/** Stable UUID v4 for this computer, shared by every account on it. */
|
|
3
|
+
computerId: string;
|
|
4
|
+
/** The name the user chose, or null to use the computer's own name. */
|
|
5
|
+
name: string | null;
|
|
6
|
+
/** The computer's own name (macOS Computer Name, else the host name). */
|
|
7
|
+
defaultName: string;
|
|
8
|
+
/** `name ?? defaultName`, what the server and the user see. */
|
|
9
|
+
displayName: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ComputerIdentityOptions {
|
|
13
|
+
/** The CLI config file computer.json sits next to. Defaults to NOTIS_CLI_CONFIG_FILE or ~/.notis/config.json. */
|
|
14
|
+
configFile?: string;
|
|
15
|
+
platform?: NodeJS.Platform;
|
|
16
|
+
hostname?: () => string;
|
|
17
|
+
readMacComputerName?: () => string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const COMPUTER_IDENTITY_VERSION: number;
|
|
21
|
+
export const COMPUTER_NAME_MAX_LENGTH: number;
|
|
22
|
+
|
|
23
|
+
export function computerIdentityFile(configFile?: string): string;
|
|
24
|
+
export function normalizeComputerName(value: unknown): string | null;
|
|
25
|
+
export function defaultComputerName(options?: ComputerIdentityOptions): string;
|
|
26
|
+
export function readComputerIdentity(options?: ComputerIdentityOptions): ComputerIdentity;
|
|
27
|
+
export function setComputerName(
|
|
28
|
+
name: string | null,
|
|
29
|
+
options?: ComputerIdentityOptions,
|
|
30
|
+
): ComputerIdentity;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { hostname as osHostname } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
import { resolveCliConfigFile, withConfigFileWriteLock } from './config-lock.js';
|
|
7
|
+
|
|
8
|
+
// This computer's identity, shared by the CLI and Desktop: one stable id per
|
|
9
|
+
// computer (the server keeps one CLI sign-in and one local shell connection per
|
|
10
|
+
// computer) and the name the user sees for it. It lives next to the CLI config
|
|
11
|
+
// file, so NOTIS_CLI_CONFIG_FILE moves it too, and is written under the same
|
|
12
|
+
// lock as the config.
|
|
13
|
+
export const COMPUTER_IDENTITY_VERSION = 1;
|
|
14
|
+
export const COMPUTER_NAME_MAX_LENGTH = 64;
|
|
15
|
+
const COMPUTER_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
16
|
+
const FALLBACK_COMPUTER_NAME = 'Computer';
|
|
17
|
+
|
|
18
|
+
export function computerIdentityFile(configFile = resolveCliConfigFile()) {
|
|
19
|
+
return join(dirname(configFile), 'computer.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Trim, collapse whitespace and cap at 64 characters. Returns null for a name
|
|
24
|
+
* that is empty after cleanup, which means "use the computer's own name".
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeComputerName(value) {
|
|
27
|
+
if (typeof value !== 'string') return null;
|
|
28
|
+
const collapsed = value.replace(/\s+/gu, ' ').trim();
|
|
29
|
+
if (!collapsed) return null;
|
|
30
|
+
// Count code points so a cut never splits a character in two.
|
|
31
|
+
const characters = Array.from(collapsed);
|
|
32
|
+
if (characters.length <= COMPUTER_NAME_MAX_LENGTH) return collapsed;
|
|
33
|
+
return characters.slice(0, COMPUTER_NAME_MAX_LENGTH).join('').trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readMacComputerName() {
|
|
37
|
+
try {
|
|
38
|
+
return execFileSync('/usr/sbin/scutil', ['--get', 'ComputerName'], {
|
|
39
|
+
encoding: 'utf8',
|
|
40
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
41
|
+
timeout: 2_000,
|
|
42
|
+
});
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The computer's own name: the macOS Computer Name, else the host name without
|
|
50
|
+
* a trailing `.local`.
|
|
51
|
+
*/
|
|
52
|
+
export function defaultComputerName(options = {}) {
|
|
53
|
+
const platform = options.platform ?? process.platform;
|
|
54
|
+
if (platform === 'darwin') {
|
|
55
|
+
const macName = normalizeComputerName((options.readMacComputerName ?? readMacComputerName)());
|
|
56
|
+
if (macName) return macName;
|
|
57
|
+
}
|
|
58
|
+
let host = '';
|
|
59
|
+
try {
|
|
60
|
+
host = String((options.hostname ?? osHostname)() || '');
|
|
61
|
+
} catch {
|
|
62
|
+
host = '';
|
|
63
|
+
}
|
|
64
|
+
return normalizeComputerName(host.replace(/\.local\.?$/iu, '')) || FALLBACK_COMPUTER_NAME;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseIdentityFile(file) {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = JSON.parse(readFileSync(file, 'utf8'));
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validStored(raw) {
|
|
79
|
+
if (!raw || raw.version !== COMPUTER_IDENTITY_VERSION) return null;
|
|
80
|
+
if (typeof raw.computer_id !== 'string' || !COMPUTER_ID_PATTERN.test(raw.computer_id)) return null;
|
|
81
|
+
return {
|
|
82
|
+
computerId: raw.computer_id.toLowerCase(),
|
|
83
|
+
name: normalizeComputerName(raw.name),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeIdentityFile(file, stored) {
|
|
88
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
89
|
+
const contents = {
|
|
90
|
+
version: COMPUTER_IDENTITY_VERSION,
|
|
91
|
+
computer_id: stored.computerId,
|
|
92
|
+
...(stored.name ? { name: stored.name } : {}),
|
|
93
|
+
};
|
|
94
|
+
const temporaryFile = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
95
|
+
writeFileSync(temporaryFile, `${JSON.stringify(contents, null, 2)}\n`, { mode: 0o600 });
|
|
96
|
+
renameSync(temporaryFile, file);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function withIdentity(stored, options) {
|
|
100
|
+
const defaultName = defaultComputerName(options);
|
|
101
|
+
return {
|
|
102
|
+
computerId: stored.computerId,
|
|
103
|
+
name: stored.name,
|
|
104
|
+
defaultName,
|
|
105
|
+
displayName: stored.name ?? defaultName,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Under the config lock: re-read so two first reads agree on one id, then
|
|
110
|
+
// create (or repair) the file only when it holds no valid identity.
|
|
111
|
+
function readOrCreateLocked(configFile, file) {
|
|
112
|
+
const raw = parseIdentityFile(file);
|
|
113
|
+
const existing = validStored(raw);
|
|
114
|
+
if (existing) return existing;
|
|
115
|
+
const created = {
|
|
116
|
+
computerId: randomUUID(),
|
|
117
|
+
// A file with a broken id keeps the name the user chose.
|
|
118
|
+
name: normalizeComputerName(raw?.name),
|
|
119
|
+
};
|
|
120
|
+
writeIdentityFile(file, created);
|
|
121
|
+
return created;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Read this computer's identity, creating `computer.json` on first read.
|
|
126
|
+
*/
|
|
127
|
+
export function readComputerIdentity(options = {}) {
|
|
128
|
+
const configFile = options.configFile ?? resolveCliConfigFile();
|
|
129
|
+
const file = computerIdentityFile(configFile);
|
|
130
|
+
const stored = validStored(parseIdentityFile(file))
|
|
131
|
+
?? withConfigFileWriteLock(configFile, () => readOrCreateLocked(configFile, file));
|
|
132
|
+
return withIdentity(stored, options);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Rename this computer for every account on it. An empty name goes back to the
|
|
137
|
+
* computer's own name.
|
|
138
|
+
*/
|
|
139
|
+
export function setComputerName(name, options = {}) {
|
|
140
|
+
const configFile = options.configFile ?? resolveCliConfigFile();
|
|
141
|
+
const file = computerIdentityFile(configFile);
|
|
142
|
+
const stored = withConfigFileWriteLock(configFile, () => {
|
|
143
|
+
const current = readOrCreateLocked(configFile, file);
|
|
144
|
+
const next = { computerId: current.computerId, name: normalizeComputerName(name) };
|
|
145
|
+
if (next.name !== current.name) {
|
|
146
|
+
writeIdentityFile(file, next);
|
|
147
|
+
}
|
|
148
|
+
return next;
|
|
149
|
+
});
|
|
150
|
+
return withIdentity(stored, options);
|
|
151
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
statSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
|
|
14
|
+
// Cross-process write lock over ~/.notis/config.json. Independent `notis`
|
|
15
|
+
// processes (a `login` in one terminal and a `tools exec` in another) and
|
|
16
|
+
// Desktop all rewrite the files in this directory, so the lock directory name
|
|
17
|
+
// and these three timings are the whole coordination protocol. Desktop keeps a
|
|
18
|
+
// TypeScript copy in electron/src/notis-config-file.ts. Guarded by the
|
|
19
|
+
// concurrency test in packages/cli/test/runtime-auth.test.js.
|
|
20
|
+
const CONFIG_WRITE_LOCK_TIMEOUT_MS = 5_000;
|
|
21
|
+
const CONFIG_WRITE_LOCK_STALE_MS = 2_000;
|
|
22
|
+
const CONFIG_WRITE_LOCK_POLL_MS = 10;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The shared CLI config file. NOTIS_CLI_CONFIG_FILE moves it (and every file
|
|
26
|
+
* kept next to it, such as computer.json) for tests and isolated runtimes.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveCliConfigFile() {
|
|
29
|
+
const envConfigFile = process.env.NOTIS_CLI_CONFIG_FILE;
|
|
30
|
+
if (envConfigFile) {
|
|
31
|
+
return resolve(envConfigFile);
|
|
32
|
+
}
|
|
33
|
+
return join(homedir(), '.notis', 'config.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The lock records who holds it and when they took it. Reclaiming a stale lock
|
|
37
|
+
// by mtime alone is not safe: the reclaimer cannot tell an abandoned lock from
|
|
38
|
+
// one it is about to steal from a live writer, and the victim's own release
|
|
39
|
+
// would then delete the thief's lock and admit a third writer. Both sides are
|
|
40
|
+
// on the same host, so the stored timestamp and Date.now() share a clock.
|
|
41
|
+
function readLockOwner(lockDirectory) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(join(lockDirectory, 'owner'), 'utf-8'));
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function lockDirectoryMtime(lockDirectory) {
|
|
50
|
+
try {
|
|
51
|
+
return statSync(lockDirectory).mtimeMs;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function publishConfigWriteLock(lockDirectory, ownerId) {
|
|
58
|
+
const candidateDirectory = `${lockDirectory}.candidate-${ownerId}`;
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(candidateDirectory);
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(candidateDirectory, 'owner'),
|
|
63
|
+
JSON.stringify({ id: ownerId, at: Date.now() }),
|
|
64
|
+
{ mode: 0o600 },
|
|
65
|
+
);
|
|
66
|
+
try {
|
|
67
|
+
renameSync(candidateDirectory, lockDirectory);
|
|
68
|
+
return true;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (!existsSync(lockDirectory)) throw error;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
} finally {
|
|
74
|
+
rmSync(candidateDirectory, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run `callback(configFile)` while holding `<configFile>.write-lock`.
|
|
80
|
+
*/
|
|
81
|
+
export function withConfigFileWriteLock(configFile, callback) {
|
|
82
|
+
const lockDirectory = `${configFile}.write-lock`;
|
|
83
|
+
const ownerId = `${process.pid}.${randomUUID()}`;
|
|
84
|
+
const deadline = Date.now() + CONFIG_WRITE_LOCK_TIMEOUT_MS;
|
|
85
|
+
mkdirSync(dirname(configFile), { recursive: true });
|
|
86
|
+
for (;;) {
|
|
87
|
+
if (!existsSync(lockDirectory) && publishConfigWriteLock(lockDirectory, ownerId)) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const owner = readLockOwner(lockDirectory);
|
|
92
|
+
const observedMtime = lockDirectoryMtime(lockDirectory);
|
|
93
|
+
const ownerIsStale =
|
|
94
|
+
owner?.id && Date.now() - Number(owner.at) > CONFIG_WRITE_LOCK_STALE_MS;
|
|
95
|
+
const ownerlessIsStale =
|
|
96
|
+
!owner
|
|
97
|
+
&& observedMtime !== null
|
|
98
|
+
&& Date.now() - observedMtime > CONFIG_WRITE_LOCK_STALE_MS;
|
|
99
|
+
if (ownerIsStale || ownerlessIsStale) {
|
|
100
|
+
try {
|
|
101
|
+
const currentOwner = readLockOwner(lockDirectory);
|
|
102
|
+
const ownerUnchanged = owner?.id
|
|
103
|
+
? currentOwner?.id === owner.id
|
|
104
|
+
: !currentOwner && lockDirectoryMtime(lockDirectory) === observedMtime;
|
|
105
|
+
if (ownerUnchanged) {
|
|
106
|
+
rmSync(lockDirectory, { recursive: true, force: true });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Losing the reclaim race is normal; fall through and keep waiting.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (Date.now() >= deadline) {
|
|
114
|
+
throw new Error(`Timed out waiting to update ${configFile}`);
|
|
115
|
+
}
|
|
116
|
+
Atomics.wait(
|
|
117
|
+
new Int32Array(new SharedArrayBuffer(4)),
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
CONFIG_WRITE_LOCK_POLL_MS,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
return callback(configFile);
|
|
125
|
+
} finally {
|
|
126
|
+
try {
|
|
127
|
+
// Never release a lock that was reclaimed from us while we worked.
|
|
128
|
+
if (readLockOwner(lockDirectory)?.id === ownerId) {
|
|
129
|
+
rmSync(lockDirectory, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// The lock may already have been removed after an interrupted write.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pinned first build of one Space for a confined local effect
|
|
3
|
+
* (server/lib/confined_effects/space_build.py).
|
|
4
|
+
*
|
|
5
|
+
* node confined-space-build.js <build-request.json>
|
|
6
|
+
*
|
|
7
|
+
* The backend starts this file from a hashed, read-only toolchain under the macOS
|
|
8
|
+
* sandbox (no network). It reuses the canonical selected-source freeze, manifest,
|
|
9
|
+
* V4 checks and build receipt (buildSpaceArtifact) and replaces only the build
|
|
10
|
+
* runner: the canonical Vite command runs with this same node executable and a
|
|
11
|
+
* rebuilt environment, never PATH npm, a package script or the caller's
|
|
12
|
+
* environment. It never refreshes the SDK or installs anything.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
18
|
+
import { buildSpaceArtifact } from './space-platform.js';
|
|
19
|
+
|
|
20
|
+
const CANONICAL_BUILD_SCRIPTS = new Set(['vite build', 'vite build --configLoader runner']);
|
|
21
|
+
const OUTPUT_LIMIT = 32 * 1024;
|
|
22
|
+
|
|
23
|
+
export function pinnedBuildEnvironment(environment = process.env) {
|
|
24
|
+
return { PATH: [dirname(process.execPath), '/usr/bin', '/bin'].join(':'), HOME: environment.HOME || '',
|
|
25
|
+
TMPDIR: environment.TMPDIR || '', LANG: 'C', LC_ALL: 'C', NODE_ENV: 'production' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pinnedViteCommand(projectDir, executable = process.execPath) {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8'));
|
|
30
|
+
const script = String(pkg.scripts?.build || '').trim();
|
|
31
|
+
if (!CANONICAL_BUILD_SCRIPTS.has(script)) throw new Error('Only the canonical vite build script can run in a confined build.');
|
|
32
|
+
const vite = realpathSync(join(projectDir, 'node_modules', 'vite', 'bin', 'vite.js'));
|
|
33
|
+
return [executable, vite, 'build', '--configLoader', 'runner'];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** buildArtifact runBuild seam: the canonical build, pinned, with bounded captured output. */
|
|
37
|
+
export function runPinnedVite({ projectDir }) {
|
|
38
|
+
const [command, ...args] = pinnedViteCommand(projectDir);
|
|
39
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
40
|
+
const child = spawn(command, args, { cwd: projectDir, env: pinnedBuildEnvironment(), stdio: ['ignore', 'pipe', 'pipe'] });
|
|
41
|
+
let output = '';
|
|
42
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
43
|
+
stream.on('data', chunk => { if (output.length < OUTPUT_LIMIT) output += chunk.toString().slice(0, OUTPUT_LIMIT - output.length); });
|
|
44
|
+
}
|
|
45
|
+
child.on('error', rejectPromise);
|
|
46
|
+
child.on('exit', (code, signal) => {
|
|
47
|
+
if (code === 0) resolvePromise();
|
|
48
|
+
else rejectPromise(new Error(`vite build failed (${signal || code})${output.trim() ? `:\n${output.trim()}` : ''}`));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function runConfinedBuild(request) {
|
|
54
|
+
if (!request || typeof request.projectDir !== 'string' || typeof request.key !== 'string') {
|
|
55
|
+
throw new Error('The build request names one workspace and one Space key.');
|
|
56
|
+
}
|
|
57
|
+
const result = await buildSpaceArtifact(request.projectDir, request.key, { stdio: 'pipe', refreshSdk: false, runBuild: runPinnedVite });
|
|
58
|
+
// A summary only: the backend grades the saved build receipt bytes, not this line.
|
|
59
|
+
const { local_key, kind, name, spec_version } = result.manifest || {};
|
|
60
|
+
return { status: 'built', key: request.key, receipt_path: result.receiptPath, source_hash: result.sourceHash,
|
|
61
|
+
manifest: { local_key, kind, name, spec_version } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function main(argv) {
|
|
65
|
+
const request = JSON.parse(readFileSync(argv[2], 'utf8'));
|
|
66
|
+
try {
|
|
67
|
+
process.stdout.write(JSON.stringify(await runConfinedBuild(request)) + '\n');
|
|
68
|
+
} catch (error) {
|
|
69
|
+
process.stdout.write(JSON.stringify({ status: 'failed', error: String(error?.message || error).slice(0, OUTPUT_LIMIT) }) + '\n');
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) await main(process.argv);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-free canonical verification of one accepted confined build
|
|
3
|
+
* (server/lib/confined_effects/space_build.py).
|
|
4
|
+
*
|
|
5
|
+
* node confined-space-verify.js <verify-request.json>
|
|
6
|
+
*
|
|
7
|
+
* Runs outside the build sandbox so Chromium keeps its own sandbox
|
|
8
|
+
* (chromiumSandbox: true in the shared renderer pool). The request carries the
|
|
9
|
+
* capabilities returned by backend source verification and the accepted build's
|
|
10
|
+
* exact source and artifact digests; anything else refuses before rendering.
|
|
11
|
+
* Rendering uses only the declared offline fixtures at 390 and 1440 (no live
|
|
12
|
+
* data, no deployment, no Store publication).
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
15
|
+
import { pathToFileURL } from 'node:url';
|
|
16
|
+
import { appFilesDigest } from './app-platform.js';
|
|
17
|
+
import { prepareSpaceRelease } from './space-platform.js';
|
|
18
|
+
import { verifySpaceRelease } from './space-verification.js';
|
|
19
|
+
|
|
20
|
+
export async function runConfinedVerification(request, { verify = verifySpaceRelease } = {}) {
|
|
21
|
+
if (!request || typeof request.projectDir !== 'string' || typeof request.key !== 'string'
|
|
22
|
+
|| typeof request.sourceDigest !== 'string' || typeof request.artifactDigest !== 'string'
|
|
23
|
+
|| !request.capabilities || typeof request.capabilities !== 'object') {
|
|
24
|
+
throw new Error('The verification request names one accepted build and its capabilities.');
|
|
25
|
+
}
|
|
26
|
+
const release = await prepareSpaceRelease(request.projectDir, request.key);
|
|
27
|
+
try {
|
|
28
|
+
const sourceDigest = appFilesDigest(release.sourceFiles), artifactDigest = appFilesDigest(release.files);
|
|
29
|
+
if (sourceDigest !== request.sourceDigest || artifactDigest !== request.artifactDigest) {
|
|
30
|
+
return { status: 'failed', error: 'build_changed', source_digest: sourceDigest, artifact_digest: artifactDigest };
|
|
31
|
+
}
|
|
32
|
+
const diagnostic = await verify(release, { capabilities: request.capabilities, timeoutMs: 45_000 });
|
|
33
|
+
return { status: diagnostic.status === 'passed' ? 'verified' : 'failed', diagnostic };
|
|
34
|
+
} finally { release.close(); }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main(argv) {
|
|
38
|
+
const request = JSON.parse(readFileSync(argv[2], 'utf8'));
|
|
39
|
+
try {
|
|
40
|
+
const result = await runConfinedVerification(request);
|
|
41
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
42
|
+
process.exitCode = result.status === 'verified' ? 0 : 3;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
process.stdout.write(JSON.stringify({ status: 'failed', error: String(error?.message || error).slice(0, 32 * 1024) }) + '\n');
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) await main(process.argv);
|