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.
Files changed (704) hide show
  1. package/.dockerignore +10 -0
  2. package/.env.example +3 -0
  3. package/.github/workflows/publish-cli.yml +49 -0
  4. package/.mailmap +1 -0
  5. package/AGENTS.md +148 -0
  6. package/CONTRIBUTING.md +75 -0
  7. package/Dockerfile +59 -0
  8. package/Dockerfile.onboard-smoke +42 -0
  9. package/LICENSE +21 -0
  10. package/README.md +93 -0
  11. package/cli/esbuild.config.mjs +11 -0
  12. package/cli/package.json +24 -0
  13. package/cli/scripts/build-cli.mjs +5 -0
  14. package/cli/src/index.ts +27 -0
  15. package/docker-compose.quickstart.yml +18 -0
  16. package/docker-compose.untrusted-review.yml +33 -0
  17. package/docker-compose.yml +38 -0
  18. package/package.json +56 -0
  19. package/patches/embedded-postgres@18.1.0-beta.16.patch +0 -0
  20. package/pnpm-workspace.yaml +4 -0
  21. package/releases/.gitkeep +0 -0
  22. package/releases/v0.0.1.md +36 -0
  23. package/releases/v0.2.7.md +15 -0
  24. package/releases/v0.3.0.md +54 -0
  25. package/releases/v0.3.1.md +55 -0
  26. package/releases/v2026.318.0.md +66 -0
  27. package/releases/v2026.325.0.md +78 -0
  28. package/report/2026-03-13-08-46-token-optimization-implementation.md +48 -0
  29. package/scripts/backup-db.sh +17 -0
  30. package/scripts/build-npm.sh +80 -0
  31. package/scripts/check-forbidden-tokens.mjs +115 -0
  32. package/scripts/clean-onboard-git.sh +14 -0
  33. package/scripts/clean-onboard-npm.sh +13 -0
  34. package/scripts/clean-onboard-ref.sh +86 -0
  35. package/scripts/create-github-release.sh +99 -0
  36. package/scripts/dev-runner-paths.mjs +38 -0
  37. package/scripts/dev-runner.mjs +606 -0
  38. package/scripts/docker-onboard-smoke.sh +306 -0
  39. package/scripts/ensure-plugin-build-deps.mjs +47 -0
  40. package/scripts/generate-company-assets.ts +365 -0
  41. package/scripts/generate-npm-package-json.mjs +113 -0
  42. package/scripts/generate-org-chart-images.ts +694 -0
  43. package/scripts/generate-org-chart-satori-comparison.ts +225 -0
  44. package/scripts/generate-ui-package-json.mjs +31 -0
  45. package/scripts/kill-dev.sh +71 -0
  46. package/scripts/migrate-inline-env-secrets.ts +126 -0
  47. package/scripts/prepare-server-ui-dist.sh +22 -0
  48. package/scripts/provision-worktree.sh +333 -0
  49. package/scripts/release-lib.sh +306 -0
  50. package/scripts/release-package-map.mjs +169 -0
  51. package/scripts/release.sh +312 -0
  52. package/scripts/rollback-latest.sh +111 -0
  53. package/scripts/smoke/openclaw-docker-ui.sh +329 -0
  54. package/scripts/smoke/openclaw-gateway-e2e.sh +954 -0
  55. package/scripts/smoke/openclaw-join.sh +295 -0
  56. package/scripts/smoke/openclaw-sse-standalone.sh +146 -0
  57. package/scripts/workspace-compat.mjs +60 -0
  58. package/server/CHANGELOG.md +130 -0
  59. package/server/package.json +96 -0
  60. package/server/scripts/copy-onboarding-assets.mjs +10 -0
  61. package/server/scripts/dev-watch.ts +33 -0
  62. package/server/src/__tests__/activity-routes.test.ts +70 -0
  63. package/server/src/__tests__/adapter-models.test.ts +105 -0
  64. package/server/src/__tests__/adapter-session-codecs.test.ts +194 -0
  65. package/server/src/__tests__/agent-auth-jwt.test.ts +79 -0
  66. package/server/src/__tests__/agent-instructions-routes.test.ts +318 -0
  67. package/server/src/__tests__/agent-instructions-service.test.ts +361 -0
  68. package/server/src/__tests__/agent-permissions-routes.test.ts +275 -0
  69. package/server/src/__tests__/agent-shortname-collision.test.ts +69 -0
  70. package/server/src/__tests__/agent-skill-contract.test.ts +50 -0
  71. package/server/src/__tests__/agent-skills-routes.test.ts +462 -0
  72. package/server/src/__tests__/app-hmr-port.test.ts +19 -0
  73. package/server/src/__tests__/approval-routes-idempotency.test.ts +110 -0
  74. package/server/src/__tests__/approvals-service.test.ts +107 -0
  75. package/server/src/__tests__/assets.test.ts +250 -0
  76. package/server/src/__tests__/attachment-types.test.ts +97 -0
  77. package/server/src/__tests__/board-mutation-guard.test.ts +105 -0
  78. package/server/src/__tests__/budgets-service.test.ts +311 -0
  79. package/server/src/__tests__/claude-local-adapter-environment.test.ts +92 -0
  80. package/server/src/__tests__/claude-local-adapter.test.ts +31 -0
  81. package/server/src/__tests__/claude-local-skill-sync.test.ts +111 -0
  82. package/server/src/__tests__/cli-auth-routes.test.ts +230 -0
  83. package/server/src/__tests__/codex-local-adapter-environment.test.ts +143 -0
  84. package/server/src/__tests__/codex-local-adapter.test.ts +253 -0
  85. package/server/src/__tests__/codex-local-execute.test.ts +391 -0
  86. package/server/src/__tests__/codex-local-skill-injection.test.ts +175 -0
  87. package/server/src/__tests__/codex-local-skill-sync.test.ts +123 -0
  88. package/server/src/__tests__/companies-route-path-guard.test.ts +56 -0
  89. package/server/src/__tests__/company-branding-route.test.ts +196 -0
  90. package/server/src/__tests__/company-portability-routes.test.ts +175 -0
  91. package/server/src/__tests__/company-portability.test.ts +2186 -0
  92. package/server/src/__tests__/company-skills-routes.test.ts +113 -0
  93. package/server/src/__tests__/company-skills.test.ts +229 -0
  94. package/server/src/__tests__/costs-service.test.ts +226 -0
  95. package/server/src/__tests__/cursor-local-adapter-environment.test.ts +196 -0
  96. package/server/src/__tests__/cursor-local-adapter.test.ts +406 -0
  97. package/server/src/__tests__/cursor-local-execute.test.ts +263 -0
  98. package/server/src/__tests__/cursor-local-skill-injection.test.ts +104 -0
  99. package/server/src/__tests__/cursor-local-skill-sync.test.ts +145 -0
  100. package/server/src/__tests__/dev-runner-paths.test.ts +25 -0
  101. package/server/src/__tests__/dev-server-status.test.ts +66 -0
  102. package/server/src/__tests__/dev-watch-ignore.test.ts +42 -0
  103. package/server/src/__tests__/documents.test.ts +29 -0
  104. package/server/src/__tests__/error-handler.test.ts +53 -0
  105. package/server/src/__tests__/execution-workspace-policy.test.ts +170 -0
  106. package/server/src/__tests__/forbidden-tokens.test.ts +77 -0
  107. package/server/src/__tests__/gemini-local-adapter-environment.test.ts +135 -0
  108. package/server/src/__tests__/gemini-local-adapter.test.ts +190 -0
  109. package/server/src/__tests__/gemini-local-execute.test.ts +172 -0
  110. package/server/src/__tests__/gemini-local-skill-sync.test.ts +90 -0
  111. package/server/src/__tests__/health.test.ts +16 -0
  112. package/server/src/__tests__/heartbeat-process-recovery.test.ts +256 -0
  113. package/server/src/__tests__/heartbeat-run-summary.test.ts +33 -0
  114. package/server/src/__tests__/heartbeat-workspace-session.test.ts +334 -0
  115. package/server/src/__tests__/helpers/embedded-postgres.ts +7 -0
  116. package/server/src/__tests__/hire-hook.test.ts +181 -0
  117. package/server/src/__tests__/instance-settings-routes.test.ts +156 -0
  118. package/server/src/__tests__/invite-accept-gateway-defaults.test.ts +119 -0
  119. package/server/src/__tests__/invite-accept-replay.test.ts +92 -0
  120. package/server/src/__tests__/invite-expiry.test.ts +10 -0
  121. package/server/src/__tests__/invite-join-grants.test.ts +57 -0
  122. package/server/src/__tests__/invite-join-manager.test.ts +33 -0
  123. package/server/src/__tests__/invite-onboarding-text.test.ts +116 -0
  124. package/server/src/__tests__/issue-comment-reopen-routes.test.ts +146 -0
  125. package/server/src/__tests__/issue-goal-fallback.test.ts +99 -0
  126. package/server/src/__tests__/issues-checkout-wakeup.test.ts +48 -0
  127. package/server/src/__tests__/issues-goal-context-routes.test.ts +187 -0
  128. package/server/src/__tests__/issues-service.test.ts +317 -0
  129. package/server/src/__tests__/issues-user-context.test.ts +113 -0
  130. package/server/src/__tests__/log-redaction.test.ts +74 -0
  131. package/server/src/__tests__/monthly-spend-service.test.ts +90 -0
  132. package/server/src/__tests__/normalize-agent-mention-token.test.ts +41 -0
  133. package/server/src/__tests__/openclaw-gateway-adapter.test.ts +626 -0
  134. package/server/src/__tests__/openclaw-invite-prompt-route.test.ts +192 -0
  135. package/server/src/__tests__/opencode-local-adapter-environment.test.ts +97 -0
  136. package/server/src/__tests__/opencode-local-adapter.test.ts +226 -0
  137. package/server/src/__tests__/opencode-local-skill-sync.test.ts +91 -0
  138. package/server/src/__tests__/paperclip-env.test.ts +58 -0
  139. package/server/src/__tests__/paperclip-skill-utils.test.ts +63 -0
  140. package/server/src/__tests__/pi-local-adapter-environment.test.ts +102 -0
  141. package/server/src/__tests__/pi-local-skill-sync.test.ts +95 -0
  142. package/server/src/__tests__/plugin-dev-watcher.test.ts +68 -0
  143. package/server/src/__tests__/plugin-worker-manager.test.ts +43 -0
  144. package/server/src/__tests__/private-hostname-guard.test.ts +56 -0
  145. package/server/src/__tests__/project-shortname-resolution.test.ts +45 -0
  146. package/server/src/__tests__/quota-windows-service.test.ts +56 -0
  147. package/server/src/__tests__/quota-windows.test.ts +1109 -0
  148. package/server/src/__tests__/redaction.test.ts +66 -0
  149. package/server/src/__tests__/routines-e2e.test.ts +276 -0
  150. package/server/src/__tests__/routines-routes.test.ts +271 -0
  151. package/server/src/__tests__/routines-service.test.ts +424 -0
  152. package/server/src/__tests__/storage-local-provider.test.ts +78 -0
  153. package/server/src/__tests__/ui-branding.test.ts +82 -0
  154. package/server/src/__tests__/work-products.test.ts +95 -0
  155. package/server/src/__tests__/workspace-runtime.test.ts +1131 -0
  156. package/server/src/__tests__/worktree-config.test.ts +426 -0
  157. package/server/src/adapters/codex-models.ts +105 -0
  158. package/server/src/adapters/cursor-models.ts +171 -0
  159. package/server/src/adapters/http/execute.ts +42 -0
  160. package/server/src/adapters/http/index.ts +21 -0
  161. package/server/src/adapters/http/test.ts +116 -0
  162. package/server/src/adapters/index.ts +18 -0
  163. package/server/src/adapters/process/execute.ts +77 -0
  164. package/server/src/adapters/process/index.ts +24 -0
  165. package/server/src/adapters/process/test.ts +89 -0
  166. package/server/src/adapters/registry.ts +225 -0
  167. package/server/src/adapters/server-utils-compat.ts +57 -0
  168. package/server/src/adapters/types.ts +30 -0
  169. package/server/src/adapters/utils.ts +48 -0
  170. package/server/src/agent-auth-jwt.ts +141 -0
  171. package/server/src/app.ts +321 -0
  172. package/server/src/attachment-types.ts +74 -0
  173. package/server/src/auth/better-auth.ts +148 -0
  174. package/server/src/board-claim.ts +150 -0
  175. package/server/src/config-file.ts +17 -0
  176. package/server/src/config.ts +260 -0
  177. package/server/src/dev-server-status.ts +103 -0
  178. package/server/src/dev-watch-ignore.ts +36 -0
  179. package/server/src/errors.ts +34 -0
  180. package/server/src/home-paths.ts +95 -0
  181. package/server/src/index.ts +799 -0
  182. package/server/src/log-redaction.ts +146 -0
  183. package/server/src/middleware/auth.ts +178 -0
  184. package/server/src/middleware/board-mutation-guard.ts +66 -0
  185. package/server/src/middleware/error-handler.ts +71 -0
  186. package/server/src/middleware/index.ts +3 -0
  187. package/server/src/middleware/logger.ts +90 -0
  188. package/server/src/middleware/private-hostname-guard.ts +92 -0
  189. package/server/src/middleware/validate.ts +9 -0
  190. package/server/src/onboarding-assets/ceo/AGENTS.md +54 -0
  191. package/server/src/onboarding-assets/ceo/HEARTBEAT.md +72 -0
  192. package/server/src/onboarding-assets/ceo/SOUL.md +33 -0
  193. package/server/src/onboarding-assets/ceo/TOOLS.md +3 -0
  194. package/server/src/onboarding-assets/default/AGENTS.md +3 -0
  195. package/server/src/paths.ts +34 -0
  196. package/server/src/realtime/live-events-ws.ts +274 -0
  197. package/server/src/redaction.ts +59 -0
  198. package/server/src/routes/access.ts +2888 -0
  199. package/server/src/routes/activity.ts +89 -0
  200. package/server/src/routes/agents.ts +2313 -0
  201. package/server/src/routes/approvals.ts +346 -0
  202. package/server/src/routes/assets.ts +341 -0
  203. package/server/src/routes/authz.ts +52 -0
  204. package/server/src/routes/companies.ts +343 -0
  205. package/server/src/routes/company-skills.ts +300 -0
  206. package/server/src/routes/costs.ts +335 -0
  207. package/server/src/routes/dashboard.ts +19 -0
  208. package/server/src/routes/execution-workspaces.ts +182 -0
  209. package/server/src/routes/goals.ts +107 -0
  210. package/server/src/routes/health.ts +94 -0
  211. package/server/src/routes/index.ts +17 -0
  212. package/server/src/routes/instance-settings.ts +95 -0
  213. package/server/src/routes/issues-checkout-wakeup.ts +14 -0
  214. package/server/src/routes/issues.ts +1680 -0
  215. package/server/src/routes/llms.ts +86 -0
  216. package/server/src/routes/org-chart-svg.ts +777 -0
  217. package/server/src/routes/plugin-ui-static.ts +497 -0
  218. package/server/src/routes/plugins.ts +2220 -0
  219. package/server/src/routes/projects.ts +295 -0
  220. package/server/src/routes/routines.ts +300 -0
  221. package/server/src/routes/secrets.ts +166 -0
  222. package/server/src/routes/sidebar-badges.ts +52 -0
  223. package/server/src/secrets/external-stub-providers.ts +32 -0
  224. package/server/src/secrets/local-encrypted-provider.ts +135 -0
  225. package/server/src/secrets/provider-registry.ts +31 -0
  226. package/server/src/secrets/types.ts +23 -0
  227. package/server/src/services/access.ts +381 -0
  228. package/server/src/services/activity-log.ts +95 -0
  229. package/server/src/services/activity.ts +164 -0
  230. package/server/src/services/agent-instructions.ts +735 -0
  231. package/server/src/services/agent-permissions.ts +27 -0
  232. package/server/src/services/agents.ts +694 -0
  233. package/server/src/services/approvals.ts +273 -0
  234. package/server/src/services/assets.ts +23 -0
  235. package/server/src/services/board-auth.ts +355 -0
  236. package/server/src/services/budgets.ts +959 -0
  237. package/server/src/services/companies.ts +313 -0
  238. package/server/src/services/company-export-readme.ts +173 -0
  239. package/server/src/services/company-portability.ts +4263 -0
  240. package/server/src/services/company-skills.ts +2356 -0
  241. package/server/src/services/costs.ts +365 -0
  242. package/server/src/services/cron.ts +373 -0
  243. package/server/src/services/dashboard.ts +110 -0
  244. package/server/src/services/default-agent-instructions.ts +27 -0
  245. package/server/src/services/documents.ts +434 -0
  246. package/server/src/services/execution-workspace-policy.ts +210 -0
  247. package/server/src/services/execution-workspaces.ts +100 -0
  248. package/server/src/services/finance.ts +135 -0
  249. package/server/src/services/goals.ts +81 -0
  250. package/server/src/services/heartbeat-run-summary.ts +35 -0
  251. package/server/src/services/heartbeat.ts +3863 -0
  252. package/server/src/services/hire-hook.ts +114 -0
  253. package/server/src/services/index.ts +32 -0
  254. package/server/src/services/instance-settings.ts +138 -0
  255. package/server/src/services/issue-approvals.ts +175 -0
  256. package/server/src/services/issue-assignment-wakeup.ts +48 -0
  257. package/server/src/services/issue-goal-fallback.ts +56 -0
  258. package/server/src/services/issues.ts +1828 -0
  259. package/server/src/services/live-events.ts +55 -0
  260. package/server/src/services/plugin-capability-validator.ts +450 -0
  261. package/server/src/services/plugin-config-validator.ts +55 -0
  262. package/server/src/services/plugin-dev-watcher.ts +339 -0
  263. package/server/src/services/plugin-event-bus.ts +413 -0
  264. package/server/src/services/plugin-host-service-cleanup.ts +59 -0
  265. package/server/src/services/plugin-host-services.ts +1132 -0
  266. package/server/src/services/plugin-job-coordinator.ts +261 -0
  267. package/server/src/services/plugin-job-scheduler.ts +753 -0
  268. package/server/src/services/plugin-job-store.ts +466 -0
  269. package/server/src/services/plugin-lifecycle.ts +822 -0
  270. package/server/src/services/plugin-loader.ts +1955 -0
  271. package/server/src/services/plugin-log-retention.ts +87 -0
  272. package/server/src/services/plugin-manifest-validator.ts +164 -0
  273. package/server/src/services/plugin-registry.ts +683 -0
  274. package/server/src/services/plugin-runtime-sandbox.ts +222 -0
  275. package/server/src/services/plugin-secrets-handler.ts +355 -0
  276. package/server/src/services/plugin-state-store.ts +238 -0
  277. package/server/src/services/plugin-stream-bus.ts +81 -0
  278. package/server/src/services/plugin-tool-dispatcher.ts +449 -0
  279. package/server/src/services/plugin-tool-registry.ts +450 -0
  280. package/server/src/services/plugin-worker-manager.ts +1343 -0
  281. package/server/src/services/projects.ts +860 -0
  282. package/server/src/services/quota-windows.ts +65 -0
  283. package/server/src/services/routines.ts +1269 -0
  284. package/server/src/services/run-log-store.ts +156 -0
  285. package/server/src/services/secrets.ts +370 -0
  286. package/server/src/services/sidebar-badges.ts +56 -0
  287. package/server/src/services/work-products.ts +124 -0
  288. package/server/src/services/workspace-operation-log-store.ts +156 -0
  289. package/server/src/services/workspace-operations.ts +262 -0
  290. package/server/src/services/workspace-runtime.ts +1565 -0
  291. package/server/src/startup-banner.ts +176 -0
  292. package/server/src/storage/index.ts +35 -0
  293. package/server/src/storage/local-disk-provider.ts +89 -0
  294. package/server/src/storage/provider-registry.ts +18 -0
  295. package/server/src/storage/s3-provider.ts +153 -0
  296. package/server/src/storage/service.ts +131 -0
  297. package/server/src/storage/types.ts +63 -0
  298. package/server/src/ui-branding.ts +217 -0
  299. package/server/src/version.ts +10 -0
  300. package/server/src/worktree-config.ts +468 -0
  301. package/server/tsconfig.json +9 -0
  302. package/server/vitest.config.ts +7 -0
  303. package/skills/paperclip/SKILL.md +365 -0
  304. package/skills/paperclip/references/api-reference.md +647 -0
  305. package/skills/paperclip/references/company-skills.md +193 -0
  306. package/skills/paperclip-create-agent/SKILL.md +142 -0
  307. package/skills/paperclip-create-agent/references/api-reference.md +105 -0
  308. package/skills/paperclip-create-plugin/SKILL.md +102 -0
  309. package/skills/para-memory-files/SKILL.md +104 -0
  310. package/skills/para-memory-files/references/schemas.md +35 -0
  311. package/tests/e2e/onboarding.spec.ts +142 -0
  312. package/tests/e2e/playwright.config.ts +35 -0
  313. package/tests/release-smoke/docker-auth-onboarding.spec.ts +141 -0
  314. package/tests/release-smoke/playwright.config.ts +28 -0
  315. package/tsconfig.base.json +18 -0
  316. package/tsconfig.json +18 -0
  317. package/ui/README.md +12 -0
  318. package/ui/components.json +21 -0
  319. package/ui/index.html +47 -0
  320. package/ui/package.json +73 -0
  321. package/ui/public/android-chrome-192x192.png +0 -0
  322. package/ui/public/android-chrome-512x512.png +0 -0
  323. package/ui/public/apple-touch-icon.png +0 -0
  324. package/ui/public/brands/opencode-logo-dark-square.svg +18 -0
  325. package/ui/public/brands/opencode-logo-light-square.svg +18 -0
  326. package/ui/public/favicon-16x16.png +0 -0
  327. package/ui/public/favicon-32x32.png +0 -0
  328. package/ui/public/favicon.ico +0 -0
  329. package/ui/public/favicon.svg +9 -0
  330. package/ui/public/site.webmanifest +30 -0
  331. package/ui/public/sprites/1-D-1.png +0 -0
  332. package/ui/public/sprites/1-D-2.png +0 -0
  333. package/ui/public/sprites/1-D-3.png +0 -0
  334. package/ui/public/sprites/1-L-1.png +0 -0
  335. package/ui/public/sprites/1-R-1.png +0 -0
  336. package/ui/public/sprites/10-D-1.png +0 -0
  337. package/ui/public/sprites/10-D-2.png +0 -0
  338. package/ui/public/sprites/10-D-3.png +0 -0
  339. package/ui/public/sprites/10-L-1.png +0 -0
  340. package/ui/public/sprites/10-R-1.png +0 -0
  341. package/ui/public/sprites/11-D-1.png +0 -0
  342. package/ui/public/sprites/11-D-2.png +0 -0
  343. package/ui/public/sprites/11-D-3.png +0 -0
  344. package/ui/public/sprites/11-L-1.png +0 -0
  345. package/ui/public/sprites/11-R-1.png +0 -0
  346. package/ui/public/sprites/12-D-1.png +0 -0
  347. package/ui/public/sprites/12-D-2.png +0 -0
  348. package/ui/public/sprites/12-D-3.png +0 -0
  349. package/ui/public/sprites/12-L-1.png +0 -0
  350. package/ui/public/sprites/12-R-1.png +0 -0
  351. package/ui/public/sprites/13-D-1.png +0 -0
  352. package/ui/public/sprites/13-D-2.png +0 -0
  353. package/ui/public/sprites/13-D-3.png +0 -0
  354. package/ui/public/sprites/13-L-1.png +0 -0
  355. package/ui/public/sprites/13-R-1.png +0 -0
  356. package/ui/public/sprites/14-D-1.png +0 -0
  357. package/ui/public/sprites/14-D-2.png +0 -0
  358. package/ui/public/sprites/14-D-3.png +0 -0
  359. package/ui/public/sprites/14-L-1.png +0 -0
  360. package/ui/public/sprites/14-R-1.png +0 -0
  361. package/ui/public/sprites/2-D-1.png +0 -0
  362. package/ui/public/sprites/2-D-2.png +0 -0
  363. package/ui/public/sprites/2-D-3.png +0 -0
  364. package/ui/public/sprites/2-L-1.png +0 -0
  365. package/ui/public/sprites/2-R-1.png +0 -0
  366. package/ui/public/sprites/3-D-1.png +0 -0
  367. package/ui/public/sprites/3-D-2.png +0 -0
  368. package/ui/public/sprites/3-D-3.png +0 -0
  369. package/ui/public/sprites/3-L-1.png +0 -0
  370. package/ui/public/sprites/3-R-1.png +0 -0
  371. package/ui/public/sprites/4-D-1.png +0 -0
  372. package/ui/public/sprites/4-D-2.png +0 -0
  373. package/ui/public/sprites/4-D-3.png +0 -0
  374. package/ui/public/sprites/4-L-1.png +0 -0
  375. package/ui/public/sprites/4-R-1.png +0 -0
  376. package/ui/public/sprites/5-D-1.png +0 -0
  377. package/ui/public/sprites/5-D-2.png +0 -0
  378. package/ui/public/sprites/5-D-3.png +0 -0
  379. package/ui/public/sprites/5-L-1.png +0 -0
  380. package/ui/public/sprites/5-R-1.png +0 -0
  381. package/ui/public/sprites/6-D-1.png +0 -0
  382. package/ui/public/sprites/6-D-2.png +0 -0
  383. package/ui/public/sprites/6-D-3.png +0 -0
  384. package/ui/public/sprites/6-L-1.png +0 -0
  385. package/ui/public/sprites/6-R-1.png +0 -0
  386. package/ui/public/sprites/7-D-1.png +0 -0
  387. package/ui/public/sprites/7-D-2.png +0 -0
  388. package/ui/public/sprites/7-D-3.png +0 -0
  389. package/ui/public/sprites/7-L-1.png +0 -0
  390. package/ui/public/sprites/7-R-1.png +0 -0
  391. package/ui/public/sprites/8-D-1.png +0 -0
  392. package/ui/public/sprites/8-D-2.png +0 -0
  393. package/ui/public/sprites/8-D-3.png +0 -0
  394. package/ui/public/sprites/8-L-1.png +0 -0
  395. package/ui/public/sprites/8-R-1.png +0 -0
  396. package/ui/public/sprites/9-D-1.png +0 -0
  397. package/ui/public/sprites/9-D-2.png +0 -0
  398. package/ui/public/sprites/9-D-3.png +0 -0
  399. package/ui/public/sprites/9-L-1.png +0 -0
  400. package/ui/public/sprites/9-R-1.png +0 -0
  401. package/ui/public/sprites/ceo-lobster.png +0 -0
  402. package/ui/public/sw.js +42 -0
  403. package/ui/public/worktree-favicon-16x16.png +0 -0
  404. package/ui/public/worktree-favicon-32x32.png +0 -0
  405. package/ui/public/worktree-favicon.ico +0 -0
  406. package/ui/public/worktree-favicon.svg +9 -0
  407. package/ui/src/App.tsx +354 -0
  408. package/ui/src/adapters/claude-local/config-fields.tsx +138 -0
  409. package/ui/src/adapters/claude-local/index.ts +13 -0
  410. package/ui/src/adapters/codex-local/config-fields.tsx +104 -0
  411. package/ui/src/adapters/codex-local/index.ts +13 -0
  412. package/ui/src/adapters/cursor/config-fields.tsx +49 -0
  413. package/ui/src/adapters/cursor/index.ts +13 -0
  414. package/ui/src/adapters/gemini-local/config-fields.tsx +51 -0
  415. package/ui/src/adapters/gemini-local/index.ts +13 -0
  416. package/ui/src/adapters/http/build-config.ts +9 -0
  417. package/ui/src/adapters/http/config-fields.tsx +38 -0
  418. package/ui/src/adapters/http/index.ts +12 -0
  419. package/ui/src/adapters/http/parse-stdout.ts +5 -0
  420. package/ui/src/adapters/index.ts +9 -0
  421. package/ui/src/adapters/local-workspace-runtime-fields.tsx +5 -0
  422. package/ui/src/adapters/openclaw-gateway/config-fields.tsx +237 -0
  423. package/ui/src/adapters/openclaw-gateway/index.ts +13 -0
  424. package/ui/src/adapters/opencode-local/config-fields.tsx +72 -0
  425. package/ui/src/adapters/opencode-local/index.ts +13 -0
  426. package/ui/src/adapters/pi-local/config-fields.tsx +49 -0
  427. package/ui/src/adapters/pi-local/index.ts +13 -0
  428. package/ui/src/adapters/process/build-config.ts +18 -0
  429. package/ui/src/adapters/process/config-fields.tsx +77 -0
  430. package/ui/src/adapters/process/index.ts +12 -0
  431. package/ui/src/adapters/process/parse-stdout.ts +5 -0
  432. package/ui/src/adapters/registry.ts +34 -0
  433. package/ui/src/adapters/runtime-json-fields.tsx +122 -0
  434. package/ui/src/adapters/transcript.test.ts +30 -0
  435. package/ui/src/adapters/transcript.ts +62 -0
  436. package/ui/src/adapters/types.ts +34 -0
  437. package/ui/src/api/access.ts +160 -0
  438. package/ui/src/api/activity.ts +37 -0
  439. package/ui/src/api/agents.ts +194 -0
  440. package/ui/src/api/approvals.ts +25 -0
  441. package/ui/src/api/assets.ts +30 -0
  442. package/ui/src/api/auth.ts +74 -0
  443. package/ui/src/api/budgets.ts +21 -0
  444. package/ui/src/api/client.ts +50 -0
  445. package/ui/src/api/companies.ts +59 -0
  446. package/ui/src/api/companySkills.ts +55 -0
  447. package/ui/src/api/costs.ts +60 -0
  448. package/ui/src/api/dashboard.ts +7 -0
  449. package/ui/src/api/execution-workspaces.ts +27 -0
  450. package/ui/src/api/goals.ts +12 -0
  451. package/ui/src/api/health.ts +41 -0
  452. package/ui/src/api/heartbeats.ts +62 -0
  453. package/ui/src/api/index.ts +18 -0
  454. package/ui/src/api/instanceSettings.ts +19 -0
  455. package/ui/src/api/issues.ts +115 -0
  456. package/ui/src/api/plugins.ts +424 -0
  457. package/ui/src/api/projects.ts +34 -0
  458. package/ui/src/api/routines.ts +59 -0
  459. package/ui/src/api/secrets.ts +26 -0
  460. package/ui/src/api/sidebarBadges.ts +7 -0
  461. package/ui/src/components/AccountingModelCard.tsx +69 -0
  462. package/ui/src/components/ActiveAgentsPanel.tsx +157 -0
  463. package/ui/src/components/ActivityCharts.tsx +264 -0
  464. package/ui/src/components/ActivityRow.tsx +147 -0
  465. package/ui/src/components/AgentActionButtons.tsx +51 -0
  466. package/ui/src/components/AgentConfigForm.tsx +1468 -0
  467. package/ui/src/components/AgentIconPicker.tsx +81 -0
  468. package/ui/src/components/AgentProperties.tsx +107 -0
  469. package/ui/src/components/ApprovalCard.tsx +107 -0
  470. package/ui/src/components/ApprovalPayload.tsx +134 -0
  471. package/ui/src/components/AsciiArtAnimation.tsx +355 -0
  472. package/ui/src/components/BillerSpendCard.tsx +146 -0
  473. package/ui/src/components/BreadcrumbBar.tsx +113 -0
  474. package/ui/src/components/BudgetIncidentCard.tsx +101 -0
  475. package/ui/src/components/BudgetPolicyCard.tsx +220 -0
  476. package/ui/src/components/BudgetSidebarMarker.tsx +13 -0
  477. package/ui/src/components/ClaudeSubscriptionPanel.tsx +141 -0
  478. package/ui/src/components/CodexSubscriptionPanel.tsx +158 -0
  479. package/ui/src/components/CommandPalette.tsx +239 -0
  480. package/ui/src/components/CommentThread.tsx +503 -0
  481. package/ui/src/components/CompanyPatternIcon.tsx +212 -0
  482. package/ui/src/components/CompanyRail.tsx +329 -0
  483. package/ui/src/components/CompanySwitcher.tsx +81 -0
  484. package/ui/src/components/CopyText.tsx +56 -0
  485. package/ui/src/components/DevRestartBanner.tsx +89 -0
  486. package/ui/src/components/EmptyState.tsx +27 -0
  487. package/ui/src/components/EntityRow.tsx +69 -0
  488. package/ui/src/components/FilterBar.tsx +39 -0
  489. package/ui/src/components/FinanceBillerCard.tsx +45 -0
  490. package/ui/src/components/FinanceKindCard.tsx +44 -0
  491. package/ui/src/components/FinanceTimelineCard.tsx +72 -0
  492. package/ui/src/components/GoalProperties.tsx +165 -0
  493. package/ui/src/components/GoalTree.tsx +118 -0
  494. package/ui/src/components/Identity.tsx +39 -0
  495. package/ui/src/components/InlineEditor.tsx +248 -0
  496. package/ui/src/components/InlineEntitySelector.tsx +206 -0
  497. package/ui/src/components/InstanceSidebar.tsx +53 -0
  498. package/ui/src/components/IssueDocumentsSection.tsx +892 -0
  499. package/ui/src/components/IssueProperties.tsx +621 -0
  500. package/ui/src/components/IssueRow.tsx +149 -0
  501. package/ui/src/components/IssueWorkspaceCard.tsx +404 -0
  502. package/ui/src/components/IssuesList.tsx +889 -0
  503. package/ui/src/components/JsonSchemaForm.tsx +1048 -0
  504. package/ui/src/components/KanbanBoard.tsx +275 -0
  505. package/ui/src/components/Layout.tsx +441 -0
  506. package/ui/src/components/LiveRunWidget.tsx +160 -0
  507. package/ui/src/components/MarkdownBody.test.tsx +50 -0
  508. package/ui/src/components/MarkdownBody.tsx +152 -0
  509. package/ui/src/components/MarkdownEditor.tsx +622 -0
  510. package/ui/src/components/MetricCard.tsx +53 -0
  511. package/ui/src/components/MobileBottomNav.tsx +123 -0
  512. package/ui/src/components/NewAgentDialog.tsx +223 -0
  513. package/ui/src/components/NewGoalDialog.tsx +283 -0
  514. package/ui/src/components/NewIssueDialog.tsx +1473 -0
  515. package/ui/src/components/NewProjectDialog.tsx +451 -0
  516. package/ui/src/components/OnboardingWizard.tsx +1392 -0
  517. package/ui/src/components/OpenCodeLogoIcon.tsx +22 -0
  518. package/ui/src/components/PackageFileTree.tsx +318 -0
  519. package/ui/src/components/PageSkeleton.tsx +180 -0
  520. package/ui/src/components/PageTabBar.tsx +45 -0
  521. package/ui/src/components/PathInstructionsModal.tsx +143 -0
  522. package/ui/src/components/PriorityIcon.tsx +77 -0
  523. package/ui/src/components/ProjectProperties.tsx +1127 -0
  524. package/ui/src/components/PropertiesPanel.tsx +29 -0
  525. package/ui/src/components/ProviderQuotaCard.tsx +417 -0
  526. package/ui/src/components/QuotaBar.tsx +65 -0
  527. package/ui/src/components/ReportsToPicker.tsx +127 -0
  528. package/ui/src/components/ScheduleEditor.tsx +344 -0
  529. package/ui/src/components/ScrollToBottom.tsx +79 -0
  530. package/ui/src/components/Sidebar.tsx +130 -0
  531. package/ui/src/components/SidebarAgents.tsx +146 -0
  532. package/ui/src/components/SidebarNavItem.tsx +92 -0
  533. package/ui/src/components/SidebarProjects.tsx +234 -0
  534. package/ui/src/components/SidebarSection.tsx +17 -0
  535. package/ui/src/components/StatusBadge.tsx +15 -0
  536. package/ui/src/components/StatusIcon.tsx +71 -0
  537. package/ui/src/components/SwipeToArchive.tsx +152 -0
  538. package/ui/src/components/ToastViewport.tsx +99 -0
  539. package/ui/src/components/WorktreeBanner.tsx +25 -0
  540. package/ui/src/components/agent-config-defaults.ts +31 -0
  541. package/ui/src/components/agent-config-primitives.tsx +476 -0
  542. package/ui/src/components/transcript/RunTranscriptView.test.tsx +84 -0
  543. package/ui/src/components/transcript/RunTranscriptView.tsx +1015 -0
  544. package/ui/src/components/transcript/useLiveRunTranscripts.ts +297 -0
  545. package/ui/src/components/ui/avatar.tsx +107 -0
  546. package/ui/src/components/ui/badge.tsx +48 -0
  547. package/ui/src/components/ui/breadcrumb.tsx +109 -0
  548. package/ui/src/components/ui/button.tsx +64 -0
  549. package/ui/src/components/ui/card.tsx +92 -0
  550. package/ui/src/components/ui/checkbox.tsx +32 -0
  551. package/ui/src/components/ui/collapsible.tsx +33 -0
  552. package/ui/src/components/ui/command.tsx +194 -0
  553. package/ui/src/components/ui/dialog.tsx +156 -0
  554. package/ui/src/components/ui/dropdown-menu.tsx +257 -0
  555. package/ui/src/components/ui/input.tsx +21 -0
  556. package/ui/src/components/ui/label.tsx +22 -0
  557. package/ui/src/components/ui/popover.tsx +88 -0
  558. package/ui/src/components/ui/scroll-area.tsx +56 -0
  559. package/ui/src/components/ui/select.tsx +188 -0
  560. package/ui/src/components/ui/separator.tsx +28 -0
  561. package/ui/src/components/ui/sheet.tsx +143 -0
  562. package/ui/src/components/ui/skeleton.tsx +13 -0
  563. package/ui/src/components/ui/tabs.tsx +89 -0
  564. package/ui/src/components/ui/textarea.tsx +18 -0
  565. package/ui/src/components/ui/tooltip.tsx +57 -0
  566. package/ui/src/components/visual-office/AgentAvatar.tsx +99 -0
  567. package/ui/src/components/visual-office/OfficeViewExact.tsx +417 -0
  568. package/ui/src/components/visual-office/i18n.ts +52 -0
  569. package/ui/src/components/visual-office/office-view/CliUsagePanel.tsx +240 -0
  570. package/ui/src/components/visual-office/office-view/VirtualPadOverlay.tsx +104 -0
  571. package/ui/src/components/visual-office/office-view/buildScene-break-room.ts +248 -0
  572. package/ui/src/components/visual-office/office-view/buildScene-ceo-hallway.ts +345 -0
  573. package/ui/src/components/visual-office/office-view/buildScene-department-agent.ts +242 -0
  574. package/ui/src/components/visual-office/office-view/buildScene-departments.ts +360 -0
  575. package/ui/src/components/visual-office/office-view/buildScene-final-layers.ts +113 -0
  576. package/ui/src/components/visual-office/office-view/buildScene-types.ts +91 -0
  577. package/ui/src/components/visual-office/office-view/buildScene.ts +232 -0
  578. package/ui/src/components/visual-office/office-view/drawing-core.ts +374 -0
  579. package/ui/src/components/visual-office/office-view/drawing-furniture-a.ts +338 -0
  580. package/ui/src/components/visual-office/office-view/drawing-furniture-b.ts +241 -0
  581. package/ui/src/components/visual-office/office-view/model.ts +301 -0
  582. package/ui/src/components/visual-office/office-view/officeTicker.ts +455 -0
  583. package/ui/src/components/visual-office/office-view/officeTickerRoomAndDelivery.ts +133 -0
  584. package/ui/src/components/visual-office/office-view/themes-locale.ts +460 -0
  585. package/ui/src/components/visual-office/office-view/useCliUsage.ts +37 -0
  586. package/ui/src/components/visual-office/office-view/useOfficeDeliveryEffects.ts +465 -0
  587. package/ui/src/components/visual-office/office-view/useOfficePixiRuntime.ts +282 -0
  588. package/ui/src/components/visual-office/types.ts +123 -0
  589. package/ui/src/context/BreadcrumbContext.tsx +44 -0
  590. package/ui/src/context/CompanyContext.tsx +151 -0
  591. package/ui/src/context/DialogContext.tsx +135 -0
  592. package/ui/src/context/LiveUpdatesProvider.test.ts +119 -0
  593. package/ui/src/context/LiveUpdatesProvider.tsx +760 -0
  594. package/ui/src/context/PanelContext.tsx +73 -0
  595. package/ui/src/context/SidebarContext.tsx +43 -0
  596. package/ui/src/context/ThemeContext.tsx +83 -0
  597. package/ui/src/context/ToastContext.tsx +172 -0
  598. package/ui/src/fixtures/runTranscriptFixtures.ts +226 -0
  599. package/ui/src/hooks/useAgentOrder.ts +105 -0
  600. package/ui/src/hooks/useAutosaveIndicator.ts +72 -0
  601. package/ui/src/hooks/useCompanyPageMemory.test.ts +90 -0
  602. package/ui/src/hooks/useCompanyPageMemory.ts +79 -0
  603. package/ui/src/hooks/useDateRange.ts +120 -0
  604. package/ui/src/hooks/useInboxBadge.ts +132 -0
  605. package/ui/src/hooks/useKeyboardShortcuts.ts +40 -0
  606. package/ui/src/hooks/useProjectOrder.ts +106 -0
  607. package/ui/src/index.css +770 -0
  608. package/ui/src/lib/agent-icons.ts +99 -0
  609. package/ui/src/lib/agent-order.ts +107 -0
  610. package/ui/src/lib/agent-skills-state.test.ts +90 -0
  611. package/ui/src/lib/agent-skills-state.ts +41 -0
  612. package/ui/src/lib/assignees.test.ts +92 -0
  613. package/ui/src/lib/assignees.ts +82 -0
  614. package/ui/src/lib/color-contrast.ts +107 -0
  615. package/ui/src/lib/company-export-selection.test.ts +41 -0
  616. package/ui/src/lib/company-export-selection.ts +57 -0
  617. package/ui/src/lib/company-page-memory.ts +65 -0
  618. package/ui/src/lib/company-portability-sidebar.test.ts +101 -0
  619. package/ui/src/lib/company-portability-sidebar.ts +62 -0
  620. package/ui/src/lib/company-routes.ts +88 -0
  621. package/ui/src/lib/company-selection.test.ts +34 -0
  622. package/ui/src/lib/company-selection.ts +18 -0
  623. package/ui/src/lib/groupBy.ts +11 -0
  624. package/ui/src/lib/inbox.test.ts +404 -0
  625. package/ui/src/lib/inbox.ts +292 -0
  626. package/ui/src/lib/instance-settings.test.ts +26 -0
  627. package/ui/src/lib/instance-settings.ts +25 -0
  628. package/ui/src/lib/issueDetailBreadcrumb.ts +24 -0
  629. package/ui/src/lib/legacy-agent-config.test.ts +40 -0
  630. package/ui/src/lib/legacy-agent-config.ts +17 -0
  631. package/ui/src/lib/mention-aware-link-node.test.ts +50 -0
  632. package/ui/src/lib/mention-aware-link-node.ts +67 -0
  633. package/ui/src/lib/mention-chips.ts +168 -0
  634. package/ui/src/lib/mention-deletion.test.ts +87 -0
  635. package/ui/src/lib/mention-deletion.ts +143 -0
  636. package/ui/src/lib/model-utils.ts +16 -0
  637. package/ui/src/lib/onboarding-goal.test.ts +22 -0
  638. package/ui/src/lib/onboarding-goal.ts +18 -0
  639. package/ui/src/lib/onboarding-launch.test.ts +131 -0
  640. package/ui/src/lib/onboarding-launch.ts +54 -0
  641. package/ui/src/lib/onboarding-route.test.ts +80 -0
  642. package/ui/src/lib/onboarding-route.ts +51 -0
  643. package/ui/src/lib/portable-files.ts +42 -0
  644. package/ui/src/lib/project-order.ts +71 -0
  645. package/ui/src/lib/queryKeys.ts +140 -0
  646. package/ui/src/lib/recent-assignees.ts +36 -0
  647. package/ui/src/lib/router.tsx +76 -0
  648. package/ui/src/lib/routine-trigger-patch.test.ts +72 -0
  649. package/ui/src/lib/routine-trigger-patch.ts +31 -0
  650. package/ui/src/lib/status-colors.ts +108 -0
  651. package/ui/src/lib/timeAgo.ts +31 -0
  652. package/ui/src/lib/utils.ts +168 -0
  653. package/ui/src/lib/worktree-branding.ts +65 -0
  654. package/ui/src/lib/zip.test.ts +289 -0
  655. package/ui/src/lib/zip.ts +284 -0
  656. package/ui/src/main.tsx +67 -0
  657. package/ui/src/pages/Activity.tsx +141 -0
  658. package/ui/src/pages/AgentDetail.tsx +4053 -0
  659. package/ui/src/pages/Agents.tsx +415 -0
  660. package/ui/src/pages/ApprovalDetail.tsx +369 -0
  661. package/ui/src/pages/Approvals.tsx +132 -0
  662. package/ui/src/pages/Auth.tsx +180 -0
  663. package/ui/src/pages/BoardClaim.tsx +125 -0
  664. package/ui/src/pages/CliAuth.tsx +184 -0
  665. package/ui/src/pages/Companies.tsx +297 -0
  666. package/ui/src/pages/CompanyExport.tsx +1019 -0
  667. package/ui/src/pages/CompanyImport.tsx +1355 -0
  668. package/ui/src/pages/CompanySettings.tsx +661 -0
  669. package/ui/src/pages/CompanySkills.tsx +1171 -0
  670. package/ui/src/pages/Costs.tsx +1103 -0
  671. package/ui/src/pages/Dashboard.tsx +388 -0
  672. package/ui/src/pages/DesignGuide.tsx +1330 -0
  673. package/ui/src/pages/ExecutionWorkspaceDetail.tsx +82 -0
  674. package/ui/src/pages/GoalDetail.tsx +197 -0
  675. package/ui/src/pages/Goals.tsx +63 -0
  676. package/ui/src/pages/Inbox.tsx +1291 -0
  677. package/ui/src/pages/InstanceExperimentalSettings.tsx +139 -0
  678. package/ui/src/pages/InstanceGeneralSettings.tsx +104 -0
  679. package/ui/src/pages/InstanceSettings.tsx +284 -0
  680. package/ui/src/pages/InviteLanding.tsx +320 -0
  681. package/ui/src/pages/IssueDetail.tsx +1201 -0
  682. package/ui/src/pages/Issues.tsx +116 -0
  683. package/ui/src/pages/MyIssues.tsx +72 -0
  684. package/ui/src/pages/NewAgent.tsx +353 -0
  685. package/ui/src/pages/NotFound.tsx +66 -0
  686. package/ui/src/pages/Org.tsx +132 -0
  687. package/ui/src/pages/OrgChart.tsx +447 -0
  688. package/ui/src/pages/PluginManager.tsx +510 -0
  689. package/ui/src/pages/PluginPage.tsx +156 -0
  690. package/ui/src/pages/PluginSettings.tsx +836 -0
  691. package/ui/src/pages/ProjectDetail.tsx +633 -0
  692. package/ui/src/pages/Projects.tsx +87 -0
  693. package/ui/src/pages/RoutineDetail.tsx +1022 -0
  694. package/ui/src/pages/Routines.tsx +661 -0
  695. package/ui/src/pages/RunTranscriptUxLab.tsx +334 -0
  696. package/ui/src/pages/VisualOffice.tsx +243 -0
  697. package/ui/src/plugins/bridge-init.ts +69 -0
  698. package/ui/src/plugins/bridge.ts +476 -0
  699. package/ui/src/plugins/launchers.tsx +834 -0
  700. package/ui/src/plugins/slots.tsx +855 -0
  701. package/ui/tsconfig.json +21 -0
  702. package/ui/vite.config.ts +23 -0
  703. package/ui/vitest.config.ts +14 -0
  704. 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
+ &rarr; {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
+ &rarr; {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
+