@dyyz1993/create-agent 2.0.1 → 2.1.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 (60) hide show
  1. package/package.json +1 -1
  2. package/src/commands/create.ts +31 -31
  3. package/src/commands/workspace.ts +112 -105
  4. package/src/lib/copy.ts +117 -12
  5. package/templates/agent/.prettierignore +6 -0
  6. package/templates/agent/.prettierrc +9 -0
  7. package/templates/agent/commitlint.config.js +8 -0
  8. package/templates/agent/electron/main.js +46 -0
  9. package/templates/agent/electron/preload.js +5 -0
  10. package/templates/agent/electron-builder.json +40 -0
  11. package/templates/agent/eslint.config.mjs +2 -0
  12. package/templates/agent/package.json +75 -2
  13. package/templates/agent/src/mainview/App.tsx +29 -26
  14. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
  15. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
  16. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
  17. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +3 -3
  18. package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
  19. package/templates/agent/src/mainview/lib/api-client.ts +1 -4
  20. package/templates/agent/src/mainview/main.tsx +4 -10
  21. package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
  22. package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
  23. package/templates/agent/src/server-config.ts +1 -1
  24. package/templates/agent/src/server.ts +1 -2
  25. package/templates/agent/src/shared/handlers/chat.ts +5 -5
  26. package/templates/agent/src/shared/handlers/debug.ts +5 -1
  27. package/templates/agent/src/shared/handlers/git.ts +286 -243
  28. package/templates/agent/src/shared/http-routes.ts +1 -1
  29. package/templates/agent/src/shared/lib/bash-security.ts +43 -43
  30. package/templates/agent/tsconfig.ipc.json +5 -1
  31. package/templates/agent/tsconfig.json +3 -1
  32. package/templates/chat/package.json +3 -0
  33. package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
  34. package/templates/chat/src/mainview/lib/api-client.ts +2 -5
  35. package/templates/chat/src/mainview/main.tsx +10 -7
  36. package/templates/chat/src/server-config.ts +1 -1
  37. package/templates/chat/src/server.ts +1 -2
  38. package/templates/chat/src/shared/handlers/chat.ts +5 -5
  39. package/templates/chat/src/shared/handlers/debug.ts +5 -1
  40. package/templates/chat/src/shared/http-routes.ts +1 -1
  41. package/templates/chat/tsconfig.ipc.json +5 -1
  42. package/templates/chat/tsconfig.json +12 -2
  43. package/templates/general/package.json +3 -0
  44. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
  45. package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
  46. package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
  47. package/templates/general/src/mainview/lib/api-client.ts +2 -5
  48. package/templates/general/src/mainview/main.tsx +10 -7
  49. package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
  50. package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
  51. package/templates/general/src/server-config.ts +1 -1
  52. package/templates/general/src/server.ts +1 -2
  53. package/templates/general/src/shared/handlers/chat.ts +5 -5
  54. package/templates/general/src/shared/handlers/debug.ts +5 -1
  55. package/templates/general/src/shared/handlers/git.ts +286 -243
  56. package/templates/general/src/shared/http-routes.ts +1 -1
  57. package/templates/general/tsconfig.ipc.json +5 -1
  58. package/templates/general/tsconfig.json +12 -2
  59. package/templates/shared/components/ErrorBoundary.tsx +50 -49
  60. package/templates/shared/http-routes.ts +210 -190
@@ -4,81 +4,90 @@ const HISTORY_KEY = "pi-input-history";
4
4
  const MAX_ITEMS = 10;
5
5
 
6
6
  function getStorageKey(sessionId: string): string {
7
- return `${HISTORY_KEY}:${sessionId}`;
7
+ return `${HISTORY_KEY}:${sessionId}`;
8
8
  }
9
9
 
