@dyyz1993/create-agent 2.0.1 → 2.1.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.
Files changed (57) 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 +1 -0
  5. package/templates/agent/electron/main.js +46 -0
  6. package/templates/agent/electron/preload.js +5 -0
  7. package/templates/agent/electron-builder.json +40 -0
  8. package/templates/agent/eslint.config.mjs +2 -0
  9. package/templates/agent/package.json +60 -2
  10. package/templates/agent/src/mainview/App.tsx +29 -26
  11. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
  12. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
  13. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
  14. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +3 -3
  15. package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
  16. package/templates/agent/src/mainview/lib/api-client.ts +1 -4
  17. package/templates/agent/src/mainview/main.tsx +4 -10
  18. package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
  19. package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
  20. package/templates/agent/src/server-config.ts +1 -1
  21. package/templates/agent/src/server.ts +1 -2
  22. package/templates/agent/src/shared/handlers/chat.ts +5 -5
  23. package/templates/agent/src/shared/handlers/debug.ts +5 -1
  24. package/templates/agent/src/shared/handlers/git.ts +286 -243
  25. package/templates/agent/src/shared/http-routes.ts +1 -1
  26. package/templates/agent/src/shared/lib/bash-security.ts +43 -43
  27. package/templates/agent/tsconfig.ipc.json +5 -1
  28. package/templates/agent/tsconfig.json +3 -1
  29. package/templates/chat/package.json +3 -0
  30. package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
  31. package/templates/chat/src/mainview/lib/api-client.ts +2 -5
  32. package/templates/chat/src/mainview/main.tsx +10 -7
  33. package/templates/chat/src/server-config.ts +1 -1
  34. package/templates/chat/src/server.ts +1 -2
  35. package/templates/chat/src/shared/handlers/chat.ts +5 -5
  36. package/templates/chat/src/shared/handlers/debug.ts +5 -1
  37. package/templates/chat/src/shared/http-routes.ts +1 -1
  38. package/templates/chat/tsconfig.ipc.json +5 -1
  39. package/templates/chat/tsconfig.json +12 -2
  40. package/templates/general/package.json +3 -0
  41. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
  42. package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
  43. package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
  44. package/templates/general/src/mainview/lib/api-client.ts +2 -5
  45. package/templates/general/src/mainview/main.tsx +10 -7
  46. package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
  47. package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
  48. package/templates/general/src/server-config.ts +1 -1
  49. package/templates/general/src/server.ts +1 -2
  50. package/templates/general/src/shared/handlers/chat.ts +5 -5
  51. package/templates/general/src/shared/handlers/debug.ts +5 -1
  52. package/templates/general/src/shared/handlers/git.ts +286 -243
  53. package/templates/general/src/shared/http-routes.ts +1 -1
  54. package/templates/general/tsconfig.ipc.json +5 -1
  55. package/templates/general/tsconfig.json +12 -2
  56. package/templates/shared/components/ErrorBoundary.tsx +50 -49
  57. package/templates/shared/http-routes.ts +210 -190
@@ -106,7 +106,7 @@ export function TodoPanel() {
106
106
  ) : (
107
107
  <div className="space-y-2.5">
108
108
  {items.map((item) => {
109
- const cfg = statusConfig[item.status as TodoStatus];
109
+ const cfg = statusConfig[item.status as TodoStatus]!;
110
110
  const Icon = cfg.icon;
111
111
  return (
112
112
  <div
@@ -115,7 +115,7 @@ export function TodoPanel() {
115
115
  >
116
116
  <div className="flex items-start gap-3">
117
117
  <button
118
- onClick={() => updateItem(item.id, nextStatus[item.status as TodoStatus])}
118
+ onClick={() => updateItem(item.id, nextStatus[item.status as TodoStatus]!)}
119
119
  className={`flex-shrink-0 mt-0.5 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all ${cfg.circleColor} ${cfg.color} hover:scale-110`}
120
120
  >
121
121
  <Icon className="w-3 h-3" />
@@ -133,7 +133,7 @@ export function TodoPanel() {
133
133
  <div className="flex items-center gap-1.5 mt-2 text-[11px] text-[var(--color-text-placeholder)]">
134
134
  <span>{t("todo.status")}</span>
135
135
  {statusFlow.map((s) => {
136
- const sc = statusConfig[s];
136
+ const sc = statusConfig[s]!;
137
137
  const isActive = s === item.status;
138
138
  return (
139
139
  <span
@@ -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
  /**
@@ -8,17 +8,11 @@ import App from "./App";
8
8
  const isElectrobun = typeof window !== "undefined" && !!window.__electrobunBunBridge;
9
9
 
10
10
  if (isElectrobun) {
11
- // 桌面端:同步初始化 IPC(Electrobun 要求桥接在页面加载时同步设置)
12
- apiClient.initSyncForDesktop();
13
- }
14
- // Web 端 WS 初始化由 App.tsx 统一管理,避免竞争
15
-
16
- if (import.meta.env.DEV) {
17
- console.warn("[Main] Application starting, isElectrobun:", isElectrobun);
11
+ apiClient.initSyncForDesktop();
18
12
  }
19
13
 
20
14
  createRoot(document.getElementById("root")!).render(
21
- <StrictMode>
22
- <App />
23
- </StrictMode>
15
+ <StrictMode>
16
+ <App />
17
+ </StrictMode>
24
18
  );
@@ -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
  }));