@lobehub/lobehub 2.1.5 → 2.1.6
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/CHANGELOG.md +25 -0
- package/apps/desktop/package.json +4 -4
- package/apps/desktop/src/main/controllers/LocalFileCtr.ts +45 -11
- package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +509 -1
- package/changelog/v2.json +9 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +4 -4
- package/packages/builtin-tool-local-system/package.json +1 -2
- package/packages/builtin-tool-local-system/src/client/Inspector/GlobLocalFiles/index.tsx +21 -22
- package/packages/builtin-tool-local-system/src/executor/index.ts +12 -3
- package/packages/builtin-tool-local-system/src/manifest.ts +18 -1
- package/packages/builtin-tool-local-system/src/systemRole.ts +8 -2
- package/packages/builtin-tool-local-system/src/types.ts +1 -0
- package/packages/const/src/file.ts +11 -1
- package/packages/const/src/index.ts +1 -0
- package/packages/const/src/version.ts +1 -1
- package/packages/electron-client-ipc/src/types/localSystem.ts +32 -0
- package/packages/file-loaders/src/blackList.ts +8 -1
- package/packages/prompts/src/prompts/fileSystem/formatFileList.test.ts +297 -5
- package/packages/prompts/src/prompts/fileSystem/formatFileList.ts +86 -3
- package/src/envs/file.ts +1 -1
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/ToolTitle.tsx +100 -32
- package/src/features/Conversation/Messages/AssistantGroup/Tool/Inspector/index.tsx +5 -0
- package/src/features/Conversation/Messages/Supervisor/components/ContentBlock.tsx +1 -1
- package/src/locales/default/plugin.ts +1 -0
- package/src/services/electron/localFileService.ts +2 -1
- package/packages/builtin-tool-local-system/src/ExecutionRuntime/index.ts +0 -466
- package/src/features/Conversation/Messages/Supervisor/components/MessageContent.tsx +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -76,13 +76,13 @@ export class CloudSandboxExecutionRuntime {
|
|
|
76
76
|
const files = result.result?.files || [];
|
|
77
77
|
const state: ListLocalFilesState = { files };
|
|
78
78
|
|
|
79
|
-
const content = formatFileList(
|
|
80
|
-
|
|
79
|
+
const content = formatFileList({
|
|
80
|
+
directory: args.directoryPath,
|
|
81
|
+
files: files.map((f: { isDirectory: boolean; name: string }) => ({
|
|
81
82
|
isDirectory: f.isDirectory,
|
|
82
83
|
name: f.name,
|
|
83
84
|
})),
|
|
84
|
-
|
|
85
|
-
);
|
|
85
|
+
});
|
|
86
86
|
|
|
87
87
|
return {
|
|
88
88
|
content,
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.ts",
|
|
7
7
|
"./client": "./src/client/index.ts",
|
|
8
|
-
"./executor": "./src/executor/index.ts"
|
|
9
|
-
"./executionRuntime": "./src/ExecutionRuntime/index.ts"
|
|
8
|
+
"./executor": "./src/executor/index.ts"
|
|
10
9
|
},
|
|
11
10
|
"main": "./src/index.ts",
|
|
12
11
|
"dependencies": {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { type GlobFilesParams } from '@lobechat/electron-client-ipc';
|
|
4
4
|
import { type BuiltinInspectorProps } from '@lobechat/types';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { Text } from '@lobehub/ui';
|
|
6
|
+
import { cssVar, cx } from 'antd-style';
|
|
7
7
|
import { memo } from 'react';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
|
@@ -11,13 +11,6 @@ import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/sty
|
|
|
11
11
|
|
|
12
12
|
import { type GlobFilesState } from '../../..';
|
|
13
13
|
|
|
14
|
-
const styles = createStaticStyles(({ css }) => ({
|
|
15
|
-
statusIcon: css`
|
|
16
|
-
margin-block-end: -2px;
|
|
17
|
-
margin-inline-start: 4px;
|
|
18
|
-
`,
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
14
|
export const GlobLocalFilesInspector = memo<BuiltinInspectorProps<GlobFilesParams, GlobFilesState>>(
|
|
22
15
|
({ args, partialArgs, isArgumentsStreaming, pluginState, isLoading }) => {
|
|
23
16
|
const { t } = useTranslation('plugin');
|
|
@@ -41,22 +34,28 @@ export const GlobLocalFilesInspector = memo<BuiltinInspectorProps<GlobFilesParam
|
|
|
41
34
|
);
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
// Check
|
|
45
|
-
const
|
|
37
|
+
// Check result count
|
|
38
|
+
const resultCount = pluginState?.result?.total_files ?? 0;
|
|
39
|
+
const hasResults = resultCount > 0;
|
|
46
40
|
|
|
47
41
|
return (
|
|
48
42
|
<div className={cx(inspectorTextStyles.root, isLoading && shinyTextStyles.shinyText)}>
|
|
49
|
-
<span
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
<span>{t('builtins.lobe-local-system.apiName.globLocalFiles')}: </span>
|
|
44
|
+
{pattern && <span className={highlightTextStyles.primary}>{pattern}</span>}
|
|
45
|
+
{!isLoading &&
|
|
46
|
+
pluginState?.result &&
|
|
47
|
+
(hasResults ? (
|
|
48
|
+
<span style={{ marginInlineStart: 4 }}>({resultCount})</span>
|
|
49
|
+
) : (
|
|
50
|
+
<Text
|
|
51
|
+
as={'span'}
|
|
52
|
+
color={cssVar.colorTextDescription}
|
|
53
|
+
fontSize={12}
|
|
54
|
+
style={{ marginInlineStart: 4 }}
|
|
55
|
+
>
|
|
56
|
+
({t('builtins.lobe-local-system.inspector.noResults')})
|
|
57
|
+
</Text>
|
|
58
|
+
))}
|
|
60
59
|
</div>
|
|
61
60
|
);
|
|
62
61
|
},
|
|
@@ -87,11 +87,20 @@ class LocalSystemExecutor extends BaseExecutor<typeof LocalSystemApiEnum> {
|
|
|
87
87
|
|
|
88
88
|
listLocalFiles = async (params: ListLocalFileParams): Promise<BuiltinToolResult> => {
|
|
89
89
|
try {
|
|
90
|
-
const result
|
|
90
|
+
const result = await localFileService.listLocalFiles(params);
|
|
91
91
|
|
|
92
|
-
const state: LocalFileListState = {
|
|
92
|
+
const state: LocalFileListState = {
|
|
93
|
+
listResults: result.files,
|
|
94
|
+
totalCount: result.totalCount,
|
|
95
|
+
};
|
|
93
96
|
|
|
94
|
-
const content = formatFileList(
|
|
97
|
+
const content = formatFileList({
|
|
98
|
+
directory: params.path,
|
|
99
|
+
files: result.files,
|
|
100
|
+
sortBy: params.sortBy,
|
|
101
|
+
sortOrder: params.sortOrder,
|
|
102
|
+
totalCount: result.totalCount,
|
|
103
|
+
});
|
|
95
104
|
|
|
96
105
|
return {
|
|
97
106
|
content,
|
|
@@ -7,14 +7,31 @@ export const LocalSystemManifest: BuiltinToolManifest = {
|
|
|
7
7
|
api: [
|
|
8
8
|
{
|
|
9
9
|
description:
|
|
10
|
-
'List files and folders in a specified directory.
|
|
10
|
+
'List files and folders in a specified directory. Returns file/folder names with metadata (size, modified time). Results are sorted by modified time (newest first) by default and limited to 100 items.',
|
|
11
11
|
name: LocalSystemApiName.listLocalFiles,
|
|
12
12
|
parameters: {
|
|
13
13
|
properties: {
|
|
14
|
+
limit: {
|
|
15
|
+
default: 100,
|
|
16
|
+
description: 'Maximum number of items to return (default: 100)',
|
|
17
|
+
type: 'number',
|
|
18
|
+
},
|
|
14
19
|
path: {
|
|
15
20
|
description: 'The directory path to list',
|
|
16
21
|
type: 'string',
|
|
17
22
|
},
|
|
23
|
+
sortBy: {
|
|
24
|
+
default: 'modifiedTime',
|
|
25
|
+
description: 'Field to sort by (default: modifiedTime)',
|
|
26
|
+
enum: ['name', 'modifiedTime', 'createdTime', 'size'],
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
sortOrder: {
|
|
30
|
+
default: 'desc',
|
|
31
|
+
description: 'Sort order (default: desc)',
|
|
32
|
+
enum: ['asc', 'desc'],
|
|
33
|
+
type: 'string',
|
|
34
|
+
},
|
|
18
35
|
},
|
|
19
36
|
required: ['path'],
|
|
20
37
|
type: 'object',
|
|
@@ -21,7 +21,7 @@ Use these paths when the user refers to these common locations by name (e.g., "m
|
|
|
21
21
|
You have access to a set of tools to interact with the user's local file system:
|
|
22
22
|
|
|
23
23
|
**File Operations:**
|
|
24
|
-
1. **listLocalFiles**: Lists files and directories in a specified path.
|
|
24
|
+
1. **listLocalFiles**: Lists files and directories in a specified path. Returns metadata including file size and modification time. Results are sorted by modification time (newest first) by default and limited to 100 items.
|
|
25
25
|
2. **readLocalFile**: Reads the content of a specified file, optionally within a line range. You can read file types such as Word, Excel, PowerPoint, PDF, and plain text files.
|
|
26
26
|
3. **writeLocalFile**: Write content to a specific file, only support plain text file like \`.text\` or \`.md\`
|
|
27
27
|
4. **editLocalFile**: Performs exact string replacements in files. Must read the file first before editing.
|
|
@@ -50,7 +50,13 @@ You have access to a set of tools to interact with the user's local file system:
|
|
|
50
50
|
</workflow>
|
|
51
51
|
|
|
52
52
|
<tool_usage_guidelines>
|
|
53
|
-
- For listing directory contents: Use '
|
|
53
|
+
- For listing directory contents: Use 'listLocalFiles'. Provide the following parameters:
|
|
54
|
+
- 'path': The directory path to list.
|
|
55
|
+
- 'sortBy' (Optional): Field to sort results by. Options: 'name', 'modifiedTime', 'createdTime', 'size'. Defaults to 'modifiedTime'.
|
|
56
|
+
- 'sortOrder' (Optional): Sort order. Options: 'asc', 'desc'. Defaults to 'desc' (newest/largest first).
|
|
57
|
+
- 'limit' (Optional): Maximum number of items to return. Defaults to 100.
|
|
58
|
+
- The response includes file/folder names with metadata (size in bytes, modification time) for each item.
|
|
59
|
+
- System files (e.g., '.DS_Store', 'Thumbs.db', '$RECYCLE.BIN') are automatically filtered out.
|
|
54
60
|
- For reading a file: Use 'readFile'. Provide the following parameters:
|
|
55
61
|
- 'path': The exact file path.
|
|
56
62
|
- 'loc' (Optional): A two-element array [startLine, endLine] to specify a line range to read (e.g., '[301, 400]' reads lines 301 to 400).
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* System files to be filtered out when listing directory contents
|
|
3
|
+
*/
|
|
4
|
+
export const SYSTEM_FILES_BLACKLIST = [
|
|
2
5
|
'.DS_Store',
|
|
3
6
|
'Thumbs.db',
|
|
4
7
|
'desktop.ini',
|
|
5
8
|
'.localized',
|
|
6
9
|
'ehthumbs.db',
|
|
7
10
|
'ehthumbs_vista.db',
|
|
11
|
+
'$RECYCLE.BIN',
|
|
12
|
+
'System Volume Information',
|
|
13
|
+
'.Spotlight-V100',
|
|
14
|
+
'.fseventsd',
|
|
15
|
+
'.Trashes',
|
|
8
16
|
];
|
|
9
17
|
|
|
18
|
+
export const FILE_UPLOAD_BLACKLIST = SYSTEM_FILES_BLACKLIST;
|
|
19
|
+
|
|
10
20
|
export const MAX_UPLOAD_FILE_COUNT = 10;
|
|
@@ -16,8 +16,40 @@ export interface LocalFileItem {
|
|
|
16
16
|
type: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export type ListLocalFileSortBy = 'name' | 'modifiedTime' | 'createdTime' | 'size';
|
|
20
|
+
export type ListLocalFileSortOrder = 'asc' | 'desc';
|
|
21
|
+
|
|
19
22
|
export interface ListLocalFileParams {
|
|
23
|
+
/**
|
|
24
|
+
* Maximum number of files to return
|
|
25
|
+
* @default 100
|
|
26
|
+
*/
|
|
27
|
+
limit?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Directory path to list
|
|
30
|
+
*/
|
|
20
31
|
path: string;
|
|
32
|
+
/**
|
|
33
|
+
* Field to sort by
|
|
34
|
+
* @default 'modifiedTime'
|
|
35
|
+
*/
|
|
36
|
+
sortBy?: ListLocalFileSortBy;
|
|
37
|
+
/**
|
|
38
|
+
* Sort order
|
|
39
|
+
* @default 'desc'
|
|
40
|
+
*/
|
|
41
|
+
sortOrder?: ListLocalFileSortOrder;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ListLocalFilesResult {
|
|
45
|
+
/**
|
|
46
|
+
* List of files (truncated to limit)
|
|
47
|
+
*/
|
|
48
|
+
files: LocalFileItem[];
|
|
49
|
+
/**
|
|
50
|
+
* Total count of files before truncation
|
|
51
|
+
*/
|
|
52
|
+
totalCount: number;
|
|
21
53
|
}
|
|
22
54
|
|
|
23
55
|
export interface MoveLocalFileParams {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* System files to be filtered out when listing directory contents
|
|
3
|
+
*/
|
|
2
4
|
export const SYSTEM_FILES_TO_IGNORE = [
|
|
3
5
|
'.DS_Store',
|
|
4
6
|
'Thumbs.db',
|
|
@@ -6,4 +8,9 @@ export const SYSTEM_FILES_TO_IGNORE = [
|
|
|
6
8
|
'.localized',
|
|
7
9
|
'ehthumbs.db',
|
|
8
10
|
'ehthumbs_vista.db',
|
|
11
|
+
'$RECYCLE.BIN',
|
|
12
|
+
'System Volume Information',
|
|
13
|
+
'.Spotlight-V100',
|
|
14
|
+
'.fseventsd',
|
|
15
|
+
'.Trashes',
|
|
9
16
|
];
|
|
@@ -3,8 +3,158 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
import { formatFileList } from './formatFileList';
|
|
4
4
|
|
|
5
5
|
describe('formatFileList', () => {
|
|
6
|
+
describe('snapshot tests', () => {
|
|
7
|
+
it('should match snapshot for complete extended output', () => {
|
|
8
|
+
const files = [
|
|
9
|
+
{
|
|
10
|
+
isDirectory: true,
|
|
11
|
+
modifiedTime: new Date('2024-01-20T14:30:00'),
|
|
12
|
+
name: 'Documents',
|
|
13
|
+
size: 4096,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
isDirectory: true,
|
|
17
|
+
modifiedTime: new Date('2024-01-18T09:15:00'),
|
|
18
|
+
name: 'Downloads',
|
|
19
|
+
size: 4096,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
isDirectory: false,
|
|
23
|
+
modifiedTime: new Date('2024-01-15T11:20:00'),
|
|
24
|
+
name: 'report.pdf',
|
|
25
|
+
size: 2457600,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
isDirectory: false,
|
|
29
|
+
modifiedTime: new Date('2024-01-10T16:45:00'),
|
|
30
|
+
name: 'screenshot.png',
|
|
31
|
+
size: 1153434,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
isDirectory: false,
|
|
35
|
+
modifiedTime: new Date('2024-01-05T08:30:00'),
|
|
36
|
+
name: 'notes.txt',
|
|
37
|
+
size: 2048,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const result = formatFileList({
|
|
42
|
+
directory: '/Users/test/Desktop',
|
|
43
|
+
files,
|
|
44
|
+
sortBy: 'modifiedTime',
|
|
45
|
+
sortOrder: 'desc',
|
|
46
|
+
totalCount: 150,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result).toMatchInlineSnapshot(`
|
|
50
|
+
"Found 150 item(s) in /Users/test/Desktop (showing first 5, sorted by modifiedTime desc):
|
|
51
|
+
[D] Documents 2024-01-20 14:30 --
|
|
52
|
+
[D] Downloads 2024-01-18 09:15 --
|
|
53
|
+
[F] report.pdf 2024-01-15 11:20 2.3 MB
|
|
54
|
+
[F] screenshot.png 2024-01-10 16:45 1.1 MB
|
|
55
|
+
[F] notes.txt 2024-01-05 08:30 2 KB"
|
|
56
|
+
`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should match snapshot for simple output without extended info', () => {
|
|
60
|
+
const files = [
|
|
61
|
+
{ isDirectory: true, name: 'src' },
|
|
62
|
+
{ isDirectory: true, name: 'dist' },
|
|
63
|
+
{ isDirectory: false, name: 'package.json' },
|
|
64
|
+
{ isDirectory: false, name: 'README.md' },
|
|
65
|
+
{ isDirectory: false, name: 'tsconfig.json' },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const result = formatFileList({ directory: '/project', files });
|
|
69
|
+
|
|
70
|
+
expect(result).toMatchInlineSnapshot(`
|
|
71
|
+
"Found 5 item(s) in /project:
|
|
72
|
+
[D] src
|
|
73
|
+
[D] dist
|
|
74
|
+
[F] package.json
|
|
75
|
+
[F] README.md
|
|
76
|
+
[F] tsconfig.json"
|
|
77
|
+
`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should match snapshot for output with sorting info only', () => {
|
|
81
|
+
const files = [
|
|
82
|
+
{ isDirectory: false, name: 'alpha.txt' },
|
|
83
|
+
{ isDirectory: false, name: 'beta.txt' },
|
|
84
|
+
{ isDirectory: false, name: 'gamma.txt' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const result = formatFileList({
|
|
88
|
+
directory: '/test',
|
|
89
|
+
files,
|
|
90
|
+
sortBy: 'name',
|
|
91
|
+
sortOrder: 'asc',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result).toMatchInlineSnapshot(`
|
|
95
|
+
"Found 3 item(s) in /test (sorted by name asc):
|
|
96
|
+
[F] alpha.txt
|
|
97
|
+
[F] beta.txt
|
|
98
|
+
[F] gamma.txt"
|
|
99
|
+
`);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should match snapshot for various file sizes', () => {
|
|
103
|
+
const files = [
|
|
104
|
+
{
|
|
105
|
+
isDirectory: false,
|
|
106
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
107
|
+
name: 'empty.txt',
|
|
108
|
+
size: 0,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
isDirectory: false,
|
|
112
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
113
|
+
name: 'tiny.txt',
|
|
114
|
+
size: 512,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
isDirectory: false,
|
|
118
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
119
|
+
name: 'small.txt',
|
|
120
|
+
size: 1024,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
isDirectory: false,
|
|
124
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
125
|
+
name: 'medium.txt',
|
|
126
|
+
size: 1048576,
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
isDirectory: false,
|
|
130
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
131
|
+
name: 'large.txt',
|
|
132
|
+
size: 1073741824,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
isDirectory: false,
|
|
136
|
+
modifiedTime: new Date('2024-01-01T00:00:00'),
|
|
137
|
+
name: 'huge.txt',
|
|
138
|
+
size: 1099511627776,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const result = formatFileList({ directory: '/test', files });
|
|
143
|
+
|
|
144
|
+
expect(result).toMatchInlineSnapshot(`
|
|
145
|
+
"Found 6 item(s) in /test:
|
|
146
|
+
[F] empty.txt 2024-01-01 00:00 0 B
|
|
147
|
+
[F] tiny.txt 2024-01-01 00:00 512 B
|
|
148
|
+
[F] small.txt 2024-01-01 00:00 1 KB
|
|
149
|
+
[F] medium.txt 2024-01-01 00:00 1 MB
|
|
150
|
+
[F] large.txt 2024-01-01 00:00 1 GB
|
|
151
|
+
[F] huge.txt 2024-01-01 00:00 1 TB"
|
|
152
|
+
`);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
6
156
|
it('should format empty directory', () => {
|
|
7
|
-
const result = formatFileList(
|
|
157
|
+
const result = formatFileList({ directory: '/home/user', files: [] });
|
|
8
158
|
expect(result).toMatchInlineSnapshot(`"Directory /home/user is empty"`);
|
|
9
159
|
});
|
|
10
160
|
|
|
@@ -13,7 +163,7 @@ describe('formatFileList', () => {
|
|
|
13
163
|
{ isDirectory: false, name: 'file1.txt' },
|
|
14
164
|
{ isDirectory: false, name: 'file2.js' },
|
|
15
165
|
];
|
|
16
|
-
const result = formatFileList(
|
|
166
|
+
const result = formatFileList({ directory: '/home/user', files });
|
|
17
167
|
expect(result).toMatchInlineSnapshot(`
|
|
18
168
|
"Found 2 item(s) in /home/user:
|
|
19
169
|
[F] file1.txt
|
|
@@ -26,7 +176,7 @@ describe('formatFileList', () => {
|
|
|
26
176
|
{ isDirectory: true, name: 'src' },
|
|
27
177
|
{ isDirectory: true, name: 'dist' },
|
|
28
178
|
];
|
|
29
|
-
const result = formatFileList(
|
|
179
|
+
const result = formatFileList({ directory: '/project', files });
|
|
30
180
|
expect(result).toMatchInlineSnapshot(`
|
|
31
181
|
"Found 2 item(s) in /project:
|
|
32
182
|
[D] src
|
|
@@ -41,7 +191,7 @@ describe('formatFileList', () => {
|
|
|
41
191
|
{ isDirectory: true, name: 'node_modules' },
|
|
42
192
|
{ isDirectory: false, name: 'README.md' },
|
|
43
193
|
];
|
|
44
|
-
const result = formatFileList(
|
|
194
|
+
const result = formatFileList({ directory: '/project', files });
|
|
45
195
|
expect(result).toMatchInlineSnapshot(`
|
|
46
196
|
"Found 4 item(s) in /project:
|
|
47
197
|
[D] src
|
|
@@ -53,10 +203,152 @@ describe('formatFileList', () => {
|
|
|
53
203
|
|
|
54
204
|
it('should format single item', () => {
|
|
55
205
|
const files = [{ isDirectory: false, name: 'index.ts' }];
|
|
56
|
-
const result = formatFileList(
|
|
206
|
+
const result = formatFileList({ directory: '/src', files });
|
|
57
207
|
expect(result).toMatchInlineSnapshot(`
|
|
58
208
|
"Found 1 item(s) in /src:
|
|
59
209
|
[F] index.ts"
|
|
60
210
|
`);
|
|
61
211
|
});
|
|
212
|
+
|
|
213
|
+
describe('extended info', () => {
|
|
214
|
+
it('should format files with size and modified time', () => {
|
|
215
|
+
const files = [
|
|
216
|
+
{
|
|
217
|
+
isDirectory: true,
|
|
218
|
+
modifiedTime: new Date('2024-01-20T14:30:00'),
|
|
219
|
+
name: 'src',
|
|
220
|
+
size: 4096,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
isDirectory: false,
|
|
224
|
+
modifiedTime: new Date('2024-01-18T09:15:00'),
|
|
225
|
+
name: 'report.pdf',
|
|
226
|
+
size: 2457600, // 2.4 MB
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
isDirectory: false,
|
|
230
|
+
modifiedTime: new Date('2024-01-15T11:20:00'),
|
|
231
|
+
name: 'screenshot.png',
|
|
232
|
+
size: 1153434, // ~1.1 MB
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
const result = formatFileList({ directory: '/Users/test/Downloads', files });
|
|
236
|
+
expect(result).toContain('Found 3 item(s) in /Users/test/Downloads');
|
|
237
|
+
expect(result).toContain('[D] src');
|
|
238
|
+
expect(result).toContain('[F] report.pdf');
|
|
239
|
+
expect(result).toContain('2024-01-20 14:30');
|
|
240
|
+
expect(result).toContain('2.3 MB');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should show sorting and limit info in header', () => {
|
|
244
|
+
const files = [
|
|
245
|
+
{ isDirectory: false, name: 'file1.txt' },
|
|
246
|
+
{ isDirectory: false, name: 'file2.txt' },
|
|
247
|
+
];
|
|
248
|
+
const result = formatFileList({
|
|
249
|
+
directory: '/test',
|
|
250
|
+
files,
|
|
251
|
+
sortBy: 'modifiedTime',
|
|
252
|
+
sortOrder: 'desc',
|
|
253
|
+
totalCount: 150,
|
|
254
|
+
});
|
|
255
|
+
expect(result).toContain('Found 150 item(s)');
|
|
256
|
+
expect(result).toContain('showing first 2');
|
|
257
|
+
expect(result).toContain('sorted by modifiedTime desc');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should not show limit info when not truncated', () => {
|
|
261
|
+
const files = [
|
|
262
|
+
{ isDirectory: false, name: 'file1.txt' },
|
|
263
|
+
{ isDirectory: false, name: 'file2.txt' },
|
|
264
|
+
];
|
|
265
|
+
const result = formatFileList({
|
|
266
|
+
directory: '/test',
|
|
267
|
+
files,
|
|
268
|
+
sortBy: 'name',
|
|
269
|
+
sortOrder: 'asc',
|
|
270
|
+
});
|
|
271
|
+
expect(result).not.toContain('showing');
|
|
272
|
+
expect(result).toContain('sorted by name asc');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should show -- for directory size', () => {
|
|
276
|
+
const files = [
|
|
277
|
+
{
|
|
278
|
+
isDirectory: true,
|
|
279
|
+
modifiedTime: new Date('2024-01-20T14:30:00'),
|
|
280
|
+
name: 'my_folder',
|
|
281
|
+
size: 4096,
|
|
282
|
+
},
|
|
283
|
+
];
|
|
284
|
+
const result = formatFileList({ directory: '/test', files });
|
|
285
|
+
expect(result).toContain('--');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should format various file sizes correctly', () => {
|
|
289
|
+
const files = [
|
|
290
|
+
{ isDirectory: false, modifiedTime: new Date('2024-01-01'), name: 'zero.txt', size: 0 },
|
|
291
|
+
{ isDirectory: false, modifiedTime: new Date('2024-01-01'), name: 'bytes.txt', size: 500 },
|
|
292
|
+
{ isDirectory: false, modifiedTime: new Date('2024-01-01'), name: 'kb.txt', size: 2048 },
|
|
293
|
+
{ isDirectory: false, modifiedTime: new Date('2024-01-01'), name: 'mb.txt', size: 5242880 },
|
|
294
|
+
{
|
|
295
|
+
isDirectory: false,
|
|
296
|
+
modifiedTime: new Date('2024-01-01'),
|
|
297
|
+
name: 'gb.txt',
|
|
298
|
+
size: 1073741824,
|
|
299
|
+
},
|
|
300
|
+
];
|
|
301
|
+
const result = formatFileList({ directory: '/test', files });
|
|
302
|
+
expect(result).toContain('0 B');
|
|
303
|
+
expect(result).toContain('500 B');
|
|
304
|
+
expect(result).toContain('2 KB');
|
|
305
|
+
expect(result).toContain('5 MB');
|
|
306
|
+
expect(result).toContain('1 GB');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should handle files with only size (no modifiedTime)', () => {
|
|
310
|
+
const files = [{ isDirectory: false, name: 'file.txt', size: 1024 }];
|
|
311
|
+
const result = formatFileList({ directory: '/test', files });
|
|
312
|
+
expect(result).toContain('[F] file.txt');
|
|
313
|
+
expect(result).toContain('1 KB');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle files with only modifiedTime (no size)', () => {
|
|
317
|
+
const files = [
|
|
318
|
+
{ isDirectory: false, modifiedTime: new Date('2024-06-15T10:30:00'), name: 'file.txt' },
|
|
319
|
+
];
|
|
320
|
+
const result = formatFileList({ directory: '/test', files });
|
|
321
|
+
expect(result).toContain('[F] file.txt');
|
|
322
|
+
expect(result).toContain('2024-06-15 10:30');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle long file names', () => {
|
|
326
|
+
const files = [
|
|
327
|
+
{
|
|
328
|
+
isDirectory: false,
|
|
329
|
+
modifiedTime: new Date('2024-01-01'),
|
|
330
|
+
name: 'this_is_a_very_long_filename_that_exceeds_normal_length.txt',
|
|
331
|
+
size: 1024,
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
const result = formatFileList({ directory: '/test', files });
|
|
335
|
+
expect(result).toContain('this_is_a_very_long_filename_that_exceeds_normal_length.txt');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should handle options with only totalCount', () => {
|
|
339
|
+
const files = [{ isDirectory: false, name: 'file.txt' }];
|
|
340
|
+
const result = formatFileList({ directory: '/test', files, totalCount: 100 });
|
|
341
|
+
expect(result).toContain('Found 100 item(s)');
|
|
342
|
+
expect(result).toContain('showing first 1');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should not show extra info when totalCount equals file count', () => {
|
|
346
|
+
const files = [
|
|
347
|
+
{ isDirectory: false, name: 'file1.txt' },
|
|
348
|
+
{ isDirectory: false, name: 'file2.txt' },
|
|
349
|
+
];
|
|
350
|
+
const result = formatFileList({ directory: '/test', files, totalCount: 2 });
|
|
351
|
+
expect(result).not.toContain('showing');
|
|
352
|
+
});
|
|
353
|
+
});
|
|
62
354
|
});
|