10
10
  function readHistory(sessionId: string): string[] {
11
- try {
12
- const raw = localStorage.getItem(getStorageKey(sessionId));
13
- if (!raw) return [];
14
- const parsed = JSON.parse(raw);
15
- if (Array.isArray(parsed)) return parsed.slice(0, MAX_ITEMS);
16
- } catch { /* ignore */ }
17
- return [];
11
+ try {
12
+ const raw = localStorage.getItem(getStorageKey(sessionId));
13
+ if (!raw) return [];
14
+ const parsed = JSON.parse(raw);
15
+ if (Array.isArray(parsed)) return parsed.slice(0, MAX_ITEMS);
16
+ } catch {
17
+ /* ignore */
18
+ }
19
+ return [];
18
20
  }
19
21
 
20
22
  function writeHistory(sessionId: string, items: string[]) {
21
- try {
22
- localStorage.setItem(getStorageKey(sessionId), JSON.stringify(items.slice(0, MAX_ITEMS)));
23
- } catch { /* ignore */ }
23
+ try {
24
+ localStorage.setItem(getStorageKey(sessionId), JSON.stringify(items.slice(0, MAX_ITEMS)));
25
+ } catch {
26
+ /* ignore */
27
+ }
24
28
  }
25
29
 
26
30
  export function useInputHistory(sessionId: string) {
27
- const historyRef = useRef<string[]>(readHistory(sessionId));
28
- const indexRef = useRef(-1);
29
- const [, forceUpdate] = useState(0);
31
+ const historyRef = useRef<string[]>(readHistory(sessionId));
32
+ const indexRef = useRef(-1);
33
+ const [, forceUpdate] = useState(0);
30
34
 
31
- const hasPrev = historyRef.current.length > 0 && indexRef.current < historyRef.current.length - 1;
32
- const hasNext = indexRef.current > 0;
35
+ const hasPrev = historyRef.current.length > 0 && indexRef.current < historyRef.current.length - 1;
36
+ const hasNext = indexRef.current > 0;
33
37
 
34
- const saveToHistory = useCallback((text: string) => {
35
- const trimmed = text.trim();
36
- if (!trimmed) return;
37
- const h = historyRef.current;
38
- const filtered = h.filter((item) => item !== trimmed);
39
- const updated = [trimmed, ...filtered].slice(0, MAX_ITEMS);
40
- historyRef.current = updated;
41
- writeHistory(sessionId, updated);
42
- indexRef.current = -1;
43
- forceUpdate((n) => n + 1);
44
- }, [sessionId]);
38
+ const saveToHistory = useCallback(
39
+ (text: string) => {
40
+ const trimmed = text.trim();
41
+ if (!trimmed) return;
42
+ const h = historyRef.current;
43
+ const filtered = h.filter((item) => item !== trimmed);
44
+ const updated = [trimmed, ...filtered].slice(0, MAX_ITEMS);
45
+ historyRef.current = updated;
46
+ writeHistory(sessionId, updated);
47
+ indexRef.current = -1;
48
+ forceUpdate((n) => n + 1);
49
+ },
50
+ [sessionId]
51
+ );
45
52
 
46
- const navigatePrev = useCallback((): string | null => {
47
- const h = historyRef.current;
48
- if (h.length === 0) return null;
49
- const nextIdx = Math.min(indexRef.current + 1, h.length - 1);
50
- indexRef.current = nextIdx;
51
- forceUpdate((n) => n + 1);
52
- return h[nextIdx];
53
- }, []);
53
+ const navigatePrev = useCallback((): string | null => {
54
+ const h = historyRef.current;
55
+ if (h.length === 0) return null;
56
+ const nextIdx = Math.min(indexRef.current + 1, h.length - 1);
57
+ indexRef.current = nextIdx;
58
+ forceUpdate((n) => n + 1);
59
+ return h[nextIdx] ?? null;
60
+ }, []);
54
61
 
55
- const navigateNext = useCallback((): string | null => {
56
- const h = historyRef.current;
57
- if (h.length === 0) return null;
58
- const nextIdx = indexRef.current - 1;
59
- if (nextIdx < 0) {
60
- indexRef.current = -1;
61
- forceUpdate((n) => n + 1);
62
- return "";
63
- }
64
- indexRef.current = nextIdx;
65
- forceUpdate((n) => n + 1);
66
- return h[nextIdx];
67
- }, []);
62
+ const navigateNext = useCallback((): string | null => {
63
+ const h = historyRef.current;
64
+ if (h.length === 0) return null;
65
+ const nextIdx = indexRef.current - 1;
66
+ if (nextIdx < 0) {
67
+ indexRef.current = -1;
68
+ forceUpdate((n) => n + 1);
69
+ return "";
70
+ }
71
+ indexRef.current = nextIdx;
72
+ forceUpdate((n) => n + 1);
73
+ return h[nextIdx] ?? null;
74
+ }, []);
68
75
 
69
- const clearHistory = useCallback(() => {
70
- historyRef.current = [];
71
- indexRef.current = -1;
72
- try {
73
- localStorage.removeItem(getStorageKey(sessionId));
74
- } catch { /* ignore */ }
75
- forceUpdate((n) => n + 1);
76
- }, [sessionId]);
76
+ const clearHistory = useCallback(() => {
77
+ historyRef.current = [];
78
+ indexRef.current = -1;
79
+ try {
80
+ localStorage.removeItem(getStorageKey(sessionId));
81
+ } catch {
82
+ /* ignore */
83
+ }
84
+ forceUpdate((n) => n + 1);
85
+ }, [sessionId]);
77
86
 
78
- const resetIndex = useCallback(() => {
79
- indexRef.current = -1;
80
- forceUpdate((n) => n + 1);
81
- }, []);
87
+ const resetIndex = useCallback(() => {
88
+ indexRef.current = -1;
89
+ forceUpdate((n) => n + 1);
90
+ }, []);
82
91
 
83
- return { saveToHistory, navigatePrev, navigateNext, clearHistory, resetIndex, hasPrev, hasNext };
92
+ return { saveToHistory, navigatePrev, navigateNext, clearHistory, resetIndex, hasPrev, hasNext };
84
93
  }
