corporateai 0.0.1
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/.dockerignore +10 -0
- package/.env.example +3 -0
- package/.github/workflows/publish-cli.yml +49 -0
- package/.mailmap +1 -0
- package/AGENTS.md +148 -0
- package/CONTRIBUTING.md +75 -0
- package/Dockerfile +59 -0
- package/Dockerfile.onboard-smoke +42 -0
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/cli/esbuild.config.mjs +11 -0
- package/cli/package.json +24 -0
- package/cli/scripts/build-cli.mjs +5 -0
- package/cli/src/index.ts +27 -0
- package/docker-compose.quickstart.yml +18 -0
- package/docker-compose.untrusted-review.yml +33 -0
- package/docker-compose.yml +38 -0
- package/package.json +56 -0
- package/patches/embedded-postgres@18.1.0-beta.16.patch +0 -0
- package/pnpm-workspace.yaml +4 -0
- package/releases/.gitkeep +0 -0
- package/releases/v0.0.1.md +36 -0
- package/releases/v0.2.7.md +15 -0
- package/releases/v0.3.0.md +54 -0
- package/releases/v0.3.1.md +55 -0
- package/releases/v2026.318.0.md +66 -0
- package/releases/v2026.325.0.md +78 -0
- package/report/2026-03-13-08-46-token-optimization-implementation.md +48 -0
- package/scripts/backup-db.sh +17 -0
- package/scripts/build-npm.sh +80 -0
- package/scripts/check-forbidden-tokens.mjs +115 -0
- package/scripts/clean-onboard-git.sh +14 -0
- package/scripts/clean-onboard-npm.sh +13 -0
- package/scripts/clean-onboard-ref.sh +86 -0
- package/scripts/create-github-release.sh +99 -0
- package/scripts/dev-runner-paths.mjs +38 -0
- package/scripts/dev-runner.mjs +606 -0
- package/scripts/docker-onboard-smoke.sh +306 -0
- package/scripts/ensure-plugin-build-deps.mjs +47 -0
- package/scripts/generate-company-assets.ts +365 -0
- package/scripts/generate-npm-package-json.mjs +113 -0
- package/scripts/generate-org-chart-images.ts +694 -0
- package/scripts/generate-org-chart-satori-comparison.ts +225 -0
- package/scripts/generate-ui-package-json.mjs +31 -0
- package/scripts/kill-dev.sh +71 -0
- package/scripts/migrate-inline-env-secrets.ts +126 -0
- package/scripts/prepare-server-ui-dist.sh +22 -0
- package/scripts/provision-worktree.sh +333 -0
- package/scripts/release-lib.sh +306 -0
- package/scripts/release-package-map.mjs +169 -0
- package/scripts/release.sh +312 -0
- package/scripts/rollback-latest.sh +111 -0
- package/scripts/smoke/openclaw-docker-ui.sh +329 -0
- package/scripts/smoke/openclaw-gateway-e2e.sh +954 -0
- package/scripts/smoke/openclaw-join.sh +295 -0
- package/scripts/smoke/openclaw-sse-standalone.sh +146 -0
- package/scripts/workspace-compat.mjs +60 -0
- package/server/CHANGELOG.md +130 -0
- package/server/package.json +96 -0
- package/server/scripts/copy-onboarding-assets.mjs +10 -0
- package/server/scripts/dev-watch.ts +33 -0
- package/server/src/__tests__/activity-routes.test.ts +70 -0
- package/server/src/__tests__/adapter-models.test.ts +105 -0
- package/server/src/__tests__/adapter-session-codecs.test.ts +194 -0
- package/server/src/__tests__/agent-auth-jwt.test.ts +79 -0
- package/server/src/__tests__/agent-instructions-routes.test.ts +318 -0
- package/server/src/__tests__/agent-instructions-service.test.ts +361 -0
- package/server/src/__tests__/agent-permissions-routes.test.ts +275 -0
- package/server/src/__tests__/agent-shortname-collision.test.ts +69 -0
- package/server/src/__tests__/agent-skill-contract.test.ts +50 -0
- package/server/src/__tests__/agent-skills-routes.test.ts +462 -0
- package/server/src/__tests__/app-hmr-port.test.ts +19 -0
- package/server/src/__tests__/approval-routes-idempotency.test.ts +110 -0
- package/server/src/__tests__/approvals-service.test.ts +107 -0
- package/server/src/__tests__/assets.test.ts +250 -0
- package/server/src/__tests__/attachment-types.test.ts +97 -0
- package/server/src/__tests__/board-mutation-guard.test.ts +105 -0
- package/server/src/__tests__/budgets-service.test.ts +311 -0
- package/server/src/__tests__/claude-local-adapter-environment.test.ts +92 -0
- package/server/src/__tests__/claude-local-adapter.test.ts +31 -0
- package/server/src/__tests__/claude-local-skill-sync.test.ts +111 -0
- package/server/src/__tests__/cli-auth-routes.test.ts +230 -0
- package/server/src/__tests__/codex-local-adapter-environment.test.ts +143 -0
- package/server/src/__tests__/codex-local-adapter.test.ts +253 -0
- package/server/src/__tests__/codex-local-execute.test.ts +391 -0
- package/server/src/__tests__/codex-local-skill-injection.test.ts +175 -0
- package/server/src/__tests__/codex-local-skill-sync.test.ts +123 -0
- package/server/src/__tests__/companies-route-path-guard.test.ts +56 -0
- package/server/src/__tests__/company-branding-route.test.ts +196 -0
- package/server/src/__tests__/company-portability-routes.test.ts +175 -0
- package/server/src/__tests__/company-portability.test.ts +2186 -0
- package/server/src/__tests__/company-skills-routes.test.ts +113 -0
- package/server/src/__tests__/company-skills.test.ts +229 -0
- package/server/src/__tests__/costs-service.test.ts +226 -0
- package/server/src/__tests__/cursor-local-adapter-environment.test.ts +196 -0
- package/server/src/__tests__/cursor-local-adapter.test.ts +406 -0
- package/server/src/__tests__/cursor-local-execute.test.ts +263 -0
- package/server/src/__tests__/cursor-local-skill-injection.test.ts +104 -0
- package/server/src/__tests__/cursor-local-skill-sync.test.ts +145 -0
- package/server/src/__tests__/dev-runner-paths.test.ts +25 -0
- package/server/src/__tests__/dev-server-status.test.ts +66 -0
- package/server/src/__tests__/dev-watch-ignore.test.ts +42 -0
- package/server/src/__tests__/documents.test.ts +29 -0
- package/server/src/__tests__/error-handler.test.ts +53 -0
- package/server/src/__tests__/execution-workspace-policy.test.ts +170 -0
- package/server/src/__tests__/forbidden-tokens.test.ts +77 -0
- package/server/src/__tests__/gemini-local-adapter-environment.test.ts +135 -0
- package/server/src/__tests__/gemini-local-adapter.test.ts +190 -0
- package/server/src/__tests__/gemini-local-execute.test.ts +172 -0
- package/server/src/__tests__/gemini-local-skill-sync.test.ts +90 -0
- package/server/src/__tests__/health.test.ts +16 -0
- package/server/src/__tests__/heartbeat-process-recovery.test.ts +256 -0
- package/server/src/__tests__/heartbeat-run-summary.test.ts +33 -0
- package/server/src/__tests__/heartbeat-workspace-session.test.ts +334 -0
- package/server/src/__tests__/helpers/embedded-postgres.ts +7 -0
- package/server/src/__tests__/hire-hook.test.ts +181 -0
- package/server/src/__tests__/instance-settings-routes.test.ts +156 -0
- package/server/src/__tests__/invite-accept-gateway-defaults.test.ts +119 -0
- package/server/src/__tests__/invite-accept-replay.test.ts +92 -0
- package/server/src/__tests__/invite-expiry.test.ts +10 -0
- package/server/src/__tests__/invite-join-grants.test.ts +57 -0
- package/server/src/__tests__/invite-join-manager.test.ts +33 -0
- package/server/src/__tests__/invite-onboarding-text.test.ts +116 -0
- package/server/src/__tests__/issue-comment-reopen-routes.test.ts +146 -0
- package/server/src/__tests__/issue-goal-fallback.test.ts +99 -0
- package/server/src/__tests__/issues-checkout-wakeup.test.ts +48 -0
- package/server/src/__tests__/issues-goal-context-routes.test.ts +187 -0
- package/server/src/__tests__/issues-service.test.ts +317 -0
- package/server/src/__tests__/issues-user-context.test.ts +113 -0
- package/server/src/__tests__/log-redaction.test.ts +74 -0
- package/server/src/__tests__/monthly-spend-service.test.ts +90 -0
- package/server/src/__tests__/normalize-agent-mention-token.test.ts +41 -0
- package/server/src/__tests__/openclaw-gateway-adapter.test.ts +626 -0
- package/server/src/__tests__/openclaw-invite-prompt-route.test.ts +192 -0
- package/server/src/__tests__/opencode-local-adapter-environment.test.ts +97 -0
- package/server/src/__tests__/opencode-local-adapter.test.ts +226 -0
- package/server/src/__tests__/opencode-local-skill-sync.test.ts +91 -0
- package/server/src/__tests__/paperclip-env.test.ts +58 -0
- package/server/src/__tests__/paperclip-skill-utils.test.ts +63 -0
- package/server/src/__tests__/pi-local-adapter-environment.test.ts +102 -0
- package/server/src/__tests__/pi-local-skill-sync.test.ts +95 -0
- package/server/src/__tests__/plugin-dev-watcher.test.ts +68 -0
- package/server/src/__tests__/plugin-worker-manager.test.ts +43 -0
- package/server/src/__tests__/private-hostname-guard.test.ts +56 -0
- package/server/src/__tests__/project-shortname-resolution.test.ts +45 -0
- package/server/src/__tests__/quota-windows-service.test.ts +56 -0
- package/server/src/__tests__/quota-windows.test.ts +1109 -0
- package/server/src/__tests__/redaction.test.ts +66 -0
- package/server/src/__tests__/routines-e2e.test.ts +276 -0
- package/server/src/__tests__/routines-routes.test.ts +271 -0
- package/server/src/__tests__/routines-service.test.ts +424 -0
- package/server/src/__tests__/storage-local-provider.test.ts +78 -0
- package/server/src/__tests__/ui-branding.test.ts +82 -0
- package/server/src/__tests__/work-products.test.ts +95 -0
- package/server/src/__tests__/workspace-runtime.test.ts +1131 -0
- package/server/src/__tests__/worktree-config.test.ts +426 -0
- package/server/src/adapters/codex-models.ts +105 -0
- package/server/src/adapters/cursor-models.ts +171 -0
- package/server/src/adapters/http/execute.ts +42 -0
- package/server/src/adapters/http/index.ts +21 -0
- package/server/src/adapters/http/test.ts +116 -0
- package/server/src/adapters/index.ts +18 -0
- package/server/src/adapters/process/execute.ts +77 -0
- package/server/src/adapters/process/index.ts +24 -0
- package/server/src/adapters/process/test.ts +89 -0
- package/server/src/adapters/registry.ts +225 -0
- package/server/src/adapters/server-utils-compat.ts +57 -0
- package/server/src/adapters/types.ts +30 -0
- package/server/src/adapters/utils.ts +48 -0
- package/server/src/agent-auth-jwt.ts +141 -0
- package/server/src/app.ts +321 -0
- package/server/src/attachment-types.ts +74 -0
- package/server/src/auth/better-auth.ts +148 -0
- package/server/src/board-claim.ts +150 -0
- package/server/src/config-file.ts +17 -0
- package/server/src/config.ts +260 -0
- package/server/src/dev-server-status.ts +103 -0
- package/server/src/dev-watch-ignore.ts +36 -0
- package/server/src/errors.ts +34 -0
- package/server/src/home-paths.ts +95 -0
- package/server/src/index.ts +799 -0
- package/server/src/log-redaction.ts +146 -0
- package/server/src/middleware/auth.ts +178 -0
- package/server/src/middleware/board-mutation-guard.ts +66 -0
- package/server/src/middleware/error-handler.ts +71 -0
- package/server/src/middleware/index.ts +3 -0
- package/server/src/middleware/logger.ts +90 -0
- package/server/src/middleware/private-hostname-guard.ts +92 -0
- package/server/src/middleware/validate.ts +9 -0
- package/server/src/onboarding-assets/ceo/AGENTS.md +54 -0
- package/server/src/onboarding-assets/ceo/HEARTBEAT.md +72 -0
- package/server/src/onboarding-assets/ceo/SOUL.md +33 -0
- package/server/src/onboarding-assets/ceo/TOOLS.md +3 -0
- package/server/src/onboarding-assets/default/AGENTS.md +3 -0
- package/server/src/paths.ts +34 -0
- package/server/src/realtime/live-events-ws.ts +274 -0
- package/server/src/redaction.ts +59 -0
- package/server/src/routes/access.ts +2888 -0
- package/server/src/routes/activity.ts +89 -0
- package/server/src/routes/agents.ts +2313 -0
- package/server/src/routes/approvals.ts +346 -0
- package/server/src/routes/assets.ts +341 -0
- package/server/src/routes/authz.ts +52 -0
- package/server/src/routes/companies.ts +343 -0
- package/server/src/routes/company-skills.ts +300 -0
- package/server/src/routes/costs.ts +335 -0
- package/server/src/routes/dashboard.ts +19 -0
- package/server/src/routes/execution-workspaces.ts +182 -0
- package/server/src/routes/goals.ts +107 -0
- package/server/src/routes/health.ts +94 -0
- package/server/src/routes/index.ts +17 -0
- package/server/src/routes/instance-settings.ts +95 -0
- package/server/src/routes/issues-checkout-wakeup.ts +14 -0
- package/server/src/routes/issues.ts +1680 -0
- package/server/src/routes/llms.ts +86 -0
- package/server/src/routes/org-chart-svg.ts +777 -0
- package/server/src/routes/plugin-ui-static.ts +497 -0
- package/server/src/routes/plugins.ts +2220 -0
- package/server/src/routes/projects.ts +295 -0
- package/server/src/routes/routines.ts +300 -0
- package/server/src/routes/secrets.ts +166 -0
- package/server/src/routes/sidebar-badges.ts +52 -0
- package/server/src/secrets/external-stub-providers.ts +32 -0
- package/server/src/secrets/local-encrypted-provider.ts +135 -0
- package/server/src/secrets/provider-registry.ts +31 -0
- package/server/src/secrets/types.ts +23 -0
- package/server/src/services/access.ts +381 -0
- package/server/src/services/activity-log.ts +95 -0
- package/server/src/services/activity.ts +164 -0
- package/server/src/services/agent-instructions.ts +735 -0
- package/server/src/services/agent-permissions.ts +27 -0
- package/server/src/services/agents.ts +694 -0
- package/server/src/services/approvals.ts +273 -0
- package/server/src/services/assets.ts +23 -0
- package/server/src/services/board-auth.ts +355 -0
- package/server/src/services/budgets.ts +959 -0
- package/server/src/services/companies.ts +313 -0
- package/server/src/services/company-export-readme.ts +173 -0
- package/server/src/services/company-portability.ts +4263 -0
- package/server/src/services/company-skills.ts +2356 -0
- package/server/src/services/costs.ts +365 -0
- package/server/src/services/cron.ts +373 -0
- package/server/src/services/dashboard.ts +110 -0
- package/server/src/services/default-agent-instructions.ts +27 -0
- package/server/src/services/documents.ts +434 -0
- package/server/src/services/execution-workspace-policy.ts +210 -0
- package/server/src/services/execution-workspaces.ts +100 -0
- package/server/src/services/finance.ts +135 -0
- package/server/src/services/goals.ts +81 -0
- package/server/src/services/heartbeat-run-summary.ts +35 -0
- package/server/src/services/heartbeat.ts +3863 -0
- package/server/src/services/hire-hook.ts +114 -0
- package/server/src/services/index.ts +32 -0
- package/server/src/services/instance-settings.ts +138 -0
- package/server/src/services/issue-approvals.ts +175 -0
- package/server/src/services/issue-assignment-wakeup.ts +48 -0
- package/server/src/services/issue-goal-fallback.ts +56 -0
- package/server/src/services/issues.ts +1828 -0
- package/server/src/services/live-events.ts +55 -0
- package/server/src/services/plugin-capability-validator.ts +450 -0
- package/server/src/services/plugin-config-validator.ts +55 -0
- package/server/src/services/plugin-dev-watcher.ts +339 -0
- package/server/src/services/plugin-event-bus.ts +413 -0
- package/server/src/services/plugin-host-service-cleanup.ts +59 -0
- package/server/src/services/plugin-host-services.ts +1132 -0
- package/server/src/services/plugin-job-coordinator.ts +261 -0
- package/server/src/services/plugin-job-scheduler.ts +753 -0
- package/server/src/services/plugin-job-store.ts +466 -0
- package/server/src/services/plugin-lifecycle.ts +822 -0
- package/server/src/services/plugin-loader.ts +1955 -0
- package/server/src/services/plugin-log-retention.ts +87 -0
- package/server/src/services/plugin-manifest-validator.ts +164 -0
- package/server/src/services/plugin-registry.ts +683 -0
- package/server/src/services/plugin-runtime-sandbox.ts +222 -0
- package/server/src/services/plugin-secrets-handler.ts +355 -0
- package/server/src/services/plugin-state-store.ts +238 -0
- package/server/src/services/plugin-stream-bus.ts +81 -0
- package/server/src/services/plugin-tool-dispatcher.ts +449 -0
- package/server/src/services/plugin-tool-registry.ts +450 -0
- package/server/src/services/plugin-worker-manager.ts +1343 -0
- package/server/src/services/projects.ts +860 -0
- package/server/src/services/quota-windows.ts +65 -0
- package/server/src/services/routines.ts +1269 -0
- package/server/src/services/run-log-store.ts +156 -0
- package/server/src/services/secrets.ts +370 -0
- package/server/src/services/sidebar-badges.ts +56 -0
- package/server/src/services/work-products.ts +124 -0
- package/server/src/services/workspace-operation-log-store.ts +156 -0
- package/server/src/services/workspace-operations.ts +262 -0
- package/server/src/services/workspace-runtime.ts +1565 -0
- package/server/src/startup-banner.ts +176 -0
- package/server/src/storage/index.ts +35 -0
- package/server/src/storage/local-disk-provider.ts +89 -0
- package/server/src/storage/provider-registry.ts +18 -0
- package/server/src/storage/s3-provider.ts +153 -0
- package/server/src/storage/service.ts +131 -0
- package/server/src/storage/types.ts +63 -0
- package/server/src/ui-branding.ts +217 -0
- package/server/src/version.ts +10 -0
- package/server/src/worktree-config.ts +468 -0
- package/server/tsconfig.json +9 -0
- package/server/vitest.config.ts +7 -0
- package/skills/paperclip/SKILL.md +365 -0
- package/skills/paperclip/references/api-reference.md +647 -0
- package/skills/paperclip/references/company-skills.md +193 -0
- package/skills/paperclip-create-agent/SKILL.md +142 -0
- package/skills/paperclip-create-agent/references/api-reference.md +105 -0
- package/skills/paperclip-create-plugin/SKILL.md +102 -0
- package/skills/para-memory-files/SKILL.md +104 -0
- package/skills/para-memory-files/references/schemas.md +35 -0
- package/tests/e2e/onboarding.spec.ts +142 -0
- package/tests/e2e/playwright.config.ts +35 -0
- package/tests/release-smoke/docker-auth-onboarding.spec.ts +141 -0
- package/tests/release-smoke/playwright.config.ts +28 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +18 -0
- package/ui/README.md +12 -0
- package/ui/components.json +21 -0
- package/ui/index.html +47 -0
- package/ui/package.json +73 -0
- package/ui/public/android-chrome-192x192.png +0 -0
- package/ui/public/android-chrome-512x512.png +0 -0
- package/ui/public/apple-touch-icon.png +0 -0
- package/ui/public/brands/opencode-logo-dark-square.svg +18 -0
- package/ui/public/brands/opencode-logo-light-square.svg +18 -0
- package/ui/public/favicon-16x16.png +0 -0
- package/ui/public/favicon-32x32.png +0 -0
- package/ui/public/favicon.ico +0 -0
- package/ui/public/favicon.svg +9 -0
- package/ui/public/site.webmanifest +30 -0
- package/ui/public/sprites/1-D-1.png +0 -0
- package/ui/public/sprites/1-D-2.png +0 -0
- package/ui/public/sprites/1-D-3.png +0 -0
- package/ui/public/sprites/1-L-1.png +0 -0
- package/ui/public/sprites/1-R-1.png +0 -0
- package/ui/public/sprites/10-D-1.png +0 -0
- package/ui/public/sprites/10-D-2.png +0 -0
- package/ui/public/sprites/10-D-3.png +0 -0
- package/ui/public/sprites/10-L-1.png +0 -0
- package/ui/public/sprites/10-R-1.png +0 -0
- package/ui/public/sprites/11-D-1.png +0 -0
- package/ui/public/sprites/11-D-2.png +0 -0
- package/ui/public/sprites/11-D-3.png +0 -0
- package/ui/public/sprites/11-L-1.png +0 -0
- package/ui/public/sprites/11-R-1.png +0 -0
- package/ui/public/sprites/12-D-1.png +0 -0
- package/ui/public/sprites/12-D-2.png +0 -0
- package/ui/public/sprites/12-D-3.png +0 -0
- package/ui/public/sprites/12-L-1.png +0 -0
- package/ui/public/sprites/12-R-1.png +0 -0
- package/ui/public/sprites/13-D-1.png +0 -0
- package/ui/public/sprites/13-D-2.png +0 -0
- package/ui/public/sprites/13-D-3.png +0 -0
- package/ui/public/sprites/13-L-1.png +0 -0
- package/ui/public/sprites/13-R-1.png +0 -0
- package/ui/public/sprites/14-D-1.png +0 -0
- package/ui/public/sprites/14-D-2.png +0 -0
- package/ui/public/sprites/14-D-3.png +0 -0
- package/ui/public/sprites/14-L-1.png +0 -0
- package/ui/public/sprites/14-R-1.png +0 -0
- package/ui/public/sprites/2-D-1.png +0 -0
- package/ui/public/sprites/2-D-2.png +0 -0
- package/ui/public/sprites/2-D-3.png +0 -0
- package/ui/public/sprites/2-L-1.png +0 -0
- package/ui/public/sprites/2-R-1.png +0 -0
- package/ui/public/sprites/3-D-1.png +0 -0
- package/ui/public/sprites/3-D-2.png +0 -0
- package/ui/public/sprites/3-D-3.png +0 -0
- package/ui/public/sprites/3-L-1.png +0 -0
- package/ui/public/sprites/3-R-1.png +0 -0
- package/ui/public/sprites/4-D-1.png +0 -0
- package/ui/public/sprites/4-D-2.png +0 -0
- package/ui/public/sprites/4-D-3.png +0 -0
- package/ui/public/sprites/4-L-1.png +0 -0
- package/ui/public/sprites/4-R-1.png +0 -0
- package/ui/public/sprites/5-D-1.png +0 -0
- package/ui/public/sprites/5-D-2.png +0 -0
- package/ui/public/sprites/5-D-3.png +0 -0
- package/ui/public/sprites/5-L-1.png +0 -0
- package/ui/public/sprites/5-R-1.png +0 -0
- package/ui/public/sprites/6-D-1.png +0 -0
- package/ui/public/sprites/6-D-2.png +0 -0
- package/ui/public/sprites/6-D-3.png +0 -0
- package/ui/public/sprites/6-L-1.png +0 -0
- package/ui/public/sprites/6-R-1.png +0 -0
- package/ui/public/sprites/7-D-1.png +0 -0
- package/ui/public/sprites/7-D-2.png +0 -0
- package/ui/public/sprites/7-D-3.png +0 -0
- package/ui/public/sprites/7-L-1.png +0 -0
- package/ui/public/sprites/7-R-1.png +0 -0
- package/ui/public/sprites/8-D-1.png +0 -0
- package/ui/public/sprites/8-D-2.png +0 -0
- package/ui/public/sprites/8-D-3.png +0 -0
- package/ui/public/sprites/8-L-1.png +0 -0
- package/ui/public/sprites/8-R-1.png +0 -0
- package/ui/public/sprites/9-D-1.png +0 -0
- package/ui/public/sprites/9-D-2.png +0 -0
- package/ui/public/sprites/9-D-3.png +0 -0
- package/ui/public/sprites/9-L-1.png +0 -0
- package/ui/public/sprites/9-R-1.png +0 -0
- package/ui/public/sprites/ceo-lobster.png +0 -0
- package/ui/public/sw.js +42 -0
- package/ui/public/worktree-favicon-16x16.png +0 -0
- package/ui/public/worktree-favicon-32x32.png +0 -0
- package/ui/public/worktree-favicon.ico +0 -0
- package/ui/public/worktree-favicon.svg +9 -0
- package/ui/src/App.tsx +354 -0
- package/ui/src/adapters/claude-local/config-fields.tsx +138 -0
- package/ui/src/adapters/claude-local/index.ts +13 -0
- package/ui/src/adapters/codex-local/config-fields.tsx +104 -0
- package/ui/src/adapters/codex-local/index.ts +13 -0
- package/ui/src/adapters/cursor/config-fields.tsx +49 -0
- package/ui/src/adapters/cursor/index.ts +13 -0
- package/ui/src/adapters/gemini-local/config-fields.tsx +51 -0
- package/ui/src/adapters/gemini-local/index.ts +13 -0
- package/ui/src/adapters/http/build-config.ts +9 -0
- package/ui/src/adapters/http/config-fields.tsx +38 -0
- package/ui/src/adapters/http/index.ts +12 -0
- package/ui/src/adapters/http/parse-stdout.ts +5 -0
- package/ui/src/adapters/index.ts +9 -0
- package/ui/src/adapters/local-workspace-runtime-fields.tsx +5 -0
- package/ui/src/adapters/openclaw-gateway/config-fields.tsx +237 -0
- package/ui/src/adapters/openclaw-gateway/index.ts +13 -0
- package/ui/src/adapters/opencode-local/config-fields.tsx +72 -0
- package/ui/src/adapters/opencode-local/index.ts +13 -0
- package/ui/src/adapters/pi-local/config-fields.tsx +49 -0
- package/ui/src/adapters/pi-local/index.ts +13 -0
- package/ui/src/adapters/process/build-config.ts +18 -0
- package/ui/src/adapters/process/config-fields.tsx +77 -0
- package/ui/src/adapters/process/index.ts +12 -0
- package/ui/src/adapters/process/parse-stdout.ts +5 -0
- package/ui/src/adapters/registry.ts +34 -0
- package/ui/src/adapters/runtime-json-fields.tsx +122 -0
- package/ui/src/adapters/transcript.test.ts +30 -0
- package/ui/src/adapters/transcript.ts +62 -0
- package/ui/src/adapters/types.ts +34 -0
- package/ui/src/api/access.ts +160 -0
- package/ui/src/api/activity.ts +37 -0
- package/ui/src/api/agents.ts +194 -0
- package/ui/src/api/approvals.ts +25 -0
- package/ui/src/api/assets.ts +30 -0
- package/ui/src/api/auth.ts +74 -0
- package/ui/src/api/budgets.ts +21 -0
- package/ui/src/api/client.ts +50 -0
- package/ui/src/api/companies.ts +59 -0
- package/ui/src/api/companySkills.ts +55 -0
- package/ui/src/api/costs.ts +60 -0
- package/ui/src/api/dashboard.ts +7 -0
- package/ui/src/api/execution-workspaces.ts +27 -0
- package/ui/src/api/goals.ts +12 -0
- package/ui/src/api/health.ts +41 -0
- package/ui/src/api/heartbeats.ts +62 -0
- package/ui/src/api/index.ts +18 -0
- package/ui/src/api/instanceSettings.ts +19 -0
- package/ui/src/api/issues.ts +115 -0
- package/ui/src/api/plugins.ts +424 -0
- package/ui/src/api/projects.ts +34 -0
- package/ui/src/api/routines.ts +59 -0
- package/ui/src/api/secrets.ts +26 -0
- package/ui/src/api/sidebarBadges.ts +7 -0
- package/ui/src/components/AccountingModelCard.tsx +69 -0
- package/ui/src/components/ActiveAgentsPanel.tsx +157 -0
- package/ui/src/components/ActivityCharts.tsx +264 -0
- package/ui/src/components/ActivityRow.tsx +147 -0
- package/ui/src/components/AgentActionButtons.tsx +51 -0
- package/ui/src/components/AgentConfigForm.tsx +1468 -0
- package/ui/src/components/AgentIconPicker.tsx +81 -0
- package/ui/src/components/AgentProperties.tsx +107 -0
- package/ui/src/components/ApprovalCard.tsx +107 -0
- package/ui/src/components/ApprovalPayload.tsx +134 -0
- package/ui/src/components/AsciiArtAnimation.tsx +355 -0
- package/ui/src/components/BillerSpendCard.tsx +146 -0
- package/ui/src/components/BreadcrumbBar.tsx +113 -0
- package/ui/src/components/BudgetIncidentCard.tsx +101 -0
- package/ui/src/components/BudgetPolicyCard.tsx +220 -0
- package/ui/src/components/BudgetSidebarMarker.tsx +13 -0
- package/ui/src/components/ClaudeSubscriptionPanel.tsx +141 -0
- package/ui/src/components/CodexSubscriptionPanel.tsx +158 -0
- package/ui/src/components/CommandPalette.tsx +239 -0
- package/ui/src/components/CommentThread.tsx +503 -0
- package/ui/src/components/CompanyPatternIcon.tsx +212 -0
- package/ui/src/components/CompanyRail.tsx +329 -0
- package/ui/src/components/CompanySwitcher.tsx +81 -0
- package/ui/src/components/CopyText.tsx +56 -0
- package/ui/src/components/DevRestartBanner.tsx +89 -0
- package/ui/src/components/EmptyState.tsx +27 -0
- package/ui/src/components/EntityRow.tsx +69 -0
- package/ui/src/components/FilterBar.tsx +39 -0
- package/ui/src/components/FinanceBillerCard.tsx +45 -0
- package/ui/src/components/FinanceKindCard.tsx +44 -0
- package/ui/src/components/FinanceTimelineCard.tsx +72 -0
- package/ui/src/components/GoalProperties.tsx +165 -0
- package/ui/src/components/GoalTree.tsx +118 -0
- package/ui/src/components/Identity.tsx +39 -0
- package/ui/src/components/InlineEditor.tsx +248 -0
- package/ui/src/components/InlineEntitySelector.tsx +206 -0
- package/ui/src/components/InstanceSidebar.tsx +53 -0
- package/ui/src/components/IssueDocumentsSection.tsx +892 -0
- package/ui/src/components/IssueProperties.tsx +621 -0
- package/ui/src/components/IssueRow.tsx +149 -0
- package/ui/src/components/IssueWorkspaceCard.tsx +404 -0
- package/ui/src/components/IssuesList.tsx +889 -0
- package/ui/src/components/JsonSchemaForm.tsx +1048 -0
- package/ui/src/components/KanbanBoard.tsx +275 -0
- package/ui/src/components/Layout.tsx +441 -0
- package/ui/src/components/LiveRunWidget.tsx +160 -0
- package/ui/src/components/MarkdownBody.test.tsx +50 -0
- package/ui/src/components/MarkdownBody.tsx +152 -0
- package/ui/src/components/MarkdownEditor.tsx +622 -0
- package/ui/src/components/MetricCard.tsx +53 -0
- package/ui/src/components/MobileBottomNav.tsx +123 -0
- package/ui/src/components/NewAgentDialog.tsx +223 -0
- package/ui/src/components/NewGoalDialog.tsx +283 -0
- package/ui/src/components/NewIssueDialog.tsx +1473 -0
- package/ui/src/components/NewProjectDialog.tsx +451 -0
- package/ui/src/components/OnboardingWizard.tsx +1392 -0
- package/ui/src/components/OpenCodeLogoIcon.tsx +22 -0
- package/ui/src/components/PackageFileTree.tsx +318 -0
- package/ui/src/components/PageSkeleton.tsx +180 -0
- package/ui/src/components/PageTabBar.tsx +45 -0
- package/ui/src/components/PathInstructionsModal.tsx +143 -0
- package/ui/src/components/PriorityIcon.tsx +77 -0
- package/ui/src/components/ProjectProperties.tsx +1127 -0
- package/ui/src/components/PropertiesPanel.tsx +29 -0
- package/ui/src/components/ProviderQuotaCard.tsx +417 -0
- package/ui/src/components/QuotaBar.tsx +65 -0
- package/ui/src/components/ReportsToPicker.tsx +127 -0
- package/ui/src/components/ScheduleEditor.tsx +344 -0
- package/ui/src/components/ScrollToBottom.tsx +79 -0
- package/ui/src/components/Sidebar.tsx +130 -0
- package/ui/src/components/SidebarAgents.tsx +146 -0
- package/ui/src/components/SidebarNavItem.tsx +92 -0
- package/ui/src/components/SidebarProjects.tsx +234 -0
- package/ui/src/components/SidebarSection.tsx +17 -0
- package/ui/src/components/StatusBadge.tsx +15 -0
- package/ui/src/components/StatusIcon.tsx +71 -0
- package/ui/src/components/SwipeToArchive.tsx +152 -0
- package/ui/src/components/ToastViewport.tsx +99 -0
- package/ui/src/components/WorktreeBanner.tsx +25 -0
- package/ui/src/components/agent-config-defaults.ts +31 -0
- package/ui/src/components/agent-config-primitives.tsx +476 -0
- package/ui/src/components/transcript/RunTranscriptView.test.tsx +84 -0
- package/ui/src/components/transcript/RunTranscriptView.tsx +1015 -0
- package/ui/src/components/transcript/useLiveRunTranscripts.ts +297 -0
- package/ui/src/components/ui/avatar.tsx +107 -0
- package/ui/src/components/ui/badge.tsx +48 -0
- package/ui/src/components/ui/breadcrumb.tsx +109 -0
- package/ui/src/components/ui/button.tsx +64 -0
- package/ui/src/components/ui/card.tsx +92 -0
- package/ui/src/components/ui/checkbox.tsx +32 -0
- package/ui/src/components/ui/collapsible.tsx +33 -0
- package/ui/src/components/ui/command.tsx +194 -0
- package/ui/src/components/ui/dialog.tsx +156 -0
- package/ui/src/components/ui/dropdown-menu.tsx +257 -0
- package/ui/src/components/ui/input.tsx +21 -0
- package/ui/src/components/ui/label.tsx +22 -0
- package/ui/src/components/ui/popover.tsx +88 -0
- package/ui/src/components/ui/scroll-area.tsx +56 -0
- package/ui/src/components/ui/select.tsx +188 -0
- package/ui/src/components/ui/separator.tsx +28 -0
- package/ui/src/components/ui/sheet.tsx +143 -0
- package/ui/src/components/ui/skeleton.tsx +13 -0
- package/ui/src/components/ui/tabs.tsx +89 -0
- package/ui/src/components/ui/textarea.tsx +18 -0
- package/ui/src/components/ui/tooltip.tsx +57 -0
- package/ui/src/components/visual-office/AgentAvatar.tsx +99 -0
- package/ui/src/components/visual-office/OfficeViewExact.tsx +417 -0
- package/ui/src/components/visual-office/i18n.ts +52 -0
- package/ui/src/components/visual-office/office-view/CliUsagePanel.tsx +240 -0
- package/ui/src/components/visual-office/office-view/VirtualPadOverlay.tsx +104 -0
- package/ui/src/components/visual-office/office-view/buildScene-break-room.ts +248 -0
- package/ui/src/components/visual-office/office-view/buildScene-ceo-hallway.ts +345 -0
- package/ui/src/components/visual-office/office-view/buildScene-department-agent.ts +242 -0
- package/ui/src/components/visual-office/office-view/buildScene-departments.ts +360 -0
- package/ui/src/components/visual-office/office-view/buildScene-final-layers.ts +113 -0
- package/ui/src/components/visual-office/office-view/buildScene-types.ts +91 -0
- package/ui/src/components/visual-office/office-view/buildScene.ts +232 -0
- package/ui/src/components/visual-office/office-view/drawing-core.ts +374 -0
- package/ui/src/components/visual-office/office-view/drawing-furniture-a.ts +338 -0
- package/ui/src/components/visual-office/office-view/drawing-furniture-b.ts +241 -0
- package/ui/src/components/visual-office/office-view/model.ts +301 -0
- package/ui/src/components/visual-office/office-view/officeTicker.ts +455 -0
- package/ui/src/components/visual-office/office-view/officeTickerRoomAndDelivery.ts +133 -0
- package/ui/src/components/visual-office/office-view/themes-locale.ts +460 -0
- package/ui/src/components/visual-office/office-view/useCliUsage.ts +37 -0
- package/ui/src/components/visual-office/office-view/useOfficeDeliveryEffects.ts +465 -0
- package/ui/src/components/visual-office/office-view/useOfficePixiRuntime.ts +282 -0
- package/ui/src/components/visual-office/types.ts +123 -0
- package/ui/src/context/BreadcrumbContext.tsx +44 -0
- package/ui/src/context/CompanyContext.tsx +151 -0
- package/ui/src/context/DialogContext.tsx +135 -0
- package/ui/src/context/LiveUpdatesProvider.test.ts +119 -0
- package/ui/src/context/LiveUpdatesProvider.tsx +760 -0
- package/ui/src/context/PanelContext.tsx +73 -0
- package/ui/src/context/SidebarContext.tsx +43 -0
- package/ui/src/context/ThemeContext.tsx +83 -0
- package/ui/src/context/ToastContext.tsx +172 -0
- package/ui/src/fixtures/runTranscriptFixtures.ts +226 -0
- package/ui/src/hooks/useAgentOrder.ts +105 -0
- package/ui/src/hooks/useAutosaveIndicator.ts +72 -0
- package/ui/src/hooks/useCompanyPageMemory.test.ts +90 -0
- package/ui/src/hooks/useCompanyPageMemory.ts +79 -0
- package/ui/src/hooks/useDateRange.ts +120 -0
- package/ui/src/hooks/useInboxBadge.ts +132 -0
- package/ui/src/hooks/useKeyboardShortcuts.ts +40 -0
- package/ui/src/hooks/useProjectOrder.ts +106 -0
- package/ui/src/index.css +770 -0
- package/ui/src/lib/agent-icons.ts +99 -0
- package/ui/src/lib/agent-order.ts +107 -0
- package/ui/src/lib/agent-skills-state.test.ts +90 -0
- package/ui/src/lib/agent-skills-state.ts +41 -0
- package/ui/src/lib/assignees.test.ts +92 -0
- package/ui/src/lib/assignees.ts +82 -0
- package/ui/src/lib/color-contrast.ts +107 -0
- package/ui/src/lib/company-export-selection.test.ts +41 -0
- package/ui/src/lib/company-export-selection.ts +57 -0
- package/ui/src/lib/company-page-memory.ts +65 -0
- package/ui/src/lib/company-portability-sidebar.test.ts +101 -0
- package/ui/src/lib/company-portability-sidebar.ts +62 -0
- package/ui/src/lib/company-routes.ts +88 -0
- package/ui/src/lib/company-selection.test.ts +34 -0
- package/ui/src/lib/company-selection.ts +18 -0
- package/ui/src/lib/groupBy.ts +11 -0
- package/ui/src/lib/inbox.test.ts +404 -0
- package/ui/src/lib/inbox.ts +292 -0
- package/ui/src/lib/instance-settings.test.ts +26 -0
- package/ui/src/lib/instance-settings.ts +25 -0
- package/ui/src/lib/issueDetailBreadcrumb.ts +24 -0
- package/ui/src/lib/legacy-agent-config.test.ts +40 -0
- package/ui/src/lib/legacy-agent-config.ts +17 -0
- package/ui/src/lib/mention-aware-link-node.test.ts +50 -0
- package/ui/src/lib/mention-aware-link-node.ts +67 -0
- package/ui/src/lib/mention-chips.ts +168 -0
- package/ui/src/lib/mention-deletion.test.ts +87 -0
- package/ui/src/lib/mention-deletion.ts +143 -0
- package/ui/src/lib/model-utils.ts +16 -0
- package/ui/src/lib/onboarding-goal.test.ts +22 -0
- package/ui/src/lib/onboarding-goal.ts +18 -0
- package/ui/src/lib/onboarding-launch.test.ts +131 -0
- package/ui/src/lib/onboarding-launch.ts +54 -0
- package/ui/src/lib/onboarding-route.test.ts +80 -0
- package/ui/src/lib/onboarding-route.ts +51 -0
- package/ui/src/lib/portable-files.ts +42 -0
- package/ui/src/lib/project-order.ts +71 -0
- package/ui/src/lib/queryKeys.ts +140 -0
- package/ui/src/lib/recent-assignees.ts +36 -0
- package/ui/src/lib/router.tsx +76 -0
- package/ui/src/lib/routine-trigger-patch.test.ts +72 -0
- package/ui/src/lib/routine-trigger-patch.ts +31 -0
- package/ui/src/lib/status-colors.ts +108 -0
- package/ui/src/lib/timeAgo.ts +31 -0
- package/ui/src/lib/utils.ts +168 -0
- package/ui/src/lib/worktree-branding.ts +65 -0
- package/ui/src/lib/zip.test.ts +289 -0
- package/ui/src/lib/zip.ts +284 -0
- package/ui/src/main.tsx +67 -0
- package/ui/src/pages/Activity.tsx +141 -0
- package/ui/src/pages/AgentDetail.tsx +4053 -0
- package/ui/src/pages/Agents.tsx +415 -0
- package/ui/src/pages/ApprovalDetail.tsx +369 -0
- package/ui/src/pages/Approvals.tsx +132 -0
- package/ui/src/pages/Auth.tsx +180 -0
- package/ui/src/pages/BoardClaim.tsx +125 -0
- package/ui/src/pages/CliAuth.tsx +184 -0
- package/ui/src/pages/Companies.tsx +297 -0
- package/ui/src/pages/CompanyExport.tsx +1019 -0
- package/ui/src/pages/CompanyImport.tsx +1355 -0
- package/ui/src/pages/CompanySettings.tsx +661 -0
- package/ui/src/pages/CompanySkills.tsx +1171 -0
- package/ui/src/pages/Costs.tsx +1103 -0
- package/ui/src/pages/Dashboard.tsx +388 -0
- package/ui/src/pages/DesignGuide.tsx +1330 -0
- package/ui/src/pages/ExecutionWorkspaceDetail.tsx +82 -0
- package/ui/src/pages/GoalDetail.tsx +197 -0
- package/ui/src/pages/Goals.tsx +63 -0
- package/ui/src/pages/Inbox.tsx +1291 -0
- package/ui/src/pages/InstanceExperimentalSettings.tsx +139 -0
- package/ui/src/pages/InstanceGeneralSettings.tsx +104 -0
- package/ui/src/pages/InstanceSettings.tsx +284 -0
- package/ui/src/pages/InviteLanding.tsx +320 -0
- package/ui/src/pages/IssueDetail.tsx +1201 -0
- package/ui/src/pages/Issues.tsx +116 -0
- package/ui/src/pages/MyIssues.tsx +72 -0
- package/ui/src/pages/NewAgent.tsx +353 -0
- package/ui/src/pages/NotFound.tsx +66 -0
- package/ui/src/pages/Org.tsx +132 -0
- package/ui/src/pages/OrgChart.tsx +447 -0
- package/ui/src/pages/PluginManager.tsx +510 -0
- package/ui/src/pages/PluginPage.tsx +156 -0
- package/ui/src/pages/PluginSettings.tsx +836 -0
- package/ui/src/pages/ProjectDetail.tsx +633 -0
- package/ui/src/pages/Projects.tsx +87 -0
- package/ui/src/pages/RoutineDetail.tsx +1022 -0
- package/ui/src/pages/Routines.tsx +661 -0
- package/ui/src/pages/RunTranscriptUxLab.tsx +334 -0
- package/ui/src/pages/VisualOffice.tsx +243 -0
- package/ui/src/plugins/bridge-init.ts +69 -0
- package/ui/src/plugins/bridge.ts +476 -0
- package/ui/src/plugins/launchers.tsx +834 -0
- package/ui/src/plugins/slots.tsx +855 -0
- package/ui/tsconfig.json +21 -0
- package/ui/vite.config.ts +23 -0
- package/ui/vitest.config.ts +14 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
3
|
+
import type {
|
|
4
|
+
Agent,
|
|
5
|
+
CompanyPortabilityFileEntry,
|
|
6
|
+
CompanyPortabilityExportPreviewResult,
|
|
7
|
+
CompanyPortabilityExportResult,
|
|
8
|
+
CompanyPortabilityManifest,
|
|
9
|
+
Project,
|
|
10
|
+
} from "@corporateai/shared";
|
|
11
|
+
import { useNavigate, useLocation } from "@/lib/router";
|
|
12
|
+
import { useCompany } from "../context/CompanyContext";
|
|
13
|
+
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
|
14
|
+
import { useToast } from "../context/ToastContext";
|
|
15
|
+
import { agentsApi } from "../api/agents";
|
|
16
|
+
import { authApi } from "../api/auth";
|
|
17
|
+
import { companiesApi } from "../api/companies";
|
|
18
|
+
import { projectsApi } from "../api/projects";
|
|
19
|
+
import { Button } from "@/components/ui/button";
|
|
20
|
+
import { EmptyState } from "../components/EmptyState";
|
|
21
|
+
import { PageSkeleton } from "../components/PageSkeleton";
|
|
22
|
+
import { MarkdownBody } from "../components/MarkdownBody";
|
|
23
|
+
import { cn } from "../lib/utils";
|
|
24
|
+
import { queryKeys } from "../lib/queryKeys";
|
|
25
|
+
import { createZipArchive } from "../lib/zip";
|
|
26
|
+
import { buildInitialExportCheckedFiles } from "../lib/company-export-selection";
|
|
27
|
+
import { useAgentOrder } from "../hooks/useAgentOrder";
|
|
28
|
+
import { useProjectOrder } from "../hooks/useProjectOrder";
|
|
29
|
+
import { buildPortableSidebarOrder } from "../lib/company-portability-sidebar";
|
|
30
|
+
import { getPortableFileDataUrl, getPortableFileText, isPortableImageFile } from "../lib/portable-files";
|
|
31
|
+
import {
|
|
32
|
+
Download,
|
|
33
|
+
Package,
|
|
34
|
+
Search,
|
|
35
|
+
} from "lucide-react";
|
|
36
|
+
import {
|
|
37
|
+
type FileTreeNode,
|
|
38
|
+
type FrontmatterData,
|
|
39
|
+
buildFileTree,
|
|
40
|
+
countFiles,
|
|
41
|
+
collectAllPaths,
|
|
42
|
+
parseFrontmatter,
|
|
43
|
+
FRONTMATTER_FIELD_LABELS,
|
|
44
|
+
PackageFileTree,
|
|
45
|
+
} from "../components/PackageFileTree";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract the set of agent/project/task slugs that are "checked" based on
|
|
49
|
+
* which file paths are in the checked set.
|
|
50
|
+
* agents/{slug}/AGENT.md → agents slug
|
|
51
|
+
* projects/{slug}/PROJECT.md → projects slug
|
|
52
|
+
* tasks/{slug}/TASK.md → tasks slug
|
|
53
|
+
*/
|
|
54
|
+
function checkedSlugs(checkedFiles: Set<string>): {
|
|
55
|
+
agents: Set<string>;
|
|
56
|
+
projects: Set<string>;
|
|
57
|
+
tasks: Set<string>;
|
|
58
|
+
routines: Set<string>;
|
|
59
|
+
} {
|
|
60
|
+
const agents = new Set<string>();
|
|
61
|
+
const projects = new Set<string>();
|
|
62
|
+
const tasks = new Set<string>();
|
|
63
|
+
for (const p of checkedFiles) {
|
|
64
|
+
const agentMatch = p.match(/^agents\/([^/]+)\//);
|
|
65
|
+
if (agentMatch) agents.add(agentMatch[1]);
|
|
66
|
+
const projectMatch = p.match(/^projects\/([^/]+)\//);
|
|
67
|
+
if (projectMatch) projects.add(projectMatch[1]);
|
|
68
|
+
const taskMatch = p.match(/^tasks\/([^/]+)\//);
|
|
69
|
+
if (taskMatch) tasks.add(taskMatch[1]);
|
|
70
|
+
}
|
|
71
|
+
return { agents, projects, tasks, routines: new Set(tasks) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Filter .paperclip.yaml content so it only includes entries whose
|
|
76
|
+
* corresponding files are checked. Works by line-level YAML parsing
|
|
77
|
+
* since the file has a known, simple structure produced by our own
|
|
78
|
+
* renderYamlBlock.
|
|
79
|
+
*/
|
|
80
|
+
function filterPaperclipYaml(yaml: string, checkedFiles: Set<string>): string {
|
|
81
|
+
const slugs = checkedSlugs(checkedFiles);
|
|
82
|
+
const lines = yaml.split("\n");
|
|
83
|
+
const out: string[] = [];
|
|
84
|
+
|
|
85
|
+
// Sections whose entries are slug-keyed and should be filtered
|
|
86
|
+
const filterableSections = new Set(["agents", "projects", "tasks", "routines"]);
|
|
87
|
+
const sidebarSections = new Set(["agents", "projects"]);
|
|
88
|
+
|
|
89
|
+
let currentSection: string | null = null; // top-level key (e.g. "agents")
|
|
90
|
+
let currentEntry: string | null = null; // slug under that section
|
|
91
|
+
let includeEntry = true;
|
|
92
|
+
let currentSidebarList: string | null = null;
|
|
93
|
+
let currentSidebarHeaderLine: string | null = null;
|
|
94
|
+
let currentSidebarBuffer: string[] = [];
|
|
95
|
+
// Collect entries per section so we can omit empty section headers
|
|
96
|
+
let sectionHeaderLine: string | null = null;
|
|
97
|
+
let sectionBuffer: string[] = [];
|
|
98
|
+
|
|
99
|
+
function flushSidebarSection() {
|
|
100
|
+
if (currentSidebarHeaderLine !== null && currentSidebarBuffer.length > 0) {
|
|
101
|
+
sectionBuffer.push(currentSidebarHeaderLine);
|
|
102
|
+
sectionBuffer.push(...currentSidebarBuffer);
|
|
103
|
+
}
|
|
104
|
+
currentSidebarHeaderLine = null;
|
|
105
|
+
currentSidebarBuffer = [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function flushSection() {
|
|
109
|
+
flushSidebarSection();
|
|
110
|
+
if (sectionHeaderLine !== null && sectionBuffer.length > 0) {
|
|
111
|
+
out.push(sectionHeaderLine);
|
|
112
|
+
out.push(...sectionBuffer);
|
|
113
|
+
}
|
|
114
|
+
sectionHeaderLine = null;
|
|
115
|
+
sectionBuffer = [];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
// Detect top-level key (no indentation)
|
|
120
|
+
const topMatch = line.match(/^([a-zA-Z_][\w-]*):\s*(.*)$/);
|
|
121
|
+
if (topMatch && !line.startsWith(" ")) {
|
|
122
|
+
// Flush previous section
|
|
123
|
+
flushSection();
|
|
124
|
+
currentEntry = null;
|
|
125
|
+
includeEntry = true;
|
|
126
|
+
|
|
127
|
+
const key = topMatch[0].split(":")[0];
|
|
128
|
+
if (filterableSections.has(key)) {
|
|
129
|
+
currentSection = key;
|
|
130
|
+
sectionHeaderLine = line;
|
|
131
|
+
continue;
|
|
132
|
+
} else if (key === "sidebar") {
|
|
133
|
+
currentSection = key;
|
|
134
|
+
currentSidebarList = null;
|
|
135
|
+
sectionHeaderLine = line;
|
|
136
|
+
continue;
|
|
137
|
+
} else {
|
|
138
|
+
currentSection = null;
|
|
139
|
+
out.push(line);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (currentSection === "sidebar") {
|
|
145
|
+
const sidebarMatch = line.match(/^ ([\w-]+):\s*$/);
|
|
146
|
+
if (sidebarMatch && !line.startsWith(" ")) {
|
|
147
|
+
flushSidebarSection();
|
|
148
|
+
const sidebarKey = sidebarMatch[1];
|
|
149
|
+
currentSidebarList = sidebarKey && sidebarSections.has(sidebarKey) ? sidebarKey : null;
|
|
150
|
+
currentSidebarHeaderLine = currentSidebarList ? line : null;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const sidebarEntryMatch = line.match(/^ - ["']?([^"'\n]+)["']?\s*$/);
|
|
155
|
+
if (sidebarEntryMatch && currentSidebarList) {
|
|
156
|
+
const slug = sidebarEntryMatch[1];
|
|
157
|
+
const sectionSlugs = slugs[currentSidebarList as keyof typeof slugs];
|
|
158
|
+
if (slug && sectionSlugs.has(slug)) {
|
|
159
|
+
currentSidebarBuffer.push(line);
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (currentSidebarList) {
|
|
165
|
+
currentSidebarBuffer.push(line);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Inside a filterable section
|
|
171
|
+
if (currentSection && filterableSections.has(currentSection)) {
|
|
172
|
+
// 2-space indented key = entry slug (slugs may start with digits/hyphens)
|
|
173
|
+
const entryMatch = line.match(/^ ([\w][\w-]*):\s*(.*)$/);
|
|
174
|
+
if (entryMatch && !line.startsWith(" ")) {
|
|
175
|
+
const slug = entryMatch[1];
|
|
176
|
+
currentEntry = slug;
|
|
177
|
+
const sectionSlugs = slugs[currentSection as keyof typeof slugs];
|
|
178
|
+
includeEntry = sectionSlugs.has(slug);
|
|
179
|
+
if (includeEntry) sectionBuffer.push(line);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Deeper indented line belongs to current entry
|
|
184
|
+
if (currentEntry !== null) {
|
|
185
|
+
if (includeEntry) sectionBuffer.push(line);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Shouldn't happen in well-formed output, but pass through
|
|
190
|
+
sectionBuffer.push(line);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Outside filterable sections — pass through
|
|
195
|
+
out.push(line);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Flush last section
|
|
199
|
+
flushSection();
|
|
200
|
+
|
|
201
|
+
let filtered = out.join("\n");
|
|
202
|
+
const logoPathMatch = filtered.match(/^\s{2}logoPath:\s*["']?([^"'\n]+)["']?\s*$/m);
|
|
203
|
+
if (logoPathMatch && !checkedFiles.has(logoPathMatch[1]!)) {
|
|
204
|
+
filtered = filtered.replace(/^\s{2}logoPath:\s*["']?([^"'\n]+)["']?\s*\n?/m, "");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return filtered;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Filter tree nodes whose path (or descendant paths) match a search string */
|
|
211
|
+
function filterTree(nodes: FileTreeNode[], query: string): FileTreeNode[] {
|
|
212
|
+
if (!query) return nodes;
|
|
213
|
+
const lower = query.toLowerCase();
|
|
214
|
+
return nodes
|
|
215
|
+
.map((node) => {
|
|
216
|
+
if (node.kind === "file") {
|
|
217
|
+
return node.name.toLowerCase().includes(lower) || node.path.toLowerCase().includes(lower)
|
|
218
|
+
? node
|
|
219
|
+
: null;
|
|
220
|
+
}
|
|
221
|
+
const filteredChildren = filterTree(node.children, query);
|
|
222
|
+
return filteredChildren.length > 0
|
|
223
|
+
? { ...node, children: filteredChildren }
|
|
224
|
+
: null;
|
|
225
|
+
})
|
|
226
|
+
.filter((n): n is FileTreeNode => n !== null);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Collect all ancestor dir paths for files that match a filter */
|
|
230
|
+
function collectMatchedParentDirs(nodes: FileTreeNode[], query: string): Set<string> {
|
|
231
|
+
const dirs = new Set<string>();
|
|
232
|
+
const lower = query.toLowerCase();
|
|
233
|
+
|
|
234
|
+
function walk(node: FileTreeNode, ancestors: string[]) {
|
|
235
|
+
if (node.kind === "file") {
|
|
236
|
+
if (node.name.toLowerCase().includes(lower) || node.path.toLowerCase().includes(lower)) {
|
|
237
|
+
for (const a of ancestors) dirs.add(a);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
for (const child of node.children) {
|
|
241
|
+
walk(child, [...ancestors, node.path]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const node of nodes) walk(node, []);
|
|
247
|
+
return dirs;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Sort tree: checked files first, then unchecked */
|
|
251
|
+
function sortByChecked(nodes: FileTreeNode[], checkedFiles: Set<string>): FileTreeNode[] {
|
|
252
|
+
return nodes.map((node) => {
|
|
253
|
+
if (node.kind === "dir") {
|
|
254
|
+
return { ...node, children: sortByChecked(node.children, checkedFiles) };
|
|
255
|
+
}
|
|
256
|
+
return node;
|
|
257
|
+
}).sort((a, b) => {
|
|
258
|
+
if (a.kind !== b.kind) return a.kind === "file" ? -1 : 1;
|
|
259
|
+
if (a.kind === "file" && b.kind === "file") {
|
|
260
|
+
const aChecked = checkedFiles.has(a.path);
|
|
261
|
+
const bChecked = checkedFiles.has(b.path);
|
|
262
|
+
if (aChecked !== bChecked) return aChecked ? -1 : 1;
|
|
263
|
+
}
|
|
264
|
+
return a.name.localeCompare(b.name);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const TASKS_PAGE_SIZE = 10;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Paginate children of `tasks/` directories: show up to `limit` entries,
|
|
272
|
+
* but always include children that are checked or match the search query.
|
|
273
|
+
* Returns the paginated tree and the total count of task children.
|
|
274
|
+
*/
|
|
275
|
+
function paginateTaskNodes(
|
|
276
|
+
nodes: FileTreeNode[],
|
|
277
|
+
limit: number,
|
|
278
|
+
checkedFiles: Set<string>,
|
|
279
|
+
searchQuery: string,
|
|
280
|
+
): { nodes: FileTreeNode[]; totalTaskChildren: number; visibleTaskChildren: number } {
|
|
281
|
+
let totalTaskChildren = 0;
|
|
282
|
+
let visibleTaskChildren = 0;
|
|
283
|
+
|
|
284
|
+
const result = nodes.map((node) => {
|
|
285
|
+
// Only paginate direct children of "tasks" directories
|
|
286
|
+
if (node.kind === "dir" && node.name === "tasks") {
|
|
287
|
+
totalTaskChildren = node.children.length;
|
|
288
|
+
|
|
289
|
+
// Partition children: pinned (checked or search-matched) vs rest
|
|
290
|
+
const pinned: FileTreeNode[] = [];
|
|
291
|
+
const rest: FileTreeNode[] = [];
|
|
292
|
+
const lower = searchQuery.toLowerCase();
|
|
293
|
+
|
|
294
|
+
for (const child of node.children) {
|
|
295
|
+
const childFiles = collectAllPaths([child], "file");
|
|
296
|
+
const isChecked = [...childFiles].some((p) => checkedFiles.has(p));
|
|
297
|
+
const isSearchMatch = searchQuery && (
|
|
298
|
+
child.name.toLowerCase().includes(lower) ||
|
|
299
|
+
child.path.toLowerCase().includes(lower) ||
|
|
300
|
+
[...childFiles].some((p) => p.toLowerCase().includes(lower))
|
|
301
|
+
);
|
|
302
|
+
if (isChecked || isSearchMatch) {
|
|
303
|
+
pinned.push(child);
|
|
304
|
+
} else {
|
|
305
|
+
rest.push(child);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Show pinned + up to `limit` from rest
|
|
310
|
+
const remaining = Math.max(0, limit - pinned.length);
|
|
311
|
+
const visible = [...pinned, ...rest.slice(0, remaining)];
|
|
312
|
+
visibleTaskChildren = visible.length;
|
|
313
|
+
|
|
314
|
+
return { ...node, children: visible };
|
|
315
|
+
}
|
|
316
|
+
return node;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return { nodes: result, totalTaskChildren, visibleTaskChildren };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function downloadZip(
|
|
323
|
+
exported: CompanyPortabilityExportResult,
|
|
324
|
+
selectedFiles: Set<string>,
|
|
325
|
+
effectiveFiles: Record<string, CompanyPortabilityFileEntry>,
|
|
326
|
+
) {
|
|
327
|
+
const filteredFiles: Record<string, CompanyPortabilityFileEntry> = {};
|
|
328
|
+
for (const [path] of Object.entries(exported.files)) {
|
|
329
|
+
if (selectedFiles.has(path)) filteredFiles[path] = effectiveFiles[path] ?? exported.files[path];
|
|
330
|
+
}
|
|
331
|
+
const zipBytes = createZipArchive(filteredFiles, exported.rootPath);
|
|
332
|
+
const zipBuffer = new ArrayBuffer(zipBytes.byteLength);
|
|
333
|
+
new Uint8Array(zipBuffer).set(zipBytes);
|
|
334
|
+
const blob = new Blob([zipBuffer], { type: "application/zip" });
|
|
335
|
+
const url = URL.createObjectURL(blob);
|
|
336
|
+
const anchor = document.createElement("a");
|
|
337
|
+
anchor.href = url;
|
|
338
|
+
anchor.download = `${exported.rootPath}.zip`;
|
|
339
|
+
document.body.appendChild(anchor);
|
|
340
|
+
anchor.click();
|
|
341
|
+
anchor.remove();
|
|
342
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Frontmatter card (export-specific: skill click support) ──────────
|
|
346
|
+
|
|
347
|
+
function FrontmatterCard({
|
|
348
|
+
data,
|
|
349
|
+
onSkillClick,
|
|
350
|
+
}: {
|
|
351
|
+
data: FrontmatterData;
|
|
352
|
+
onSkillClick?: (skill: string) => void;
|
|
353
|
+
}) {
|
|
354
|
+
return (
|
|
355
|
+
<div className="rounded-md border border-border bg-accent/20 px-4 py-3 mb-4">
|
|
356
|
+
<dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-sm">
|
|
357
|
+
{Object.entries(data).map(([key, value]) => (
|
|
358
|
+
<div key={key} className="contents">
|
|
359
|
+
<dt className="text-muted-foreground whitespace-nowrap py-0.5">
|
|
360
|
+
{FRONTMATTER_FIELD_LABELS[key] ?? key}
|
|
361
|
+
</dt>
|
|
362
|
+
<dd className="py-0.5">
|
|
363
|
+
{Array.isArray(value) ? (
|
|
364
|
+
<div className="flex flex-wrap gap-1.5">
|
|
365
|
+
{value.map((item) => (
|
|
366
|
+
<button
|
|
367
|
+
key={item}
|
|
368
|
+
type="button"
|
|
369
|
+
className={cn(
|
|
370
|
+
"inline-flex items-center rounded-md border border-border bg-background px-2 py-0.5 text-xs",
|
|
371
|
+
key === "skills" && onSkillClick && "cursor-pointer hover:bg-accent/50 hover:border-foreground/30 transition-colors",
|
|
372
|
+
)}
|
|
373
|
+
onClick={() => key === "skills" && onSkillClick?.(item)}
|
|
374
|
+
>
|
|
375
|
+
{item}
|
|
376
|
+
</button>
|
|
377
|
+
))}
|
|
378
|
+
</div>
|
|
379
|
+
) : (
|
|
380
|
+
<span>{value}</span>
|
|
381
|
+
)}
|
|
382
|
+
</dd>
|
|
383
|
+
</div>
|
|
384
|
+
))}
|
|
385
|
+
</dl>
|
|
386
|
+
</div>
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Client-side README generation ────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
const ROLE_LABELS: Record<string, string> = {
|
|
393
|
+
ceo: "CEO", cto: "CTO", cmo: "CMO", cfo: "CFO", coo: "COO",
|
|
394
|
+
vp: "VP", manager: "Manager", engineer: "Engineer", agent: "Agent",
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Regenerate README.md content based on the currently checked files.
|
|
399
|
+
* Only counts/lists entities whose files are in the checked set.
|
|
400
|
+
*/
|
|
401
|
+
function generateReadmeFromSelection(
|
|
402
|
+
manifest: CompanyPortabilityManifest,
|
|
403
|
+
checkedFiles: Set<string>,
|
|
404
|
+
companyName: string,
|
|
405
|
+
companyDescription: string | null,
|
|
406
|
+
): string {
|
|
407
|
+
const slugs = checkedSlugs(checkedFiles);
|
|
408
|
+
|
|
409
|
+
const agents = manifest.agents.filter((a) => slugs.agents.has(a.slug));
|
|
410
|
+
const projects = manifest.projects.filter((p) => slugs.projects.has(p.slug));
|
|
411
|
+
const tasks = manifest.issues.filter((t) => slugs.tasks.has(t.slug));
|
|
412
|
+
const skills = manifest.skills.filter((s) => {
|
|
413
|
+
// Skill files live under skills/{key}/...
|
|
414
|
+
return [...checkedFiles].some((f) => f.startsWith(`skills/${s.key}/`) || f.startsWith(`skills/`) && f.includes(`/${s.slug}/`));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const lines: string[] = [];
|
|
418
|
+
lines.push(`# ${companyName}`);
|
|
419
|
+
lines.push("");
|
|
420
|
+
if (companyDescription) {
|
|
421
|
+
lines.push(`> ${companyDescription}`);
|
|
422
|
+
lines.push("");
|
|
423
|
+
}
|
|
424
|
+
// Org chart image (generated during export as images/org-chart.png)
|
|
425
|
+
if (agents.length > 0) {
|
|
426
|
+
lines.push("");
|
|
427
|
+
lines.push("");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
lines.push("## What's Inside");
|
|
431
|
+
lines.push("");
|
|
432
|
+
lines.push("This is a Corporate package.");
|
|
433
|
+
lines.push("");
|
|
434
|
+
|
|
435
|
+
const counts: Array<[string, number]> = [];
|
|
436
|
+
if (agents.length > 0) counts.push(["Agents", agents.length]);
|
|
437
|
+
if (projects.length > 0) counts.push(["Projects", projects.length]);
|
|
438
|
+
if (skills.length > 0) counts.push(["Skills", skills.length]);
|
|
439
|
+
if (tasks.length > 0) counts.push(["Tasks", tasks.length]);
|
|
440
|
+
|
|
441
|
+
if (counts.length > 0) {
|
|
442
|
+
lines.push("| Content | Count |");
|
|
443
|
+
lines.push("|---------|-------|");
|
|
444
|
+
for (const [label, count] of counts) {
|
|
445
|
+
lines.push(`| ${label} | ${count} |`);
|
|
446
|
+
}
|
|
447
|
+
lines.push("");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (agents.length > 0) {
|
|
451
|
+
lines.push("### Agents");
|
|
452
|
+
lines.push("");
|
|
453
|
+
lines.push("| Agent | Role | Reports To |");
|
|
454
|
+
lines.push("|-------|------|------------|");
|
|
455
|
+
for (const agent of agents) {
|
|
456
|
+
const roleLabel = ROLE_LABELS[agent.role] ?? agent.role;
|
|
457
|
+
const reportsTo = agent.reportsToSlug ?? "\u2014";
|
|
458
|
+
lines.push(`| ${agent.name} | ${roleLabel} | ${reportsTo} |`);
|
|
459
|
+
}
|
|
460
|
+
lines.push("");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (projects.length > 0) {
|
|
464
|
+
lines.push("### Projects");
|
|
465
|
+
lines.push("");
|
|
466
|
+
for (const project of projects) {
|
|
467
|
+
const desc = project.description ? ` \u2014 ${project.description}` : "";
|
|
468
|
+
lines.push(`- **${project.name}**${desc}`);
|
|
469
|
+
}
|
|
470
|
+
lines.push("");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
lines.push("## Getting Started");
|
|
474
|
+
lines.push("");
|
|
475
|
+
lines.push("```bash");
|
|
476
|
+
lines.push("pnpm corporateai company import this-github-url-or-folder");
|
|
477
|
+
lines.push("```");
|
|
478
|
+
lines.push("");
|
|
479
|
+
lines.push("See Corporate docs for more information.");
|
|
480
|
+
lines.push("");
|
|
481
|
+
lines.push("---");
|
|
482
|
+
lines.push(`Exported from Corporate on ${new Date().toISOString().split("T")[0]}`);
|
|
483
|
+
lines.push("");
|
|
484
|
+
|
|
485
|
+
return lines.join("\n");
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ── Preview pane ──────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
function ExportPreviewPane({
|
|
491
|
+
selectedFile,
|
|
492
|
+
content,
|
|
493
|
+
allFiles,
|
|
494
|
+
onSkillClick,
|
|
495
|
+
}: {
|
|
496
|
+
selectedFile: string | null;
|
|
497
|
+
content: CompanyPortabilityFileEntry | null;
|
|
498
|
+
allFiles: Record<string, CompanyPortabilityFileEntry>;
|
|
499
|
+
onSkillClick?: (skill: string) => void;
|
|
500
|
+
}) {
|
|
501
|
+
if (!selectedFile || content === null) {
|
|
502
|
+
return (
|
|
503
|
+
<EmptyState icon={Package} message="Select a file to preview its contents." />
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const textContent = getPortableFileText(content);
|
|
508
|
+
const isMarkdown = selectedFile.endsWith(".md") && textContent !== null;
|
|
509
|
+
const parsed = isMarkdown && textContent ? parseFrontmatter(textContent) : null;
|
|
510
|
+
const imageSrc = isPortableImageFile(selectedFile, content) ? getPortableFileDataUrl(selectedFile, content) : null;
|
|
511
|
+
|
|
512
|
+
// Resolve relative image paths within the export package (e.g. images/org-chart.png)
|
|
513
|
+
const resolveImageSrc = isMarkdown
|
|
514
|
+
? (src: string) => {
|
|
515
|
+
// Skip absolute URLs and data URIs
|
|
516
|
+
if (/^(?:https?:|data:)/i.test(src)) return null;
|
|
517
|
+
// Resolve relative to the directory of the current markdown file
|
|
518
|
+
const dir = selectedFile.includes("/") ? selectedFile.slice(0, selectedFile.lastIndexOf("/") + 1) : "";
|
|
519
|
+
const resolved = dir + src;
|
|
520
|
+
const entry = allFiles[resolved] ?? allFiles[src];
|
|
521
|
+
if (!entry) return null;
|
|
522
|
+
return getPortableFileDataUrl(resolved in allFiles ? resolved : src, entry);
|
|
523
|
+
}
|
|
524
|
+
: undefined;
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<div className="min-w-0">
|
|
528
|
+
<div className="border-b border-border px-5 py-3">
|
|
529
|
+
<div className="truncate font-mono text-sm">{selectedFile}</div>
|
|
530
|
+
</div>
|
|
531
|
+
<div className="min-h-[560px] px-5 py-5">
|
|
532
|
+
{parsed ? (
|
|
533
|
+
<>
|
|
534
|
+
<FrontmatterCard data={parsed.data} onSkillClick={onSkillClick} />
|
|
535
|
+
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc}>{parsed.body}</MarkdownBody>}
|
|
536
|
+
</>
|
|
537
|
+
) : isMarkdown ? (
|
|
538
|
+
<MarkdownBody resolveImageSrc={resolveImageSrc}>{textContent ?? ""}</MarkdownBody>
|
|
539
|
+
) : imageSrc ? (
|
|
540
|
+
<div className="flex min-h-[520px] items-center justify-center rounded-lg border border-border bg-accent/10 p-6">
|
|
541
|
+
<img src={imageSrc} alt={selectedFile} className="max-h-[480px] max-w-full object-contain" />
|
|
542
|
+
</div>
|
|
543
|
+
) : textContent !== null ? (
|
|
544
|
+
<pre className="overflow-x-auto whitespace-pre-wrap break-words border-0 bg-transparent p-0 font-mono text-sm text-foreground">
|
|
545
|
+
<code>{textContent}</code>
|
|
546
|
+
</pre>
|
|
547
|
+
) : (
|
|
548
|
+
<div className="rounded-lg border border-border bg-accent/10 px-4 py-3 text-sm text-muted-foreground">
|
|
549
|
+
Binary asset preview is not available for this file type.
|
|
550
|
+
</div>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ── Main page ─────────────────────────────────────────────────────────
|
|
558
|
+
|
|
559
|
+
/** Extract the file path from the current URL pathname (after /company/export/files/) */
|
|
560
|
+
function filePathFromLocation(pathname: string): string | null {
|
|
561
|
+
const marker = "/company/export/files/";
|
|
562
|
+
const idx = pathname.indexOf(marker);
|
|
563
|
+
if (idx === -1) return null;
|
|
564
|
+
const filePath = decodeURIComponent(pathname.slice(idx + marker.length));
|
|
565
|
+
return filePath || null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** Expand all ancestor directories for a given file path */
|
|
569
|
+
function expandAncestors(filePath: string): string[] {
|
|
570
|
+
const parts = filePath.split("/").slice(0, -1);
|
|
571
|
+
const dirs: string[] = [];
|
|
572
|
+
let current = "";
|
|
573
|
+
for (const part of parts) {
|
|
574
|
+
current = current ? `${current}/${part}` : part;
|
|
575
|
+
dirs.push(current);
|
|
576
|
+
}
|
|
577
|
+
return dirs;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
export function CompanyExport() {
|
|
581
|
+
const { selectedCompanyId, selectedCompany } = useCompany();
|
|
582
|
+
const { setBreadcrumbs } = useBreadcrumbs();
|
|
583
|
+
const { pushToast } = useToast();
|
|
584
|
+
const navigate = useNavigate();
|
|
585
|
+
const location = useLocation();
|
|
586
|
+
const { data: session, isFetched: isSessionFetched } = useQuery({
|
|
587
|
+
queryKey: queryKeys.auth.session,
|
|
588
|
+
queryFn: () => authApi.getSession(),
|
|
589
|
+
});
|
|
590
|
+
const { data: agents = [], isFetched: areAgentsFetched } = useQuery({
|
|
591
|
+
queryKey: queryKeys.agents.list(selectedCompanyId!),
|
|
592
|
+
queryFn: () => agentsApi.list(selectedCompanyId!),
|
|
593
|
+
enabled: !!selectedCompanyId,
|
|
594
|
+
});
|
|
595
|
+
const { data: projects = [], isFetched: areProjectsFetched } = useQuery({
|
|
596
|
+
queryKey: queryKeys.projects.list(selectedCompanyId!),
|
|
597
|
+
queryFn: () => projectsApi.list(selectedCompanyId!),
|
|
598
|
+
enabled: !!selectedCompanyId,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
const [exportData, setExportData] = useState<CompanyPortabilityExportPreviewResult | null>(null);
|
|
602
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
|
603
|
+
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
|
|
604
|
+
const [checkedFiles, setCheckedFiles] = useState<Set<string>>(new Set());
|
|
605
|
+
const [treeSearch, setTreeSearch] = useState("");
|
|
606
|
+
const [taskLimit, setTaskLimit] = useState(TASKS_PAGE_SIZE);
|
|
607
|
+
const savedExpandedRef = useRef<Set<string> | null>(null);
|
|
608
|
+
const initialFileFromUrl = useRef(filePathFromLocation(location.pathname));
|
|
609
|
+
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
|
|
610
|
+
const visibleAgents = useMemo(
|
|
611
|
+
() => agents.filter((agent: Agent) => agent.status !== "terminated"),
|
|
612
|
+
[agents],
|
|
613
|
+
);
|
|
614
|
+
const visibleProjects = useMemo(
|
|
615
|
+
() => projects.filter((project: Project) => !project.archivedAt),
|
|
616
|
+
[projects],
|
|
617
|
+
);
|
|
618
|
+
const { orderedAgents } = useAgentOrder({
|
|
619
|
+
agents: visibleAgents,
|
|
620
|
+
companyId: selectedCompanyId,
|
|
621
|
+
userId: currentUserId,
|
|
622
|
+
});
|
|
623
|
+
const { orderedProjects } = useProjectOrder({
|
|
624
|
+
projects: visibleProjects,
|
|
625
|
+
companyId: selectedCompanyId,
|
|
626
|
+
userId: currentUserId,
|
|
627
|
+
});
|
|
628
|
+
const sidebarOrder = useMemo(
|
|
629
|
+
() => buildPortableSidebarOrder({
|
|
630
|
+
agents: visibleAgents,
|
|
631
|
+
orderedAgents,
|
|
632
|
+
projects: visibleProjects,
|
|
633
|
+
orderedProjects,
|
|
634
|
+
}),
|
|
635
|
+
[orderedAgents, orderedProjects, visibleAgents, visibleProjects],
|
|
636
|
+
);
|
|
637
|
+
const sidebarOrderKey = useMemo(
|
|
638
|
+
() => JSON.stringify(sidebarOrder ?? null),
|
|
639
|
+
[sidebarOrder],
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Navigate-aware file selection: updates state + URL without page reload.
|
|
643
|
+
// `replace` = true skips history entry (used for initial load); false = pushes (used for clicks).
|
|
644
|
+
const selectFile = useCallback(
|
|
645
|
+
(filePath: string | null, replace = false) => {
|
|
646
|
+
setSelectedFile(filePath);
|
|
647
|
+
if (filePath) {
|
|
648
|
+
navigate(`/company/export/files/${encodeURI(filePath)}`, { replace });
|
|
649
|
+
} else {
|
|
650
|
+
navigate("/company/export", { replace });
|
|
651
|
+
}
|
|
652
|
+
},
|
|
653
|
+
[navigate],
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
// Sync selectedFile from URL on browser back/forward
|
|
657
|
+
useEffect(() => {
|
|
658
|
+
if (!exportData) return;
|
|
659
|
+
const urlFile = filePathFromLocation(location.pathname);
|
|
660
|
+
if (urlFile && urlFile in exportData.files && urlFile !== selectedFile) {
|
|
661
|
+
setSelectedFile(urlFile);
|
|
662
|
+
// Expand ancestors so the file is visible in the tree
|
|
663
|
+
setExpandedDirs((prev) => {
|
|
664
|
+
const next = new Set(prev);
|
|
665
|
+
for (const dir of expandAncestors(urlFile)) next.add(dir);
|
|
666
|
+
return next;
|
|
667
|
+
});
|
|
668
|
+
} else if (!urlFile && selectedFile) {
|
|
669
|
+
setSelectedFile(null);
|
|
670
|
+
}
|
|
671
|
+
}, [location.pathname, exportData]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
672
|
+
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
setBreadcrumbs([
|
|
675
|
+
{ label: "Org Chart", href: "/org" },
|
|
676
|
+
{ label: "Export" },
|
|
677
|
+
]);
|
|
678
|
+
}, [setBreadcrumbs]);
|
|
679
|
+
|
|
680
|
+
const exportPreviewMutation = useMutation({
|
|
681
|
+
mutationFn: () =>
|
|
682
|
+
companiesApi.exportPreview(selectedCompanyId!, {
|
|
683
|
+
include: { company: true, agents: true, projects: true, issues: true },
|
|
684
|
+
sidebarOrder,
|
|
685
|
+
}),
|
|
686
|
+
onSuccess: (result) => {
|
|
687
|
+
setExportData(result);
|
|
688
|
+
setCheckedFiles((prev) =>
|
|
689
|
+
buildInitialExportCheckedFiles(
|
|
690
|
+
Object.keys(result.files),
|
|
691
|
+
result.manifest.issues,
|
|
692
|
+
prev,
|
|
693
|
+
),
|
|
694
|
+
);
|
|
695
|
+
// Expand top-level dirs (except tasks — collapsed by default)
|
|
696
|
+
const tree = buildFileTree(result.files);
|
|
697
|
+
const topDirs = new Set<string>();
|
|
698
|
+
for (const node of tree) {
|
|
699
|
+
if (node.kind === "dir" && node.name !== "tasks") topDirs.add(node.path);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// If URL contains a deep-linked file path, select it and expand ancestors
|
|
703
|
+
const urlFile = initialFileFromUrl.current;
|
|
704
|
+
if (urlFile && urlFile in result.files) {
|
|
705
|
+
setSelectedFile(urlFile);
|
|
706
|
+
const ancestors = expandAncestors(urlFile);
|
|
707
|
+
setExpandedDirs(new Set([...topDirs, ...ancestors]));
|
|
708
|
+
} else {
|
|
709
|
+
// Default to README.md if present, otherwise fall back to first file
|
|
710
|
+
const defaultFile = "README.md" in result.files
|
|
711
|
+
? "README.md"
|
|
712
|
+
: Object.keys(result.files)[0];
|
|
713
|
+
if (defaultFile) {
|
|
714
|
+
selectFile(defaultFile, true);
|
|
715
|
+
}
|
|
716
|
+
setExpandedDirs(topDirs);
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
onError: (err) => {
|
|
720
|
+
pushToast({
|
|
721
|
+
tone: "error",
|
|
722
|
+
title: "Export failed",
|
|
723
|
+
body: err instanceof Error ? err.message : "Failed to load export data.",
|
|
724
|
+
});
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const downloadMutation = useMutation({
|
|
729
|
+
mutationFn: () =>
|
|
730
|
+
companiesApi.exportPackage(selectedCompanyId!, {
|
|
731
|
+
include: { company: true, agents: true, projects: true, issues: true },
|
|
732
|
+
selectedFiles: Array.from(checkedFiles).sort(),
|
|
733
|
+
sidebarOrder,
|
|
734
|
+
}),
|
|
735
|
+
onSuccess: (result) => {
|
|
736
|
+
const resultCheckedFiles = new Set(Object.keys(result.files));
|
|
737
|
+
downloadZip(result, resultCheckedFiles, result.files);
|
|
738
|
+
pushToast({
|
|
739
|
+
tone: "success",
|
|
740
|
+
title: "Export downloaded",
|
|
741
|
+
body: `${resultCheckedFiles.size} file${resultCheckedFiles.size === 1 ? "" : "s"} exported as ${result.rootPath}.zip`,
|
|
742
|
+
});
|
|
743
|
+
},
|
|
744
|
+
onError: (err) => {
|
|
745
|
+
pushToast({
|
|
746
|
+
tone: "error",
|
|
747
|
+
title: "Export failed",
|
|
748
|
+
body: err instanceof Error ? err.message : "Failed to build export package.",
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
useEffect(() => {
|
|
754
|
+
if (!selectedCompanyId || exportPreviewMutation.isPending) return;
|
|
755
|
+
if (!isSessionFetched || !areAgentsFetched || !areProjectsFetched) return;
|
|
756
|
+
setExportData(null);
|
|
757
|
+
exportPreviewMutation.mutate();
|
|
758
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
759
|
+
}, [selectedCompanyId, isSessionFetched, areAgentsFetched, areProjectsFetched, sidebarOrderKey]);
|
|
760
|
+
|
|
761
|
+
const tree = useMemo(
|
|
762
|
+
() => (exportData ? buildFileTree(exportData.files) : []),
|
|
763
|
+
[exportData],
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
const { displayTree, totalTaskChildren, visibleTaskChildren } = useMemo(() => {
|
|
767
|
+
let result = tree;
|
|
768
|
+
if (treeSearch) result = filterTree(result, treeSearch);
|
|
769
|
+
result = sortByChecked(result, checkedFiles);
|
|
770
|
+
const paginated = paginateTaskNodes(result, taskLimit, checkedFiles, treeSearch);
|
|
771
|
+
return {
|
|
772
|
+
displayTree: paginated.nodes,
|
|
773
|
+
totalTaskChildren: paginated.totalTaskChildren,
|
|
774
|
+
visibleTaskChildren: paginated.visibleTaskChildren,
|
|
775
|
+
};
|
|
776
|
+
}, [tree, treeSearch, checkedFiles, taskLimit]);
|
|
777
|
+
|
|
778
|
+
// Recompute .paperclip.yaml and README.md content whenever checked files
|
|
779
|
+
// change so the preview & download always reflect the current selection.
|
|
780
|
+
const effectiveFiles = useMemo(() => {
|
|
781
|
+
if (!exportData) return {} as Record<string, CompanyPortabilityFileEntry>;
|
|
782
|
+
const filtered = { ...exportData.files };
|
|
783
|
+
|
|
784
|
+
// Filter .paperclip.yaml
|
|
785
|
+
const yamlPath = exportData.paperclipExtensionPath;
|
|
786
|
+
if (yamlPath && typeof exportData.files[yamlPath] === "string") {
|
|
787
|
+
filtered[yamlPath] = filterPaperclipYaml(exportData.files[yamlPath], checkedFiles);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Regenerate README.md based on checked selection
|
|
791
|
+
if (typeof exportData.files["README.md"] === "string") {
|
|
792
|
+
const companyName = exportData.manifest.company?.name ?? selectedCompany?.name ?? "Company";
|
|
793
|
+
const companyDescription = exportData.manifest.company?.description ?? null;
|
|
794
|
+
filtered["README.md"] = generateReadmeFromSelection(
|
|
795
|
+
exportData.manifest,
|
|
796
|
+
checkedFiles,
|
|
797
|
+
companyName,
|
|
798
|
+
companyDescription,
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
return filtered;
|
|
803
|
+
}, [exportData, checkedFiles, selectedCompany?.name]);
|
|
804
|
+
|
|
805
|
+
const totalFiles = useMemo(() => countFiles(tree), [tree]);
|
|
806
|
+
const selectedCount = checkedFiles.size;
|
|
807
|
+
|
|
808
|
+
// Filter out terminated agent messages — they don't need to be shown
|
|
809
|
+
const warnings = useMemo(() => {
|
|
810
|
+
if (!exportData) return [] as string[];
|
|
811
|
+
return exportData.warnings.filter((w) => !/terminated agent/i.test(w));
|
|
812
|
+
}, [exportData]);
|
|
813
|
+
|
|
814
|
+
function handleToggleDir(path: string) {
|
|
815
|
+
setExpandedDirs((prev) => {
|
|
816
|
+
const next = new Set(prev);
|
|
817
|
+
if (next.has(path)) next.delete(path);
|
|
818
|
+
else next.add(path);
|
|
819
|
+
return next;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function handleToggleCheck(path: string, kind: "file" | "dir") {
|
|
824
|
+
if (!exportData) return;
|
|
825
|
+
setCheckedFiles((prev) => {
|
|
826
|
+
const next = new Set(prev);
|
|
827
|
+
if (kind === "file") {
|
|
828
|
+
if (next.has(path)) next.delete(path);
|
|
829
|
+
else next.add(path);
|
|
830
|
+
} else {
|
|
831
|
+
// Find all child file paths under this dir
|
|
832
|
+
const dirTree = buildFileTree(exportData.files);
|
|
833
|
+
const findNode = (nodes: FileTreeNode[], target: string): FileTreeNode | null => {
|
|
834
|
+
for (const n of nodes) {
|
|
835
|
+
if (n.path === target) return n;
|
|
836
|
+
const found = findNode(n.children, target);
|
|
837
|
+
if (found) return found;
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
};
|
|
841
|
+
const dirNode = findNode(dirTree, path);
|
|
842
|
+
if (dirNode) {
|
|
843
|
+
const childFiles = collectAllPaths(dirNode.children, "file");
|
|
844
|
+
// Add the dir's own file children
|
|
845
|
+
for (const child of dirNode.children) {
|
|
846
|
+
if (child.kind === "file") childFiles.add(child.path);
|
|
847
|
+
}
|
|
848
|
+
const allChecked = [...childFiles].every((p) => next.has(p));
|
|
849
|
+
for (const f of childFiles) {
|
|
850
|
+
if (allChecked) next.delete(f);
|
|
851
|
+
else next.add(f);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return next;
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function handleSearchChange(query: string) {
|
|
860
|
+
const wasSearching = treeSearch.length > 0;
|
|
861
|
+
const isSearching = query.length > 0;
|
|
862
|
+
|
|
863
|
+
if (isSearching && !wasSearching) {
|
|
864
|
+
// Save current expansion state before search
|
|
865
|
+
savedExpandedRef.current = new Set(expandedDirs);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
setTreeSearch(query);
|
|
869
|
+
|
|
870
|
+
if (isSearching) {
|
|
871
|
+
// Expand all parent dirs of matched files
|
|
872
|
+
const matchedParents = collectMatchedParentDirs(tree, query);
|
|
873
|
+
setExpandedDirs((prev) => {
|
|
874
|
+
const next = new Set(prev);
|
|
875
|
+
for (const d of matchedParents) next.add(d);
|
|
876
|
+
return next;
|
|
877
|
+
});
|
|
878
|
+
} else if (wasSearching) {
|
|
879
|
+
// Restore pre-search expansion state
|
|
880
|
+
if (savedExpandedRef.current) {
|
|
881
|
+
setExpandedDirs(savedExpandedRef.current);
|
|
882
|
+
savedExpandedRef.current = null;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function handleSkillClick(skillKey: string) {
|
|
888
|
+
if (!exportData) return;
|
|
889
|
+
const manifestSkill = exportData.manifest.skills.find(
|
|
890
|
+
(skill) => skill.key === skillKey || skill.slug === skillKey,
|
|
891
|
+
);
|
|
892
|
+
const skillPath = manifestSkill?.path ?? `skills/${skillKey}/SKILL.md`;
|
|
893
|
+
if (!(skillPath in exportData.files)) return;
|
|
894
|
+
selectFile(skillPath);
|
|
895
|
+
setExpandedDirs((prev) => {
|
|
896
|
+
const next = new Set(prev);
|
|
897
|
+
next.add("skills");
|
|
898
|
+
const parts = skillPath.split("/").slice(0, -1);
|
|
899
|
+
let current = "";
|
|
900
|
+
for (const part of parts) {
|
|
901
|
+
current = current ? `${current}/${part}` : part;
|
|
902
|
+
next.add(current);
|
|
903
|
+
}
|
|
904
|
+
return next;
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function handleDownload() {
|
|
909
|
+
if (!exportData || checkedFiles.size === 0 || downloadMutation.isPending) return;
|
|
910
|
+
downloadMutation.mutate();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (!selectedCompanyId) {
|
|
914
|
+
return <EmptyState icon={Package} message="Select a company to export." />;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (exportPreviewMutation.isPending && !exportData) {
|
|
918
|
+
return <PageSkeleton variant="detail" />;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (!exportData) {
|
|
922
|
+
return <EmptyState icon={Package} message="Loading export data..." />;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
const previewContent = selectedFile
|
|
926
|
+
? (() => {
|
|
927
|
+
return effectiveFiles[selectedFile] ?? null;
|
|
928
|
+
})()
|
|
929
|
+
: null;
|
|
930
|
+
|
|
931
|
+
return (
|
|
932
|
+
<div>
|
|
933
|
+
{/* Sticky top action bar */}
|
|
934
|
+
<div className="sticky top-0 z-10 border-b border-border bg-background px-5 py-3">
|
|
935
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
936
|
+
<div className="flex items-center gap-4 text-sm">
|
|
937
|
+
<span className="font-medium">
|
|
938
|
+
{selectedCompany?.name ?? "Company"} export
|
|
939
|
+
</span>
|
|
940
|
+
<span className="text-muted-foreground">
|
|
941
|
+
{selectedCount} / {totalFiles} file{totalFiles === 1 ? "" : "s"} selected
|
|
942
|
+
</span>
|
|
943
|
+
{warnings.length > 0 && (
|
|
944
|
+
<span className="text-amber-500">
|
|
945
|
+
{warnings.length} warning{warnings.length === 1 ? "" : "s"}
|
|
946
|
+
</span>
|
|
947
|
+
)}
|
|
948
|
+
</div>
|
|
949
|
+
<Button
|
|
950
|
+
size="sm"
|
|
951
|
+
onClick={handleDownload}
|
|
952
|
+
disabled={selectedCount === 0 || downloadMutation.isPending}
|
|
953
|
+
>
|
|
954
|
+
<Download className="mr-1.5 h-3.5 w-3.5" />
|
|
955
|
+
{downloadMutation.isPending
|
|
956
|
+
? "Building export..."
|
|
957
|
+
: `Export ${selectedCount} file${selectedCount === 1 ? "" : "s"}`}
|
|
958
|
+
</Button>
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
|
|
962
|
+
{/* Warnings */}
|
|
963
|
+
{warnings.length > 0 && (
|
|
964
|
+
<div className="mx-5 mt-3 rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
|
965
|
+
{warnings.map((w) => (
|
|
966
|
+
<div key={w} className="text-xs text-amber-500">{w}</div>
|
|
967
|
+
))}
|
|
968
|
+
</div>
|
|
969
|
+
)}
|
|
970
|
+
|
|
971
|
+
{/* Two-column layout */}
|
|
972
|
+
<div className="grid h-[calc(100vh-12rem)] gap-0 xl:grid-cols-[19rem_minmax(0,1fr)]">
|
|
973
|
+
<aside className="flex flex-col border-r border-border overflow-hidden">
|
|
974
|
+
<div className="border-b border-border px-4 py-3 shrink-0">
|
|
975
|
+
<h2 className="text-base font-semibold">Package files</h2>
|
|
976
|
+
</div>
|
|
977
|
+
<div className="border-b border-border px-3 py-2 shrink-0">
|
|
978
|
+
<div className="flex items-center gap-2 rounded-md border border-border px-2 py-1">
|
|
979
|
+
<Search className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
980
|
+
<input
|
|
981
|
+
type="text"
|
|
982
|
+
value={treeSearch}
|
|
983
|
+
onChange={(e) => handleSearchChange(e.target.value)}
|
|
984
|
+
placeholder="Search files..."
|
|
985
|
+
className="w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
986
|
+
/>
|
|
987
|
+
</div>
|
|
988
|
+
</div>
|
|
989
|
+
<div className="flex-1 overflow-y-auto">
|
|
990
|
+
<PackageFileTree
|
|
991
|
+
nodes={displayTree}
|
|
992
|
+
selectedFile={selectedFile}
|
|
993
|
+
expandedDirs={expandedDirs}
|
|
994
|
+
checkedFiles={checkedFiles}
|
|
995
|
+
onToggleDir={handleToggleDir}
|
|
996
|
+
onSelectFile={selectFile}
|
|
997
|
+
onToggleCheck={handleToggleCheck}
|
|
998
|
+
/>
|
|
999
|
+
{totalTaskChildren > visibleTaskChildren && !treeSearch && (
|
|
1000
|
+
<div className="px-4 py-2">
|
|
1001
|
+
<button
|
|
1002
|
+
type="button"
|
|
1003
|
+
onClick={() => setTaskLimit((prev) => prev + TASKS_PAGE_SIZE)}
|
|
1004
|
+
className="w-full rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground hover:bg-accent/30 hover:text-foreground transition-colors"
|
|
1005
|
+
>
|
|
1006
|
+
Show more issues ({visibleTaskChildren} of {totalTaskChildren})
|
|
1007
|
+
</button>
|
|
1008
|
+
</div>
|
|
1009
|
+
)}
|
|
1010
|
+
</div>
|
|
1011
|
+
</aside>
|
|
1012
|
+
<div className="min-w-0 overflow-y-auto pl-6">
|
|
1013
|
+
<ExportPreviewPane selectedFile={selectedFile} content={previewContent} allFiles={effectiveFiles} onSkillClick={handleSkillClick} />
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
</div>
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|