@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
@@ -1,64 +1,64 @@
1
1
  export interface CommandPolicy {
2
- enabled: boolean;
3
- blockedPatterns: RegExp[];
4
- allowedCommands: string[] | null;
2
+ enabled: boolean;
3
+ blockedPatterns: RegExp[];
4
+ allowedCommands: string[] | null;
5
5
  }
6
6
 
7
7
  let policy: CommandPolicy = {
8
- enabled: true,
9
- blockedPatterns: [
10
- /rm\s+-rf\s+(.*\s)?\/($|\s)/,
11
- /rm\s+-rf\s+--no-preserve-root/,
12
- /mkfs/,
13
- /dd\s+if=/,
14
- />\s*\/dev\//,
15
- /:()\s*\{.*\|.*&\s*\}/,
16
- /shutdown/,
17
- /reboot/,
18
- ],
19
- allowedCommands: null,
8
+ enabled: true,
9
+ blockedPatterns: [
10
+ /rm\s+-rf\s+(.*\s)?\/($|\s)/,
11
+ /rm\s+-rf\s+--no-preserve-root/,
12
+ /mkfs/,
13
+ /dd\s+if=/,
14
+ />\s*\/dev\//,
15
+ /:()\s*\{.*\|.*&\s*\}/,
16
+ /shutdown/,
17
+ /reboot/,
18
+ ],
19
+ allowedCommands: null,
20
20
  };
21
21
 
22
22
  export function setCommandPolicy(p: CommandPolicy): void {
23
- policy = p;
23
+ policy = p;
24
24
  }
25
25
 
26
26
  export function isCommandAllowed(command: string): boolean {
27
- if (!policy.enabled) return false;
27
+ if (!policy.enabled) return false;
28
28
 
29
- const trimmed = command.trim();
30
- if (!trimmed) return false;
29
+ const trimmed = command.trim();
30
+ if (!trimmed) return false;
31
31
 
32
- for (const pattern of policy.blockedPatterns) {
33
- if (pattern.test(trimmed)) return false;
34
- }
32
+ for (const pattern of policy.blockedPatterns) {
33
+ if (pattern.test(trimmed)) return false;
34
+ }
35
35
 
36
- if (policy.allowedCommands) {
37
- const baseCommand = trimmed.split(/\s+/)[0];
38
- return policy.allowedCommands.some(
39
- (allowed) => baseCommand === allowed || baseCommand.startsWith(allowed),
40
- );
41
- }
36
+ if (policy.allowedCommands) {
37
+ const baseCommand = trimmed.split(/\s+/)[0]!;
38
+ return policy.allowedCommands.some(
39
+ (allowed) => baseCommand === allowed || baseCommand.startsWith(allowed)
40
+ );
41
+ }
42
42
 
43
- return true;
43
+ return true;
44
44
  }
45
45
 
46
46
  export function validateCommand(command: string): string {
47
- if (!policy.enabled) {
48
- throw new Error("Bash execution is disabled");
49
- }
47
+ if (!policy.enabled) {
48
+ throw new Error("Bash execution is disabled");
49
+ }
50
50
 
51
- const trimmed = command.trim();
52
- if (!trimmed) {
53
- throw new Error("Command cannot be empty");
54
- }
51
+ const trimmed = command.trim();
52
+ if (!trimmed) {
53
+ throw new Error("Command cannot be empty");
54
+ }
55
55
 
56
- if (!isCommandAllowed(trimmed)) {
57
- if (policy.allowedCommands) {
58
- throw new Error(`Command not in whitelist: "${trimmed}"`);
59
- }
60
- throw new Error(`Command blocked for safety: "${trimmed}"`);
61
- }
56
+ if (!isCommandAllowed(trimmed)) {
57
+ if (policy.allowedCommands) {
58
+ throw new Error(`Command not in whitelist: "${trimmed}"`);
59
+ }
60
+ throw new Error(`Command blocked for safety: "${trimmed}"`);
61
+ }
62
62
 
63
- return trimmed;
63
+ return trimmed;
64
64
  }
@@ -3,7 +3,11 @@
3
3
  "compilerOptions": {
4
4
  "noUnusedLocals": false,
5
5
  "noUnusedParameters": false,
6
- "skipLibCheck": true
6
+ "skipLibCheck": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "noImplicitReturns": true,
9
+ "noFallthroughCasesInSwitch": true,
10
+ "strict": true
7
11
  },
8
12
  "include": ["src/bun", "src/gateway/ipc-transport.ts", "src/shared"],
9
13
  "exclude": ["node_modules", "dist", "build", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"]
@@ -19,7 +19,9 @@
19
19
  "paths": {
20
20
  "@dyyz1993/rpc-core": ["../../packages/rpc-core/src/index.ts"],
21
21
  "@shared/*": ["../shared/*"]
22
- }
22
+ },
23
+ "noUncheckedIndexedAccess": true,
24
+ "noImplicitReturns": true
23
25
  },
24
26
  "include": ["src", "../shared"],
25
27
  "exclude": [
@@ -61,5 +61,8 @@
61
61
  "typescript-eslint": "^8.0.0",
62
62
  "vite": "^6.0.3",
63
63
  "vitest": "^4.1.5"
64
+ },
65
+ "overrides": {
66
+ "minimatch": "^10.0.1"
64
67
  }
65
68
  }
@@ -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
  );
