@jk3labs/paperclip-plugin-file-browser 0.1.4

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 ADDED
@@ -0,0 +1,30 @@
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.
@@ -0,0 +1,81 @@
1
+ // src/manifest.ts
2
+ var manifest = {
3
+ id: "jkl.file-browser",
4
+ apiVersion: 1,
5
+ version: "0.1.4",
6
+ displayName: "File Browser",
7
+ description: "A file browser plugin for Paperclip workspaces",
8
+ author: "Plugin Author",
9
+ categories: ["workspace"],
10
+ capabilities: [
11
+ "events.subscribe",
12
+ "plugin.state.read",
13
+ "plugin.state.write",
14
+ "local.folders",
15
+ "ui.dashboardWidget.register",
16
+ "ui.detailTab.register",
17
+ "api.routes.register",
18
+ "project.workspaces.read"
19
+ ],
20
+ localFolders: [
21
+ {
22
+ folderKey: "workspace",
23
+ displayName: "Workspace Root",
24
+ description: "Root directory of the project workspace to browse files",
25
+ access: "read"
26
+ }
27
+ ],
28
+ apiRoutes: [
29
+ {
30
+ routeKey: "list-files",
31
+ method: "GET",
32
+ path: "/files/list",
33
+ auth: "board-or-agent",
34
+ capability: "api.routes.register",
35
+ companyResolution: { from: "query", key: "companyId" }
36
+ },
37
+ {
38
+ routeKey: "download-file",
39
+ method: "GET",
40
+ path: "/files/download",
41
+ auth: "board-or-agent",
42
+ capability: "api.routes.register",
43
+ companyResolution: { from: "query", key: "companyId" }
44
+ },
45
+ {
46
+ routeKey: "download-zip",
47
+ method: "GET",
48
+ path: "/files/zip",
49
+ auth: "board-or-agent",
50
+ capability: "api.routes.register",
51
+ companyResolution: { from: "query", key: "companyId" }
52
+ }
53
+ ],
54
+ entrypoints: {
55
+ worker: "./dist/worker.js",
56
+ ui: "./dist/ui"
57
+ },
58
+ ui: {
59
+ slots: [
60
+ {
61
+ type: "dashboardWidget",
62
+ id: "file-browser-widget",
63
+ displayName: "File Browser",
64
+ exportName: "DashboardWidget"
65
+ },
66
+ {
67
+ type: "detailTab",
68
+ id: "file-browser-tab",
69
+ displayName: "Files",
70
+ exportName: "FileBrowserTab",
71
+ entityTypes: ["project", "project_workspace", "issue"],
72
+ order: 50
73
+ }
74
+ ]
75
+ }
76
+ };
77
+ var manifest_default = manifest;
78
+ export {
79
+ manifest_default as default
80
+ };
81
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/manifest.ts"],
4
+ "sourcesContent": ["import type { PaperclipPluginManifestV1 } from \"@paperclipai/plugin-sdk\";\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: \"jkl.file-browser\",\n apiVersion: 1,\n version: \"0.1.4\",\n displayName: \"File Browser\",\n description: \"A file browser plugin for Paperclip workspaces\",\n author: \"Plugin Author\",\n categories: [\"workspace\"],\n capabilities: [\n \"events.subscribe\",\n \"plugin.state.read\",\n \"plugin.state.write\",\n \"local.folders\",\n \"ui.dashboardWidget.register\",\n \"ui.detailTab.register\",\n \"api.routes.register\",\n \"project.workspaces.read\"\n ],\n localFolders: [\n {\n folderKey: \"workspace\",\n displayName: \"Workspace Root\",\n description: \"Root directory of the project workspace to browse files\",\n access: \"read\"\n }\n ],\n apiRoutes: [\n {\n routeKey: \"list-files\",\n method: \"GET\",\n path: \"/files/list\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n },\n {\n routeKey: \"download-file\",\n method: \"GET\",\n path: \"/files/download\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n },\n {\n routeKey: \"download-zip\",\n method: \"GET\",\n path: \"/files/zip\",\n auth: \"board-or-agent\",\n capability: \"api.routes.register\",\n companyResolution: { from: \"query\", key: \"companyId\" }\n }\n ],\n entrypoints: {\n worker: \"./dist/worker.js\",\n ui: \"./dist/ui\"\n },\n ui: {\n slots: [\n {\n type: \"dashboardWidget\",\n id: \"file-browser-widget\",\n displayName: \"File Browser\",\n exportName: \"DashboardWidget\"\n },\n {\n type: \"detailTab\",\n id: \"file-browser-tab\",\n displayName: \"Files\",\n exportName: \"FileBrowserTab\",\n entityTypes: [\"project\", \"project_workspace\", \"issue\"],\n order: 50\n }\n ]\n }\n};\n\nexport default manifest;\n"],
5
+ "mappings": ";AAEA,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,WAAW;AAAA,EACxB,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ;AAAA,MACE,WAAW;AAAA,MACX,aAAa;AAAA,MACb,aAAa;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,WAAW;AAAA,IACT;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,IACA;AAAA,MACE,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,mBAAmB,EAAE,MAAM,SAAS,KAAK,YAAY;AAAA,IACvD;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,aAAa,CAAC,WAAW,qBAAqB,OAAO;AAAA,QACrD,OAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,139 @@
1
+ // src/ui/index.tsx
2
+ import { useCallback, useState } from "react";
3
+ import {
4
+ usePluginData,
5
+ usePluginAction,
6
+ useHostContext
7
+ } from "@paperclipai/plugin-sdk/ui";
8
+ import {
9
+ FileTree,
10
+ MetricCard
11
+ } from "@paperclipai/plugin-sdk/ui";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ function buildNodes(data) {
14
+ if (!data) return [];
15
+ const dirNodes = data.directories.map((d) => ({
16
+ name: d.name,
17
+ path: d.path,
18
+ kind: "dir",
19
+ children: []
20
+ }));
21
+ const fileNodes = data.files.map((f) => ({
22
+ name: f.name,
23
+ path: f.path,
24
+ kind: "file",
25
+ children: []
26
+ }));
27
+ return [...dirNodes, ...fileNodes];
28
+ }
29
+ function formatSize(bytes) {
30
+ if (bytes == null) return "-";
31
+ if (bytes < 1024) return `${bytes} B`;
32
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
33
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
34
+ }
35
+ function FileBrowser({ companyId }) {
36
+ const [currentPath, setCurrentPath] = useState("");
37
+ const [selectedFile, setSelectedFile] = useState(null);
38
+ const { data, loading, error, refresh } = usePluginData(
39
+ "file-tree",
40
+ { companyId: companyId ?? "", relativePath: currentPath }
41
+ );
42
+ const downloadFile = usePluginAction("download-file");
43
+ const downloadZip = usePluginAction("download-zip");
44
+ const nodes = buildNodes(data);
45
+ const handleSelectFile = useCallback((path) => {
46
+ setSelectedFile(path);
47
+ }, []);
48
+ const handleToggleDir = useCallback((path) => {
49
+ setCurrentPath(path);
50
+ setSelectedFile(null);
51
+ }, []);
52
+ const handleDownloadFile = useCallback(async () => {
53
+ if (!selectedFile || !companyId) return;
54
+ try {
55
+ await downloadFile({ companyId, relativePath: selectedFile });
56
+ } catch {
57
+ }
58
+ }, [selectedFile, companyId, downloadFile]);
59
+ const handleDownloadZip = useCallback(async () => {
60
+ if (!currentPath || !companyId) return;
61
+ try {
62
+ await downloadZip({ companyId, relativePath: currentPath });
63
+ } catch {
64
+ }
65
+ }, [currentPath, companyId, downloadZip]);
66
+ const handleNavigateUp = useCallback(() => {
67
+ if (!currentPath) return;
68
+ const parts = currentPath.split("/");
69
+ parts.pop();
70
+ setCurrentPath(parts.join("/"));
71
+ setSelectedFile(null);
72
+ }, [currentPath]);
73
+ const handleRefresh = useCallback(() => {
74
+ refresh();
75
+ }, [refresh]);
76
+ const fileCount = data?.files.length ?? 0;
77
+ const dirCount = data?.directories.length ?? 0;
78
+ const totalSize = data?.files.reduce((sum, f) => sum + (f.size ?? 0), 0) ?? 0;
79
+ if (!companyId) {
80
+ return /* @__PURE__ */ jsx("div", { style: { padding: "1rem" }, children: "Select a company to browse files." });
81
+ }
82
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.75rem", padding: "0.5rem 0" }, children: [
83
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", flexWrap: "wrap" }, children: [
84
+ /* @__PURE__ */ jsx(MetricCard, { label: "Files", value: fileCount }),
85
+ /* @__PURE__ */ jsx(MetricCard, { label: "Directories", value: dirCount }),
86
+ /* @__PURE__ */ jsx(MetricCard, { label: "Total Size", value: formatSize(totalSize) })
87
+ ] }),
88
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.5rem", alignItems: "center", flexWrap: "wrap" }, children: [
89
+ currentPath && /* @__PURE__ */ jsx("button", { onClick: handleNavigateUp, style: { fontSize: "0.85rem" }, children: ".. /" }),
90
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: "0.85rem", color: "var(--text-secondary, #888)" }, children: [
91
+ "/",
92
+ currentPath || ""
93
+ ] }),
94
+ /* @__PURE__ */ jsxs("div", { style: { marginLeft: "auto", display: "flex", gap: "0.5rem" }, children: [
95
+ /* @__PURE__ */ jsx("button", { onClick: handleRefresh, style: { fontSize: "0.85rem" }, children: "Refresh" }),
96
+ selectedFile && /* @__PURE__ */ jsx("button", { onClick: handleDownloadFile, style: { fontSize: "0.85rem" }, children: "Download File" }),
97
+ currentPath && /* @__PURE__ */ jsx("button", { onClick: handleDownloadZip, style: { fontSize: "0.85rem" }, children: "Download Folder (ZIP)" })
98
+ ] })
99
+ ] }),
100
+ data?.error && /* @__PURE__ */ jsxs("div", { style: { color: "var(--text-error, red)", fontSize: "0.85rem" }, children: [
101
+ "Error: ",
102
+ data.error
103
+ ] }),
104
+ /* @__PURE__ */ jsx(
105
+ FileTree,
106
+ {
107
+ nodes,
108
+ selectedFile,
109
+ expandedPaths: [],
110
+ onSelectFile: handleSelectFile,
111
+ onToggleDir: handleToggleDir,
112
+ loading,
113
+ error: error ? { message: error.message } : null,
114
+ empty: { title: "Empty directory", description: "No files or directories found." },
115
+ ariaLabel: "File browser"
116
+ }
117
+ ),
118
+ selectedFile && /* @__PURE__ */ jsxs("div", { style: { fontSize: "0.8rem", color: "var(--text-secondary, #888)", paddingTop: "0.25rem" }, children: [
119
+ "Selected: ",
120
+ selectedFile
121
+ ] })
122
+ ] });
123
+ }
124
+ function DashboardWidget(_props) {
125
+ const { companyId } = useHostContext();
126
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: "0.5rem" }, children: [
127
+ /* @__PURE__ */ jsx("strong", { children: "File Browser" }),
128
+ /* @__PURE__ */ jsx(FileBrowser, { companyId })
129
+ ] });
130
+ }
131
+ function FileBrowserTab(props) {
132
+ const { companyId } = props.context;
133
+ return /* @__PURE__ */ jsx(FileBrowser, { companyId });
134
+ }
135
+ export {
136
+ DashboardWidget,
137
+ FileBrowserTab
138
+ };
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/ui/index.tsx"],
4
+ "sourcesContent": ["import { useCallback, useState } from \"react\";\nimport {\n usePluginData,\n usePluginAction,\n useHostContext,\n type PluginWidgetProps,\n type PluginDetailTabProps,\n} from \"@paperclipai/plugin-sdk/ui\";\nimport {\n FileTree,\n MetricCard,\n Spinner,\n type FileTreeNode,\n} from \"@paperclipai/plugin-sdk/ui\";\n\ninterface FileEntry {\n name: string;\n path: string;\n kind: \"file\" | \"directory\";\n size?: number | null;\n modifiedAt?: string | null;\n}\n\ninterface FileTreeData {\n currentPath: string;\n directories: FileEntry[];\n files: FileEntry[];\n error?: string;\n}\n\nfunction buildNodes(data: FileTreeData | null): FileTreeNode[] {\n if (!data) return [];\n const dirNodes: FileTreeNode[] = data.directories.map((d) => ({\n name: d.name,\n path: d.path,\n kind: \"dir\" as const,\n children: [],\n }));\n const fileNodes: FileTreeNode[] = data.files.map((f) => ({\n name: f.name,\n path: f.path,\n kind: \"file\" as const,\n children: [],\n }));\n return [...dirNodes, ...fileNodes];\n}\n\nfunction formatSize(bytes: number | null | undefined): string {\n if (bytes == null) return \"-\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction FileBrowser({ companyId }: { companyId: string | null }) {\n const [currentPath, setCurrentPath] = useState(\"\");\n const [selectedFile, setSelectedFile] = useState<string | null>(null);\n\n const { data, loading, error, refresh } = usePluginData<FileTreeData>(\n \"file-tree\",\n { companyId: companyId ?? \"\", relativePath: currentPath }\n );\n\n const downloadFile = usePluginAction(\"download-file\");\n const downloadZip = usePluginAction(\"download-zip\");\n\n const nodes = buildNodes(data);\n\n const handleSelectFile = useCallback((path: string) => {\n setSelectedFile(path);\n }, []);\n\n const handleToggleDir = useCallback((path: string) => {\n setCurrentPath(path);\n setSelectedFile(null);\n }, []);\n\n const handleDownloadFile = useCallback(async () => {\n if (!selectedFile || !companyId) return;\n try {\n await downloadFile({ companyId, relativePath: selectedFile });\n } catch {\n // Bridge error propagated\n }\n }, [selectedFile, companyId, downloadFile]);\n\n const handleDownloadZip = useCallback(async () => {\n if (!currentPath || !companyId) return;\n try {\n await downloadZip({ companyId, relativePath: currentPath });\n } catch {\n // Bridge error propagated\n }\n }, [currentPath, companyId, downloadZip]);\n\n const handleNavigateUp = useCallback(() => {\n if (!currentPath) return;\n const parts = currentPath.split(\"/\");\n parts.pop();\n setCurrentPath(parts.join(\"/\"));\n setSelectedFile(null);\n }, [currentPath]);\n\n const handleRefresh = useCallback(() => {\n refresh();\n }, [refresh]);\n\n const fileCount = data?.files.length ?? 0;\n const dirCount = data?.directories.length ?? 0;\n const totalSize = data?.files.reduce((sum, f) => sum + (f.size ?? 0), 0) ?? 0;\n\n if (!companyId) {\n return <div style={{ padding: \"1rem\" }}>Select a company to browse files.</div>;\n }\n\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: \"0.75rem\", padding: \"0.5rem 0\" }}>\n <div style={{ display: \"flex\", gap: \"0.5rem\", flexWrap: \"wrap\" }}>\n <MetricCard label=\"Files\" value={fileCount} />\n <MetricCard label=\"Directories\" value={dirCount} />\n <MetricCard label=\"Total Size\" value={formatSize(totalSize)} />\n </div>\n\n <div style={{ display: \"flex\", gap: \"0.5rem\", alignItems: \"center\", flexWrap: \"wrap\" }}>\n {currentPath && (\n <button onClick={handleNavigateUp} style={{ fontSize: \"0.85rem\" }}>\n .. / \n </button>\n )}\n <span style={{ fontSize: \"0.85rem\", color: \"var(--text-secondary, #888)\" }}>\n /{currentPath || \"\"}\n </span>\n <div style={{ marginLeft: \"auto\", display: \"flex\", gap: \"0.5rem\" }}>\n <button onClick={handleRefresh} style={{ fontSize: \"0.85rem\" }}>\n Refresh\n </button>\n {selectedFile && (\n <button onClick={handleDownloadFile} style={{ fontSize: \"0.85rem\" }}>\n Download File\n </button>\n )}\n {currentPath && (\n <button onClick={handleDownloadZip} style={{ fontSize: \"0.85rem\" }}>\n Download Folder (ZIP)\n </button>\n )}\n </div>\n </div>\n\n {data?.error && (\n <div style={{ color: \"var(--text-error, red)\", fontSize: \"0.85rem\" }}>\n Error: {data.error}\n </div>\n )}\n\n <FileTree\n nodes={nodes}\n selectedFile={selectedFile}\n expandedPaths={[]}\n onSelectFile={handleSelectFile}\n onToggleDir={handleToggleDir}\n loading={loading}\n error={error ? { message: error.message } : null}\n empty={{ title: \"Empty directory\", description: \"No files or directories found.\" }}\n ariaLabel=\"File browser\"\n />\n\n {selectedFile && (\n <div style={{ fontSize: \"0.8rem\", color: \"var(--text-secondary, #888)\", paddingTop: \"0.25rem\" }}>\n Selected: {selectedFile}\n </div>\n )}\n </div>\n );\n}\n\nexport function DashboardWidget(_props: PluginWidgetProps) {\n const { companyId } = useHostContext();\n return (\n <div style={{ display: \"grid\", gap: \"0.5rem\" }}>\n <strong>File Browser</strong>\n <FileBrowser companyId={companyId} />\n </div>\n );\n}\n\nexport function FileBrowserTab(props: PluginDetailTabProps) {\n const { companyId } = props.context;\n return <FileBrowser companyId={companyId} />;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }