@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.
- package/package.json +1 -1
- package/src/commands/create.ts +31 -31
- package/src/commands/workspace.ts +112 -105
- package/src/lib/copy.ts +117 -12
- package/templates/agent/.prettierignore +6 -0
- package/templates/agent/.prettierrc +9 -0
- package/templates/agent/commitlint.config.js +8 -0
- package/templates/agent/electron/main.js +46 -0
- package/templates/agent/electron/preload.js +5 -0
- package/templates/agent/electron-builder.json +40 -0
- package/templates/agent/eslint.config.mjs +2 -0
- package/templates/agent/package.json +75 -2
- package/templates/agent/src/mainview/App.tsx +29 -26
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +3 -3
- package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/agent/src/mainview/lib/api-client.ts +1 -4
- package/templates/agent/src/mainview/main.tsx +4 -10
- package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/agent/src/server-config.ts +1 -1
- package/templates/agent/src/server.ts +1 -2
- package/templates/agent/src/shared/handlers/chat.ts +5 -5
- package/templates/agent/src/shared/handlers/debug.ts +5 -1
- package/templates/agent/src/shared/handlers/git.ts +286 -243
- package/templates/agent/src/shared/http-routes.ts +1 -1
- package/templates/agent/src/shared/lib/bash-security.ts +43 -43
- package/templates/agent/tsconfig.ipc.json +5 -1
- package/templates/agent/tsconfig.json +3 -1
- package/templates/chat/package.json +3 -0
- package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/chat/src/mainview/lib/api-client.ts +2 -5
- package/templates/chat/src/mainview/main.tsx +10 -7
- package/templates/chat/src/server-config.ts +1 -1
- package/templates/chat/src/server.ts +1 -2
- package/templates/chat/src/shared/handlers/chat.ts +5 -5
- package/templates/chat/src/shared/handlers/debug.ts +5 -1
- package/templates/chat/src/shared/http-routes.ts +1 -1
- package/templates/chat/tsconfig.ipc.json +5 -1
- package/templates/chat/tsconfig.json +12 -2
- package/templates/general/package.json +3 -0
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
- package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/general/src/mainview/lib/api-client.ts +2 -5
- package/templates/general/src/mainview/main.tsx +10 -7
- package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/general/src/server-config.ts +1 -1
- package/templates/general/src/server.ts +1 -2
- package/templates/general/src/shared/handlers/chat.ts +5 -5
- package/templates/general/src/shared/handlers/debug.ts +5 -1
- package/templates/general/src/shared/handlers/git.ts +286 -243
- package/templates/general/src/shared/http-routes.ts +1 -1
- package/templates/general/tsconfig.ipc.json +5 -1
- package/templates/general/tsconfig.json +12 -2
- package/templates/shared/components/ErrorBoundary.tsx +50 -49
- 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
|
-
|
|
7
|
+
return `${HISTORY_KEY}:${sessionId}`;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function readHistory(sessionId: string): string[] {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const historyRef = useRef<string[]>(readHistory(sessionId));
|
|
32
|
+
const indexRef = useRef(-1);
|
|
33
|
+
const [, forceUpdate] = useState(0);
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const hasPrev = historyRef.current.length > 0 && indexRef.current < historyRef.current.length - 1;
|
|
36
|
+
const hasNext = indexRef.current > 0;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const resetIndex = useCallback(() => {
|
|
88
|
+
indexRef.current = -1;
|
|
89
|
+
forceUpdate((n) => n + 1);
|
|
90
|
+
}, []);
|
|
82
91
|
|
|
83
|
-
|
|
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;
|
|
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
|
-
|
|
15
|
+
apiClient.initSyncForDesktop();
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
createRoot(document.getElementById("root")!).render(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
}));
|