@jk3labs/paperclip-plugin-file-browser 0.2.6 → 0.2.8

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/README.md CHANGED
@@ -1,30 +1,91 @@
1
- # JKL File Browser
2
-
3
- A file browser plugin for Paperclip workspaces
4
-
5
- ## Development
6
-
7
- ```bash
8
- pnpm install
9
- pnpm dev # watch builds
10
- pnpm dev:ui # local dev server with hot-reload events
11
- pnpm test
12
- ```
13
-
14
- `pnpm dev` rebuilds the worker, manifest, and UI bundles into `dist/`.
15
- When this package is installed from a local path, Paperclip watches that rebuilt
16
- output and reloads the plugin worker. Local installs run trusted code from this
17
- folder on your machine.
18
-
19
-
20
-
21
- ## Install Into Paperclip
22
-
23
- ```bash
24
- paperclipai plugin install C:/Users/Jad_K/.paperclip/instances/default/projects/d0b61e38-82d9-4bcf-b9e8-8ea970388ba9/2ba5752a-ea82-4afc-8d60-2ede6be97f44/_default/file-browser
25
- ```
26
-
27
- ## Build Options
28
-
29
- - `pnpm build` uses esbuild presets from `@jkawwa/paperclipai-plugin-sdk/bundlers`.
30
- - `pnpm build:rollup` uses rollup presets from the same SDK.
1
+ # Paperclip File Browser Plugin
2
+
3
+ A Paperclip plugin for browsing, downloading, and zipping files in the default workspace.
4
+
5
+ ## Installation
6
+
7
+ 1. Clone this repository:
8
+
9
+ ```bash
10
+ git clone https://github.com/jk3labs/paperclip-plugin-file-browser.git
11
+ cd paperclip-plugin-file-browser
12
+ ```
13
+
14
+ 2. Install dependencies:
15
+
16
+ ```bash
17
+ npm install
18
+ ```
19
+
20
+ 3. Compile the plugin:
21
+
22
+ ```bash
23
+ npx tsc
24
+ ```
25
+
26
+ 4. Install the plugin in Paperclip:
27
+
28
+ ```bash
29
+ paperclipai plugin install ./paperclip-plugin-file-browser
30
+ ```
31
+
32
+ ## API Endpoints
33
+
34
+ ### 1. List Directory Contents
35
+
36
+ **Endpoint:** `GET /files/list`
37
+
38
+ **Query Parameters:**
39
+ - `path` (optional): Relative path to the directory (defaults to root).
40
+
41
+ **Response:**
42
+ ```json
43
+ {
44
+ "files": ["file1.txt", "file2.txt"],
45
+ "directories": ["dir1", "dir2"],
46
+ "currentPath": "relative/path",
47
+ "rootDir": "/absolute/root/path"
48
+ }
49
+ ```
50
+
51
+ **Errors:**
52
+ - `403 Forbidden`: Access denied (path outside root).
53
+ - `404 Not Found`: Directory does not exist.
54
+ - `500 Internal Server Error`: Server error.
55
+
56
+ ### 2. Download File
57
+
58
+ **Endpoint:** `GET /files/download`
59
+
60
+ **Query Parameters:**
61
+ - `path` (required): Relative path to the file.
62
+
63
+ **Response:**
64
+ - Streams the file for download.
65
+
66
+ **Errors:**
67
+ - `400 Bad Request`: Path parameter missing.
68
+ - `403 Forbidden`: Access denied (path outside root).
69
+ - `404 Not Found`: File does not exist.
70
+ - `500 Internal Server Error`: Server error.
71
+
72
+ ### 3. Download Directory as ZIP
73
+
74
+ **Endpoint:** `GET /files/zip`
75
+
76
+ **Query Parameters:**
77
+ - `path` (required): Relative path to the directory.
78
+
79
+ **Response:**
80
+ - Streams the directory as a ZIP file.
81
+
82
+ **Errors:**
83
+ - `400 Bad Request`: Path parameter missing.
84
+ - `403 Forbidden`: Access denied (path outside root).
85
+ - `404 Not Found`: Directory does not exist.
86
+ - `500 Internal Server Error`: Server error.
87
+
88
+ ## Development
89
+
90
+ - Run `npx tsc --watch` to enable TypeScript watch mode.
91
+ - Reinstall the plugin after making changes.
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ const express_1 = __importStar(require("express"));
40
+ const supertest_1 = __importDefault(require("supertest"));
41
+ const fs_extra_1 = __importDefault(require("fs-extra"));
42
+ const path_1 = __importDefault(require("path"));
43
+ // Mock the plugin router directly
44
+ const mockRouter = (0, express_1.Router)();
45
+ mockRouter.get('/list', (req, res) => {
46
+ const relativePath = req.query.path || '';
47
+ const mockRootDir = path_1.default.join(__dirname, 'mock-root');
48
+ const targetPath = path_1.default.join(mockRootDir, relativePath);
49
+ fs_extra_1.default.pathExists(targetPath)
50
+ .then(exists => {
51
+ if (!exists)
52
+ return res.status(404).json({ error: 'Directory not found' });
53
+ fs_extra_1.default.readdir(targetPath, { withFileTypes: true })
54
+ .then(entries => {
55
+ const files = entries.filter(dirent => dirent.isFile()).map(dirent => dirent.name);
56
+ const directories = entries.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
57
+ res.json({ files, directories, currentPath: relativePath });
58
+ })
59
+ .catch(() => res.status(500).json({ error: 'Failed to read directory' }));
60
+ })
61
+ .catch(() => res.status(500).json({ error: 'Server error' }));
62
+ });
63
+ mockRouter.get('/download', (req, res) => {
64
+ const relativePath = req.query.path;
65
+ if (!relativePath)
66
+ return res.status(400).json({ error: 'Path parameter is required' });
67
+ const mockRootDir = path_1.default.join(__dirname, 'mock-root');
68
+ const targetPath = path_1.default.join(mockRootDir, relativePath);
69
+ fs_extra_1.default.pathExists(targetPath)
70
+ .then(exists => {
71
+ if (!exists)
72
+ return res.status(404).json({ error: 'File not found' });
73
+ fs_extra_1.default.stat(targetPath)
74
+ .then(stats => {
75
+ if (!stats.isFile())
76
+ return res.status(404).json({ error: 'File not found' });
77
+ res.sendFile(targetPath);
78
+ })
79
+ .catch(() => res.status(500).json({ error: 'Failed to read file' }));
80
+ })
81
+ .catch(() => res.status(500).json({ error: 'Server error' }));
82
+ });
83
+ mockRouter.get('/zip', (req, res) => {
84
+ const relativePath = req.query.path;
85
+ if (!relativePath)
86
+ return res.status(400).json({ error: 'Path parameter is required' });
87
+ const mockRootDir = path_1.default.join(__dirname, 'mock-root');
88
+ const targetPath = path_1.default.join(mockRootDir, relativePath);
89
+ fs_extra_1.default.pathExists(targetPath)
90
+ .then(exists => {
91
+ if (!exists)
92
+ return res.status(404).json({ error: 'Directory not found' });
93
+ fs_extra_1.default.stat(targetPath)
94
+ .then(stats => {
95
+ if (!stats.isDirectory())
96
+ return res.status(404).json({ error: 'Directory not found' });
97
+ res.status(500).json({ error: 'ZIP functionality not implemented in mock' });
98
+ })
99
+ .catch(() => res.status(500).json({ error: 'Failed to read directory' }));
100
+ })
101
+ .catch(() => res.status(500).json({ error: 'Server error' }));
102
+ });
103
+ describe('File Browser Plugin', () => {
104
+ let app;
105
+ const mockRootDir = path_1.default.join(__dirname, 'mock-root');
106
+ const mockFilePath = path_1.default.join(mockRootDir, 'test.txt');
107
+ const mockDirPath = path_1.default.join(mockRootDir, 'test-dir');
108
+ beforeAll(async () => {
109
+ // Setup mock filesystem
110
+ await fs_extra_1.default.ensureDir(mockRootDir);
111
+ await fs_extra_1.default.writeFile(mockFilePath, 'test content');
112
+ await fs_extra_1.default.ensureDir(mockDirPath);
113
+ await fs_extra_1.default.writeFile(path_1.default.join(mockDirPath, 'nested.txt'), 'nested content');
114
+ // Setup Express app
115
+ app = (0, express_1.default)();
116
+ app.use('/files', mockRouter);
117
+ });
118
+ afterAll(async () => {
119
+ // Cleanup mock filesystem
120
+ await fs_extra_1.default.remove(mockRootDir);
121
+ });
122
+ describe('GET /files/list', () => {
123
+ it('should list files and directories in root', async () => {
124
+ const response = await (0, supertest_1.default)(app).get('/files/list');
125
+ expect(response.status).toBe(200);
126
+ expect(response.body.files).toContain('test.txt');
127
+ expect(response.body.directories).toContain('test-dir');
128
+ });
129
+ it('should list files and directories in subdirectory', async () => {
130
+ const response = await (0, supertest_1.default)(app).get('/files/list?path=test-dir');
131
+ expect(response.status).toBe(200);
132
+ expect(response.body.files).toContain('nested.txt');
133
+ });
134
+ it('should return 404 for non-existent directory', async () => {
135
+ const response = await (0, supertest_1.default)(app).get('/files/list?path=non-existent');
136
+ expect(response.status).toBe(404);
137
+ });
138
+ });
139
+ describe('GET /files/download', () => {
140
+ it('should download a file', async () => {
141
+ const response = await (0, supertest_1.default)(app).get('/files/download?path=test.txt');
142
+ expect(response.status).toBe(200);
143
+ expect(response.text).toBe('test content');
144
+ });
145
+ it('should return 404 for non-existent file', async () => {
146
+ const response = await (0, supertest_1.default)(app).get('/files/download?path=non-existent.txt');
147
+ expect(response.status).toBe(404);
148
+ });
149
+ });
150
+ describe('GET /files/zip', () => {
151
+ it('should return 404 for non-existent directory', async () => {
152
+ const response = await (0, supertest_1.default)(app).get('/files/zip?path=non-existent');
153
+ expect(response.status).toBe(404);
154
+ });
155
+ });
156
+ });
package/dist/index.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const archiver_1 = __importDefault(require("archiver"));
10
+ ;
11
+ const plugin = {
12
+ id: 'file-browser',
13
+ name: 'File Browser Plugin',
14
+ description: 'A plugin to browse, download, and zip files in the Paperclip default workspace.',
15
+ register: (context) => {
16
+ console.debug('[File Browser Plugin] Registering plugin');
17
+ const router = express_1.default.Router();
18
+ const rootDir = context.environment.rootDir;
19
+ // Validate rootDir exists
20
+ console.debug(`[File Browser Plugin] Root directory: ${rootDir}`);
21
+ if (!rootDir || !(fs_extra_1.default.existsSync(rootDir))) {
22
+ console.error(`[File Browser Plugin] Invalid root directory: ${rootDir}`);
23
+ throw new Error(`Invalid root directory: ${rootDir}`);
24
+ }
25
+ router.get('/list', async (req, res) => {
26
+ try {
27
+ const relativePath = req.query.path || '';
28
+ const targetPath = path_1.default.join(rootDir, relativePath);
29
+ // Security: Ensure targetPath is within rootDir
30
+ if (!targetPath.startsWith(rootDir)) {
31
+ return res.status(403).json({ error: 'Access denied' });
32
+ }
33
+ const entries = await fs_extra_1.default.readdir(targetPath, { withFileTypes: true });
34
+ const files = entries
35
+ .filter(dirent => dirent.isFile())
36
+ .map(dirent => dirent.name);
37
+ const directories = entries
38
+ .filter(dirent => dirent.isDirectory())
39
+ .map(dirent => dirent.name);
40
+ res.json({
41
+ files,
42
+ directories,
43
+ currentPath: path_1.default.relative(rootDir, targetPath),
44
+ rootDir
45
+ });
46
+ }
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : String(error);
49
+ const statusCode = error.code === 'ENOENT' ? 404 : 500;
50
+ res.status(statusCode).json({ error: message });
51
+ }
52
+ });
53
+ router.get('/download', async (req, res) => {
54
+ try {
55
+ const relativePath = req.query.path;
56
+ if (!relativePath) {
57
+ return res.status(400).json({ error: 'Path parameter is required' });
58
+ }
59
+ const targetPath = path_1.default.join(rootDir, relativePath);
60
+ // Security: Ensure targetPath is within rootDir
61
+ if (!targetPath.startsWith(rootDir)) {
62
+ return res.status(403).json({ error: 'Access denied' });
63
+ }
64
+ // Check if path exists and is a file
65
+ const stats = await fs_extra_1.default.stat(targetPath);
66
+ if (!stats.isFile()) {
67
+ return res.status(404).json({ error: 'File not found' });
68
+ }
69
+ // Stream file for download
70
+ res.download(targetPath, path_1.default.basename(targetPath));
71
+ }
72
+ catch (error) {
73
+ const message = error instanceof Error ? error.message : String(error);
74
+ const statusCode = error.code === 'ENOENT' ? 404 : 500;
75
+ res.status(statusCode).json({ error: message });
76
+ }
77
+ });
78
+ router.get('/zip', async (req, res) => {
79
+ try {
80
+ const relativePath = req.query.path;
81
+ if (!relativePath) {
82
+ return res.status(400).json({ error: 'Path parameter is required' });
83
+ }
84
+ const targetPath = path_1.default.join(rootDir, relativePath);
85
+ // Security: Ensure targetPath is within rootDir
86
+ if (!targetPath.startsWith(rootDir)) {
87
+ return res.status(403).json({ error: 'Access denied' });
88
+ }
89
+ // Check if path exists and is a directory
90
+ const stats = await fs_extra_1.default.stat(targetPath);
91
+ if (!stats.isDirectory()) {
92
+ return res.status(404).json({ error: 'Directory not found' });
93
+ }
94
+ // Set headers for ZIP download
95
+ res.attachment(`${path_1.default.basename(targetPath)}.zip`);
96
+ // Create ZIP stream
97
+ const archive = (0, archiver_1.default)('zip', {
98
+ zlib: { level: 9 } // Maximum compression
99
+ });
100
+ archive.pipe(res);
101
+ archive.directory(targetPath, false);
102
+ await archive.finalize();
103
+ }
104
+ catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ const statusCode = error.code === 'ENOENT' ? 404 : 500;
107
+ res.status(statusCode).json({ error: message });
108
+ }
109
+ });
110
+ context.addRouter('/files', router);
111
+ },
112
+ };
113
+ exports.default = plugin;
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "file-browser",
3
+ "name": "File Browser Plugin",
4
+ "description": "A plugin to browse, download, and zip files in the Paperclip default workspace."
5
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const react_1 = require("react");
5
+ const FileBrowser = ({ apiBaseUrl }) => {
6
+ const [files, setFiles] = (0, react_1.useState)([]);
7
+ const [directories, setDirectories] = (0, react_1.useState)([]);
8
+ const [currentPath, setCurrentPath] = (0, react_1.useState)('');
9
+ const [error, setError] = (0, react_1.useState)(null);
10
+ const [loading, setLoading] = (0, react_1.useState)(true);
11
+ (0, react_1.useEffect)(() => {
12
+ console.debug('[File Browser UI] Fetching file list');
13
+ const fetchFiles = async () => {
14
+ try {
15
+ const response = await fetch(`${apiBaseUrl}/list?path=${encodeURIComponent(currentPath)}`);
16
+ if (!response.ok) {
17
+ const errorData = await response.json();
18
+ throw new Error(errorData.error || 'Failed to fetch files');
19
+ }
20
+ const data = await response.json();
21
+ setFiles(data.files);
22
+ setDirectories(data.directories);
23
+ setCurrentPath(data.currentPath);
24
+ }
25
+ catch (err) {
26
+ console.error('[File Browser UI] Error fetching files:', err);
27
+ setError(err instanceof Error ? err.message : 'Failed to load files');
28
+ }
29
+ finally {
30
+ setLoading(false);
31
+ }
32
+ };
33
+ fetchFiles();
34
+ }, [currentPath, apiBaseUrl]);
35
+ const handleDirectoryClick = (dir) => {
36
+ setCurrentPath(currentPath ? `${currentPath}/${dir}` : dir);
37
+ };
38
+ if (loading) {
39
+ return (0, jsx_runtime_1.jsx)("div", { children: "Loading..." });
40
+ }
41
+ if (error) {
42
+ return (0, jsx_runtime_1.jsxs)("div", { children: ["File Browser: ", error] });
43
+ }
44
+ return ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '16px', fontFamily: 'sans-serif' }, children: [(0, jsx_runtime_1.jsxs)("h3", { children: ["File Browser: ", currentPath || 'Root'] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { children: "Directories" }), (0, jsx_runtime_1.jsx)("ul", { children: directories.map((dir) => ((0, jsx_runtime_1.jsxs)("li", { onClick: () => handleDirectoryClick(dir), style: { cursor: 'pointer', color: 'blue' }, children: ["\uD83D\uDCC1 ", dir] }, dir))) })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h4", { children: "Files" }), (0, jsx_runtime_1.jsx)("ul", { children: files.map((file) => ((0, jsx_runtime_1.jsxs)("li", { children: ["\uD83D\uDCC4 ", file] }, file))) })] })] }));
45
+ };
46
+ exports.default = FileBrowser;
package/index.js ADDED
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ const express = require("express");
4
+ const fs = require("fs-extra");
5
+ const path = require("path");
6
+
7
+ module.exports = (pluginContext) => {
8
+ const router = express.Router();
9
+
10
+ // Dynamic root lookup: Use Paperclip's workspace root
11
+ const rootDir = pluginContext.workspaceRoot || process.env.PAPERCLIP_WORKSPACE_ROOT || process.cwd();
12
+
13
+ /**
14
+ * GET /files/list
15
+ * List files and directories at the given path
16
+ */
17
+ router.get("/list", async (req, res) => {
18
+ const { dir } = req.query;
19
+ if (!dir) {
20
+ return res.status(400).json({ error: "Query parameter 'dir' is required" });
21
+ }
22
+
23
+ const targetPath = path.join(rootDir, dir);
24
+ try {
25
+ const stats = await fs.stat(targetPath);
26
+ if (!stats.isDirectory()) {
27
+ return res.status(400).json({ error: "Path is not a directory" });
28
+ }
29
+
30
+ const items = await fs.readdir(targetPath);
31
+ const detailedItems = await Promise.all(
32
+ items.map(async (item) => {
33
+ const itemPath = path.join(targetPath, item);
34
+ const itemStats = await fs.stat(itemPath);
35
+ return {
36
+ name: item,
37
+ path: path.relative(rootDir, itemPath),
38
+ isDirectory: itemStats.isDirectory(),
39
+ size: itemStats.size,
40
+ modified: itemStats.mtime
41
+ };
42
+ })
43
+ );
44
+
45
+ res.json({
46
+ path: dir,
47
+ items: detailedItems
48
+ });
49
+ } catch (error) {
50
+ res.status(500).json({ error: error.message });
51
+ }
52
+ });
53
+
54
+ /**
55
+ * GET /files/read
56
+ * Read file content
57
+ */
58
+ router.get("/read", async (req, res) => {
59
+ const { file } = req.query;
60
+ if (!file) {
61
+ return res.status(400).json({ error: "Query parameter 'file' is required" });
62
+ }
63
+
64
+ const filePath = path.join(rootDir, file);
65
+ try {
66
+ const stats = await fs.stat(filePath);
67
+ if (!stats.isFile()) {
68
+ return res.status(400).json({ error: "Path is not a file" });
69
+ }
70
+
71
+ const content = await fs.readFile(filePath, "utf-8");
72
+ res.json({
73
+ path: file,
74
+ content
75
+ });
76
+ } catch (error) {
77
+ res.status(500).json({ error: error.message });
78
+ }
79
+ });
80
+
81
+ return router;
82
+ };
package/package.json CHANGED
@@ -1,48 +1,44 @@
1
- {
2
- "name": "@jk3labs/paperclip-plugin-file-browser",
3
- "version": "0.2.6",
4
- "type": "module",
5
- "description": "A file browser plugin for Paperclip workspaces",
6
- "files": [
7
- "dist"
8
- ],
9
- "publishConfig": {
10
- "access": "public"
11
- },
12
- "scripts": {
13
- "build": "node ./esbuild.config.mjs",
14
- "build:rollup": "rollup -c",
15
- "dev": "node ./esbuild.config.mjs --watch",
16
- "dev:ui": "paperclip-plugin-dev-server --root . --ui-dir dist/ui --port 4177",
17
- "test": "vitest run --config ./vitest.config.ts",
18
- "typecheck": "tsc --noEmit"
19
- },
20
- "paperclipPlugin": {
21
- "manifest": "./dist/manifest.js",
22
- "worker": "./dist/worker.js",
23
- "ui": "./dist/ui/"
24
- },
25
- "keywords": [
26
- "paperclip",
27
- "plugin",
28
- "workspace"
29
- ],
30
- "author": "JK3Labs",
31
- "license": "MIT",
32
- "dependencies": {
33
- "react": "^18.2.0",
34
- "react-dom": "^18.2.0"
35
- },
36
- "devDependencies": {
37
- "@paperclipai/plugin-sdk": "2026.525.0",
38
- "@rollup/plugin-node-resolve": "^16.0.1",
39
- "@rollup/plugin-typescript": "^12.1.2",
40
- "@types/node": "^24.6.0",
41
- "@types/react": "^19.0.8",
42
- "esbuild": "^0.27.3",
43
- "rollup": "^4.38.0",
44
- "tslib": "^2.8.1",
45
- "typescript": "^5.7.3",
46
- "vitest": "^3.0.5"
47
- }
48
- }
1
+ {
2
+ "name": "@jk3labs/paperclip-plugin-file-browser",
3
+ "version": "0.2.8",
4
+ "description": "A Paperclip plugin for browsing files in the default workspace",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "jest",
8
+ "test:watch": "jest --watch",
9
+ "build": "tsc && cp plugin.json dist/"
10
+ },
11
+ "keywords": [
12
+ "paperclip",
13
+ "plugin",
14
+ "file-browser"
15
+ ],
16
+ "author": "JKL",
17
+ "license": "ISC",
18
+ "files": [
19
+ "dist",
20
+ "index.js",
21
+ "plugin.json"
22
+ ],
23
+ "dependencies": {
24
+ "@types/archiver": "^7.0.0",
25
+ "archiver": "^8.0.0",
26
+ "express": "^4.18.2",
27
+ "fs-extra": "^11.1.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/express": "^5.0.6",
31
+ "@types/fs-extra": "^11.0.4",
32
+ "@types/jest": "^30.0.0",
33
+ "@types/node": "^25.9.1",
34
+ "@types/react": "^19.2.16",
35
+ "@types/react-dom": "^19.2.3",
36
+ "@types/supertest": "^7.2.0",
37
+ "jest": "^30.4.2",
38
+ "react": "^19.2.7",
39
+ "react-dom": "^19.2.7",
40
+ "supertest": "^7.2.2",
41
+ "ts-jest": "^29.4.11",
42
+ "typescript": "^6.0.3"
43
+ }
44
+ }
package/plugin.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "file-browser",
3
+ "name": "File Browser Plugin",
4
+ "description": "A plugin to browse, download, and zip files in the Paperclip default workspace."
5
+ }