@@ -42,12 +42,9 @@ class APIClientImpl {
42
42
 
43
43
  const ipcTransport = new IPCTransport();
44
44
  this._transport = "ipc";
45
- this._baseUrl = null; // 桌面端不走 HTTP
45
+ this._baseUrl = null;
46
46
  this.client = createTypedClient<RPCMethods, RPCEvents>(ipcTransport);
47
47
  this.setupElectrobunBridge(ipcTransport);
48
- if (import.meta.env.DEV) {
49
- console.warn("[APIClient] Desktop (IPC) initialized synchronously");
50
- }
51
48
  }
52
49
 
53
50
  /**
@@ -82,7 +79,7 @@ class APIClientImpl {
82
79
  url: wsUrl,
83
80
  authToken: AUTH_TOKEN,
84
81
  reconnect: true,
85
- });
82
+ } as never);
86
83
  await this.wsTransport.connect();
87
84
  this.client = createTypedClient<RPCMethods, RPCEvents>(this.wsTransport);
88
85
 
@@ -1,21 +1,24 @@
1
1
  import "./lib/i18n";
2
2
  import { StrictMode } from "react";
3
+ import type { ReactNode } from "react";
3
4
  import { createRoot } from "react-dom/client";
4
5
  import { apiClient } from "./lib/api-client";
5
- import { ErrorBoundary } from "@shared/components/ErrorBoundary";
6
+ import { ErrorBoundary as _EB } from "@shared/components/ErrorBoundary";
6
7
  import "./index.css";
7
8
  import App from "./App";
8
9
 
10
+ const ErrorBoundary = _EB as unknown as React.FC<{ children: ReactNode }>;
11
+
9
12
  const isElectrobun = typeof window !== "undefined" && !!window.__electrobunBunBridge;
10
13
 
11
14
  if (isElectrobun) {
12
- apiClient.initSyncForDesktop();
15
+ apiClient.initSyncForDesktop();
13
16
  }
14
17
 
15
18
  createRoot(document.getElementById("root")!).render(
16
- <StrictMode>
17
- <ErrorBoundary>
18
- <App />
19
- </ErrorBoundary>
20
- </StrictMode>
19
+ <StrictMode>
20
+ <ErrorBoundary>
21
+ <App />
22
+ </ErrorBoundary>
23
+ </StrictMode>
21
24
  );
@@ -3,53 +3,53 @@ import { apiClient } from "../lib/api-client";
3
3
  import type { FeedCategory, FeedPost } from "../../shared/modules/feed";
4
4
 
5
5
  interface FeedState {
6
- posts: FeedPost[];
7
- author: string;
8
- category: FeedCategory;
9
- content: string;
10
- loading: boolean;
11
-
12
- setAuthor: (author: string) => void;
13
- setCategory: (category: FeedCategory) => void;
14
- setContent: (content: string) => void;
15
- createPost: () => Promise<void>;
16
- loadPosts: () => Promise<void>;
17
- addPost: (post: FeedPost) => void;
6
+ posts: FeedPost[];
7
+ author: string;
8
+ category: FeedCategory;
9
+ content: string;
10
+ loading: boolean;
11
+
12
+ setAuthor: (author: string) => void;
13
+ setCategory: (category: FeedCategory) => void;
14
+ setContent: (content: string) => void;
15
+ createPost: () => Promise<void>;
16
+ loadPosts: () => Promise<void>;
17
+ addPost: (post: FeedPost) => void;
18
18
  }
19
19
 
20
20
  export const useFeedStore = create<FeedState>((set, get) => ({
21
- posts: [],
22
- author: "alice",
23
- category: "tech",
24
- content: "",
25
- loading: false,
26
-
27
- setAuthor: (author) => set({ author }),
28
- setCategory: (category) => set({ category }),
29
- setContent: (content) => set({ content }),
30
-
31
- createPost: async () => {
32
- const { content, category, author } = get();
33
- if (!content.trim()) return;
34
- set({ loading: true });
35
- try {
36
- await apiClient.call("feed.post", { content: content.trim(), category, author });
37
- set({ content: "", loading: false });
38
- } catch {
39
- set({ loading: false });
40
- }
41
- },
42
-
43
- loadPosts: async () => {
44
- try {
45
- const res = await apiClient.call("feed.list", { limit: 100 });
46
- set({ posts: res.posts });
47
- } catch {
48
- // ignore
49
- }
50
- },
51
-
52
- addPost: (post) => set((s) => ({ posts: [...s.posts, post] })),
21
+ posts: [],
22
+ author: "alice",
23
+ category: "tech",
24
+ content: "",
25
+ loading: false,
26
+
27
+ setAuthor: (author) => set({ author }),
28
+ setCategory: (category) => set({ category }),
29
+ setContent: (content) => set({ content }),
30
+
31
+ createPost: async () => {
32
+ const { content, category, author } = get();
33
+ if (!content.trim()) return;
34
+ set({ loading: true });
35
+ try {
36
+ await apiClient.call("feed.post", { content: content.trim(), category, author });
37
+ set({ content: "", loading: false });
38
+ } catch {
39
+ set({ loading: false });
40
+ }
41
+ },
42
+
43
+ loadPosts: async () => {
44
+ try {
45
+ const res = await apiClient.call("feed.list", { limit: 100 });
46
+ set({ posts: res.posts });
47
+ } catch {
48
+ // ignore
49
+ }
50
+ },
51
+
52
+ addPost: (post) => set((s) => ({ posts: [...s.posts, post] })),
53
53
  }));
54
54
 
55
55
  // --- Event Stream (订阅面板) ---
@@ -57,73 +57,73 @@ export const useFeedStore = create<FeedState>((set, get) => ({
57
57
  export type SubEventType = "feed.update" | "chat.message" | "timer.tick";
58
58
 
59
59
  interface EventEntry {
60
- id: string;
61
- eventType: SubEventType;
62
- payload: unknown;
63
- filter: Record<string, unknown>;
64
- timestamp: number;
60
+ id: string;
61
+ eventType: SubEventType;
62
+ payload: unknown;
63
+ filter: Record<string, unknown>;
64
+ timestamp: number;
65
65
  }
66
66
 
67
67
  interface EventStreamState {
68
- entries: EventEntry[];
69
- activeEventType: SubEventType;
70
- activeFilter: string; // JSON string for the filter
71
- subscriptionId: string | null;
72
- subscribed: boolean;
73
-
74
- setActiveEventType: (t: SubEventType) => void;
75
- setActiveFilter: (f: string) => void;
76
- handleSubscribe: () => Promise<void>;
77
- handleUnsubscribe: () => void;
78
- addEntry: (eventType: SubEventType, payload: unknown, filter: Record<string, unknown>) => void;
68
+ entries: EventEntry[];
69
+ activeEventType: SubEventType;
70
+ activeFilter: string; // JSON string for the filter
71
+ subscriptionId: string | null;
72
+ subscribed: boolean;
73
+
74
+ setActiveEventType: (t: SubEventType) => void;
75
+ setActiveFilter: (f: string) => void;
76
+ handleSubscribe: () => Promise<void>;
77
+ handleUnsubscribe: () => void;
78
+ addEntry: (eventType: SubEventType, payload: unknown, filter: Record<string, unknown>) => void;
79
79
  }
80
80
 
81
81
  export const useEventStreamStore = create<EventStreamState>((set, get) => ({
82
- entries: [],
83
- activeEventType: "feed.update",
84
- activeFilter: "",
85
- subscriptionId: null,
86
- subscribed: false,
87
-
88
- setActiveEventType: (t) => set({ activeEventType: t }),
89
- setActiveFilter: (f) => set({ activeFilter: f }),
90
-
91
- handleSubscribe: async () => {
92
- const { activeEventType, activeFilter } = get();
93
- try {
94
- const filter = activeFilter.trim() ? JSON.parse(activeFilter) : {};
95
- const subId = await apiClient.subscribe(
96
- activeEventType,
97
- (payload: unknown) => {
98
- get().addEntry(activeEventType, payload, filter);
99
- },
100
- filter,
101
- );
102
- set({ subscriptionId: subId, subscribed: true });
103
- } catch (err) {
104
- console.error("Subscribe error:", err);
105
- }
106
- },
107
-
108
- handleUnsubscribe: () => {
109
- const { subscriptionId } = get();
110
- if (subscriptionId) {
111
- apiClient.unsubscribe(subscriptionId);
112
- }
113
- set({ subscriptionId: null, subscribed: false });
114
- },
115
-
116
- addEntry: (eventType, payload, filter) =>
117
- set((s) => ({
118
- entries: [
119
- ...s.entries.slice(-49),
120
- {
121
- id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
122
- eventType,
123
- payload,
124
- filter,
125
- timestamp: Date.now(),
126
- },
127
- ],
128
- })),
82
+ entries: [],
83
+ activeEventType: "feed.update",
84
+ activeFilter: "",
85
+ subscriptionId: null,
86
+ subscribed: false,
87
+
88
+ setActiveEventType: (t) => set({ activeEventType: t }),
89
+ setActiveFilter: (f) => set({ activeFilter: f }),
90
+
91
+ handleSubscribe: async () => {
92
+ const { activeEventType, activeFilter } = get();
93
+ try {
94
+ const filter = activeFilter.trim() ? JSON.parse(activeFilter) : {};
95
+ const subId = await apiClient.subscribe(
96
+ activeEventType,
97
+ (payload: unknown) => {
98
+ get().addEntry(activeEventType, payload, filter);
99
+ },
100
+ filter
101
+ );
102
+ set({ subscriptionId: subId, subscribed: true });
103
+ } catch {
104
+ // subscription failed — UI stays unsubscribed
105
+ }
106
+ },
107
+
108
+ handleUnsubscribe: () => {
109
+ const { subscriptionId } = get();
110
+ if (subscriptionId) {
111
+ apiClient.unsubscribe(subscriptionId);
112
+ }
113
+ set({ subscriptionId: null, subscribed: false });
114
+ },
115
+
116
+ addEntry: (eventType, payload, filter) =>
117
+ set((s) => ({
118
+ entries: [
119
+ ...s.entries.slice(-49),
120
+ {
121
+ id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
122
+ eventType,
123
+ payload,
124
+ filter,
125
+ timestamp: Date.now(),
126
+ },
127
+ ],
128
+ })),
129
129
  }));