@@ -22,7 +22,7 @@ export function parseEnvInt(
22
22
  if (value === undefined || value === "") return defaultValue;
23
23
  const parsed = parseInt(value, 10);
24
24
  if (isNaN(parsed) || parsed < min || parsed > max) {
25
- console.warn(`[config] Invalid ${key}: "${value}", using default: ${defaultValue}`);
25
+ process.stderr.write(`[config] Invalid ${key}: "${value}", using default: ${defaultValue}\n`);
26
26
  return defaultValue;
27
27
  }
28
28
  return parsed;
@@ -62,8 +62,7 @@ async function start() {
62
62
  log.info(`WebSocket: ws://localhost:${port}/ws (auth required)`);
63
63
  log.info(`Available RPC methods: ${discoverMethodNames().join(", ")}`);
64
64
  log.info("File endpoints: GET /file/{path}, GET /info/{path}");
65
- // eslint-disable-next-line no-console
66
- console.log("\n" + formatRegistryForOutput() + "\n");
65
+ log.info(formatRegistryForOutput());
67
66
  }
68
67
 
69
68
  start().catch((err) => {
@@ -69,7 +69,7 @@ export function generateReply(input: string): string {
69
69
  "Hello! Great to see you. What would you like to know?",
70
70
  "Hi! I'm your desktop assistant. Ask me anything!",
71
71
  ];
72
- return greetings[Math.floor(Math.random() * greetings.length)];
72
+ return greetings[Math.floor(Math.random() * greetings.length)]!;
73
73
  }
74
74
 
75
75
  if (
@@ -90,9 +90,9 @@ export function generateReply(input: string): string {
90
90
  /(?:what(?:'s| is)\s+)?(\d+(?:\.\d+)?)\s*([+\-*/x×÷^])\s*(\d+(?:\.\d+)?)/
91
91
  );
92
92
  if (mathMatch) {
93
- const a = parseFloat(mathMatch[1]);
94
- const op = mathMatch[2];
95
- const b = parseFloat(mathMatch[3]);
93
+ const a = parseFloat(mathMatch[1]!);
94
+ const op = mathMatch[2]!;
95
+ const b = parseFloat(mathMatch[3]!);
96
96
  let result: number;
97
97
  switch (op) {
98
98
  case "+":
@@ -166,7 +166,7 @@ export function generateReply(input: string): string {
166
166
  "I wish I could help with that! For now I can answer questions about time, math, files, and git. Type **help** to see what I can do.",
167
167
  "Hmm, I'm not sure about that one. But I *can* do math, tell you the time, and talk about files and git. Give it a shot!",
168
168
  ];
169
- return defaults[Math.floor(Math.random() * defaults.length)];
169
+ return defaults[Math.floor(Math.random() * defaults.length)]!;
170
170
  }
171
171
 
172
172
  export function register(server: RPCServer, options: HandlerOptions): void {
@@ -3,6 +3,10 @@ import type { HandlerOptions } from "../rpc-schema";
3
3
 
4
4
  export function register(server: RPCServer, _options: HandlerOptions): void {
5
5
  server.register("debug.subscriptions", async () => {
6
- return { subscriptions: server.getActiveSubscriptions() };
6
+ return {
7
+ subscriptions: (
8
+ server as unknown as { getActiveSubscriptions(): unknown }
9
+ ).getActiveSubscriptions(),
10
+ };
7
11
  });
8
12
  }
@@ -178,7 +178,7 @@ async function handleFileContent(
178
178
  const range = req.headers["range"];
179
179
  if (range) {
180
180
  const parts = range.replace(/bytes=/, "").split("-");
181
- const start = parseInt(parts[0], 10);
181
+ const start = parseInt(parts[0]!, 10);
182
182
  const end = parts[1] ? parseInt(parts[1], 10) : s.size - 1;
183
183
  const chunkSize = end - start + 1;
184
184
 
@@ -3,7 +3,11 @@
3
3
  "compilerOptions": {
4
4
  "noUnusedLocals": false,
5
5
  "noUnusedParameters": false,
6
- "skipLibCheck": true
6
+ "skipLibCheck": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "noImplicitReturns": true,
9
+ "noFallthroughCasesInSwitch": true,
10
+ "strict": true
7
11
  },
8
12
  "include": ["src/bun", "src/gateway/ipc-transport.ts", "src/shared"],
9
13
  "exclude": ["node_modules", "dist", "build", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"]
@@ -19,8 +19,18 @@
19
19
  "paths": {
20
20
  "@dyyz1993/rpc-core": ["../packages/rpc-core/src/index.ts"],
21
21
  "@shared/*": ["../shared/*"]
22
- }
22
+ },
23
+ "noUncheckedIndexedAccess": true,
24
+ "noImplicitReturns": true
23
25
  },
24
26
  "include": ["src", "../shared"],
25
- "exclude": ["node_modules", "dist", "build", "../../package/dist", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"]
27
+ "exclude": [
28
+ "node_modules",
29
+ "dist",
30
+ "build",
31
+ "../../package/dist",
32
+ "**/__tests__/**",
33
+ "**/*.test.ts",
34
+ "**/*.test.tsx"
35
+ ]
26
36
  }
@@ -62,5 +62,8 @@
62
62
  "typescript-eslint": "^8.0.0",
63
63
  "vite": "^6.0.3",
64
64
  "vitest": "^4.1.5"
65
+ },
66
+ "overrides": {
67
+ "minimatch": "^10.0.1"
65
68
  }
66
69
  }