@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,150 +1,149 @@
1
1
  import { apiClient } from "../lib/api-client";
2
2
 
3
3
  export interface DropEntry {
4
- name: string;
5
- relativePath: string;
6
- file?: File;
7
- isDirectory: boolean;
8
- children?: DropEntry[];
4
+ name: string;
5
+ relativePath: string;
6
+ file?: File;
7
+ isDirectory: boolean;
8
+ children?: DropEntry[];
9
9
  }
10
10
 
11
11
  /**
12
12
  * 递归读取 webkitGetAsEntry,返回扁平文件列表 + 目录结构
13
13
  */
14
- function readEntry(
15
- entry: FileSystemEntry,
16
- path: string,
17
- ): Promise<DropEntry> {
18
- if (entry.isFile) {
19
- return new Promise((resolve) => {
20
- (entry as FileSystemFileEntry).file((file) => {
21
- resolve({ name: entry.name, relativePath: path, file, isDirectory: false });
22
- });
23
- });
24
- }
25
- // Directory
26
- return new Promise((resolve) => {
27
- const reader = (entry as FileSystemDirectoryEntry).createReader();
28
- const children: DropEntry[] = [];
14
+ function readEntry(entry: FileSystemEntry, path: string): Promise<DropEntry> {
15
+ if (entry.isFile) {
16
+ return new Promise((resolve) => {
17
+ (entry as FileSystemFileEntry).file((file) => {
18
+ resolve({ name: entry.name, relativePath: path, file, isDirectory: false });
19
+ });
20
+ });
21
+ }
22
+ // Directory
23
+ return new Promise((resolve) => {
24
+ const reader = (entry as FileSystemDirectoryEntry).createReader();
25
+ const children: DropEntry[] = [];
29
26
 
30
- const readBatch = () => {
31
- reader.readEntries(async (entries) => {
32
- if (entries.length === 0) {
33
- resolve({ name: entry.name, relativePath: path, isDirectory: true, children });
34
- return;
35
- }
36
- for (const e of entries) {
37
- children.push(await readEntry(e, `${path}/${e.name}`));
38
- }
39
- readBatch(); // readEntries may not return all entries in one call
40
- });
41
- };
42
- readBatch();
43
- });
27
+ const readBatch = () => {
28
+ reader.readEntries(async (entries) => {
29
+ if (entries.length === 0) {
30
+ resolve({ name: entry.name, relativePath: path, isDirectory: true, children });
31
+ return;
32
+ }
33
+ for (const e of entries) {
34
+ children.push(await readEntry(e, `${path}/${e.name}`));
35
+ }
36
+ readBatch(); // readEntries may not return all entries in one call
37
+ });
38
+ };
39
+ readBatch();
40
+ });
44
41
  }
45
42
 
46
43
  /**
47
44
  * 从 DataTransfer 递归读取所有文件和目录
48
45
  */
49
46
  export async function readDropItems(dataTransfer: DataTransfer): Promise<DropEntry[]> {
50
- const items = dataTransfer.items;
51
- if (!items) return [];
47
+ const items = dataTransfer.items;
48
+ if (!items) return [];
52
49
 
53
- const entries: DropEntry[] = [];
54
- const tasks: Promise<void>[] = [];
50
+ const entries: DropEntry[] = [];
51
+ const tasks: Promise<void>[] = [];
55
52
 
56
- for (let i = 0; i < items.length; i++) {
57
- const item = items[i];
58
- // Try webkitGetAsEntry (Chrome, Edge, Safari)
59
- const entry = item.webkitGetAsEntry?.();
60
- if (entry) {
61
- tasks.push(
62
- readEntry(entry, entry.name).then<void>((e) => { entries.push(e); }),
63
- );
64
- }
65
- }
53
+ for (let i = 0; i < items.length; i++) {
54
+ const item = items[i]!;
55
+ // Try webkitGetAsEntry (Chrome, Edge, Safari)
56
+ const entry = item.webkitGetAsEntry?.();
57
+ if (entry) {
58
+ tasks.push(
59
+ readEntry(entry, entry.name).then<void>((e) => {
60
+ entries.push(e);
61
+ })
62
+ );
63
+ }
64
+ }
66
65
 
67
- await Promise.all(tasks);
68
- return entries;
66
+ await Promise.all(tasks);
67
+ return entries;
69
68
  }
70
69
 
71
70
  /**
72
71
  * Web 端:递归上传 entries 到目标目录
73
72
  */
74
73
  export async function uploadEntriesWeb(entries: DropEntry[], destDir: string): Promise<number> {
75
- let count = 0;
74
+ let count = 0;
76
75
 
77
- async function process(entry: DropEntry, currentDir: string): Promise<void> {
78
- if (entry.isDirectory) {
79
- // Create directory
80
- const dirPath = `${currentDir}/${entry.name}`;
81
- await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
82
- count++;
83
- if (entry.children) {
84
- for (const child of entry.children) {
85
- await process(child, dirPath);
86
- }
87
- }
88
- } else if (entry.file) {
89
- // Upload file via HTTP
90
- const filePath = `${currentDir}/${entry.name}`;
91
- const arrayBuffer = await entry.file.arrayBuffer();
92
- const baseUrl = apiClient.getBaseUrl();
93
- const token = apiClient.getAuthToken();
94
- const res = await fetch(
95
- `${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
96
- { method: "POST", body: arrayBuffer },
97
- );
98
- if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
99
- count++;
100
- }
101
- }
76
+ async function process(entry: DropEntry, currentDir: string): Promise<void> {
77
+ if (entry.isDirectory) {
78
+ // Create directory
79
+ const dirPath = `${currentDir}/${entry.name}`;
80
+ await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
81
+ count++;
82
+ if (entry.children) {
83
+ for (const child of entry.children) {
84
+ await process(child, dirPath);
85
+ }
86
+ }
87
+ } else if (entry.file) {
88
+ // Upload file via HTTP
89
+ const filePath = `${currentDir}/${entry.name}`;
90
+ const arrayBuffer = await entry.file.arrayBuffer();
91
+ const baseUrl = apiClient.getBaseUrl();
92
+ const token = apiClient.getAuthToken();
93
+ const res = await fetch(
94
+ `${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
95
+ { method: "POST", body: arrayBuffer }
96
+ );
97
+ if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
98
+ count++;
99
+ }
100
+ }
102
101
 
103
- for (const entry of entries) {
104
- await process(entry, destDir);
105
- }
106
- return count;
102
+ for (const entry of entries) {
103
+ await process(entry, destDir);
104
+ }
105
+ return count;
107
106
  }
108
107
 
109
108
  /**
110
109
  * 桌面端:通过 RPC file.copy 直接复制
111
110
  */
112
111
  export async function importFilesDesktop(entries: DropEntry[], destDir: string): Promise<number> {
113
- let count = 0;
112
+ let count = 0;
114
113
 
115
- async function process(entry: DropEntry, currentDir: string): Promise<void> {
116
- if (entry.isDirectory) {
117
- const dirPath = `${currentDir}/${entry.name}`;
118
- await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
119
- count++;
120
- if (entry.children) {
121
- for (const child of entry.children) {
122
- await process(child, dirPath);
123
- }
124
- }
125
- } else if (entry.file) {
126
- // Desktop: File object has .path property (Electron/Electrobun)
127
- const srcPath = (entry.file as File & { path?: string }).path;
128
- if (srcPath) {
129
- await apiClient.call("file.copy", { srcPath, destDir: currentDir });
130
- } else {
131
- // Fallback: no path available, upload via HTTP
132
- const filePath = `${currentDir}/${entry.name}`;
133
- const arrayBuffer = await entry.file.arrayBuffer();
134
- const baseUrl = apiClient.getBaseUrl();
135
- const token = apiClient.getAuthToken();
136
- const res = await fetch(
137
- `${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
138
- { method: "POST", body: arrayBuffer },
139
- );
140
- if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
141
- }
142
- count++;
143
- }
144
- }
114
+ async function process(entry: DropEntry, currentDir: string): Promise<void> {
115
+ if (entry.isDirectory) {
116
+ const dirPath = `${currentDir}/${entry.name}`;
117
+ await apiClient.call("file.createDir", { dirPath: currentDir, name: entry.name });
118
+ count++;
119
+ if (entry.children) {
120
+ for (const child of entry.children) {
121
+ await process(child, dirPath);
122
+ }
123
+ }
124
+ } else if (entry.file) {
125
+ // Desktop: File object has .path property (Electron/Electrobun)
126
+ const srcPath = (entry.file as File & { path?: string }).path;
127
+ if (srcPath) {
128
+ await apiClient.call("file.copy", { srcPath, destDir: currentDir });
129
+ } else {
130
+ // Fallback: no path available, upload via HTTP
131
+ const filePath = `${currentDir}/${entry.name}`;
132
+ const arrayBuffer = await entry.file.arrayBuffer();
133
+ const baseUrl = apiClient.getBaseUrl();
134
+ const token = apiClient.getAuthToken();
135
+ const res = await fetch(
136
+ `${baseUrl}/file/upload?path=${encodeURIComponent(filePath)}&token=${token}`,
137
+ { method: "POST", body: arrayBuffer }
138
+ );
139
+ if (!res.ok) throw new Error(`Upload failed: ${entry.name}`);
140
+ }
141
+ count++;
142
+ }
143
+ }
145
144
 
146
- for (const entry of entries) {
147
- await process(entry, destDir);
148
- }
149
- return count;
145
+ for (const entry of entries) {
146
+ await process(entry, destDir);
147
+ }
148
+ return count;
150
149
  }
@@ -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;
@@ -68,8 +68,7 @@ async function start() {
68
68
  log.info(`WebSocket: ws://localhost:${port}/ws (auth required)`);
69
69
  log.info(`Available RPC methods: ${discoverMethodNames().join(", ")}`);
70
70
  log.info("File endpoints: GET /file/{path}, GET /info/{path}");
71
- // eslint-disable-next-line no-console
72
- console.log("\n" + formatRegistryForOutput() + "\n");
71
+ log.info(formatRegistryForOutput());
73
72
  }
74
73
 
75
74
  start().catch((err) => {
@@ -69,7 +69,7 @@ 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 @@ 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 "+":
@@ -159,7 +159,7 @@ function generateReply(input: string): string {
159
159
  "I wish I could help with that! For now I can answer questions about time, do simple math, and explain the app's features. Type **help** to see what I can do.",
160
160
  "Hmm, I'm not sure about that one. But I *can* do math, tell you the time, and explain features. Give it a shot!",
161
161
  ];
162
- return defaults[Math.floor(Math.random() * defaults.length)];
162
+ return defaults[Math.floor(Math.random() * defaults.length)]!;
163
163
  }
164
164
 
165
165
  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
  }