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,1355 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react";
|
|
2
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type {
|
|
4
|
+
CompanyPortabilityCollisionStrategy,
|
|
5
|
+
CompanyPortabilityFileEntry,
|
|
6
|
+
CompanyPortabilityPreviewResult,
|
|
7
|
+
CompanyPortabilitySource,
|
|
8
|
+
CompanyPortabilityAdapterOverride,
|
|
9
|
+
} from "@corporateai/shared";
|
|
10
|
+
import { useCompany } from "../context/CompanyContext";
|
|
11
|
+
import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
|
12
|
+
import { useToast } from "../context/ToastContext";
|
|
13
|
+
import { authApi } from "../api/auth";
|
|
14
|
+
import { companiesApi } from "../api/companies";
|
|
15
|
+
import { agentsApi } from "../api/agents";
|
|
16
|
+
import { queryKeys } from "../lib/queryKeys";
|
|
17
|
+
import { getAgentOrderStorageKey, writeAgentOrder } from "../lib/agent-order";
|
|
18
|
+
import { getProjectOrderStorageKey, writeProjectOrder } from "../lib/project-order";
|
|
19
|
+
import { MarkdownBody } from "../components/MarkdownBody";
|
|
20
|
+
import { Button } from "@/components/ui/button";
|
|
21
|
+
import { EmptyState } from "../components/EmptyState";
|
|
22
|
+
import { AgentConfigForm } from "../components/AgentConfigForm";
|
|
23
|
+
import { cn } from "../lib/utils";
|
|
24
|
+
import {
|
|
25
|
+
ArrowRight,
|
|
26
|
+
Check,
|
|
27
|
+
ChevronRight,
|
|
28
|
+
Download,
|
|
29
|
+
Github,
|
|
30
|
+
Package,
|
|
31
|
+
Upload,
|
|
32
|
+
} from "lucide-react";
|
|
33
|
+
import { Field, adapterLabels } from "../components/agent-config-primitives";
|
|
34
|
+
import { defaultCreateValues } from "../components/agent-config-defaults";
|
|
35
|
+
import { getUIAdapter, listUIAdapters } from "../adapters";
|
|
36
|
+
import type { CreateConfigValues } from "@corporateai/adapter-utils";
|
|
37
|
+
import {
|
|
38
|
+
type FileTreeNode,
|
|
39
|
+
type FrontmatterData,
|
|
40
|
+
buildFileTree,
|
|
41
|
+
countFiles,
|
|
42
|
+
collectAllPaths,
|
|
43
|
+
parseFrontmatter,
|
|
44
|
+
FRONTMATTER_FIELD_LABELS,
|
|
45
|
+
PackageFileTree,
|
|
46
|
+
} from "../components/PackageFileTree";
|
|
47
|
+
import { readZipArchive } from "../lib/zip";
|
|
48
|
+
import { getPortableFileDataUrl, getPortableFileText, isPortableImageFile } from "../lib/portable-files";
|
|
49
|
+
|
|
50
|
+
// ── Import-specific helpers ───────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/** Build a map from file path → planned action (create/update/skip) using the manifest + plan */
|
|
53
|
+
function buildActionMap(preview: CompanyPortabilityPreviewResult): Map<string, string> {
|
|
54
|
+
const map = new Map<string, string>();
|
|
55
|
+
const manifest = preview.manifest;
|
|
56
|
+
|
|
57
|
+
for (const ap of preview.plan.agentPlans) {
|
|
58
|
+
const agent = manifest.agents.find((a) => a.slug === ap.slug);
|
|
59
|
+
if (agent) {
|
|
60
|
+
const path = ensureMarkdownPath(agent.path);
|
|
61
|
+
map.set(path, ap.action);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const pp of preview.plan.projectPlans) {
|
|
66
|
+
const project = manifest.projects.find((p) => p.slug === pp.slug);
|
|
67
|
+
if (project) {
|
|
68
|
+
const path = ensureMarkdownPath(project.path);
|
|
69
|
+
map.set(path, pp.action);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
for (const ip of preview.plan.issuePlans) {
|
|
74
|
+
const issue = manifest.issues.find((i) => i.slug === ip.slug);
|
|
75
|
+
if (issue) {
|
|
76
|
+
const path = ensureMarkdownPath(issue.path);
|
|
77
|
+
map.set(path, ip.action);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const skill of manifest.skills) {
|
|
82
|
+
const path = ensureMarkdownPath(skill.path);
|
|
83
|
+
map.set(path, "create");
|
|
84
|
+
// Also mark skill file inventory
|
|
85
|
+
for (const file of skill.fileInventory) {
|
|
86
|
+
if (preview.files[file.path]) {
|
|
87
|
+
map.set(file.path, "create");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Company file
|
|
93
|
+
if (manifest.company) {
|
|
94
|
+
const path = ensureMarkdownPath(manifest.company.path);
|
|
95
|
+
map.set(path, preview.plan.companyAction === "none" ? "skip" : preview.plan.companyAction);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return map;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ensureMarkdownPath(p: string): string {
|
|
102
|
+
return p.endsWith(".md") ? p : `${p}.md`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ACTION_COLORS: Record<string, string> = {
|
|
106
|
+
create: "text-emerald-500 border-emerald-500/30",
|
|
107
|
+
update: "text-amber-500 border-amber-500/30",
|
|
108
|
+
overwrite: "text-red-500 border-red-500/30",
|
|
109
|
+
replace: "text-red-500 border-red-500/30",
|
|
110
|
+
skip: "text-muted-foreground border-border",
|
|
111
|
+
none: "text-muted-foreground border-border",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function FrontmatterCard({ data }: { data: FrontmatterData }) {
|
|
115
|
+
return (
|
|
116
|
+
<div className="rounded-md border border-border bg-accent/20 px-4 py-3 mb-4">
|
|
117
|
+
<dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-sm">
|
|
118
|
+
{Object.entries(data).map(([key, value]) => (
|
|
119
|
+
<div key={key} className="contents">
|
|
120
|
+
<dt className="text-muted-foreground whitespace-nowrap py-0.5">
|
|
121
|
+
{FRONTMATTER_FIELD_LABELS[key] ?? key}
|
|
122
|
+
</dt>
|
|
123
|
+
<dd className="py-0.5">
|
|
124
|
+
{Array.isArray(value) ? (
|
|
125
|
+
<div className="flex flex-wrap gap-1.5">
|
|
126
|
+
{value.map((item) => (
|
|
127
|
+
<span
|
|
128
|
+
key={item}
|
|
129
|
+
className="inline-flex items-center rounded-md border border-border bg-background px-2 py-0.5 text-xs"
|
|
130
|
+
>
|
|
131
|
+
{item}
|
|
132
|
+
</span>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
) : (
|
|
136
|
+
<span>{value}</span>
|
|
137
|
+
)}
|
|
138
|
+
</dd>
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</dl>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Import file tree customization ───────────────────────────────────
|
|
147
|
+
|
|
148
|
+
function renderImportFileExtra(node: FileTreeNode, checked: boolean, renameMap: Map<string, string>) {
|
|
149
|
+
// Show rename indicator only on directories (folders), not individual files
|
|
150
|
+
const renamedTo = node.kind === "dir" ? renameMap.get(node.path) : undefined;
|
|
151
|
+
const actionBadge = node.action ? (
|
|
152
|
+
<span className={cn(
|
|
153
|
+
"shrink-0 rounded-full border px-2 py-0.5 text-[10px] uppercase tracking-wide",
|
|
154
|
+
ACTION_COLORS[node.action] ?? ACTION_COLORS.skip,
|
|
155
|
+
)}>
|
|
156
|
+
{checked ? node.action : "skip"}
|
|
157
|
+
</span>
|
|
158
|
+
) : null;
|
|
159
|
+
|
|
160
|
+
if (!actionBadge && !renamedTo) return null;
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<span className="inline-flex items-center gap-1.5 shrink-0">
|
|
164
|
+
{renamedTo && checked && (
|
|
165
|
+
<span className="text-[10px] text-cyan-500 font-mono truncate max-w-[7rem]" title={renamedTo}>
|
|
166
|
+
→ {renamedTo}
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
169
|
+
{actionBadge}
|
|
170
|
+
</span>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function importFileRowClassName(_node: FileTreeNode, checked: boolean) {
|
|
175
|
+
return !checked ? "opacity-50" : undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Preview pane ──────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
function ImportPreviewPane({
|
|
181
|
+
selectedFile,
|
|
182
|
+
content,
|
|
183
|
+
allFiles,
|
|
184
|
+
action,
|
|
185
|
+
renamedTo,
|
|
186
|
+
}: {
|
|
187
|
+
selectedFile: string | null;
|
|
188
|
+
content: CompanyPortabilityFileEntry | null;
|
|
189
|
+
allFiles: Record<string, CompanyPortabilityFileEntry>;
|
|
190
|
+
action: string | null;
|
|
191
|
+
renamedTo: string | null;
|
|
192
|
+
}) {
|
|
193
|
+
if (!selectedFile || content === null) {
|
|
194
|
+
return (
|
|
195
|
+
<EmptyState icon={Package} message="Select a file to preview its contents." />
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const textContent = getPortableFileText(content);
|
|
200
|
+
const isMarkdown = selectedFile.endsWith(".md") && textContent !== null;
|
|
201
|
+
const parsed = isMarkdown && textContent ? parseFrontmatter(textContent) : null;
|
|
202
|
+
const imageSrc = isPortableImageFile(selectedFile, content) ? getPortableFileDataUrl(selectedFile, content) : null;
|
|
203
|
+
const actionColor = action ? (ACTION_COLORS[action] ?? ACTION_COLORS.skip) : "";
|
|
204
|
+
|
|
205
|
+
// Resolve relative image paths within the import package
|
|
206
|
+
const resolveImageSrc = isMarkdown
|
|
207
|
+
? (src: string) => {
|
|
208
|
+
if (/^(?:https?:|data:)/i.test(src)) return null;
|
|
209
|
+
const dir = selectedFile.includes("/") ? selectedFile.slice(0, selectedFile.lastIndexOf("/") + 1) : "";
|
|
210
|
+
const resolved = dir + src;
|
|
211
|
+
const entry = allFiles[resolved] ?? allFiles[src];
|
|
212
|
+
if (!entry) return null;
|
|
213
|
+
return getPortableFileDataUrl(resolved in allFiles ? resolved : src, entry);
|
|
214
|
+
}
|
|
215
|
+
: undefined;
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div className="min-w-0">
|
|
219
|
+
<div className="border-b border-border px-5 py-3">
|
|
220
|
+
<div className="flex items-center justify-between gap-3">
|
|
221
|
+
<div className="min-w-0 flex items-center gap-2">
|
|
222
|
+
<span className="truncate font-mono text-sm">{selectedFile}</span>
|
|
223
|
+
{renamedTo && (
|
|
224
|
+
<span className="shrink-0 font-mono text-sm text-cyan-500">
|
|
225
|
+
→ {renamedTo}
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
{action && (
|
|
230
|
+
<span className={cn(
|
|
231
|
+
"shrink-0 rounded-full border px-2 py-0.5 text-xs uppercase tracking-wide",
|
|
232
|
+
actionColor,
|
|
233
|
+
)}>
|
|
234
|
+
{action}
|
|
235
|
+
</span>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div className="min-h-[560px] px-5 py-5">
|
|
240
|
+
{parsed ? (
|
|
241
|
+
<>
|
|
242
|
+
<FrontmatterCard data={parsed.data} />
|
|
243
|
+
{parsed.body.trim() && <MarkdownBody resolveImageSrc={resolveImageSrc}>{parsed.body}</MarkdownBody>}
|
|
244
|
+
</>
|
|
245
|
+
) : isMarkdown ? (
|
|
246
|
+
<MarkdownBody resolveImageSrc={resolveImageSrc}>{textContent ?? ""}</MarkdownBody>
|
|
247
|
+
) : imageSrc ? (
|
|
248
|
+
<div className="flex min-h-[520px] items-center justify-center rounded-lg border border-border bg-accent/10 p-6">
|
|
249
|
+
<img src={imageSrc} alt={selectedFile} className="max-h-[480px] max-w-full object-contain" />
|
|
250
|
+
</div>
|
|
251
|
+
) : textContent !== null ? (
|
|
252
|
+
<pre className="overflow-x-auto whitespace-pre-wrap break-words border-0 bg-transparent p-0 font-mono text-sm text-foreground">
|
|
253
|
+
<code>{textContent}</code>
|
|
254
|
+
</pre>
|
|
255
|
+
) : (
|
|
256
|
+
<div className="rounded-lg border border-border bg-accent/10 px-4 py-3 text-sm text-muted-foreground">
|
|
257
|
+
Binary asset preview is not available for this file type.
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── Conflict item type ───────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
interface ConflictItem {
|
|
268
|
+
slug: string;
|
|
269
|
+
kind: "agent" | "project" | "issue" | "skill";
|
|
270
|
+
originalName: string;
|
|
271
|
+
plannedName: string;
|
|
272
|
+
filePath: string | null;
|
|
273
|
+
action: "rename" | "update";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function buildConflictList(
|
|
277
|
+
preview: CompanyPortabilityPreviewResult,
|
|
278
|
+
): ConflictItem[] {
|
|
279
|
+
const conflicts: ConflictItem[] = [];
|
|
280
|
+
const manifest = preview.manifest;
|
|
281
|
+
|
|
282
|
+
// Agents with collisions
|
|
283
|
+
for (const ap of preview.plan.agentPlans) {
|
|
284
|
+
if (ap.existingAgentId) {
|
|
285
|
+
const agent = manifest.agents.find((a) => a.slug === ap.slug);
|
|
286
|
+
conflicts.push({
|
|
287
|
+
slug: ap.slug,
|
|
288
|
+
kind: "agent",
|
|
289
|
+
originalName: agent?.name ?? ap.slug,
|
|
290
|
+
plannedName: ap.plannedName,
|
|
291
|
+
filePath: agent ? ensureMarkdownPath(agent.path) : null,
|
|
292
|
+
action: ap.action === "update" ? "update" : "rename",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Projects with collisions
|
|
298
|
+
for (const pp of preview.plan.projectPlans) {
|
|
299
|
+
if (pp.existingProjectId) {
|
|
300
|
+
const project = manifest.projects.find((p) => p.slug === pp.slug);
|
|
301
|
+
conflicts.push({
|
|
302
|
+
slug: pp.slug,
|
|
303
|
+
kind: "project",
|
|
304
|
+
originalName: project?.name ?? pp.slug,
|
|
305
|
+
plannedName: pp.plannedName,
|
|
306
|
+
filePath: project ? ensureMarkdownPath(project.path) : null,
|
|
307
|
+
action: pp.action === "update" ? "update" : "rename",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return conflicts;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Extract a prefix from the import source URL or uploaded zip package name */
|
|
316
|
+
function deriveSourcePrefix(
|
|
317
|
+
sourceMode: string,
|
|
318
|
+
importUrl: string,
|
|
319
|
+
localPackageName: string | null,
|
|
320
|
+
localRootPath: string | null,
|
|
321
|
+
): string | null {
|
|
322
|
+
if (sourceMode === "local") {
|
|
323
|
+
if (localRootPath) return localRootPath.split("/").pop() ?? null;
|
|
324
|
+
if (!localPackageName) return null;
|
|
325
|
+
return localPackageName.replace(/\.zip$/i, "") || null;
|
|
326
|
+
}
|
|
327
|
+
if (sourceMode === "github") {
|
|
328
|
+
const url = importUrl.trim();
|
|
329
|
+
if (!url) return null;
|
|
330
|
+
try {
|
|
331
|
+
const pathname = new URL(url.startsWith("http") ? url : `https://${url}`).pathname;
|
|
332
|
+
// For github URLs like /owner/repo/tree/branch/path - take last segment
|
|
333
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
334
|
+
return segments.length > 0 ? segments[segments.length - 1] : null;
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Generate a prefix-based rename: e.g. "gstack" + "CEO" → "gstack-CEO" */
|
|
343
|
+
function prefixedName(prefix: string | null, originalName: string): string {
|
|
344
|
+
if (!prefix) return originalName;
|
|
345
|
+
return `${prefix}-${originalName}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function applyImportedSidebarOrder(
|
|
349
|
+
preview: CompanyPortabilityPreviewResult | null,
|
|
350
|
+
result: {
|
|
351
|
+
company: { id: string };
|
|
352
|
+
agents: Array<{ slug: string; id: string | null }>;
|
|
353
|
+
projects: Array<{ slug: string; id: string | null }>;
|
|
354
|
+
},
|
|
355
|
+
userId: string | null | undefined,
|
|
356
|
+
) {
|
|
357
|
+
const sidebar = preview?.manifest.sidebar;
|
|
358
|
+
if (!sidebar) return;
|
|
359
|
+
if (!userId?.trim()) return;
|
|
360
|
+
|
|
361
|
+
const agentIdBySlug = new Map(
|
|
362
|
+
result.agents
|
|
363
|
+
.filter((agent): agent is { slug: string; id: string } => typeof agent.id === "string" && agent.id.length > 0)
|
|
364
|
+
.map((agent) => [agent.slug, agent.id]),
|
|
365
|
+
);
|
|
366
|
+
const projectIdBySlug = new Map(
|
|
367
|
+
result.projects
|
|
368
|
+
.filter((project): project is { slug: string; id: string } => typeof project.id === "string" && project.id.length > 0)
|
|
369
|
+
.map((project) => [project.slug, project.id]),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
const orderedAgentIds = sidebar.agents
|
|
373
|
+
.map((slug) => agentIdBySlug.get(slug))
|
|
374
|
+
.filter((id): id is string => Boolean(id));
|
|
375
|
+
const orderedProjectIds = sidebar.projects
|
|
376
|
+
.map((slug) => projectIdBySlug.get(slug))
|
|
377
|
+
.filter((id): id is string => Boolean(id));
|
|
378
|
+
|
|
379
|
+
if (orderedAgentIds.length > 0) {
|
|
380
|
+
writeAgentOrder(getAgentOrderStorageKey(result.company.id, userId), orderedAgentIds);
|
|
381
|
+
}
|
|
382
|
+
if (orderedProjectIds.length > 0) {
|
|
383
|
+
writeProjectOrder(getProjectOrderStorageKey(result.company.id, userId), orderedProjectIds);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ── Conflict resolution UI ───────────────────────────────────────────
|
|
388
|
+
|
|
389
|
+
function ConflictResolutionList({
|
|
390
|
+
conflicts,
|
|
391
|
+
nameOverrides,
|
|
392
|
+
skippedSlugs,
|
|
393
|
+
confirmedSlugs,
|
|
394
|
+
onRename,
|
|
395
|
+
onToggleSkip,
|
|
396
|
+
onToggleConfirm,
|
|
397
|
+
}: {
|
|
398
|
+
conflicts: ConflictItem[];
|
|
399
|
+
nameOverrides: Record<string, string>;
|
|
400
|
+
skippedSlugs: Set<string>;
|
|
401
|
+
confirmedSlugs: Set<string>;
|
|
402
|
+
onRename: (slug: string, newName: string) => void;
|
|
403
|
+
onToggleSkip: (slug: string, filePath: string | null) => void;
|
|
404
|
+
onToggleConfirm: (slug: string) => void;
|
|
405
|
+
}) {
|
|
406
|
+
if (conflicts.length === 0) return null;
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<div className="mx-5 mt-3">
|
|
410
|
+
<div className="rounded-md border border-border">
|
|
411
|
+
<div className="flex items-center gap-2 border-b border-border px-4 py-2.5">
|
|
412
|
+
<h3 className="text-sm font-medium">
|
|
413
|
+
Renames
|
|
414
|
+
</h3>
|
|
415
|
+
<span className="text-xs text-muted-foreground">
|
|
416
|
+
{conflicts.length} item{conflicts.length === 1 ? "" : "s"}
|
|
417
|
+
</span>
|
|
418
|
+
</div>
|
|
419
|
+
<div className="divide-y divide-border">
|
|
420
|
+
{conflicts.map((item) => {
|
|
421
|
+
const isSkipped = skippedSlugs.has(item.slug);
|
|
422
|
+
const isConfirmed = confirmedSlugs.has(item.slug);
|
|
423
|
+
const currentName = nameOverrides[item.slug] ?? item.plannedName;
|
|
424
|
+
return (
|
|
425
|
+
<div
|
|
426
|
+
key={item.slug}
|
|
427
|
+
className={cn(
|
|
428
|
+
"flex items-center gap-3 px-4 py-2.5 text-sm",
|
|
429
|
+
isSkipped && "opacity-40",
|
|
430
|
+
isConfirmed && !isSkipped && "bg-emerald-500/5",
|
|
431
|
+
)}
|
|
432
|
+
>
|
|
433
|
+
{/* Skip button on the left */}
|
|
434
|
+
<button
|
|
435
|
+
type="button"
|
|
436
|
+
className={cn(
|
|
437
|
+
"shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors",
|
|
438
|
+
isSkipped
|
|
439
|
+
? "border-foreground bg-accent text-foreground"
|
|
440
|
+
: "border-border text-muted-foreground hover:bg-accent/50",
|
|
441
|
+
)}
|
|
442
|
+
onClick={() => onToggleSkip(item.slug, item.filePath)}
|
|
443
|
+
>
|
|
444
|
+
{isSkipped ? "skipped" : "skip"}
|
|
445
|
+
</button>
|
|
446
|
+
|
|
447
|
+
<span className={cn(
|
|
448
|
+
"shrink-0 rounded-full border px-2 py-0.5 text-[10px] uppercase tracking-wide",
|
|
449
|
+
isSkipped
|
|
450
|
+
? "text-muted-foreground border-border"
|
|
451
|
+
: isConfirmed
|
|
452
|
+
? "text-emerald-500 border-emerald-500/30"
|
|
453
|
+
: "text-amber-500 border-amber-500/30",
|
|
454
|
+
)}>
|
|
455
|
+
{item.kind}
|
|
456
|
+
</span>
|
|
457
|
+
|
|
458
|
+
<span className={cn(
|
|
459
|
+
"shrink-0 font-mono text-xs",
|
|
460
|
+
isSkipped ? "text-muted-foreground line-through" : "text-muted-foreground",
|
|
461
|
+
)}>
|
|
462
|
+
{item.originalName}
|
|
463
|
+
</span>
|
|
464
|
+
|
|
465
|
+
{!isSkipped && (
|
|
466
|
+
<>
|
|
467
|
+
<ArrowRight className="h-3 w-3 shrink-0 text-muted-foreground" />
|
|
468
|
+
{isConfirmed ? (
|
|
469
|
+
<span className="min-w-0 flex-1 font-mono text-xs text-emerald-500">
|
|
470
|
+
{currentName}
|
|
471
|
+
</span>
|
|
472
|
+
) : (
|
|
473
|
+
<input
|
|
474
|
+
className="min-w-0 flex-1 rounded-md border border-border bg-transparent px-2 py-1 font-mono text-xs outline-none focus:border-foreground"
|
|
475
|
+
value={currentName}
|
|
476
|
+
onChange={(e) => onRename(item.slug, e.target.value)}
|
|
477
|
+
/>
|
|
478
|
+
)}
|
|
479
|
+
</>
|
|
480
|
+
)}
|
|
481
|
+
|
|
482
|
+
{/* Confirm rename button on the right */}
|
|
483
|
+
{!isSkipped && (
|
|
484
|
+
<button
|
|
485
|
+
type="button"
|
|
486
|
+
className={cn(
|
|
487
|
+
"ml-auto shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors inline-flex items-center gap-1.5",
|
|
488
|
+
isConfirmed
|
|
489
|
+
? "border-emerald-500/30 bg-emerald-500/10 text-emerald-500"
|
|
490
|
+
: "border-border text-muted-foreground hover:bg-accent/50",
|
|
491
|
+
)}
|
|
492
|
+
onClick={() => onToggleConfirm(item.slug)}
|
|
493
|
+
>
|
|
494
|
+
{isConfirmed ? (
|
|
495
|
+
<>
|
|
496
|
+
<Check className="h-3 w-3" />
|
|
497
|
+
confirmed
|
|
498
|
+
</>
|
|
499
|
+
) : (
|
|
500
|
+
"confirm rename"
|
|
501
|
+
)}
|
|
502
|
+
</button>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
})}
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ── Adapter type options for import ───────────────────────────────────
|
|
514
|
+
|
|
515
|
+
const IMPORT_ADAPTER_OPTIONS: { value: string; label: string }[] = listUIAdapters().map((adapter) => ({
|
|
516
|
+
value: adapter.type,
|
|
517
|
+
label: adapterLabels[adapter.type] ?? adapter.label,
|
|
518
|
+
}));
|
|
519
|
+
|
|
520
|
+
// ── Adapter picker for imported agents ───────────────────────────────
|
|
521
|
+
|
|
522
|
+
interface AdapterPickerItem {
|
|
523
|
+
slug: string;
|
|
524
|
+
name: string;
|
|
525
|
+
adapterType: string;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function AdapterPickerList({
|
|
529
|
+
agents,
|
|
530
|
+
adapterOverrides,
|
|
531
|
+
expandedSlugs,
|
|
532
|
+
configValues,
|
|
533
|
+
onChangeAdapter,
|
|
534
|
+
onToggleExpand,
|
|
535
|
+
onChangeConfig,
|
|
536
|
+
}: {
|
|
537
|
+
agents: AdapterPickerItem[];
|
|
538
|
+
adapterOverrides: Record<string, string>;
|
|
539
|
+
expandedSlugs: Set<string>;
|
|
540
|
+
configValues: Record<string, CreateConfigValues>;
|
|
541
|
+
onChangeAdapter: (slug: string, adapterType: string) => void;
|
|
542
|
+
onToggleExpand: (slug: string) => void;
|
|
543
|
+
onChangeConfig: (slug: string, patch: Partial<CreateConfigValues>) => void;
|
|
544
|
+
}) {
|
|
545
|
+
if (agents.length === 0) return null;
|
|
546
|
+
|
|
547
|
+
return (
|
|
548
|
+
<div className="mx-5 mt-3">
|
|
549
|
+
<div className="rounded-md border border-border">
|
|
550
|
+
<div className="flex items-center gap-2 border-b border-border px-4 py-2.5">
|
|
551
|
+
<h3 className="text-sm font-medium">Adapters</h3>
|
|
552
|
+
<span className="text-xs text-muted-foreground">
|
|
553
|
+
{agents.length} agent{agents.length === 1 ? "" : "s"}
|
|
554
|
+
</span>
|
|
555
|
+
</div>
|
|
556
|
+
<div className="divide-y divide-border">
|
|
557
|
+
{agents.map((agent) => {
|
|
558
|
+
const selectedType = adapterOverrides[agent.slug] ?? agent.adapterType;
|
|
559
|
+
const isExpanded = expandedSlugs.has(agent.slug);
|
|
560
|
+
const vals = configValues[agent.slug] ?? { ...defaultCreateValues, adapterType: selectedType };
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<div key={agent.slug}>
|
|
564
|
+
<div className="flex items-center gap-3 px-4 py-2.5 text-sm">
|
|
565
|
+
<span className={cn(
|
|
566
|
+
"shrink-0 rounded-full border px-2 py-0.5 text-[10px] uppercase tracking-wide",
|
|
567
|
+
"text-blue-500 border-blue-500/30",
|
|
568
|
+
)}>
|
|
569
|
+
agent
|
|
570
|
+
</span>
|
|
571
|
+
<span className="shrink-0 font-mono text-xs text-muted-foreground">
|
|
572
|
+
{agent.name}
|
|
573
|
+
</span>
|
|
574
|
+
<ArrowRight className="h-3 w-3 shrink-0 text-muted-foreground" />
|
|
575
|
+
<select
|
|
576
|
+
className="min-w-0 flex-1 rounded-md border border-border bg-transparent px-2 py-1 text-xs outline-none focus:border-foreground"
|
|
577
|
+
value={selectedType}
|
|
578
|
+
onChange={(e) => onChangeAdapter(agent.slug, e.target.value)}
|
|
579
|
+
>
|
|
580
|
+
{IMPORT_ADAPTER_OPTIONS.map((opt) => (
|
|
581
|
+
<option key={opt.value} value={opt.value}>
|
|
582
|
+
{opt.label}
|
|
583
|
+
</option>
|
|
584
|
+
))}
|
|
585
|
+
</select>
|
|
586
|
+
<button
|
|
587
|
+
type="button"
|
|
588
|
+
className={cn(
|
|
589
|
+
"ml-auto shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors inline-flex items-center gap-1.5",
|
|
590
|
+
isExpanded
|
|
591
|
+
? "border-foreground bg-accent text-foreground"
|
|
592
|
+
: "border-border text-muted-foreground hover:bg-accent/50",
|
|
593
|
+
)}
|
|
594
|
+
onClick={() => onToggleExpand(agent.slug)}
|
|
595
|
+
>
|
|
596
|
+
<ChevronRight className={cn("h-3 w-3 transition-transform", isExpanded && "rotate-90")} />
|
|
597
|
+
configure adapter
|
|
598
|
+
</button>
|
|
599
|
+
</div>
|
|
600
|
+
{isExpanded && (
|
|
601
|
+
<div className="border-t border-border bg-accent/10 px-4 py-3 space-y-3">
|
|
602
|
+
<AgentConfigForm
|
|
603
|
+
mode="create"
|
|
604
|
+
values={vals}
|
|
605
|
+
onChange={(patch) => onChangeConfig(agent.slug, patch)}
|
|
606
|
+
showAdapterTypeField={false}
|
|
607
|
+
showAdapterTestEnvironmentButton={false}
|
|
608
|
+
showCreateRunPolicySection={false}
|
|
609
|
+
hideInstructionsFile
|
|
610
|
+
sectionLayout="cards"
|
|
611
|
+
/>
|
|
612
|
+
</div>
|
|
613
|
+
)}
|
|
614
|
+
</div>
|
|
615
|
+
);
|
|
616
|
+
})}
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</div>
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
624
|
+
|
|
625
|
+
async function readLocalPackageZip(file: File): Promise<{
|
|
626
|
+
name: string;
|
|
627
|
+
rootPath: string | null;
|
|
628
|
+
files: Record<string, CompanyPortabilityFileEntry>;
|
|
629
|
+
}> {
|
|
630
|
+
if (!/\.zip$/i.test(file.name)) {
|
|
631
|
+
throw new Error("Select a .zip company package.");
|
|
632
|
+
}
|
|
633
|
+
const archive = await readZipArchive(await file.arrayBuffer());
|
|
634
|
+
if (Object.keys(archive.files).length === 0) {
|
|
635
|
+
throw new Error("No package files were found in the selected zip archive.");
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
name: file.name,
|
|
639
|
+
rootPath: archive.rootPath,
|
|
640
|
+
files: archive.files,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ── Main page ─────────────────────────────────────────────────────────
|
|
645
|
+
|
|
646
|
+
export function CompanyImport() {
|
|
647
|
+
const {
|
|
648
|
+
selectedCompanyId,
|
|
649
|
+
selectedCompany,
|
|
650
|
+
setSelectedCompanyId,
|
|
651
|
+
} = useCompany();
|
|
652
|
+
const { setBreadcrumbs } = useBreadcrumbs();
|
|
653
|
+
const { pushToast } = useToast();
|
|
654
|
+
const queryClient = useQueryClient();
|
|
655
|
+
const packageInputRef = useRef<HTMLInputElement | null>(null);
|
|
656
|
+
const { data: session } = useQuery({
|
|
657
|
+
queryKey: queryKeys.auth.session,
|
|
658
|
+
queryFn: () => authApi.getSession(),
|
|
659
|
+
});
|
|
660
|
+
const currentUserId = session?.user?.id ?? session?.session?.userId ?? null;
|
|
661
|
+
|
|
662
|
+
// Source state
|
|
663
|
+
const [sourceMode, setSourceMode] = useState<"github" | "local">("github");
|
|
664
|
+
const [importUrl, setImportUrl] = useState("");
|
|
665
|
+
const [localPackage, setLocalPackage] = useState<{
|
|
666
|
+
name: string;
|
|
667
|
+
rootPath: string | null;
|
|
668
|
+
files: Record<string, CompanyPortabilityFileEntry>;
|
|
669
|
+
} | null>(null);
|
|
670
|
+
|
|
671
|
+
// Target state
|
|
672
|
+
const [targetMode, setTargetMode] = useState<"existing" | "new">("new");
|
|
673
|
+
const [newCompanyName, setNewCompanyName] = useState("");
|
|
674
|
+
|
|
675
|
+
// Preview state
|
|
676
|
+
const [importPreview, setImportPreview] =
|
|
677
|
+
useState<CompanyPortabilityPreviewResult | null>(null);
|
|
678
|
+
const [selectedFile, setSelectedFile] = useState<string | null>(null);
|
|
679
|
+
const [expandedDirs, setExpandedDirs] = useState<Set<string>>(new Set());
|
|
680
|
+
const [checkedFiles, setCheckedFiles] = useState<Set<string>>(new Set());
|
|
681
|
+
|
|
682
|
+
// Conflict resolution state
|
|
683
|
+
const [nameOverrides, setNameOverrides] = useState<Record<string, string>>({});
|
|
684
|
+
const [skippedSlugs, setSkippedSlugs] = useState<Set<string>>(new Set());
|
|
685
|
+
const [confirmedSlugs, setConfirmedSlugs] = useState<Set<string>>(new Set());
|
|
686
|
+
const [collisionStrategy, setCollisionStrategy] = useState<CompanyPortabilityCollisionStrategy>("rename");
|
|
687
|
+
|
|
688
|
+
// Adapter override state
|
|
689
|
+
const [adapterOverrides, setAdapterOverrides] = useState<Record<string, string>>({});
|
|
690
|
+
const [adapterExpandedSlugs, setAdapterExpandedSlugs] = useState<Set<string>>(new Set());
|
|
691
|
+
const [adapterConfigValues, setAdapterConfigValues] = useState<Record<string, CreateConfigValues>>({});
|
|
692
|
+
|
|
693
|
+
// Fetch current company agents to find CEO adapter type
|
|
694
|
+
const { data: companyAgents } = useQuery({
|
|
695
|
+
queryKey: selectedCompanyId ? queryKeys.agents.list(selectedCompanyId) : ["agents", "none"],
|
|
696
|
+
queryFn: () => agentsApi.list(selectedCompanyId!),
|
|
697
|
+
enabled: Boolean(selectedCompanyId),
|
|
698
|
+
});
|
|
699
|
+
const ceoAdapterType = useMemo(() => {
|
|
700
|
+
if (!companyAgents) return "claude_local";
|
|
701
|
+
const ceo = companyAgents.find((a) => a.role === "ceo");
|
|
702
|
+
return ceo?.adapterType ?? "claude_local";
|
|
703
|
+
}, [companyAgents]);
|
|
704
|
+
|
|
705
|
+
const localZipHelpText =
|
|
706
|
+
"Upload a .zip exported directly from Corporate. Re-zipped archives created by Finder, Explorer, or other zip tools may not import correctly.";
|
|
707
|
+
|
|
708
|
+
useEffect(() => {
|
|
709
|
+
setBreadcrumbs([
|
|
710
|
+
{ label: "Org Chart", href: "/org" },
|
|
711
|
+
{ label: "Import" },
|
|
712
|
+
]);
|
|
713
|
+
}, [setBreadcrumbs]);
|
|
714
|
+
|
|
715
|
+
function buildSource(): CompanyPortabilitySource | null {
|
|
716
|
+
if (sourceMode === "local") {
|
|
717
|
+
if (!localPackage) return null;
|
|
718
|
+
return { type: "inline", rootPath: localPackage.rootPath, files: localPackage.files };
|
|
719
|
+
}
|
|
720
|
+
const url = importUrl.trim();
|
|
721
|
+
if (!url) return null;
|
|
722
|
+
return { type: "github", url };
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Preview mutation
|
|
726
|
+
const previewMutation = useMutation({
|
|
727
|
+
mutationFn: () => {
|
|
728
|
+
const source = buildSource();
|
|
729
|
+
if (!source) throw new Error("No source configured.");
|
|
730
|
+
return companiesApi.importPreview({
|
|
731
|
+
source,
|
|
732
|
+
include: { company: true, agents: true, projects: true, issues: true },
|
|
733
|
+
target:
|
|
734
|
+
targetMode === "new"
|
|
735
|
+
? { mode: "new_company", newCompanyName: newCompanyName || null }
|
|
736
|
+
: { mode: "existing_company", companyId: selectedCompanyId! },
|
|
737
|
+
collisionStrategy,
|
|
738
|
+
});
|
|
739
|
+
},
|
|
740
|
+
onSuccess: (result) => {
|
|
741
|
+
setImportPreview(result);
|
|
742
|
+
|
|
743
|
+
// Build conflicts and set default name overrides with prefix
|
|
744
|
+
const conflicts = buildConflictList(result);
|
|
745
|
+
const prefix = deriveSourcePrefix(
|
|
746
|
+
sourceMode,
|
|
747
|
+
importUrl,
|
|
748
|
+
localPackage?.name ?? null,
|
|
749
|
+
localPackage?.rootPath ?? null,
|
|
750
|
+
);
|
|
751
|
+
const defaultOverrides: Record<string, string> = {};
|
|
752
|
+
|
|
753
|
+
for (const c of conflicts) {
|
|
754
|
+
if (c.action === "rename" && prefix) {
|
|
755
|
+
// Use prefix-based default rename
|
|
756
|
+
defaultOverrides[c.slug] = prefixedName(prefix, c.originalName);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
setNameOverrides(defaultOverrides);
|
|
760
|
+
setSkippedSlugs(new Set());
|
|
761
|
+
setConfirmedSlugs(new Set());
|
|
762
|
+
|
|
763
|
+
// Initialize adapter overrides — default all agents to the CEO's adapter type
|
|
764
|
+
const defaultAdapters: Record<string, string> = {};
|
|
765
|
+
for (const agent of result.manifest.agents) {
|
|
766
|
+
defaultAdapters[agent.slug] = ceoAdapterType;
|
|
767
|
+
}
|
|
768
|
+
setAdapterOverrides(defaultAdapters);
|
|
769
|
+
setAdapterExpandedSlugs(new Set());
|
|
770
|
+
setAdapterConfigValues({});
|
|
771
|
+
|
|
772
|
+
// Check all files by default, then uncheck COMPANY.md for existing company
|
|
773
|
+
const allFiles = new Set(Object.keys(result.files));
|
|
774
|
+
if (targetMode === "existing" && result.manifest.company && result.plan.companyAction === "update") {
|
|
775
|
+
const companyPath = ensureMarkdownPath(result.manifest.company.path);
|
|
776
|
+
allFiles.delete(companyPath);
|
|
777
|
+
}
|
|
778
|
+
setCheckedFiles(allFiles);
|
|
779
|
+
|
|
780
|
+
// Expand top-level dirs + all ancestor dirs of files with conflicts (update action)
|
|
781
|
+
const am = buildActionMap(result);
|
|
782
|
+
const tree = buildFileTree(result.files, am);
|
|
783
|
+
const dirsToExpand = new Set<string>();
|
|
784
|
+
for (const node of tree) {
|
|
785
|
+
if (node.kind === "dir") dirsToExpand.add(node.path);
|
|
786
|
+
}
|
|
787
|
+
// Auto-expand directories containing conflicting files so they're visible
|
|
788
|
+
for (const [filePath, action] of am) {
|
|
789
|
+
if (action === "update") {
|
|
790
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
791
|
+
let current = "";
|
|
792
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
793
|
+
current = current ? `${current}/${segments[i]}` : segments[i];
|
|
794
|
+
dirsToExpand.add(current);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
setExpandedDirs(dirsToExpand);
|
|
799
|
+
// Select first file
|
|
800
|
+
const firstFile = Object.keys(result.files)[0];
|
|
801
|
+
if (firstFile) setSelectedFile(firstFile);
|
|
802
|
+
},
|
|
803
|
+
onError: (err) => {
|
|
804
|
+
pushToast({
|
|
805
|
+
tone: "error",
|
|
806
|
+
title: "Preview failed",
|
|
807
|
+
body: err instanceof Error ? err.message : "Failed to preview import.",
|
|
808
|
+
});
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
// Build the final nameOverrides to send (only overrides that differ from plannedName)
|
|
813
|
+
function buildFinalNameOverrides(): Record<string, string> | undefined {
|
|
814
|
+
if (!importPreview) return undefined;
|
|
815
|
+
const overrides: Record<string, string> = {};
|
|
816
|
+
for (const [slug, name] of Object.entries(nameOverrides)) {
|
|
817
|
+
if (name.trim()) {
|
|
818
|
+
overrides[slug] = name.trim();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return Object.keys(overrides).length > 0 ? overrides : undefined;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function buildSelectedFiles(): string[] | undefined {
|
|
825
|
+
const selected = Array.from(checkedFiles).sort();
|
|
826
|
+
return selected.length > 0 ? selected : undefined;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Apply mutation
|
|
830
|
+
const importMutation = useMutation({
|
|
831
|
+
mutationFn: () => {
|
|
832
|
+
const source = buildSource();
|
|
833
|
+
if (!source) throw new Error("No source configured.");
|
|
834
|
+
return companiesApi.importBundle({
|
|
835
|
+
source,
|
|
836
|
+
include: { company: true, agents: true, projects: true, issues: true },
|
|
837
|
+
target:
|
|
838
|
+
targetMode === "new"
|
|
839
|
+
? { mode: "new_company", newCompanyName: newCompanyName || null }
|
|
840
|
+
: { mode: "existing_company", companyId: selectedCompanyId! },
|
|
841
|
+
collisionStrategy,
|
|
842
|
+
nameOverrides: buildFinalNameOverrides(),
|
|
843
|
+
selectedFiles: buildSelectedFiles(),
|
|
844
|
+
adapterOverrides: buildFinalAdapterOverrides(),
|
|
845
|
+
});
|
|
846
|
+
},
|
|
847
|
+
onSuccess: async (result) => {
|
|
848
|
+
await queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
|
849
|
+
const importedCompany = await companiesApi.get(result.company.id);
|
|
850
|
+
const refreshedSession = currentUserId
|
|
851
|
+
? null
|
|
852
|
+
: await queryClient.fetchQuery({
|
|
853
|
+
queryKey: queryKeys.auth.session,
|
|
854
|
+
queryFn: () => authApi.getSession(),
|
|
855
|
+
});
|
|
856
|
+
const sidebarOrderUserId =
|
|
857
|
+
currentUserId
|
|
858
|
+
?? refreshedSession?.user?.id
|
|
859
|
+
?? refreshedSession?.session?.userId
|
|
860
|
+
?? null;
|
|
861
|
+
applyImportedSidebarOrder(importPreview, result, sidebarOrderUserId);
|
|
862
|
+
setSelectedCompanyId(importedCompany.id);
|
|
863
|
+
pushToast({
|
|
864
|
+
tone: "success",
|
|
865
|
+
title: "Import complete",
|
|
866
|
+
body: `${result.company.name}: ${result.agents.length} agent${result.agents.length === 1 ? "" : "s"} processed.`,
|
|
867
|
+
});
|
|
868
|
+
// Force a fresh dashboard load so newly imported agents are immediately visible.
|
|
869
|
+
window.location.assign(`/${importedCompany.issuePrefix}/dashboard`);
|
|
870
|
+
},
|
|
871
|
+
onError: (err) => {
|
|
872
|
+
pushToast({
|
|
873
|
+
tone: "error",
|
|
874
|
+
title: "Import failed",
|
|
875
|
+
body: err instanceof Error ? err.message : "Failed to apply import.",
|
|
876
|
+
});
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
async function handleChooseLocalPackage(e: ChangeEvent<HTMLInputElement>) {
|
|
881
|
+
const fileList = e.target.files;
|
|
882
|
+
if (!fileList || fileList.length === 0) return;
|
|
883
|
+
try {
|
|
884
|
+
const pkg = await readLocalPackageZip(fileList[0]!);
|
|
885
|
+
setLocalPackage(pkg);
|
|
886
|
+
setImportPreview(null);
|
|
887
|
+
} catch (err) {
|
|
888
|
+
pushToast({
|
|
889
|
+
tone: "error",
|
|
890
|
+
title: "Package read failed",
|
|
891
|
+
body: err instanceof Error ? err.message : "Failed to read folder.",
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const actionMap = useMemo(
|
|
897
|
+
() => (importPreview ? buildActionMap(importPreview) : new Map<string, string>()),
|
|
898
|
+
[importPreview],
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
const tree = useMemo(
|
|
902
|
+
() => (importPreview ? buildFileTree(importPreview.files, actionMap) : []),
|
|
903
|
+
[importPreview, actionMap],
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
const conflicts = useMemo(
|
|
907
|
+
() => (importPreview ? buildConflictList(importPreview) : []),
|
|
908
|
+
[importPreview],
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
// Map directory paths → planned rename name for display in the file tree
|
|
912
|
+
// Also maps file paths for use in the preview header
|
|
913
|
+
const renameMap = useMemo(() => {
|
|
914
|
+
const map = new Map<string, string>();
|
|
915
|
+
if (!importPreview) return map;
|
|
916
|
+
for (const c of conflicts) {
|
|
917
|
+
if (!c.filePath) continue;
|
|
918
|
+
const isSkipped = skippedSlugs.has(c.slug);
|
|
919
|
+
if (isSkipped) continue;
|
|
920
|
+
const renamedTo = nameOverrides[c.slug] ?? c.plannedName;
|
|
921
|
+
if (renamedTo === c.originalName) continue;
|
|
922
|
+
// Map the parent directory (e.g. agents/ceo → gstack-ceo) for the file tree
|
|
923
|
+
const parentDir = c.filePath.split("/").slice(0, -1).join("/");
|
|
924
|
+
if (parentDir) map.set(parentDir, renamedTo);
|
|
925
|
+
// Map the file path too — used by the preview header, not shown in tree
|
|
926
|
+
map.set(c.filePath, renamedTo);
|
|
927
|
+
}
|
|
928
|
+
return map;
|
|
929
|
+
}, [importPreview, conflicts, nameOverrides, skippedSlugs]);
|
|
930
|
+
|
|
931
|
+
const totalFiles = useMemo(() => countFiles(tree), [tree]);
|
|
932
|
+
const selectedCount = checkedFiles.size;
|
|
933
|
+
|
|
934
|
+
function handleToggleDir(path: string) {
|
|
935
|
+
setExpandedDirs((prev) => {
|
|
936
|
+
const next = new Set(prev);
|
|
937
|
+
if (next.has(path)) next.delete(path);
|
|
938
|
+
else next.add(path);
|
|
939
|
+
return next;
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function handleToggleCheck(path: string, kind: "file" | "dir") {
|
|
944
|
+
if (!importPreview) return;
|
|
945
|
+
setCheckedFiles((prev) => {
|
|
946
|
+
const next = new Set(prev);
|
|
947
|
+
if (kind === "file") {
|
|
948
|
+
if (next.has(path)) next.delete(path);
|
|
949
|
+
else next.add(path);
|
|
950
|
+
} else {
|
|
951
|
+
const findNode = (nodes: FileTreeNode[], target: string): FileTreeNode | null => {
|
|
952
|
+
for (const n of nodes) {
|
|
953
|
+
if (n.path === target) return n;
|
|
954
|
+
const found = findNode(n.children, target);
|
|
955
|
+
if (found) return found;
|
|
956
|
+
}
|
|
957
|
+
return null;
|
|
958
|
+
};
|
|
959
|
+
const dirNode = findNode(tree, path);
|
|
960
|
+
if (dirNode) {
|
|
961
|
+
const childFiles = collectAllPaths(dirNode.children, "file");
|
|
962
|
+
for (const child of dirNode.children) {
|
|
963
|
+
if (child.kind === "file") childFiles.add(child.path);
|
|
964
|
+
}
|
|
965
|
+
const allChecked = [...childFiles].every((p) => next.has(p));
|
|
966
|
+
for (const f of childFiles) {
|
|
967
|
+
if (allChecked) next.delete(f);
|
|
968
|
+
else next.add(f);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return next;
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function handleConflictRename(slug: string, newName: string) {
|
|
977
|
+
setNameOverrides((prev) => ({ ...prev, [slug]: newName }));
|
|
978
|
+
// Editing the name un-confirms
|
|
979
|
+
setConfirmedSlugs((prev) => {
|
|
980
|
+
if (!prev.has(slug)) return prev;
|
|
981
|
+
const next = new Set(prev);
|
|
982
|
+
next.delete(slug);
|
|
983
|
+
return next;
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function handleConflictToggleConfirm(slug: string) {
|
|
988
|
+
setConfirmedSlugs((prev) => {
|
|
989
|
+
const next = new Set(prev);
|
|
990
|
+
if (next.has(slug)) next.delete(slug);
|
|
991
|
+
else next.add(slug);
|
|
992
|
+
return next;
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function handleConflictToggleSkip(slug: string, filePath: string | null) {
|
|
997
|
+
setSkippedSlugs((prev) => {
|
|
998
|
+
const next = new Set(prev);
|
|
999
|
+
const wasSkipped = next.has(slug);
|
|
1000
|
+
if (wasSkipped) {
|
|
1001
|
+
next.delete(slug);
|
|
1002
|
+
} else {
|
|
1003
|
+
next.add(slug);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// Sync with file tree checkboxes
|
|
1007
|
+
if (filePath) {
|
|
1008
|
+
setCheckedFiles((prevChecked) => {
|
|
1009
|
+
const nextChecked = new Set(prevChecked);
|
|
1010
|
+
if (wasSkipped) {
|
|
1011
|
+
nextChecked.add(filePath);
|
|
1012
|
+
} else {
|
|
1013
|
+
nextChecked.delete(filePath);
|
|
1014
|
+
}
|
|
1015
|
+
return nextChecked;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return next;
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function handleAdapterChange(slug: string, adapterType: string) {
|
|
1024
|
+
setAdapterOverrides((prev) => ({ ...prev, [slug]: adapterType }));
|
|
1025
|
+
// Reset config values when adapter type changes
|
|
1026
|
+
setAdapterConfigValues((prev) => {
|
|
1027
|
+
const next = { ...prev };
|
|
1028
|
+
delete next[slug];
|
|
1029
|
+
return next;
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function handleAdapterToggleExpand(slug: string) {
|
|
1034
|
+
setAdapterExpandedSlugs((prev) => {
|
|
1035
|
+
const next = new Set(prev);
|
|
1036
|
+
if (next.has(slug)) next.delete(slug);
|
|
1037
|
+
else next.add(slug);
|
|
1038
|
+
return next;
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function handleAdapterConfigChange(slug: string, patch: Partial<CreateConfigValues>) {
|
|
1043
|
+
setAdapterConfigValues((prev) => ({
|
|
1044
|
+
...prev,
|
|
1045
|
+
[slug]: { ...(prev[slug] ?? { ...defaultCreateValues, adapterType: adapterOverrides[slug] ?? "claude_local" }), ...patch },
|
|
1046
|
+
}));
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Build the list of agents for adapter picking
|
|
1050
|
+
const adapterAgents = useMemo<AdapterPickerItem[]>(() => {
|
|
1051
|
+
if (!importPreview) return [];
|
|
1052
|
+
return importPreview.manifest.agents.map((a) => ({
|
|
1053
|
+
slug: a.slug,
|
|
1054
|
+
name: a.name,
|
|
1055
|
+
adapterType: a.adapterType,
|
|
1056
|
+
}));
|
|
1057
|
+
}, [importPreview]);
|
|
1058
|
+
|
|
1059
|
+
// Build final adapterOverrides for import request
|
|
1060
|
+
function buildFinalAdapterOverrides(): Record<string, CompanyPortabilityAdapterOverride> | undefined {
|
|
1061
|
+
if (adapterAgents.length === 0) return undefined;
|
|
1062
|
+
const overrides: Record<string, CompanyPortabilityAdapterOverride> = {};
|
|
1063
|
+
for (const agent of adapterAgents) {
|
|
1064
|
+
const selectedType = adapterOverrides[agent.slug] ?? agent.adapterType;
|
|
1065
|
+
const configVals = adapterConfigValues[agent.slug];
|
|
1066
|
+
const override: CompanyPortabilityAdapterOverride = { adapterType: selectedType };
|
|
1067
|
+
if (configVals) {
|
|
1068
|
+
const uiAdapter = getUIAdapter(selectedType);
|
|
1069
|
+
override.adapterConfig = uiAdapter.buildAdapterConfig(configVals);
|
|
1070
|
+
}
|
|
1071
|
+
overrides[agent.slug] = override;
|
|
1072
|
+
}
|
|
1073
|
+
return Object.keys(overrides).length > 0 ? overrides : undefined;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const hasSource =
|
|
1077
|
+
sourceMode === "local" ? !!localPackage : importUrl.trim().length > 0;
|
|
1078
|
+
const hasErrors = importPreview ? importPreview.errors.length > 0 : false;
|
|
1079
|
+
|
|
1080
|
+
const previewContent = selectedFile && importPreview
|
|
1081
|
+
? (() => {
|
|
1082
|
+
return importPreview.files[selectedFile] ?? null;
|
|
1083
|
+
})()
|
|
1084
|
+
: null;
|
|
1085
|
+
const selectedAction = selectedFile ? (actionMap.get(selectedFile) ?? null) : null;
|
|
1086
|
+
|
|
1087
|
+
if (!selectedCompanyId) {
|
|
1088
|
+
return <EmptyState icon={Download} message="Select a company to import into." />;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
return (
|
|
1092
|
+
<div>
|
|
1093
|
+
{/* Source form section */}
|
|
1094
|
+
<div className="border-b border-border px-5 py-5 space-y-4">
|
|
1095
|
+
<div>
|
|
1096
|
+
<h2 className="text-base font-semibold">Import source</h2>
|
|
1097
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
1098
|
+
Choose a GitHub repo or upload a local Corporate zip package.
|
|
1099
|
+
</p>
|
|
1100
|
+
</div>
|
|
1101
|
+
|
|
1102
|
+
<div className="grid gap-2 md:grid-cols-2">
|
|
1103
|
+
{(
|
|
1104
|
+
[
|
|
1105
|
+
{ key: "github", icon: Github, label: "GitHub repo" },
|
|
1106
|
+
{ key: "local", icon: Upload, label: "Local zip" },
|
|
1107
|
+
] as const
|
|
1108
|
+
).map(({ key, icon: Icon, label }) => (
|
|
1109
|
+
<button
|
|
1110
|
+
key={key}
|
|
1111
|
+
type="button"
|
|
1112
|
+
className={cn(
|
|
1113
|
+
"rounded-md border px-3 py-2 text-left text-sm transition-colors",
|
|
1114
|
+
sourceMode === key
|
|
1115
|
+
? "border-foreground bg-accent"
|
|
1116
|
+
: "border-border hover:bg-accent/50",
|
|
1117
|
+
)}
|
|
1118
|
+
onClick={() => {
|
|
1119
|
+
setSourceMode(key);
|
|
1120
|
+
setImportPreview(null);
|
|
1121
|
+
}}
|
|
1122
|
+
>
|
|
1123
|
+
<div className="flex items-center gap-2">
|
|
1124
|
+
<Icon className="h-4 w-4" />
|
|
1125
|
+
{label}
|
|
1126
|
+
</div>
|
|
1127
|
+
</button>
|
|
1128
|
+
))}
|
|
1129
|
+
</div>
|
|
1130
|
+
|
|
1131
|
+
{sourceMode === "local" ? (
|
|
1132
|
+
<div className="rounded-md border border-dashed border-border px-3 py-3">
|
|
1133
|
+
<input
|
|
1134
|
+
ref={packageInputRef}
|
|
1135
|
+
type="file"
|
|
1136
|
+
accept=".zip,application/zip"
|
|
1137
|
+
className="hidden"
|
|
1138
|
+
onChange={handleChooseLocalPackage}
|
|
1139
|
+
/>
|
|
1140
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1141
|
+
<Button
|
|
1142
|
+
size="sm"
|
|
1143
|
+
variant="outline"
|
|
1144
|
+
onClick={() => packageInputRef.current?.click()}
|
|
1145
|
+
>
|
|
1146
|
+
Choose zip
|
|
1147
|
+
</Button>
|
|
1148
|
+
{localPackage && (
|
|
1149
|
+
<span className="text-xs text-muted-foreground">
|
|
1150
|
+
{localPackage.name} with{" "}
|
|
1151
|
+
{Object.keys(localPackage.files).length} file
|
|
1152
|
+
{Object.keys(localPackage.files).length === 1 ? "" : "s"}
|
|
1153
|
+
</span>
|
|
1154
|
+
)}
|
|
1155
|
+
</div>
|
|
1156
|
+
{!localPackage && (
|
|
1157
|
+
<p className="mt-2 text-xs text-muted-foreground">
|
|
1158
|
+
{localZipHelpText}
|
|
1159
|
+
</p>
|
|
1160
|
+
)}
|
|
1161
|
+
</div>
|
|
1162
|
+
) : (
|
|
1163
|
+
<Field
|
|
1164
|
+
label="GitHub URL"
|
|
1165
|
+
hint="Repo tree path or blob URL to COMPANY.md (e.g. github.com/owner/repo/tree/main/company)."
|
|
1166
|
+
>
|
|
1167
|
+
<input
|
|
1168
|
+
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
|
1169
|
+
type="text"
|
|
1170
|
+
value={importUrl}
|
|
1171
|
+
placeholder="https://github.com/owner/repo/tree/main/company"
|
|
1172
|
+
onChange={(e) => {
|
|
1173
|
+
setImportUrl(e.target.value);
|
|
1174
|
+
setImportPreview(null);
|
|
1175
|
+
}}
|
|
1176
|
+
/>
|
|
1177
|
+
</Field>
|
|
1178
|
+
)}
|
|
1179
|
+
|
|
1180
|
+
<Field label="Target" hint="Import into this company or create a new one.">
|
|
1181
|
+
<select
|
|
1182
|
+
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
|
1183
|
+
value={targetMode}
|
|
1184
|
+
onChange={(e) => {
|
|
1185
|
+
setTargetMode(e.target.value as "existing" | "new");
|
|
1186
|
+
setImportPreview(null);
|
|
1187
|
+
}}
|
|
1188
|
+
>
|
|
1189
|
+
<option value="new">Create new company</option>
|
|
1190
|
+
<option value="existing">
|
|
1191
|
+
Existing company: {selectedCompany?.name}
|
|
1192
|
+
</option>
|
|
1193
|
+
</select>
|
|
1194
|
+
</Field>
|
|
1195
|
+
|
|
1196
|
+
{targetMode === "new" && (
|
|
1197
|
+
<Field
|
|
1198
|
+
label="New company name"
|
|
1199
|
+
hint="Optional override. Leave blank to use the package name."
|
|
1200
|
+
>
|
|
1201
|
+
<input
|
|
1202
|
+
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
|
1203
|
+
type="text"
|
|
1204
|
+
value={newCompanyName}
|
|
1205
|
+
onChange={(e) => setNewCompanyName(e.target.value)}
|
|
1206
|
+
placeholder="Imported Company"
|
|
1207
|
+
/>
|
|
1208
|
+
</Field>
|
|
1209
|
+
)}
|
|
1210
|
+
|
|
1211
|
+
<Field
|
|
1212
|
+
label="Collision strategy"
|
|
1213
|
+
hint="Board imports can rename, skip, or replace matching company content."
|
|
1214
|
+
>
|
|
1215
|
+
<select
|
|
1216
|
+
className="w-full rounded-md border border-border bg-transparent px-2.5 py-1.5 text-sm outline-none"
|
|
1217
|
+
value={collisionStrategy}
|
|
1218
|
+
onChange={(e) => {
|
|
1219
|
+
setCollisionStrategy(e.target.value as CompanyPortabilityCollisionStrategy);
|
|
1220
|
+
setImportPreview(null);
|
|
1221
|
+
}}
|
|
1222
|
+
>
|
|
1223
|
+
<option value="rename">Rename on conflict</option>
|
|
1224
|
+
<option value="skip">Skip on conflict</option>
|
|
1225
|
+
<option value="replace">Replace existing</option>
|
|
1226
|
+
</select>
|
|
1227
|
+
</Field>
|
|
1228
|
+
|
|
1229
|
+
<div className="flex items-center gap-2">
|
|
1230
|
+
<Button
|
|
1231
|
+
size="sm"
|
|
1232
|
+
variant="outline"
|
|
1233
|
+
onClick={() => previewMutation.mutate()}
|
|
1234
|
+
disabled={previewMutation.isPending || !hasSource}
|
|
1235
|
+
>
|
|
1236
|
+
{previewMutation.isPending ? "Previewing..." : "Preview import"}
|
|
1237
|
+
</Button>
|
|
1238
|
+
</div>
|
|
1239
|
+
</div>
|
|
1240
|
+
|
|
1241
|
+
{/* Preview results */}
|
|
1242
|
+
{importPreview && (
|
|
1243
|
+
<>
|
|
1244
|
+
{/* Sticky import action bar */}
|
|
1245
|
+
<div className="sticky top-0 z-10 border-b border-border bg-background px-5 py-3">
|
|
1246
|
+
<div className="flex flex-wrap items-center gap-4 text-sm">
|
|
1247
|
+
<span className="font-medium">
|
|
1248
|
+
Import preview
|
|
1249
|
+
</span>
|
|
1250
|
+
<span className="text-muted-foreground">
|
|
1251
|
+
{selectedCount} / {totalFiles} file{totalFiles === 1 ? "" : "s"} selected
|
|
1252
|
+
</span>
|
|
1253
|
+
{conflicts.length > 0 && (
|
|
1254
|
+
<span className="text-amber-500">
|
|
1255
|
+
{conflicts.length} conflict{conflicts.length === 1 ? "" : "s"}
|
|
1256
|
+
</span>
|
|
1257
|
+
)}
|
|
1258
|
+
{importPreview.errors.length > 0 && (
|
|
1259
|
+
<span className="text-destructive">
|
|
1260
|
+
{importPreview.errors.length} error{importPreview.errors.length === 1 ? "" : "s"}
|
|
1261
|
+
</span>
|
|
1262
|
+
)}
|
|
1263
|
+
</div>
|
|
1264
|
+
</div>
|
|
1265
|
+
|
|
1266
|
+
{/* Conflict resolution list */}
|
|
1267
|
+
<ConflictResolutionList
|
|
1268
|
+
conflicts={conflicts}
|
|
1269
|
+
nameOverrides={nameOverrides}
|
|
1270
|
+
skippedSlugs={skippedSlugs}
|
|
1271
|
+
confirmedSlugs={confirmedSlugs}
|
|
1272
|
+
onRename={handleConflictRename}
|
|
1273
|
+
onToggleSkip={handleConflictToggleSkip}
|
|
1274
|
+
onToggleConfirm={handleConflictToggleConfirm}
|
|
1275
|
+
/>
|
|
1276
|
+
|
|
1277
|
+
{/* Adapter picker list */}
|
|
1278
|
+
<AdapterPickerList
|
|
1279
|
+
agents={adapterAgents}
|
|
1280
|
+
adapterOverrides={adapterOverrides}
|
|
1281
|
+
expandedSlugs={adapterExpandedSlugs}
|
|
1282
|
+
configValues={adapterConfigValues}
|
|
1283
|
+
onChangeAdapter={handleAdapterChange}
|
|
1284
|
+
onToggleExpand={handleAdapterToggleExpand}
|
|
1285
|
+
onChangeConfig={handleAdapterConfigChange}
|
|
1286
|
+
/>
|
|
1287
|
+
|
|
1288
|
+
{/* Import button — below renames */}
|
|
1289
|
+
<div className="mx-5 mt-3 flex justify-end">
|
|
1290
|
+
<Button
|
|
1291
|
+
size="sm"
|
|
1292
|
+
onClick={() => importMutation.mutate()}
|
|
1293
|
+
disabled={importMutation.isPending || hasErrors || selectedCount === 0}
|
|
1294
|
+
>
|
|
1295
|
+
<Download className="mr-1.5 h-3.5 w-3.5" />
|
|
1296
|
+
{importMutation.isPending
|
|
1297
|
+
? "Importing..."
|
|
1298
|
+
: `Import ${selectedCount} file${selectedCount === 1 ? "" : "s"}`}
|
|
1299
|
+
</Button>
|
|
1300
|
+
</div>
|
|
1301
|
+
|
|
1302
|
+
{/* Warnings */}
|
|
1303
|
+
{importPreview.warnings.length > 0 && (
|
|
1304
|
+
<div className="mx-5 mt-3 rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3">
|
|
1305
|
+
{importPreview.warnings.map((w) => (
|
|
1306
|
+
<div key={w} className="text-xs text-amber-500">{w}</div>
|
|
1307
|
+
))}
|
|
1308
|
+
</div>
|
|
1309
|
+
)}
|
|
1310
|
+
|
|
1311
|
+
{/* Errors */}
|
|
1312
|
+
{importPreview.errors.length > 0 && (
|
|
1313
|
+
<div className="mx-5 mt-3 rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3">
|
|
1314
|
+
{importPreview.errors.map((e) => (
|
|
1315
|
+
<div key={e} className="text-xs text-destructive">{e}</div>
|
|
1316
|
+
))}
|
|
1317
|
+
</div>
|
|
1318
|
+
)}
|
|
1319
|
+
|
|
1320
|
+
{/* Two-column layout */}
|
|
1321
|
+
<div className="grid h-[calc(100vh-16rem)] gap-0 xl:grid-cols-[19rem_minmax(0,1fr)]">
|
|
1322
|
+
<aside className="flex flex-col border-r border-border overflow-hidden">
|
|
1323
|
+
<div className="border-b border-border px-4 py-3 shrink-0">
|
|
1324
|
+
<h2 className="text-base font-semibold">Package files</h2>
|
|
1325
|
+
</div>
|
|
1326
|
+
<div className="flex-1 overflow-y-auto">
|
|
1327
|
+
<PackageFileTree
|
|
1328
|
+
nodes={tree}
|
|
1329
|
+
selectedFile={selectedFile}
|
|
1330
|
+
expandedDirs={expandedDirs}
|
|
1331
|
+
checkedFiles={checkedFiles}
|
|
1332
|
+
onToggleDir={handleToggleDir}
|
|
1333
|
+
onSelectFile={setSelectedFile}
|
|
1334
|
+
onToggleCheck={handleToggleCheck}
|
|
1335
|
+
renderFileExtra={(node, checked) => renderImportFileExtra(node, checked, renameMap)}
|
|
1336
|
+
fileRowClassName={importFileRowClassName}
|
|
1337
|
+
/>
|
|
1338
|
+
</div>
|
|
1339
|
+
</aside>
|
|
1340
|
+
<div className="min-w-0 overflow-y-auto pl-6">
|
|
1341
|
+
<ImportPreviewPane
|
|
1342
|
+
selectedFile={selectedFile}
|
|
1343
|
+
content={previewContent}
|
|
1344
|
+
allFiles={importPreview?.files ?? {}}
|
|
1345
|
+
action={selectedAction}
|
|
1346
|
+
renamedTo={selectedFile ? (renameMap.get(selectedFile) ?? null) : null}
|
|
1347
|
+
/>
|
|
1348
|
+
</div>
|
|
1349
|
+
</div>
|
|
1350
|
+
</>
|
|
1351
|
+
)}
|
|
1352
|
+
</div>
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
|