@shepai/cli 1.22.0 → 1.23.0
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/dist/packages/core/src/infrastructure/services/file-dialog.service.d.ts +28 -0
- package/dist/packages/core/src/infrastructure/services/file-dialog.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/file-dialog.service.js +63 -0
- package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.js +16 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor-cli.json +5 -5
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/cursor.json +5 -5
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/vscode.json +2 -2
- package/dist/packages/core/src/infrastructure/services/tool-installer/tools/windsurf.json +1 -1
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.d.ts +11 -0
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.d.ts.map +1 -0
- package/dist/src/presentation/web/app/api/dialog/pick-files/route.js +16 -0
- package/dist/src/presentation/web/app/derive-state.d.ts +12 -0
- package/dist/src/presentation/web/app/derive-state.d.ts.map +1 -0
- package/dist/src/presentation/web/app/derive-state.js +21 -0
- package/dist/src/presentation/web/app/page.d.ts.map +1 -1
- package/dist/src/presentation/web/app/page.js +23 -12
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts +15 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js +102 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts +63 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.js +164 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/index.js +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.d.ts +7 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-create-drawer/pick-files.js +13 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.d.ts +7 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.js +28 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts +19 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/feature-drawer.stories.js +167 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/feature-drawer/index.js +1 -0
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts +2 -5
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node-state-config.js +4 -13
- package/dist/src/presentation/web/components/common/feature-node/feature-node.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.js +7 -9
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts +0 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-node.stories.js +4 -23
- package/dist/src/presentation/web/components/common/feature-node/index.d.ts +1 -3
- package/dist/src/presentation/web/components/common/feature-node/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/index.js +1 -3
- package/dist/src/presentation/web/components/common/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/index.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/index.js +2 -0
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +11 -3
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +4 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +20 -4
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +5 -1
- package/dist/src/presentation/web/components/ui/button.d.ts +1 -1
- package/dist/src/presentation/web/components/ui/drawer.d.ts +17 -0
- package/dist/src/presentation/web/components/ui/drawer.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/drawer.js +45 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.d.ts +17 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/drawer.stories.js +29 -0
- package/dist/src/presentation/web/components/ui/textarea.d.ts +4 -0
- package/dist/src/presentation/web/components/ui/textarea.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/textarea.js +6 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.d.ts +10 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/ui/textarea.stories.js +28 -0
- package/dist/src/presentation/web/lib/features.d.ts +30 -0
- package/dist/src/presentation/web/lib/features.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/features.js +50 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +5 -5
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/web/.next/server/app/_global-error/page.js +1 -1
- package/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/web/.next/server/app/_not-found/page.js +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +2 -2
- package/web/.next/server/app/_not-found.rsc +9 -9
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +9 -9
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/api/dialog/pick-files/route/app-paths-manifest.json +3 -0
- package/web/.next/server/app/api/dialog/pick-files/route/build-manifest.json +11 -0
- package/web/.next/server/app/api/dialog/pick-files/route/server-reference-manifest.json +4 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js +6 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js.map +5 -0
- package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -0
- package/web/.next/server/app/api/dialog/pick-files/route_client-reference-manifest.js +2 -0
- package/web/.next/server/app/api/dialog/pick-folder/route.js +2 -2
- package/web/.next/server/app/api/dialog/pick-folder/route.js.nft.json +1 -1
- package/web/.next/server/app/index.html +2 -2
- package/web/.next/server/app/index.rsc +4 -4
- package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/page/build-manifest.json +3 -3
- package/web/.next/server/app/page.js +3 -3
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/build-manifest.json +3 -3
- package/web/.next/server/app/version/page.js +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version.html +2 -2
- package/web/.next/server/app/version.rsc +4 -4
- package/web/.next/server/app/version.segments/_full.segment.rsc +4 -4
- package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/version.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/version.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_dialog_pick-files_route_actions_f9868286.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__00833626._.js +21 -0
- package/web/.next/server/chunks/[root-of-the-server]__00833626._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__22cabd43._.js +4 -0
- package/web/.next/server/chunks/[root-of-the-server]__22cabd43._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__95785ac7._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__95785ac7._.js.map +1 -0
- package/web/.next/server/chunks/ssr/403f9_next_dist_esm_build_templates_app-page_dbf21e04.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__1eade43f._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__1eade43f._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__6b3c1f2b._.js → [root-of-the-server]__685eaa45._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__71515c64._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__757c7912._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7c64f85c._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__7c64f85c._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8507ac68._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__8507ac68._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__e55e9c7f._.js → [root-of-the-server]__dbd220d2._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fa87595c._.js +1 -1
- package/web/.next/server/chunks/ssr/_09afa42a._.js +1 -1
- package/web/.next/server/chunks/ssr/_09afa42a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_1f9347c8._.js +7 -0
- package/web/.next/server/chunks/ssr/_1f9347c8._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_884ddade._.js +1 -1
- package/web/.next/server/chunks/ssr/_937b37e2._.js +3 -0
- package/web/.next/server/chunks/ssr/_937b37e2._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_a0c04017._.js +1 -5
- package/web/.next/server/chunks/ssr/_a0c04017._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_c5d377cc._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_badge_tsx_964c2a3b._.js +3 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_components_ui_badge_tsx_964c2a3b._.js.map +1 -0
- package/web/.next/server/middleware-build-manifest.js +3 -3
- package/web/.next/server/pages/404.html +2 -2
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/chunks/4431cc2d3628b6f6.js +1 -0
- package/web/.next/static/chunks/49488cbe99543598.css +1 -0
- package/web/.next/static/chunks/70993ebedee7eab0.js +2 -0
- package/web/.next/static/chunks/8bc34062bc0f4ddc.js +1 -0
- package/web/.next/static/chunks/c95b4cf170b7f301.js +1 -0
- package/web/.next/static/chunks/df7d29673e0ac11d.js +1 -0
- package/web/.next/static/chunks/e78d1b265b8110c3.js +1 -0
- package/web/.next/static/chunks/f5099c5888ef9a74.js +5 -0
- package/web/.next/static/chunks/{b29c0572633eb9e4.js → f6b5484377f4e78e.js} +2 -2
- package/web/.next/static/chunks/{turbopack-e0a0178ee911c212.js → turbopack-a5a88a85e7ac8e8d.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/types/link.d.ts +1 -0
- package/web/.next/types/routes.d.ts +2 -1
- package/web/.next/types/validator.ts +9 -0
- package/web/next.config.mjs +1 -1
- package/web/package.json +3 -3
- package/web/.next/server/chunks/[externals]_next_dist_a6d89067._.js +0 -3
- package/web/.next/server/chunks/[root-of-the-server]__ebcd3642._.js +0 -21
- package/web/.next/server/chunks/[root-of-the-server]__ebcd3642._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2127336f._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__44c4e191._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__44c4e191._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__492e833d._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__492e833d._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__80e5bed2._.js +0 -3
- package/web/.next/server/chunks/ssr/_3d152196._.js +0 -3
- package/web/.next/server/chunks/ssr/_3d152196._.js.map +0 -1
- package/web/.next/server/chunks/ssr/node_modules__pnpm_b11d5ad4._.js +0 -3
- package/web/.next/server/chunks/ssr/node_modules__pnpm_b11d5ad4._.js.map +0 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js +0 -3
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +0 -1
- package/web/.next/static/chunks/3701b9d5749e6143.css +0 -1
- package/web/.next/static/chunks/a8030d19b4d73c95.js +0 -1
- package/web/.next/static/chunks/b0d13220be9ac29c.js +0 -1
- package/web/.next/static/chunks/be34161188b4e0a0.js +0 -2
- package/web/.next/static/chunks/c87445f8cbe36c74.js +0 -1
- package/web/.next/static/chunks/f220dad9d38bff95.js +0 -5
- package/web/.next/static/chunks/f74cb59a5a6c94f1.js +0 -1
- /package/web/.next/server/chunks/{[externals]_next_dist_a6d89067._.js.map → 744ca_web__next-internal_server_app_api_dialog_pick-files_route_actions_f9868286.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__6b3c1f2b._.js.map → [root-of-the-server]__685eaa45._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__80e5bed2._.js.map → [root-of-the-server]__71515c64._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__2127336f._.js.map → [root-of-the-server]__757c7912._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__e55e9c7f._.js.map → [root-of-the-server]__dbd220d2._.js.map} +0 -0
- /package/web/.next/static/{50v5TcK4kMIcF3xxCPBNl → WqMlhB4Xv4T13f0oXQ5uG}/_buildManifest.js +0 -0
- /package/web/.next/static/{50v5TcK4kMIcF3xxCPBNl → WqMlhB4Xv4T13f0oXQ5uG}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{50v5TcK4kMIcF3xxCPBNl → WqMlhB4Xv4T13f0oXQ5uG}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface FileAttachment {
|
|
2
|
+
path: string;
|
|
3
|
+
name: string;
|
|
4
|
+
size: number;
|
|
5
|
+
}
|
|
6
|
+
export interface FileDialogDeps {
|
|
7
|
+
platform: NodeJS.Platform;
|
|
8
|
+
exec: (cmd: string, opts?: {
|
|
9
|
+
encoding: BufferEncoding;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}) => string;
|
|
12
|
+
stat: (filePath: string) => {
|
|
13
|
+
size: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare class FileDialogService {
|
|
17
|
+
private deps;
|
|
18
|
+
constructor(deps?: Partial<FileDialogDeps>);
|
|
19
|
+
/** Returns the OS-specific command string, or null if the platform is unsupported. */
|
|
20
|
+
getCommand(): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Opens a native file picker dialog and returns metadata for each selected file.
|
|
23
|
+
* Returns `null` if the user cancels the dialog.
|
|
24
|
+
* Throws if the platform is unsupported or the command fails unexpectedly.
|
|
25
|
+
*/
|
|
26
|
+
pickFiles(): FileAttachment[] | null;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=file-dialog.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-dialog.service.d.ts","sourceRoot":"","sources":["../../../../../../packages/core/src/infrastructure/services/file-dialog.service.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,EAAE,cAAc,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IACrF,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAkBD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,IAAI,CAAiB;gBAEjB,IAAI,GAAE,OAAO,CAAC,cAAc,CAAM;IAI9C,sFAAsF;IACtF,UAAU,IAAI,MAAM,GAAG,IAAI;IAI3B;;;;OAIG;IACH,SAAS,IAAI,cAAc,EAAE,GAAG,IAAI;CA8BrC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
const defaultDeps = {
|
|
4
|
+
platform: process.platform,
|
|
5
|
+
exec: (cmd, opts) => execSync(cmd, opts),
|
|
6
|
+
stat: (filePath) => statSync(filePath),
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Platform-specific commands that open a native OS file picker dialog.
|
|
10
|
+
* Each returns newline-separated absolute file paths on stdout, or exits non-zero on cancel.
|
|
11
|
+
*/
|
|
12
|
+
const PLATFORM_COMMANDS = {
|
|
13
|
+
darwin: `osascript -e 'set f to (choose file with prompt "Select files" with multiple selections allowed)' -e 'set paths to {}' -e 'repeat with i in f' -e 'copy POSIX path of i to end of paths' -e 'end repeat' -e 'set text item delimiters to "\n"' -e 'paths as text'`,
|
|
14
|
+
linux: `zenity --file-selection --multiple --separator="\\n" --title="Select files" 2>/dev/null`,
|
|
15
|
+
win32: `powershell -NoProfile -Command "Add-Type -AssemblyName System.Windows.Forms; $d = New-Object System.Windows.Forms.OpenFileDialog; $d.Multiselect = $true; $d.Title = 'Select files'; if ($d.ShowDialog() -eq 'OK') { $d.FileNames -join [char]10 } else { exit 1 }"`,
|
|
16
|
+
};
|
|
17
|
+
export class FileDialogService {
|
|
18
|
+
deps;
|
|
19
|
+
constructor(deps = {}) {
|
|
20
|
+
this.deps = { ...defaultDeps, ...deps };
|
|
21
|
+
}
|
|
22
|
+
/** Returns the OS-specific command string, or null if the platform is unsupported. */
|
|
23
|
+
getCommand() {
|
|
24
|
+
return PLATFORM_COMMANDS[this.deps.platform] ?? null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Opens a native file picker dialog and returns metadata for each selected file.
|
|
28
|
+
* Returns `null` if the user cancels the dialog.
|
|
29
|
+
* Throws if the platform is unsupported or the command fails unexpectedly.
|
|
30
|
+
*/
|
|
31
|
+
pickFiles() {
|
|
32
|
+
const command = this.getCommand();
|
|
33
|
+
if (!command) {
|
|
34
|
+
throw new Error(`Unsupported platform: ${this.deps.platform}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const result = this.deps.exec(command, { encoding: 'utf-8', timeout: 60_000 });
|
|
38
|
+
const paths = result
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.filter((line) => line.length > 0);
|
|
42
|
+
if (paths.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
return paths.map((filePath) => {
|
|
45
|
+
const stats = this.deps.stat(filePath);
|
|
46
|
+
return {
|
|
47
|
+
path: filePath,
|
|
48
|
+
name: filePath.split(/[/\\]/).pop(),
|
|
49
|
+
size: stats.size,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (isExecError(error) && error.status === 1) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isExecError(error) {
|
|
62
|
+
return typeof error === 'object' && error !== null && 'status' in error;
|
|
63
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-installer.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/tool-installer/tool-installer.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAClH,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAEnB,MAAM,qCAAqC,CAAC;AA6B7C,qBACa,wBAAyB,YAAW,qBAAqB;IACpE;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA8B1E;;;OAGG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAqB9D;;;OAGG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"tool-installer.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/tool-installer/tool-installer.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sEAAsE,CAAC;AAClH,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAEnB,MAAM,qCAAqC,CAAC;AA6B7C,qBACa,wBAAyB,YAAW,qBAAqB;IACpE;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA8B1E;;;OAGG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAqB9D;;;OAGG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC,sBAAsB,CAAC;IAuElC;;OAEG;IACH,OAAO,CAAC,6BAA6B;CA4BtC"}
|
package/dist/packages/core/src/infrastructure/services/tool-installer/tool-installer.service.js
CHANGED
|
@@ -134,9 +134,23 @@ let ToolInstallerServiceImpl = class ToolInstallerServiceImpl {
|
|
|
134
134
|
: createAvailableStatus(toolName);
|
|
135
135
|
resolve(returnStatus);
|
|
136
136
|
};
|
|
137
|
-
child.on('close', (code) => {
|
|
137
|
+
child.on('close', async (code) => {
|
|
138
138
|
if (code === 0) {
|
|
139
|
-
|
|
139
|
+
// Verify the installation by checking if binary exists
|
|
140
|
+
const metadata = TOOL_METADATA[toolName];
|
|
141
|
+
if (metadata) {
|
|
142
|
+
const binary = resolveBinary(metadata);
|
|
143
|
+
const verifyResult = await checkBinaryExists(binary);
|
|
144
|
+
if (verifyResult.found) {
|
|
145
|
+
cleanup();
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
cleanup(`Installation completed but binary '${binary}' not found in PATH. The installation may require a shell restart or PATH update.`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
cleanup();
|
|
153
|
+
}
|
|
140
154
|
}
|
|
141
155
|
else {
|
|
142
156
|
cleanup(`Installation failed with exit code ${code}. Output: ${output.slice(0, 200)}`);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "Cursor CLI",
|
|
3
|
-
"summary": "AI-powered
|
|
4
|
-
"description": "Cursor CLI
|
|
3
|
+
"summary": "AI-powered terminal coding agent",
|
|
4
|
+
"description": "Cursor CLI is a terminal-based AI coding agent that brings conversational AI assistance directly to your command line for any IDE or terminal.",
|
|
5
5
|
"category": "cli-agent",
|
|
6
|
-
"binary": "cursor",
|
|
6
|
+
"binary": "cursor-agent",
|
|
7
7
|
"packageManager": "curl",
|
|
8
8
|
"commands": {
|
|
9
9
|
"linux": "curl https://cursor.com/install -fsS | bash",
|
|
10
10
|
"darwin": "curl https://cursor.com/install -fsS | bash"
|
|
11
11
|
},
|
|
12
12
|
"timeout": 300000,
|
|
13
|
-
"documentationUrl": "https://
|
|
14
|
-
"verifyCommand": "cursor --version",
|
|
13
|
+
"documentationUrl": "https://www.codecademy.com/article/getting-started-with-cursor-cli",
|
|
14
|
+
"verifyCommand": "cursor-agent --version",
|
|
15
15
|
"autoInstall": true
|
|
16
16
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"description": "Cursor is an AI-powered code editor built on VS Code, designed to streamline development workflows with advanced AI-assisted coding capabilities.",
|
|
5
5
|
"category": "ide",
|
|
6
6
|
"binary": "cursor",
|
|
7
|
-
"packageManager": "
|
|
7
|
+
"packageManager": "manual",
|
|
8
8
|
"commands": {
|
|
9
|
-
"linux": "
|
|
10
|
-
"darwin": "
|
|
9
|
+
"linux": "Download from https://cursor.com/download and install the AppImage manually",
|
|
10
|
+
"darwin": "Download from https://cursor.com/download or use: brew install --cask cursor"
|
|
11
11
|
},
|
|
12
12
|
"timeout": 600000,
|
|
13
|
-
"documentationUrl": "https://
|
|
13
|
+
"documentationUrl": "https://cursor.com/download",
|
|
14
14
|
"verifyCommand": "cursor --version",
|
|
15
|
-
"autoInstall":
|
|
15
|
+
"autoInstall": false,
|
|
16
16
|
"openDirectory": "cursor ."
|
|
17
17
|
}
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"binary": "code",
|
|
7
7
|
"packageManager": "apt",
|
|
8
8
|
"commands": {
|
|
9
|
-
"linux": "sudo apt update && sudo apt install -y code",
|
|
9
|
+
"linux": "curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | sudo gpg --dearmor -o /usr/share/keyrings/packages.microsoft.gpg && echo \"deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main\" | sudo tee /etc/apt/sources.list.d/vscode.list > /dev/null && sudo apt-get update && sudo apt-get install -y code",
|
|
10
10
|
"darwin": "brew install visual-studio-code"
|
|
11
11
|
},
|
|
12
12
|
"timeout": 300000,
|
|
13
13
|
"documentationUrl": "https://code.visualstudio.com/docs/setup/linux",
|
|
14
|
-
"verifyCommand": "code --version",
|
|
14
|
+
"verifyCommand": "code --version --no-sandbox --user-data-dir=/tmp/vscode-test",
|
|
15
15
|
"autoInstall": true,
|
|
16
16
|
"openDirectory": "code ."
|
|
17
17
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"timeout": 300000,
|
|
13
13
|
"documentationUrl": "https://docs.windsurf.com/windsurf/getting-started",
|
|
14
|
-
"verifyCommand": "windsurf --version",
|
|
14
|
+
"verifyCommand": "windsurf --version --no-sandbox --user-data-dir=/tmp/windsurf-test",
|
|
15
15
|
"autoInstall": true,
|
|
16
16
|
"openDirectory": "windsurf ."
|
|
17
17
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
export declare function POST(): Promise<NextResponse<{
|
|
3
|
+
files: null;
|
|
4
|
+
cancelled: boolean;
|
|
5
|
+
}> | NextResponse<{
|
|
6
|
+
files: import("../../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js").FileAttachment[];
|
|
7
|
+
cancelled: boolean;
|
|
8
|
+
}> | NextResponse<{
|
|
9
|
+
error: string;
|
|
10
|
+
}>>;
|
|
11
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/dialog/pick-files/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,wBAAsB,IAAI;;;;;;;;IAezB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { FileDialogService } from '../../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
3
|
+
export async function POST() {
|
|
4
|
+
const service = new FileDialogService();
|
|
5
|
+
try {
|
|
6
|
+
const files = service.pickFiles();
|
|
7
|
+
if (files === null) {
|
|
8
|
+
return NextResponse.json({ files: null, cancelled: true });
|
|
9
|
+
}
|
|
10
|
+
return NextResponse.json({ files, cancelled: false });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const message = error instanceof Error ? error.message : 'Failed to open file dialog';
|
|
14
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FeatureNodeState } from '../components/common/feature-node/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Derives the UI feature node state from lifecycle phase and agent run status.
|
|
4
|
+
* - 'maintain' lifecycle always returns done (completed feature)
|
|
5
|
+
* - Agent status maps: completed->done, failed->error, waiting_approval->action-required
|
|
6
|
+
* - Default: running with 0 progress
|
|
7
|
+
*/
|
|
8
|
+
export declare function deriveState(lifecycle: string, agentStatus: string | undefined): {
|
|
9
|
+
state: FeatureNodeState;
|
|
10
|
+
progress: number;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=derive-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-state.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/derive-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEzE;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B;IAAE,KAAK,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAe/C"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the UI feature node state from lifecycle phase and agent run status.
|
|
3
|
+
* - 'maintain' lifecycle always returns done (completed feature)
|
|
4
|
+
* - Agent status maps: completed->done, failed->error, waiting_approval->action-required
|
|
5
|
+
* - Default: running with 0 progress
|
|
6
|
+
*/
|
|
7
|
+
export function deriveState(lifecycle, agentStatus) {
|
|
8
|
+
if (lifecycle === 'maintain') {
|
|
9
|
+
return { state: 'done', progress: 100 };
|
|
10
|
+
}
|
|
11
|
+
switch (agentStatus) {
|
|
12
|
+
case 'completed':
|
|
13
|
+
return { state: 'done', progress: 100 };
|
|
14
|
+
case 'failed':
|
|
15
|
+
return { state: 'error', progress: 0 };
|
|
16
|
+
case 'waiting_approval':
|
|
17
|
+
return { state: 'action-required', progress: 0 };
|
|
18
|
+
default:
|
|
19
|
+
return { state: 'running', progress: 0 };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/app/page.tsx"],"names":[],"mappings":"AA2BA,wBAA8B,QAAQ,qDA2ErC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { ControlCenter } from '../components/features/control-center/index.js';
|
|
3
|
-
import { getFeatures
|
|
3
|
+
import { getFeatures } from '../lib/features.js';
|
|
4
|
+
import { deriveState } from './derive-state.js';
|
|
4
5
|
import { layoutWithDagre } from '../lib/layout-with-dagre.js';
|
|
5
|
-
import { deriveNodeState, deriveProgress, } from '../components/common/feature-node/derive-feature-state.js';
|
|
6
6
|
/** Map domain SdlcLifecycle enum values to UI FeatureLifecyclePhase (1:1). */
|
|
7
7
|
const lifecycleMap = {
|
|
8
8
|
Requirements: 'requirements',
|
|
@@ -12,18 +12,24 @@ const lifecycleMap = {
|
|
|
12
12
|
'Deploy & QA': 'deploy',
|
|
13
13
|
Maintain: 'maintain',
|
|
14
14
|
};
|
|
15
|
+
/** Map agent graph node names (from agent_run.result) to UI lifecycle phases. */
|
|
16
|
+
const nodeToLifecyclePhase = {
|
|
17
|
+
analyze: 'requirements',
|
|
18
|
+
requirements: 'requirements',
|
|
19
|
+
research: 'research',
|
|
20
|
+
plan: 'implementation',
|
|
21
|
+
implement: 'implementation',
|
|
22
|
+
};
|
|
15
23
|
export default async function HomePage() {
|
|
16
24
|
const features = await getFeatures();
|
|
17
|
-
// Load agent runs for all features in parallel (mirrors CLI feat ls)
|
|
18
|
-
const agentRuns = await Promise.all(features.map((f) => (f.agentRunId ? getAgentRun(f.agentRunId) : Promise.resolve(null))));
|
|
19
25
|
// Group features by repository path
|
|
20
26
|
const featuresByRepo = {};
|
|
21
|
-
features.forEach((f
|
|
27
|
+
features.forEach((f) => {
|
|
22
28
|
const repoKey = f.repositoryPath;
|
|
23
29
|
if (!featuresByRepo[repoKey]) {
|
|
24
30
|
featuresByRepo[repoKey] = [];
|
|
25
31
|
}
|
|
26
|
-
featuresByRepo[repoKey].push(
|
|
32
|
+
featuresByRepo[repoKey].push(f);
|
|
27
33
|
});
|
|
28
34
|
const nodes = [];
|
|
29
35
|
const edges = [];
|
|
@@ -36,17 +42,22 @@ export default async function HomePage() {
|
|
|
36
42
|
position: { x: 0, y: 0 },
|
|
37
43
|
data: { name: repoName },
|
|
38
44
|
});
|
|
39
|
-
repoFeatures.forEach((
|
|
40
|
-
const
|
|
41
|
-
|
|
45
|
+
repoFeatures.forEach((feature) => {
|
|
46
|
+
const agentNode = feature.agentResult?.startsWith('node:')
|
|
47
|
+
? feature.agentResult.slice(5)
|
|
48
|
+
: undefined;
|
|
49
|
+
const lifecycle = feature.agentStatus === 'completed'
|
|
50
|
+
? 'maintain'
|
|
51
|
+
: ((agentNode ? nodeToLifecyclePhase[agentNode] : undefined) ??
|
|
52
|
+
lifecycleMap[feature.lifecycle] ??
|
|
53
|
+
'requirements');
|
|
42
54
|
const nodeData = {
|
|
43
55
|
name: feature.name,
|
|
44
56
|
description: feature.description ?? feature.slug,
|
|
45
57
|
featureId: feature.id,
|
|
46
58
|
lifecycle,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
...(run?.agentType && { agentType: run.agentType }),
|
|
59
|
+
...deriveState(lifecycle, feature.agentStatus),
|
|
60
|
+
...(feature.agentError && { errorMessage: feature.agentError }),
|
|
50
61
|
};
|
|
51
62
|
const featureNodeId = `feat-${feature.id}`;
|
|
52
63
|
nodes.push({
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
2
|
+
export type { FileAttachment } from '../../../../../../packages/core/src/infrastructure/services/file-dialog.service.js';
|
|
3
|
+
export interface CreateFeatureFormData {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
attachments: FileAttachment[];
|
|
7
|
+
}
|
|
8
|
+
export interface FeatureCreateDrawerProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onSubmit: (data: CreateFeatureFormData) => void;
|
|
12
|
+
isSubmitting?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting, }: FeatureCreateDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=feature-create-drawer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-create-drawer.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAG/F,YAAY,EAAE,cAAc,EAAE,MAAM,0DAA0D,CAAC;AAE/F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,cAAc,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,YAAoB,GACrB,EAAE,wBAAwB,2CAkK1B"}
|
package/dist/src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { XIcon, PlusIcon, FileIcon, FileTextIcon, ImageIcon, CodeIcon, Trash2Icon, } from 'lucide-react';
|
|
5
|
+
import { cn } from '../../../lib/utils.js';
|
|
6
|
+
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, } from '../../ui/drawer.js';
|
|
7
|
+
import { Button } from '../../ui/button.js';
|
|
8
|
+
import { Input } from '../../ui/input.js';
|
|
9
|
+
import { Textarea } from '../../ui/textarea.js';
|
|
10
|
+
import { Label } from '../../ui/label.js';
|
|
11
|
+
import { Separator } from '../../ui/separator.js';
|
|
12
|
+
import { Badge } from '../../ui/badge.js';
|
|
13
|
+
import { pickFiles } from './pick-files.js';
|
|
14
|
+
export function FeatureCreateDrawer({ open, onClose, onSubmit, isSubmitting = false, }) {
|
|
15
|
+
const [name, setName] = useState('');
|
|
16
|
+
const [description, setDescription] = useState('');
|
|
17
|
+
const [attachments, setAttachments] = useState([]);
|
|
18
|
+
const resetForm = useCallback(() => {
|
|
19
|
+
setName('');
|
|
20
|
+
setDescription('');
|
|
21
|
+
setAttachments([]);
|
|
22
|
+
}, []);
|
|
23
|
+
const handleOpenChange = useCallback((nextOpen) => {
|
|
24
|
+
if (!nextOpen) {
|
|
25
|
+
resetForm();
|
|
26
|
+
onClose();
|
|
27
|
+
}
|
|
28
|
+
}, [onClose, resetForm]);
|
|
29
|
+
const handleSubmit = useCallback((e) => {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
if (!name.trim())
|
|
32
|
+
return;
|
|
33
|
+
onSubmit({ name: name.trim(), description: description.trim(), attachments });
|
|
34
|
+
}, [name, description, attachments, onSubmit]);
|
|
35
|
+
const handleAddFiles = useCallback(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const files = await pickFiles();
|
|
38
|
+
if (files) {
|
|
39
|
+
setAttachments((prev) => [...prev, ...files]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Native dialog failed — silently ignore (user can retry)
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
const handleRemoveFile = useCallback((path) => {
|
|
47
|
+
setAttachments((prev) => prev.filter((f) => f.path !== path));
|
|
48
|
+
}, []);
|
|
49
|
+
return (_jsx(Drawer, { direction: "right", modal: false, open: open, onOpenChange: handleOpenChange, children: _jsxs(DrawerContent, { direction: "right", className: "w-96", showCloseButton: false, children: [_jsx("button", { type: "button", "aria-label": "Close", onClick: onClose, className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden", children: _jsx(XIcon, { className: "size-4" }) }), _jsxs(DrawerHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "h-2.5 w-2.5 shrink-0 rounded-full bg-blue-500" }), _jsx(DrawerTitle, { children: "NEW FEATURE" })] }), _jsx(DrawerDescription, { asChild: true, children: _jsx("div", { children: _jsx(Badge, { variant: "secondary", children: "Creating..." }) }) })] }), _jsx(Separator, {}), _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: _jsxs("form", { id: "create-feature-form", onSubmit: handleSubmit, className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-name", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "FEATURE NAME" }), _jsx(Input, { id: "feature-name", placeholder: "e.g. GitHub OAuth Login", value: name, onChange: (e) => setName(e.target.value), required: true, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "feature-description", className: "text-muted-foreground text-xs font-semibold tracking-wider", children: "DESCRIPTION" }), _jsx(Textarea, { id: "feature-description", placeholder: "Describe what this feature does...", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, disabled: isSubmitting })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Label, { className: "text-muted-foreground text-xs font-semibold tracking-wider", children: ["ATTACHMENTS", attachments.length > 0 && (_jsxs("span", { className: "text-muted-foreground/60 ml-1.5", children: ["(", attachments.length, ")"] }))] }), _jsxs(Button, { type: "button", variant: "outline", size: "xs", onClick: handleAddFiles, disabled: isSubmitting, children: [_jsx(PlusIcon, { className: "size-3" }), "Add Files"] })] }), attachments.length > 0 && (_jsx("div", { className: "flex flex-col gap-2", children: attachments.map((file) => (_jsx(AttachmentCard, { file: file, onRemove: () => handleRemoveFile(file.path), disabled: isSubmitting }, file.path))) }))] })] }) }), _jsx(Separator, {}), _jsxs(DrawerFooter, { className: "flex-row justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: isSubmitting, children: "Cancel" }), _jsx(Button, { type: "submit", form: "create-feature-form", disabled: !name.trim() || isSubmitting, children: isSubmitting ? 'Creating...' : '+ Create Feature' })] })] }) }));
|
|
50
|
+
}
|
|
51
|
+
/* ---------------------------------------------------------------------------
|
|
52
|
+
* Private sub-components & utilities
|
|
53
|
+
* ------------------------------------------------------------------------- */
|
|
54
|
+
function AttachmentCard({ file, onRemove, disabled, }) {
|
|
55
|
+
const ext = getExtension(file.name);
|
|
56
|
+
const Icon = getFileIcon(ext);
|
|
57
|
+
const iconColorClass = getFileIconColor(ext);
|
|
58
|
+
return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border p-2", children: [_jsx("div", { className: cn('flex h-8 w-8 shrink-0 items-center justify-center rounded', iconColorClass), children: _jsx(Icon, { className: "h-4 w-4" }) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium", children: file.name }), _jsx("span", { className: "text-muted-foreground truncate text-xs", children: file.path }), _jsx("span", { className: "text-muted-foreground text-xs", children: formatFileSize(file.size) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-xs", onClick: onRemove, disabled: disabled, "aria-label": `Remove ${file.name}`, children: _jsx(Trash2Icon, { className: "h-3 w-3" }) })] }));
|
|
59
|
+
}
|
|
60
|
+
function getExtension(filename) {
|
|
61
|
+
const dot = filename.lastIndexOf('.');
|
|
62
|
+
return dot >= 0 ? filename.slice(dot).toLowerCase() : '';
|
|
63
|
+
}
|
|
64
|
+
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp']);
|
|
65
|
+
const CODE_EXTS = new Set([
|
|
66
|
+
'.ts',
|
|
67
|
+
'.tsx',
|
|
68
|
+
'.js',
|
|
69
|
+
'.jsx',
|
|
70
|
+
'.json',
|
|
71
|
+
'.yaml',
|
|
72
|
+
'.yml',
|
|
73
|
+
'.xml',
|
|
74
|
+
'.html',
|
|
75
|
+
'.css',
|
|
76
|
+
'.md',
|
|
77
|
+
]);
|
|
78
|
+
function getFileIcon(ext) {
|
|
79
|
+
if (IMAGE_EXTS.has(ext))
|
|
80
|
+
return ImageIcon;
|
|
81
|
+
if (ext === '.pdf')
|
|
82
|
+
return FileTextIcon;
|
|
83
|
+
if (CODE_EXTS.has(ext))
|
|
84
|
+
return CodeIcon;
|
|
85
|
+
return FileIcon;
|
|
86
|
+
}
|
|
87
|
+
function getFileIconColor(ext) {
|
|
88
|
+
if (ext === '.pdf')
|
|
89
|
+
return 'bg-red-50 text-red-600';
|
|
90
|
+
if (IMAGE_EXTS.has(ext))
|
|
91
|
+
return 'bg-blue-50 text-blue-600';
|
|
92
|
+
if (CODE_EXTS.has(ext))
|
|
93
|
+
return 'bg-emerald-50 text-emerald-600';
|
|
94
|
+
return 'bg-gray-50 text-gray-600';
|
|
95
|
+
}
|
|
96
|
+
function formatFileSize(bytes) {
|
|
97
|
+
if (bytes === 0)
|
|
98
|
+
return '0 B';
|
|
99
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
100
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
101
|
+
return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
102
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { FeatureCreateDrawer } from './feature-create-drawer.js';
|
|
3
|
+
/**
|
|
4
|
+
* **FeatureCreateDrawer** is a right-side sliding drawer containing a form to
|
|
5
|
+
* create a new feature in the Control Center.
|
|
6
|
+
*
|
|
7
|
+
* ### Form sections
|
|
8
|
+
* - **Feature Name** (required) — text input, submit is disabled when empty
|
|
9
|
+
* - **Description** (optional) — multi-line textarea
|
|
10
|
+
* - **Attachments** (optional) — native OS file picker with extension-based icon system:
|
|
11
|
+
* image → blue, PDF → red, code/text → emerald, generic → gray.
|
|
12
|
+
* Each attachment card shows the **full absolute file path**.
|
|
13
|
+
*
|
|
14
|
+
* ### Props
|
|
15
|
+
* | Prop | Type | Description |
|
|
16
|
+
* |------|------|-------------|
|
|
17
|
+
* | `open` | `boolean` | Controls drawer visibility |
|
|
18
|
+
* | `onClose` | `() => void` | Called on dismiss (close button, cancel, or backdrop) |
|
|
19
|
+
* | `onSubmit` | `(data: CreateFeatureFormData) => void` | Called with `{ name, description, attachments }` |
|
|
20
|
+
* | `isSubmitting` | `boolean` | Disables all fields and shows "Creating..." on submit button |
|
|
21
|
+
*
|
|
22
|
+
* ### Behavior
|
|
23
|
+
* - Form resets (name, description, attachments cleared) when the drawer closes
|
|
24
|
+
* - Submit button is disabled when name is empty OR `isSubmitting` is true
|
|
25
|
+
* - Non-modal (`modal={false}`) — canvas stays interactive behind the drawer
|
|
26
|
+
* - Fixed width: 384px (`w-96`)
|
|
27
|
+
* - Attachments use native OS file picker via `pickFiles()` — returns `FileAttachment[]`
|
|
28
|
+
* with full absolute paths, filenames, and sizes
|
|
29
|
+
*/
|
|
30
|
+
declare const meta: Meta<typeof FeatureCreateDrawer>;
|
|
31
|
+
export default meta;
|
|
32
|
+
type Story = StoryObj<typeof FeatureCreateDrawer>;
|
|
33
|
+
/** Default empty form — click the trigger button to open the create feature drawer. */
|
|
34
|
+
export declare const Default: Story;
|
|
35
|
+
/**
|
|
36
|
+
* Pre-filled form — click to open, then name and description are typed in.
|
|
37
|
+
* Demonstrates the form in a "ready to submit" state.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PreFilled: Story;
|
|
40
|
+
/**
|
|
41
|
+
* Submitting/loading state — all form fields are disabled, submit button shows "Creating...".
|
|
42
|
+
* Click the trigger to open and observe the disabled form.
|
|
43
|
+
*/
|
|
44
|
+
export declare const Submitting: Story;
|
|
45
|
+
/**
|
|
46
|
+
* Validation state — open the drawer with an empty name field.
|
|
47
|
+
* The "+ Create Feature" button is disabled because name validation fails.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ValidationDisabled: Story;
|
|
50
|
+
/** Click to open the drawer for quick visual inspection. */
|
|
51
|
+
export declare const PreOpened: Story;
|
|
52
|
+
/**
|
|
53
|
+
* Fully interactive story — open the drawer, fill the form, and click
|
|
54
|
+
* "Add Files" to attach files via the native OS file picker.
|
|
55
|
+
*
|
|
56
|
+
* **Submitted data** is displayed in a styled panel showing `FileAttachment[]`
|
|
57
|
+
* with **full absolute file paths**, filenames, and sizes.
|
|
58
|
+
*
|
|
59
|
+
* In Storybook, the native picker won't work (no backend), but submitted data
|
|
60
|
+
* would show the paths if files were attached programmatically.
|
|
61
|
+
*/
|
|
62
|
+
export declare const Interactive: Story;
|
|
63
|
+
//# sourceMappingURL=feature-create-drawer.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-create-drawer.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-create-drawer/feature-create-drawer.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAQ9D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,mBAAmB,CA0B1C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAgDlD,uFAAuF;AACvF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,KAiBvB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAMhC,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,SAAS,EAAE,KAMvB,CAAC;AAMF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,EAAE,KAiDzB,CAAC"}
|