@astryxdesign/cli 0.7.2-canary.fdd384f → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +119 -0
- package/README.md +169 -149
- package/api/build/_adapter.d.mts +8 -0
- package/api/build/_adapter.mjs +12 -1
- package/api/build/build.doc.mjs +4 -0
- package/api/build/kit/kit.mjs +3 -11
- package/api/component/_adapter.d.mts +31 -0
- package/api/component/_adapter.mjs +119 -11
- package/api/component/component.doc.mjs +6 -1
- package/api/component/component.mjs +60 -10
- package/api/component/doc-links.test.mjs +386 -0
- package/api/discover/_adapter.d.mts +132 -12
- package/api/discover/_adapter.mjs +488 -27
- package/api/discover/_adapter.test.mjs +599 -17
- package/api/discover/_catalog-view.d.mts +16 -5
- package/api/discover/_catalog-view.mjs +32 -8
- package/api/discover/detail/doc/doc.mjs +9 -1
- package/api/discover/discover.d.mts +11 -0
- package/api/discover/discover.doc.mjs +9 -7
- package/api/discover/discover.mjs +121 -19
- package/api/discover/discover.type.d.mts +20 -7
- package/api/discover/discover.type.mjs +17 -7
- package/api/discover/list/list.d.mts +4 -3
- package/api/discover/list/list.mjs +17 -10
- package/api/docs/_adapter.d.mts +63 -0
- package/api/docs/_adapter.mjs +312 -18
- package/api/docs/docs.doc.mjs +2 -1
- package/api/docs/docs.mjs +2 -1
- package/api/docs/docs.test.mjs +5 -2
- package/api/docs/integration-tree.test.mjs +22 -0
- package/api/docs/list/list.mjs +12 -2
- package/api/docs/reference-blocks.test.mjs +1 -3
- package/api/doctor/_adapter.d.mts +108 -0
- package/api/doctor/_adapter.mjs +223 -4
- package/api/doctor/doctor.d.mts +199 -0
- package/api/doctor/doctor.doc.mjs +3 -2
- package/api/doctor/doctor.mjs +530 -34
- package/api/doctor/doctor.test.mjs +180 -14
- package/api/doctor/release-checks.test.mjs +156 -0
- package/api/doctor/theme-checks.mjs +16 -3
- package/api/doctor/theme-checks.test.mjs +223 -7
- package/api/error.d.mts +10 -0
- package/api/error.mjs +25 -0
- package/api/gap-report/gap-report.d.mts +3 -0
- package/api/gap-report/gap-report.doc.mjs +11 -3
- package/api/gap-report/gap-report.mjs +152 -48
- package/api/gap-report/gap-report.test.mjs +233 -0
- package/api/gap-report/gap-report.type.d.mts +41 -0
- package/api/gap-report/gap-report.type.mjs +18 -0
- package/api/hook/_adapter.mjs +3 -11
- package/api/hook/hook.doc.mjs +4 -0
- package/api/hook/list/list.d.mts +1 -1
- package/api/init/_adapter.d.mts +22 -0
- package/api/init/_adapter.mjs +59 -0
- package/api/init/init.d.mts +7 -5
- package/api/init/init.doc.mjs +28 -7
- package/api/init/init.mjs +28 -4
- package/api/init/init.type.d.mts +48 -3
- package/api/init/init.type.mjs +37 -3
- package/api/init/run/run.d.mts +1 -1
- package/api/init/run/run.mjs +15 -4
- package/api/init/user/user.d.mts +14 -0
- package/api/init/user/user.mjs +87 -0
- package/api/init/user/user.test.mjs +232 -0
- package/api/integration/_adapter.d.mts +33 -0
- package/api/integration/_adapter.mjs +58 -0
- package/api/integration/authoring-checks.mjs +100 -5
- package/api/integration/authoring-checks.test.mjs +769 -0
- package/api/integration/authoring-checks.type.d.mts +134 -0
- package/api/integration/authoring-checks.type.mjs +60 -1
- package/api/integration/integrationCheck.doc.mjs +1 -1
- package/api/integration/integrationComponentConflicts.doc.mjs +11 -3
- package/api/integration/integrationDocConflicts.doc.mjs +22 -5
- package/api/integration/pack-check.mjs +303 -13
- package/api/integration/pack-check.test.mjs +371 -10
- package/api/integration/validate-integration-fixes.test.mjs +67 -0
- package/api/integration/validate-integration.d.mts +8 -0
- package/api/integration/validate-integration.mjs +55 -9
- package/api/integration/validate-integration.test.mjs +77 -8
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +3 -1
- package/api/integration/validate-unread-theme-folders.test.mjs +3 -2
- package/api/integration/validateIntegration.doc.mjs +4 -2
- package/api/search/_adapter.d.mts +11 -0
- package/api/search/_adapter.mjs +25 -0
- package/api/search/search.d.mts +73 -13
- package/api/search/search.doc.mjs +8 -3
- package/api/search/search.domain-priority.test.mjs +108 -18
- package/api/search/search.mjs +541 -52
- package/api/search/search.test.mjs +143 -1
- package/api/search/search.type.d.mts +14 -1
- package/api/search/search.type.mjs +6 -1
- package/api/swizzle/_adapter.mjs +2 -2
- package/api/swizzle/copy/copy.mjs +1 -1
- package/api/template/template-integration.test.mjs +1 -1
- package/api/theme/_adapter.d.mts +16 -0
- package/api/theme/_adapter.mjs +32 -5
- package/api/theme/build/build.d.mts +2 -1
- package/api/theme/build/build.mjs +70 -14
- package/api/theme/build/build.packed-old-core.test.mjs +20 -3
- package/api/theme/build/font-warning.mjs +5 -3
- package/api/theme/build/font-warning.test.mjs +2 -1
- package/api/theme/remove/remove.test.mjs +2 -2
- package/api/theme/targets/targets.mjs +3 -10
- package/api/theme/themeTargets.doc.mjs +4 -0
- package/api/theme/use/use.test.mjs +1 -1
- package/api/upgrade/_adapter.d.mts +40 -2
- package/api/upgrade/_adapter.mjs +118 -5
- package/api/upgrade/run/cli-behind-core.test.mjs +438 -0
- package/api/upgrade/run/run.mjs +35 -2
- package/api/upgrade/status/status.d.mts +21 -5
- package/api/upgrade/status/status.mjs +45 -8
- package/api/upgrade/upgrade.doc.mjs +6 -3
- package/api/upgrade/upgrade.mjs +5 -1
- package/api/upgrade/upgrade.type.d.mts +23 -0
- package/api/upgrade/upgrade.type.mjs +16 -1
- package/assets/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/runner.mjs +7 -1
- package/assets/codemods/transforms/next/index.mjs +1 -11
- package/assets/codemods/transforms/v0.7.2/index.mjs +20 -0
- package/assets/discover/official-packages.json +367 -0
- package/assets/docs/author-a-theme.doc.mjs +2 -2
- package/assets/docs/browser-support.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +23 -3
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +12 -2
- package/assets/docs/tree/add-a-theme.doc.mjs +13 -3
- package/assets/docs/tree/add-a-topic.doc.mjs +54 -9
- package/assets/docs/tree/agent-guidance.doc.mjs +19 -5
- package/assets/docs/tree/block-template.doc.mjs +6 -18
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/building-blocks.doc.mjs +4 -0
- package/assets/docs/tree/check-the-props.doc.mjs +8 -3
- package/assets/docs/tree/check-your-docs.doc.mjs +30 -13
- package/assets/docs/tree/checks.doc.mjs +17 -17
- package/assets/docs/tree/ci-and-containers.doc.mjs +88 -0
- package/assets/docs/tree/cli-command.doc.mjs +142 -0
- package/assets/docs/tree/cli.doc.mjs +6 -0
- package/assets/docs/tree/codemods.doc.mjs +17 -7
- package/assets/docs/tree/commands.doc.mjs +7 -1
- package/assets/docs/tree/component-family.doc.mjs +3 -5
- package/assets/docs/tree/component-imports.doc.mjs +13 -3
- package/assets/docs/tree/components.doc.mjs +7 -1
- package/assets/docs/tree/computer-agent-guide.doc.mjs +160 -0
- package/assets/docs/tree/computer-integrations.doc.mjs +93 -0
- package/assets/docs/tree/configuration.doc.mjs +7 -3
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +41 -22
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
- package/assets/docs/tree/docs.doc.mjs +9 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +3 -3
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +19 -9
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +7 -5
- package/assets/docs/tree/global-integrations.doc.mjs +124 -0
- package/assets/docs/tree/global.doc.mjs +131 -0
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +5 -5
- package/assets/docs/tree/install-once.doc.mjs +105 -0
- package/assets/docs/tree/integrations.doc.mjs +13 -1
- package/assets/docs/tree/layout-spacing.doc.mjs +2 -3
- package/assets/docs/tree/layout.doc.mjs +6 -6
- package/assets/docs/tree/links.doc.mjs +6 -5
- package/assets/docs/tree/migration-setup.doc.mjs +3 -2
- package/assets/docs/tree/migration-verification.doc.mjs +0 -1
- package/assets/docs/tree/migration.doc.mjs +0 -1
- package/assets/docs/tree/monorepos.doc.mjs +84 -0
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +3 -7
- package/assets/docs/tree/problems.doc.mjs +205 -0
- package/assets/docs/tree/project-global-or-both.doc.mjs +131 -0
- package/assets/docs/tree/publishing.doc.mjs +6 -6
- package/assets/docs/tree/quick-start.doc.mjs +52 -26
- package/assets/docs/tree/reference.doc.mjs +192 -0
- package/assets/docs/tree/replace-a-core-component.doc.mjs +15 -7
- package/assets/docs/tree/replace-a-core-template.doc.mjs +9 -11
- package/assets/docs/tree/scaffold.doc.mjs +1 -1
- package/assets/docs/tree/sections-and-placement.doc.mjs +9 -11
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +17 -5
- package/assets/docs/tree/short-and-findable.doc.mjs +9 -8
- package/assets/docs/tree/single-component.doc.mjs +14 -6
- package/assets/docs/tree/start-a-template.doc.mjs +18 -8
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +6 -17
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +7 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +7 -7
- package/assets/docs/tree/test-template-in-app.doc.mjs +5 -5
- package/assets/docs/tree/themes.doc.mjs +4 -0
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +22 -14
- package/assets/docs/tree/uninstall.doc.mjs +109 -0
- package/assets/docs/tree/updates.doc.mjs +92 -0
- package/assets/docs/tree/upgrading.doc.mjs +6 -6
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +14 -2
- package/assets/docs/tree/verify-packed-template.doc.mjs +3 -3
- package/assets/docs/tree/versioning.doc.mjs +9 -9
- package/assets/docs/tree/who-answers.doc.mjs +119 -0
- package/assets/docs/tree/write-the-template-file.doc.mjs +5 -5
- package/assets/docs/working-with-ai.doc.mjs +23 -9
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/debug/parse.d.mts +5 -5
- package/authoring/discover/discover.doc.mjs +7 -1
- package/authoring/doctypes/_schema.d.mts +8 -3
- package/authoring/doctypes/_schema.mjs +1 -0
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +20 -12
- package/authoring/doctypes/command/type.ts +14 -0
- package/authoring/doctypes/component/component.doc.mjs +29 -26
- package/authoring/doctypes/component/type.ts +15 -5
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/hook/type.ts +4 -0
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +12 -11
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/template.doc.mjs +2 -2
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +22 -5
- package/authoring/integration/parse.test.mjs +11 -0
- package/authoring/integration/schema.d.mts +14 -3
- package/authoring/integration/schema.mjs +33 -2
- package/authoring/integration/type.ts +8 -0
- package/clients/cli/bin/astryx.mjs +20 -3
- package/clients/cli/cli-command.test.mjs +166 -0
- package/clients/cli/commands/blog.doc.mjs +1 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +14 -2
- package/clients/cli/commands/build-theme.font-warning.test.mjs +3 -3
- package/clients/cli/commands/build-theme.mjs +25 -12
- package/clients/cli/commands/build.doc.mjs +1 -0
- package/clients/cli/commands/build.exit-codes-doc.test.mjs +4 -0
- package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
- package/clients/cli/commands/component.doc.mjs +1 -0
- package/clients/cli/commands/discover.broken-integration.test.mjs +11 -7
- package/clients/cli/commands/discover.doc.mjs +9 -1
- package/clients/cli/commands/discover.mjs +144 -93
- package/clients/cli/commands/discover.no-source.test.mjs +448 -33
- package/clients/cli/commands/discover.sources.test.mjs +20 -1
- package/clients/cli/commands/docs.doc.mjs +1 -0
- package/clients/cli/commands/docs.mjs +49 -19
- package/clients/cli/commands/docs.test.mjs +2 -2
- package/clients/cli/commands/doctor-integration-components.doc.mjs +7 -3
- package/clients/cli/commands/doctor-integration-docs.doc.mjs +20 -5
- package/clients/cli/commands/doctor-integration-templates.doc.mjs +1 -0
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +13 -2
- package/clients/cli/commands/doctor-integration.test.mjs +52 -4
- package/clients/cli/commands/doctor.doc.mjs +12 -2
- package/clients/cli/commands/doctor.mjs +102 -7
- package/clients/cli/commands/doctor.test.mjs +10 -4
- package/clients/cli/commands/gap-report.doc.mjs +15 -2
- package/clients/cli/commands/gap-report.mjs +24 -2
- package/clients/cli/commands/gap-report.test.mjs +79 -0
- package/clients/cli/commands/hook.doc.mjs +1 -0
- package/clients/cli/commands/init.behavior.test.mjs +5 -0
- package/clients/cli/commands/init.doc.mjs +29 -3
- package/clients/cli/commands/integration-add.doc.mjs +7 -0
- package/clients/cli/commands/integration-authoring.test.mjs +49 -3
- package/clients/cli/commands/integration-check.doc.mjs +10 -2
- package/clients/cli/commands/integration-check.test.mjs +175 -1
- package/clients/cli/commands/integration-pack.doc.mjs +8 -1
- package/clients/cli/commands/integration-verify.doc.mjs +8 -1
- package/clients/cli/commands/integration.mjs +15 -9
- package/clients/cli/commands/json-contract.test.mjs +11 -2
- package/clients/cli/commands/manifest.doc.mjs +1 -0
- package/clients/cli/commands/outside-project.test.mjs +774 -0
- package/clients/cli/commands/search.doc.mjs +1 -0
- package/clients/cli/commands/search.mjs +16 -11
- package/clients/cli/commands/search.test.mjs +429 -296
- package/clients/cli/commands/setup-nudge.test.mjs +102 -2
- package/clients/cli/commands/swizzle.doc.mjs +7 -0
- package/clients/cli/commands/swizzle.mjs +5 -3
- package/clients/cli/commands/swizzle.routing.test.mjs +19 -2
- package/clients/cli/commands/template.doc.mjs +1 -0
- package/clients/cli/commands/theme-add-lifecycle.test.mjs +3 -3
- package/clients/cli/commands/theme-add.doc.mjs +8 -1
- package/clients/cli/commands/theme-build.doc.mjs +1 -0
- package/clients/cli/commands/theme-eject.doc.mjs +1 -0
- package/clients/cli/commands/theme-list.behavior.test.mjs +6 -0
- package/clients/cli/commands/theme-list.doc.mjs +1 -0
- package/clients/cli/commands/theme-palette-generate.doc.mjs +1 -0
- package/clients/cli/commands/theme-remove.doc.mjs +7 -0
- package/clients/cli/commands/theme-targets.doc.mjs +1 -0
- package/clients/cli/commands/theme-template.doc.mjs +1 -0
- package/clients/cli/commands/theme-use.doc.mjs +7 -0
- package/clients/cli/commands/upgrade.config-ordering.test.mjs +2 -1
- package/clients/cli/commands/upgrade.doc.mjs +22 -1
- package/clients/cli/commands/upgrade.file-protection.test.mjs +3 -2
- package/clients/cli/commands/upgrade.hook-output.test.mjs +2 -1
- package/clients/cli/commands/upgrade.older-cli.test.mjs +160 -0
- package/clients/cli/index.mjs +111 -16
- package/clients/cli/lib/cli-error.mjs +4 -1
- package/clients/cli/lib/component-format.test.mjs +1 -1
- package/clients/cli/lib/define-command.mjs +2 -2
- package/clients/cli/lib/json-shim.mjs +3 -1
- package/clients/cli/lib/manifest.d.ts +9 -0
- package/clients/cli/lib/manifest.mjs +17 -1
- package/clients/cli/lib/manifest.test.mjs +25 -0
- package/foundation/agent-docs/agent-doc-state.d.mts +35 -6
- package/foundation/agent-docs/agent-doc-state.mjs +66 -6
- package/foundation/agent-docs/agent-docs.d.mts +4 -3
- package/foundation/agent-docs/agent-docs.mjs +11 -9
- package/foundation/agent-docs/computer-agent-text.d.mts +133 -0
- package/foundation/agent-docs/computer-agent-text.mjs +623 -0
- package/foundation/agent-docs/computer-agent-text.test.mjs +602 -0
- package/foundation/config/project.d.mts +35 -3
- package/foundation/config/project.mjs +145 -8
- package/foundation/config/project.test.mjs +66 -0
- package/foundation/config/theme-state.mjs +5 -4
- package/foundation/discovery/authoring-self-docs.mjs +1 -1
- package/foundation/discovery/cli-self-docs.mjs +20 -3
- package/foundation/discovery/cli-self-docs.test.mjs +3 -2
- package/foundation/discovery/component-replacement.mjs +2 -1
- package/foundation/discovery/copied-passages.d.mts +216 -0
- package/foundation/discovery/copied-passages.mjs +720 -0
- package/foundation/discovery/copied-passages.test.mjs +124 -0
- package/foundation/discovery/doc-consistency.d.mts +34 -0
- package/foundation/discovery/doc-consistency.mjs +513 -0
- package/foundation/discovery/doc-consistency.test.mjs +347 -0
- package/foundation/discovery/doc-fluff.d.mts +112 -0
- package/foundation/discovery/doc-fluff.mjs +328 -0
- package/foundation/discovery/doc-fluff.test.mjs +142 -0
- package/foundation/discovery/doc-types.d.mts +1 -1
- package/foundation/discovery/doc-types.mjs +15 -8
- package/foundation/discovery/doc-types.test.mjs +94 -2
- package/foundation/discovery/docs-discovery.d.mts +7 -0
- package/foundation/discovery/docs-discovery.mjs +26 -2
- package/foundation/discovery/project-typescript.d.mts +11 -2
- package/foundation/discovery/project-typescript.mjs +32 -4
- package/foundation/discovery/prop-drift.d.mts +26 -7
- package/foundation/discovery/prop-drift.mjs +173 -8
- package/foundation/discovery/prop-drift.test.mjs +120 -2
- package/foundation/discovery/runner-allowlist.d.mts +40 -0
- package/foundation/discovery/runner-allowlist.mjs +53 -0
- package/foundation/discovery/stray-pointers.d.mts +52 -0
- package/foundation/discovery/stray-pointers.mjs +293 -0
- package/foundation/discovery/stray-pointers.test.mjs +86 -0
- package/foundation/discovery/template-needs.mjs +5 -2
- package/foundation/discovery/theme-imports.mjs +8 -4
- package/foundation/discovery/theme-imports.test.mjs +1 -1
- package/foundation/discovery/type-text.mjs +1 -1
- package/foundation/discovery/type-text.test.mjs +1 -1
- package/foundation/doc-compiler/doc-loads.test.mjs +42 -2
- package/foundation/doc-compiler/inputs.mjs +4 -1
- package/foundation/doc-compiler/links.d.mts +32 -0
- package/foundation/doc-compiler/links.mjs +163 -26
- package/foundation/doc-compiler/links.test.mjs +97 -4
- package/foundation/doc-compiler/read.d.mts +1 -1
- package/foundation/doc-compiler/tree.mjs +30 -1
- package/foundation/doc-compiler/tree.test.mjs +8 -1
- package/foundation/env/cli-command.d.mts +83 -0
- package/foundation/env/cli-command.mjs +152 -0
- package/foundation/env/cli-command.test.mjs +129 -0
- package/foundation/env/node-version.test.mjs +15 -3
- package/foundation/env/package-manager.d.mts +93 -9
- package/foundation/env/package-manager.global.test.mjs +66 -0
- package/foundation/env/package-manager.mjs +184 -13
- package/foundation/env/package-manager.test.mjs +341 -15
- package/foundation/env/project-scope.d.mts +9 -0
- package/foundation/env/project-scope.mjs +33 -0
- package/foundation/env/search-path.d.mts +42 -0
- package/foundation/env/search-path.mjs +205 -0
- package/foundation/env/search-path.test.mjs +122 -0
- package/foundation/env/user-dirs.d.mts +39 -0
- package/foundation/env/user-dirs.mjs +93 -0
- package/foundation/env/user-dirs.test.mjs +72 -0
- package/foundation/env/user-settings.d.mts +46 -0
- package/foundation/env/user-settings.mjs +106 -0
- package/foundation/env/user-settings.test.mjs +64 -0
- package/foundation/fs/file-protection.mjs +15 -1
- package/foundation/fs/module-loader.d.mts +2 -1
- package/foundation/fs/module-loader.mjs +60 -4
- package/foundation/fs/module-loader.stdout.test.mjs +3 -2
- package/foundation/fs/module-loader.test.mjs +194 -0
- package/foundation/fs/path-compare.d.mts +33 -0
- package/foundation/fs/path-compare.mjs +73 -0
- package/foundation/fs/paths.d.mts +57 -1
- package/foundation/fs/paths.mjs +131 -3
- package/foundation/fs/paths.test.mjs +10 -1
- package/foundation/integrations/computer-integrations.d.mts +78 -0
- package/foundation/integrations/computer-integrations.mjs +316 -0
- package/foundation/integrations/computer-integrations.test.mjs +292 -0
- package/foundation/integrations/contribution-inventory.d.mts +11 -0
- package/foundation/integrations/contribution-inventory.mjs +50 -2
- package/foundation/integrations/integration-warnings.mjs +3 -2
- package/foundation/integrations/integration-warnings.test.mjs +8 -9
- package/foundation/integrations/integrations.d.mts +25 -1
- package/foundation/integrations/integrations.mjs +46 -3
- package/foundation/integrations/issue.ts +6 -0
- package/foundation/integrations/validate-contributions.d.mts +2 -0
- package/foundation/integrations/validate-contributions.mjs +41 -1
- package/foundation/release/agent-text-worker.d.mts +4 -0
- package/foundation/release/agent-text-worker.mjs +52 -0
- package/foundation/release/cache.d.mts +310 -0
- package/foundation/release/cache.mjs +1216 -0
- package/foundation/release/cache.test.mjs +920 -0
- package/foundation/release/choose.d.mts +207 -0
- package/foundation/release/choose.mjs +677 -0
- package/foundation/release/choose.test.mjs +427 -0
- package/foundation/release/core-worker.d.mts +4 -0
- package/foundation/release/core-worker.mjs +38 -0
- package/foundation/release/global-state.d.mts +259 -0
- package/foundation/release/global-state.mjs +472 -0
- package/foundation/release/global-state.test.mjs +381 -0
- package/foundation/release/handoff.d.mts +60 -0
- package/foundation/release/handoff.e2e.test.mjs +278 -0
- package/foundation/release/handoff.mjs +357 -0
- package/foundation/release/lock-lease.d.mts +23 -0
- package/foundation/release/lock-lease.mjs +80 -0
- package/foundation/release/notices.d.mts +253 -0
- package/foundation/release/notices.mjs +596 -0
- package/foundation/release/notices.test.mjs +565 -0
- package/foundation/release/npm-guard.d.mts +4 -0
- package/foundation/release/npm-guard.mjs +126 -0
- package/foundation/release/project.d.mts +141 -0
- package/foundation/release/project.mjs +466 -0
- package/foundation/release/project.test.mjs +288 -0
- package/foundation/release/refresh-worker.d.mts +4 -0
- package/foundation/release/refresh-worker.mjs +27 -0
- package/foundation/release/run-context.d.mts +47 -0
- package/foundation/release/run-context.mjs +149 -0
- package/foundation/release/update-notice-env.d.mts +28 -0
- package/foundation/release/update-notice-env.mjs +39 -0
- package/foundation/response/base.d.ts +26 -3
- package/foundation/response/error-codes.d.mts +3 -1
- package/foundation/response/error-codes.doc.mjs +12 -0
- package/foundation/response/error-codes.mjs +17 -0
- package/foundation/response/error-codes.test.mjs +2 -0
- package/foundation/response/json-contract.test.mjs +7 -2
- package/foundation/response/json.mjs +4 -1
- package/foundation/response/response-types.doc.mjs +17 -11
- package/foundation/response/response-types.doc.test.mjs +46 -0
- package/foundation/response/response.doc.mjs +3 -3
- package/foundation/xle/browser-imports.test.mjs +74 -0
- package/package.json +11 -11
- package/scripts/postinstall.mjs +24 -5
- /package/assets/codemods/transforms/{next → v0.7.2}/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +0 -0
- /package/assets/codemods/transforms/{next → v0.7.2}/migrate-list-edge-compensation-to-edge.mjs +0 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file The computer-level agent text (spec AST-081 FR8): one skill file,
|
|
5
|
+
* `~/.agents/skills/astryx/SKILL.md`, which most coding agents read, linked
|
|
6
|
+
* into the skill folder of each agent that reads only its own
|
|
7
|
+
* (`SKILL_LINKS`), so every agent on the computer knows Astryx is there.
|
|
8
|
+
*
|
|
9
|
+
* - One file is written. Each other folder gets a link to its folder: a
|
|
10
|
+
* directory symlink, or a junction on Windows, which needs no
|
|
11
|
+
* administrator rights. A refresh rewrites only the file; the links follow.
|
|
12
|
+
* - `SKILL_LINKS` is the one table of link targets: a folder relative to the
|
|
13
|
+
* home folder, and the folder that must already exist for it to be linked
|
|
14
|
+
* (`null`: always). The writer, the remover, doctor, and the docs all read it.
|
|
15
|
+
* - The text is version-free and names only stable entry points
|
|
16
|
+
* (`STABLE_COMMANDS`), so an older file never misleads. It is short: the
|
|
17
|
+
* skill only has to say that Astryx is here, when to use it, and where to
|
|
18
|
+
* start; the CLI's own docs carry the rest.
|
|
19
|
+
* - The file keeps the Astryx markers, which tell a file Astryx wrote from one
|
|
20
|
+
* a person wrote, and records inside them a hash of its text, which tells an
|
|
21
|
+
* edited file from an older rendering. A file without the markers, an
|
|
22
|
+
* edited file, and a symlinked file are never changed. A link target that
|
|
23
|
+
* holds anything Astryx did not create is never replaced; doctor names it.
|
|
24
|
+
* - The links Astryx made are recorded in the settings file, so removal
|
|
25
|
+
* removes exactly those.
|
|
26
|
+
*
|
|
27
|
+
* `astryx init --user` writes and removes them, and the global copy's
|
|
28
|
+
* background step (`foundation/release/agent-text-worker.mjs`) keeps the file
|
|
29
|
+
* on its release (FR7, FR8). Both honor the settings file
|
|
30
|
+
* (`foundation/env/user-settings.mjs`). This module names fixed paths in the
|
|
31
|
+
* home folder and nothing else.
|
|
32
|
+
*
|
|
33
|
+
* @input the home folder, the host's door (if any), the settings file
|
|
34
|
+
* @output the rendered text, its state on disk, and the write/remove receipt rows
|
|
35
|
+
* @position foundation/agent-docs — read by `init --user`, by the background
|
|
36
|
+
* step, and by `doctor`.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import * as fs from 'node:fs';
|
|
40
|
+
import * as os from 'node:os';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
import {createHash} from 'node:crypto';
|
|
43
|
+
import {cliDoor} from '../env/cli-command.mjs';
|
|
44
|
+
import {astryxCommandsOnSearchPath} from '../env/search-path.mjs';
|
|
45
|
+
import {samePath} from '../fs/path-compare.mjs';
|
|
46
|
+
import {MARKER_START, MARKER_END} from './agent-doc-state.mjs';
|
|
47
|
+
|
|
48
|
+
/** The command the computer-level text uses when no host door is set. */
|
|
49
|
+
export const COMPUTER_COMMAND = 'astryx';
|
|
50
|
+
|
|
51
|
+
/** The install step for the CLI copy the computer-level text relies on. */
|
|
52
|
+
export const COMPUTER_INSTALL_STEP = 'npm i -g @astryxdesign/cli';
|
|
53
|
+
|
|
54
|
+
/** The skill folder, relative to the home folder, that holds the one file. */
|
|
55
|
+
const SKILL_FOLDER = path.join('.agents', 'skills', 'astryx');
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Where the skill folder reaches agents that read only their own skills
|
|
59
|
+
* folder, each relative to the home folder: the agent's skills folder, the
|
|
60
|
+
* folder that must already exist for it to be used (`null`: always), and how:
|
|
61
|
+
* `link`, a folder link to the skill folder with a relative target, or `copy`,
|
|
62
|
+
* a managed copy of the file for an agent whose scanner skips links. Public
|
|
63
|
+
* agents only. Never a store another tool manages, such as `~/.codex/skills`.
|
|
64
|
+
* @type {ReadonlyArray<{skills: string, when: string | null, mode: 'link' | 'copy'}>}
|
|
65
|
+
*/
|
|
66
|
+
export const SKILL_LINKS = Object.freeze([
|
|
67
|
+
{skills: path.join('.claude', 'skills'), when: null, mode: 'link'},
|
|
68
|
+
{skills: path.join('.kiro', 'skills'), when: '.kiro', mode: 'link'},
|
|
69
|
+
{
|
|
70
|
+
skills: path.join('.codeium', 'windsurf', 'skills'),
|
|
71
|
+
when: path.join('.codeium', 'windsurf'),
|
|
72
|
+
mode: 'link',
|
|
73
|
+
},
|
|
74
|
+
{skills: path.join('.letta', 'skills'), when: '.letta', mode: 'link'},
|
|
75
|
+
{
|
|
76
|
+
skills: path.join(
|
|
77
|
+
'Library',
|
|
78
|
+
'Developer',
|
|
79
|
+
'Xcode',
|
|
80
|
+
'CodingAssistant',
|
|
81
|
+
'ClaudeAgentConfig',
|
|
82
|
+
'skills',
|
|
83
|
+
),
|
|
84
|
+
when: path.join('Library', 'Developer', 'Xcode', 'CodingAssistant'),
|
|
85
|
+
mode: 'link',
|
|
86
|
+
},
|
|
87
|
+
// Kimi reads only the first of `~/.config/agents/skills` and
|
|
88
|
+
// `~/.agents/skills` that exists.
|
|
89
|
+
{
|
|
90
|
+
skills: path.join('.config', 'agents', 'skills'),
|
|
91
|
+
when: path.join('.config', 'agents', 'skills'),
|
|
92
|
+
mode: 'link',
|
|
93
|
+
},
|
|
94
|
+
{skills: path.join('.continue', 'skills'), when: '.continue', mode: 'copy'},
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The commands the text may name: stable entry points every release keeps.
|
|
99
|
+
* computer-agent-text.test.mjs fails on any other.
|
|
100
|
+
*/
|
|
101
|
+
export const STABLE_COMMANDS = Object.freeze([
|
|
102
|
+
'search',
|
|
103
|
+
'docs',
|
|
104
|
+
'component',
|
|
105
|
+
'template',
|
|
106
|
+
'build',
|
|
107
|
+
'theme',
|
|
108
|
+
'init',
|
|
109
|
+
'doctor',
|
|
110
|
+
'help',
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* When an agent should load the skill; the frontmatter `description`. One
|
|
115
|
+
* sentence.
|
|
116
|
+
*/
|
|
117
|
+
const SKILL_DESCRIPTION =
|
|
118
|
+
'Astryx, a design system and UI library, is available on this computer through the astryx command; use it when building or changing UI, choosing components, layout, styling or theming, working with a design system, or in any project that depends on @astryxdesign packages or mentions Astryx.';
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A home-folder path as a person reads it: `~/.claude/skills/astryx`.
|
|
122
|
+
* @param {string} file
|
|
123
|
+
* @param {string} [home]
|
|
124
|
+
* @returns {string}
|
|
125
|
+
*/
|
|
126
|
+
export function displayHomePath(file, home = os.homedir()) {
|
|
127
|
+
const rel = path.relative(home, file);
|
|
128
|
+
return rel && !rel.startsWith('..') && !path.isAbsolute(rel)
|
|
129
|
+
? `~${path.sep}${rel}`
|
|
130
|
+
: file;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The skill file's absolute path.
|
|
135
|
+
* @param {string} [home]
|
|
136
|
+
* @returns {string}
|
|
137
|
+
*/
|
|
138
|
+
export function computerSkillFile(home = os.homedir()) {
|
|
139
|
+
return path.join(home, SKILL_FOLDER, 'SKILL.md');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The absolute path of each link the table names, and whether it is wanted
|
|
144
|
+
* on this computer (its condition folder exists).
|
|
145
|
+
* @param {string} [home]
|
|
146
|
+
* @returns {Array<{path: string, wanted: boolean, mode: 'link' | 'copy'}>}
|
|
147
|
+
*/
|
|
148
|
+
export function computerSkillLinks(home = os.homedir()) {
|
|
149
|
+
return SKILL_LINKS.map(({skills, when, mode}) => ({
|
|
150
|
+
path: path.join(home, skills, 'astryx'),
|
|
151
|
+
wanted: when == null || fs.existsSync(path.join(home, when)),
|
|
152
|
+
mode,
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The command the computer-level text tells agents to run: the host's door
|
|
158
|
+
* when one started this run, else `astryx`. An integration's `cliCommand`
|
|
159
|
+
* never applies: no project, so no integration, is in scope.
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
export function computerAgentCommand() {
|
|
163
|
+
return cliDoor() ?? COMPUTER_COMMAND;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The body lines, with `run` as the command.
|
|
168
|
+
* @param {string} run
|
|
169
|
+
* @returns {string[]}
|
|
170
|
+
*/
|
|
171
|
+
function bodyLines(run) {
|
|
172
|
+
return [
|
|
173
|
+
'Astryx, a design system and UI library, is available on this computer.',
|
|
174
|
+
'Use it when you build or change UI, pick components, or style or theme an app.',
|
|
175
|
+
`Its docs, components, templates, and themes are all reachable through \`${run}\`. Start with \`${run} search '<what you need>'\`.`,
|
|
176
|
+
`Then read \`${run} component <Name>\` or \`${run} docs <topic>\`, not the package's files.`,
|
|
177
|
+
"In a project, the project's version of Astryx answers automatically.",
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The full skill file.
|
|
183
|
+
* @param {{command?: string}} [options]
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
export function renderComputerAgentText({
|
|
187
|
+
command = computerAgentCommand(),
|
|
188
|
+
} = {}) {
|
|
189
|
+
return withHash(
|
|
190
|
+
[
|
|
191
|
+
'---',
|
|
192
|
+
'name: astryx',
|
|
193
|
+
// Quoted: the description holds `: ` and `;`, which strict YAML parsers
|
|
194
|
+
// can misread, and an agent tool that fails to parse drops the skill.
|
|
195
|
+
`description: ${JSON.stringify(SKILL_DESCRIPTION)}`,
|
|
196
|
+
'---',
|
|
197
|
+
'',
|
|
198
|
+
MARKER_START,
|
|
199
|
+
...bodyLines(command),
|
|
200
|
+
MARKER_END,
|
|
201
|
+
'',
|
|
202
|
+
].join('\n'),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The line, right after the start marker, that records the file's hash. */
|
|
207
|
+
const HASH_LINE = /^<!-- astryx-managed sha256:([0-9a-f]{64}) -->\n/mu;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The sha256 of a file's text without its hash line.
|
|
211
|
+
* @param {string} text
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
function hashOf(text) {
|
|
215
|
+
return createHash('sha256').update(text).digest('hex');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Insert the hash line after the start marker.
|
|
220
|
+
* @param {string} text a rendering without a hash line
|
|
221
|
+
* @returns {string}
|
|
222
|
+
*/
|
|
223
|
+
function withHash(text) {
|
|
224
|
+
const at = text.indexOf(MARKER_START);
|
|
225
|
+
if (at === -1) return text;
|
|
226
|
+
const lineEnd = text.indexOf('\n', at);
|
|
227
|
+
const cut = lineEnd === -1 ? text.length : lineEnd + 1;
|
|
228
|
+
return `${text.slice(0, cut)}<!-- astryx-managed sha256:${hashOf(text)} -->\n${text.slice(cut)}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Whether a file with the markers is unedited: it carries a hash line, and
|
|
233
|
+
* the hash matches the rest of its text.
|
|
234
|
+
* @param {string} content
|
|
235
|
+
* @returns {boolean}
|
|
236
|
+
*/
|
|
237
|
+
function isUnedited(content) {
|
|
238
|
+
const match = HASH_LINE.exec(content);
|
|
239
|
+
if (!match) return false;
|
|
240
|
+
return hashOf(content.replace(HASH_LINE, '')) === match[1];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* What is at one path of the computer-level text.
|
|
245
|
+
* - `absent`: nothing there.
|
|
246
|
+
* - `current`: an Astryx file with exactly this release's rendering.
|
|
247
|
+
* - `differs`: an unedited Astryx file (its recorded hash matches) with
|
|
248
|
+
* another rendering, such as another release's.
|
|
249
|
+
* - `edited`: an Astryx file (it has the markers) whose text no longer
|
|
250
|
+
* matches its recorded hash, or that records none: a person changed it.
|
|
251
|
+
* Never changed.
|
|
252
|
+
* - `foreign`: a file without the Astryx markers, a symlink, or something
|
|
253
|
+
* other than a regular file. Never changed.
|
|
254
|
+
* - `linked`, `dangling` (a link only): a link to the skill folder, or one
|
|
255
|
+
* whose folder is missing (see linkStateOf).
|
|
256
|
+
* @typedef {'absent' | 'current' | 'differs' | 'edited' | 'foreign' | 'linked' | 'dangling'} ComputerAgentTextState
|
|
257
|
+
*/
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @param {string} content
|
|
261
|
+
* @returns {boolean}
|
|
262
|
+
*/
|
|
263
|
+
function hasMarkers(content) {
|
|
264
|
+
const start = content.indexOf(MARKER_START);
|
|
265
|
+
return start !== -1 && content.indexOf(MARKER_END, start) !== -1;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The state of one path, read without following a symlinked file.
|
|
270
|
+
* @param {string} file
|
|
271
|
+
* @param {string} expected
|
|
272
|
+
* @returns {ComputerAgentTextState}
|
|
273
|
+
*/
|
|
274
|
+
function stateOf(file, expected) {
|
|
275
|
+
/** @type {fs.Stats} */
|
|
276
|
+
let stat;
|
|
277
|
+
try {
|
|
278
|
+
stat = fs.lstatSync(file);
|
|
279
|
+
} catch {
|
|
280
|
+
return 'absent';
|
|
281
|
+
}
|
|
282
|
+
if (!stat.isFile()) return 'foreign';
|
|
283
|
+
let content;
|
|
284
|
+
try {
|
|
285
|
+
content = fs.readFileSync(file, 'utf8');
|
|
286
|
+
} catch {
|
|
287
|
+
return 'foreign';
|
|
288
|
+
}
|
|
289
|
+
if (!hasMarkers(content)) return 'foreign';
|
|
290
|
+
if (content === expected) return 'current';
|
|
291
|
+
return isUnedited(content) ? 'differs' : 'edited';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The state of one link target.
|
|
296
|
+
* - `linked`: a link to the skill folder.
|
|
297
|
+
* - `dangling`: a link to the skill folder, which is not there, as in a
|
|
298
|
+
* container that mounts only the agent's own folder.
|
|
299
|
+
* - `absent`: nothing there.
|
|
300
|
+
* - `differs`: a real folder holding only an unedited Astryx skill file, as a
|
|
301
|
+
* copy left it; replaced by the link.
|
|
302
|
+
* - `foreign`: anything else. Never changed.
|
|
303
|
+
* @param {string} at
|
|
304
|
+
* @param {string} folder the skill folder
|
|
305
|
+
* @returns {ComputerAgentTextState}
|
|
306
|
+
*/
|
|
307
|
+
function linkStateOf(at, folder) {
|
|
308
|
+
/** @type {fs.Stats} */
|
|
309
|
+
let stat;
|
|
310
|
+
try {
|
|
311
|
+
stat = fs.lstatSync(at);
|
|
312
|
+
} catch {
|
|
313
|
+
return 'absent';
|
|
314
|
+
}
|
|
315
|
+
if (stat.isSymbolicLink()) {
|
|
316
|
+
const leads = pointsAt(at, folder);
|
|
317
|
+
if (leads == null) return 'foreign';
|
|
318
|
+
// A link the system cannot follow to the folder is broken, whatever its
|
|
319
|
+
// text says.
|
|
320
|
+
if (leads === 'names') return 'dangling';
|
|
321
|
+
return fs.existsSync(path.join(folder, 'SKILL.md')) ? 'linked' : 'dangling';
|
|
322
|
+
}
|
|
323
|
+
if (!stat.isDirectory()) return 'foreign';
|
|
324
|
+
return copyStateOf(at, '') === 'differs' ? 'differs' : 'foreign';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The state of a managed copy: a real folder at `at` holding only the skill
|
|
329
|
+
* file, read like the skill file itself.
|
|
330
|
+
* @param {string} at
|
|
331
|
+
* @param {string} expected
|
|
332
|
+
* @returns {ComputerAgentTextState}
|
|
333
|
+
*/
|
|
334
|
+
function copyStateOf(at, expected) {
|
|
335
|
+
/** @type {fs.Stats} */
|
|
336
|
+
let stat;
|
|
337
|
+
try {
|
|
338
|
+
stat = fs.lstatSync(at);
|
|
339
|
+
} catch {
|
|
340
|
+
return 'absent';
|
|
341
|
+
}
|
|
342
|
+
if (!stat.isDirectory()) return 'foreign';
|
|
343
|
+
let names;
|
|
344
|
+
try {
|
|
345
|
+
names = fs.readdirSync(at);
|
|
346
|
+
} catch {
|
|
347
|
+
return 'foreign';
|
|
348
|
+
}
|
|
349
|
+
if (names.length === 0) return 'absent';
|
|
350
|
+
if (names.length !== 1 || names[0] !== 'SKILL.md') return 'foreign';
|
|
351
|
+
const state = stateOf(path.join(at, 'SKILL.md'), expected);
|
|
352
|
+
return state === 'current' && expected === '' ? 'differs' : state;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Where the link at `at` leads `folder`'s way, as the system follows it: a
|
|
357
|
+
* relative target resolves from the folder the link really sits in, which
|
|
358
|
+
* differs from its path when a folder above it is itself a link (such as
|
|
359
|
+
* `~/.claude` linked to a dotfiles folder). `reaches`: the system reaches
|
|
360
|
+
* `folder` (which may be missing). `names`: only the target's text, read from
|
|
361
|
+
* the link's path, names `folder`, as a link an earlier release made under a
|
|
362
|
+
* linked folder does; the system cannot follow it. Otherwise null.
|
|
363
|
+
* @param {string} at
|
|
364
|
+
* @param {string} folder
|
|
365
|
+
* @returns {'reaches' | 'names' | null}
|
|
366
|
+
*/
|
|
367
|
+
function pointsAt(at, folder) {
|
|
368
|
+
try {
|
|
369
|
+
const text = fs.readlinkSync(at);
|
|
370
|
+
const followed = path.resolve(realPath(path.dirname(at)), text);
|
|
371
|
+
const wanted = realPath(folder);
|
|
372
|
+
if (samePath(followed.replace(/[\\/]+$/u, ''), wanted)) return 'reaches';
|
|
373
|
+
const named = path.resolve(path.dirname(at), text);
|
|
374
|
+
return samePath(named.replace(/[\\/]+$/u, ''), folder) ? 'names' : null;
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* `p` with every link in it resolved, as far as it exists: a missing tail is
|
|
382
|
+
* kept as written.
|
|
383
|
+
* @param {string} p
|
|
384
|
+
* @returns {string}
|
|
385
|
+
*/
|
|
386
|
+
function realPath(p) {
|
|
387
|
+
try {
|
|
388
|
+
return fs.realpathSync(p);
|
|
389
|
+
} catch {
|
|
390
|
+
const parent = path.dirname(p);
|
|
391
|
+
return parent === p ? p : path.join(realPath(parent), path.basename(p));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Read-only: the state of the skill file against this release's rendering,
|
|
397
|
+
* then of each place the table wants on this computer, or that Astryx linked.
|
|
398
|
+
* @param {{home?: string, command?: string, links?: readonly string[]}} [options]
|
|
399
|
+
* `links`: the links Astryx made, from the settings file
|
|
400
|
+
* @returns {Array<{path: string, state: ComputerAgentTextState, link?: true}>}
|
|
401
|
+
*/
|
|
402
|
+
export function inspectComputerAgentText({
|
|
403
|
+
home = os.homedir(),
|
|
404
|
+
command,
|
|
405
|
+
links = [],
|
|
406
|
+
} = {}) {
|
|
407
|
+
const expected = renderComputerAgentText({command});
|
|
408
|
+
const folder = path.join(home, SKILL_FOLDER);
|
|
409
|
+
const made = new Set(links);
|
|
410
|
+
return [
|
|
411
|
+
{
|
|
412
|
+
path: computerSkillFile(home),
|
|
413
|
+
state: stateOf(computerSkillFile(home), expected),
|
|
414
|
+
},
|
|
415
|
+
...computerSkillLinks(home)
|
|
416
|
+
.filter(place => place.wanted || made.has(place.path))
|
|
417
|
+
.map(place => {
|
|
418
|
+
const linkState = linkStateOf(place.path, folder);
|
|
419
|
+
const state =
|
|
420
|
+
place.mode === 'copy' && linkState !== 'linked'
|
|
421
|
+
? copyStateOf(place.path, expected)
|
|
422
|
+
: linkState;
|
|
423
|
+
return {path: place.path, state, link: /** @type {const} */ (true)};
|
|
424
|
+
}),
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Write `content` to `file` without following a symlink planted at it.
|
|
430
|
+
* @param {string} file
|
|
431
|
+
* @param {string} content
|
|
432
|
+
*/
|
|
433
|
+
function writeNoFollow(file, content) {
|
|
434
|
+
const flags =
|
|
435
|
+
fs.constants.O_WRONLY |
|
|
436
|
+
fs.constants.O_CREAT |
|
|
437
|
+
fs.constants.O_TRUNC |
|
|
438
|
+
(fs.constants.O_NOFOLLOW ?? 0);
|
|
439
|
+
const fd = fs.openSync(file, flags, 0o644);
|
|
440
|
+
try {
|
|
441
|
+
fs.writeFileSync(fd, content);
|
|
442
|
+
} finally {
|
|
443
|
+
fs.closeSync(fd);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Why a path was kept: a person edited it, or Astryx did not create it.
|
|
449
|
+
* @param {ComputerAgentTextState} state
|
|
450
|
+
* @returns {'edited' | 'not-astryx'}
|
|
451
|
+
*/
|
|
452
|
+
const keptReason = state => (state === 'edited' ? 'edited' : 'not-astryx');
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Remove an unedited Astryx file, or a folder holding only one, and its
|
|
456
|
+
* emptied folder.
|
|
457
|
+
* @param {string} file
|
|
458
|
+
*/
|
|
459
|
+
function removeFile(file) {
|
|
460
|
+
fs.unlinkSync(file);
|
|
461
|
+
try {
|
|
462
|
+
fs.rmdirSync(path.dirname(file));
|
|
463
|
+
} catch {
|
|
464
|
+
// The folder holds something else; leave it.
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Link `at` to `folder`: a folder symlink with a relative target, which
|
|
470
|
+
* survives a moved or synced home folder, or a junction (absolute) on Windows.
|
|
471
|
+
* The target is relative to the folder the link really sits in, because the
|
|
472
|
+
* system resolves it from there: with `~/.claude` linked to
|
|
473
|
+
* `~/dotfiles/.claude`, a target worked out from `~/.claude/skills` would
|
|
474
|
+
* dangle. With no common base between the two, the target is absolute.
|
|
475
|
+
* @param {string} at
|
|
476
|
+
* @param {string} folder
|
|
477
|
+
*/
|
|
478
|
+
function makeLink(at, folder) {
|
|
479
|
+
fs.mkdirSync(path.dirname(at), {recursive: true});
|
|
480
|
+
if (process.platform === 'win32') {
|
|
481
|
+
fs.symlinkSync(folder, at, 'junction');
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const realFolder = fs.realpathSync(folder);
|
|
485
|
+
const relative = path.relative(fs.realpathSync(path.dirname(at)), realFolder);
|
|
486
|
+
fs.symlinkSync(path.isAbsolute(relative) ? realFolder : relative, at, 'dir');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Write or refresh a managed copy of the skill file in the folder `at`.
|
|
491
|
+
* @param {string} at
|
|
492
|
+
* @param {string} expected
|
|
493
|
+
* @returns {import('../../api/init/init.type.mjs').InitUserFile}
|
|
494
|
+
*/
|
|
495
|
+
function writeCopy(at, expected) {
|
|
496
|
+
const state = copyStateOf(at, expected);
|
|
497
|
+
if (state === 'current') return {path: at, action: 'unchanged'};
|
|
498
|
+
if (state === 'foreign' || state === 'edited') {
|
|
499
|
+
return {path: at, action: 'kept', reason: keptReason(state)};
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
fs.mkdirSync(at, {recursive: true});
|
|
503
|
+
writeNoFollow(path.join(at, 'SKILL.md'), expected);
|
|
504
|
+
return {path: at, action: 'written'};
|
|
505
|
+
} catch {
|
|
506
|
+
return {path: at, action: 'kept', reason: 'not-astryx'};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Write or refresh the skill file, and link it into every folder the table
|
|
512
|
+
* wants on this computer. A file without the Astryx markers, an edited one, a
|
|
513
|
+
* symlinked one, and a link target holding anything Astryx did not create are
|
|
514
|
+
* kept as they are. Returns the receipt rows and the links that now point at
|
|
515
|
+
* the skill folder, for the settings file.
|
|
516
|
+
* @param {{home?: string, command?: string}} [options]
|
|
517
|
+
* @returns {{rows: import('../../api/init/init.type.mjs').InitUserFile[], links: string[]}}
|
|
518
|
+
*/
|
|
519
|
+
export function writeComputerAgentText({home = os.homedir(), command} = {}) {
|
|
520
|
+
const expected = renderComputerAgentText({command});
|
|
521
|
+
const file = computerSkillFile(home);
|
|
522
|
+
const folder = path.dirname(file);
|
|
523
|
+
/** @type {import('../../api/init/init.type.mjs').InitUserFile[]} */
|
|
524
|
+
const rows = [];
|
|
525
|
+
const state = stateOf(file, expected);
|
|
526
|
+
if (state === 'foreign' || state === 'edited') {
|
|
527
|
+
rows.push({path: file, action: 'kept', reason: keptReason(state)});
|
|
528
|
+
} else if (state === 'current') {
|
|
529
|
+
rows.push({path: file, action: 'unchanged'});
|
|
530
|
+
} else {
|
|
531
|
+
fs.mkdirSync(folder, {recursive: true});
|
|
532
|
+
writeNoFollow(file, expected);
|
|
533
|
+
rows.push({path: file, action: 'written'});
|
|
534
|
+
}
|
|
535
|
+
/** @type {string[]} */
|
|
536
|
+
const links = [];
|
|
537
|
+
for (const place of computerSkillLinks(home)) {
|
|
538
|
+
if (!place.wanted) continue;
|
|
539
|
+
const at = place.path;
|
|
540
|
+
const linkState = linkStateOf(at, folder);
|
|
541
|
+
if (linkState === 'linked') {
|
|
542
|
+
links.push(at);
|
|
543
|
+
rows.push({path: at, action: 'unchanged'});
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (place.mode === 'copy') {
|
|
547
|
+
rows.push(writeCopy(at, expected));
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (linkState === 'foreign') {
|
|
551
|
+
rows.push({path: at, action: 'kept', reason: 'not-astryx'});
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (linkState === 'differs') removeFile(path.join(at, 'SKILL.md'));
|
|
555
|
+
// The skill folder exists now, so a link still dangling here is one
|
|
556
|
+
// whose target the system resolves from a linked folder above it: an
|
|
557
|
+
// Astryx link, remade so that it resolves.
|
|
558
|
+
if (linkState === 'dangling') fs.unlinkSync(at);
|
|
559
|
+
try {
|
|
560
|
+
makeLink(at, folder);
|
|
561
|
+
links.push(at);
|
|
562
|
+
rows.push({path: at, action: 'linked'});
|
|
563
|
+
} catch {
|
|
564
|
+
// A folder link that cannot be made (Windows without a junction, a
|
|
565
|
+
// file system without links) gets a managed copy instead.
|
|
566
|
+
rows.push(writeCopy(at, expected));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return {rows, links};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Remove the skill file and every link Astryx made that still points at its
|
|
574
|
+
* folder. A file without the Astryx markers, an edited one, or a symlinked
|
|
575
|
+
* one, and any link Astryx did not make, are kept.
|
|
576
|
+
* @param {{home?: string, links?: readonly string[]}} [options]
|
|
577
|
+
* @returns {import('../../api/init/init.type.mjs').InitUserFile[]}
|
|
578
|
+
*/
|
|
579
|
+
export function removeComputerAgentText({
|
|
580
|
+
home = os.homedir(),
|
|
581
|
+
links = [],
|
|
582
|
+
} = {}) {
|
|
583
|
+
const file = computerSkillFile(home);
|
|
584
|
+
const folder = path.dirname(file);
|
|
585
|
+
/** @type {import('../../api/init/init.type.mjs').InitUserFile[]} */
|
|
586
|
+
const rows = [];
|
|
587
|
+
for (const at of links) {
|
|
588
|
+
const linkState = linkStateOf(at, folder);
|
|
589
|
+
if (linkState !== 'linked' && linkState !== 'dangling') continue;
|
|
590
|
+
try {
|
|
591
|
+
fs.unlinkSync(at);
|
|
592
|
+
rows.push({path: at, action: 'removed'});
|
|
593
|
+
} catch {
|
|
594
|
+
rows.push({path: at, action: 'kept', reason: 'not-astryx'});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Managed copies are told apart by their markers and hash, like the file.
|
|
598
|
+
for (const place of computerSkillLinks(home)) {
|
|
599
|
+
if (copyStateOf(place.path, '') !== 'differs') continue;
|
|
600
|
+
removeFile(path.join(place.path, 'SKILL.md'));
|
|
601
|
+
rows.push({path: place.path, action: 'removed'});
|
|
602
|
+
}
|
|
603
|
+
const state = stateOf(file, '');
|
|
604
|
+
if (state === 'absent') {
|
|
605
|
+
rows.unshift({path: file, action: 'absent'});
|
|
606
|
+
} else if (state === 'foreign' || state === 'edited') {
|
|
607
|
+
rows.unshift({path: file, action: 'kept', reason: keptReason(state)});
|
|
608
|
+
} else {
|
|
609
|
+
removeFile(file);
|
|
610
|
+
rows.unshift({path: file, action: 'removed'});
|
|
611
|
+
}
|
|
612
|
+
return rows;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Whether the `astryx` a shell would run from this computer's search path is
|
|
617
|
+
* the Astryx CLI: the first `astryx` found is the one that runs.
|
|
618
|
+
* @param {{searchPath?: string, platform?: NodeJS.Platform}} [options]
|
|
619
|
+
* @returns {boolean}
|
|
620
|
+
*/
|
|
621
|
+
export function astryxOnSearchPath(options = {}) {
|
|
622
|
+
return astryxCommandsOnSearchPath(options)[0]?.astryx ?? false;
|
|
623
|
+
}
|