@getcatalystiq/agent-plane-ui 0.1.5 → 0.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/dist/index.cjs CHANGED
@@ -3100,128 +3100,388 @@ function AgentConnectorsManager({ agentId, toolkits: initialToolkits, composioAl
3100
3100
  ] });
3101
3101
  }
3102
3102
  var CodeEditor = React3.lazy(() => import('./code-editor-ZD5ZILTM.cjs'));
3103
- function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3104
- const client = useAgentPlaneClient();
3105
- const initialFiles = React3.useMemo(
3106
- () => initialSkills.flatMap(
3107
- (s) => s.files.map((f) => ({
3108
- path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3109
- content: f.content
3110
- }))
3111
- ),
3112
- [initialSkills]
3113
- );
3103
+ function buildTree(files) {
3104
+ const rootFiles = [];
3105
+ const dirMap = /* @__PURE__ */ new Map();
3106
+ function ensureDir(dirPath) {
3107
+ const existing = dirMap.get(dirPath);
3108
+ if (existing) return existing;
3109
+ const parts = dirPath.split("/");
3110
+ const node = {
3111
+ name: parts[parts.length - 1],
3112
+ fullPath: dirPath,
3113
+ children: [],
3114
+ files: []
3115
+ };
3116
+ dirMap.set(dirPath, node);
3117
+ if (parts.length > 1) {
3118
+ const parentPath = parts.slice(0, -1).join("/");
3119
+ const parent = ensureDir(parentPath);
3120
+ if (!parent.children.some((c) => c.fullPath === dirPath)) {
3121
+ parent.children.push(node);
3122
+ }
3123
+ }
3124
+ return node;
3125
+ }
3126
+ for (const file of files) {
3127
+ const slashIdx = file.path.lastIndexOf("/");
3128
+ if (slashIdx === -1) {
3129
+ rootFiles.push(file);
3130
+ } else {
3131
+ const dirPath = file.path.slice(0, slashIdx);
3132
+ const dir = ensureDir(dirPath);
3133
+ dir.files.push(file);
3134
+ }
3135
+ }
3136
+ const topLevel = [];
3137
+ for (const node of dirMap.values()) {
3138
+ if (!node.fullPath.includes("/")) {
3139
+ topLevel.push(node);
3140
+ }
3141
+ }
3142
+ function sortNode(node) {
3143
+ node.children.sort((a, b) => a.name.localeCompare(b.name));
3144
+ node.files.sort((a, b) => a.path.localeCompare(b.path));
3145
+ node.children.forEach(sortNode);
3146
+ }
3147
+ topLevel.forEach(sortNode);
3148
+ topLevel.sort((a, b) => a.name.localeCompare(b.name));
3149
+ rootFiles.sort((a, b) => a.path.localeCompare(b.path));
3150
+ return { rootFiles, rootDirs: topLevel };
3151
+ }
3152
+ function collectAllDirPaths(nodes) {
3153
+ const paths = /* @__PURE__ */ new Set();
3154
+ function walk(node) {
3155
+ paths.add(node.fullPath);
3156
+ node.children.forEach(walk);
3157
+ }
3158
+ nodes.forEach(walk);
3159
+ return paths;
3160
+ }
3161
+ function FileTreeEditor({
3162
+ initialFiles,
3163
+ onSave,
3164
+ onChange,
3165
+ readOnly = false,
3166
+ hideSave = false,
3167
+ title = "Files",
3168
+ saveLabel = "Save",
3169
+ addFolderLabel = "Folder",
3170
+ newFileTemplate = { filename: "SKILL.md", content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" },
3171
+ savedVersion
3172
+ }) {
3114
3173
  const [files, setFiles] = React3.useState(initialFiles);
3115
- const [selectedPath, setSelectedPath] = React3.useState(files[0]?.path ?? null);
3174
+ const [selectedPath, setSelectedPath] = React3.useState(
3175
+ initialFiles.length > 0 ? initialFiles[0].path : null
3176
+ );
3116
3177
  const [saving, setSaving] = React3.useState(false);
3117
- const [addingFile, setAddingFile] = React3.useState(false);
3178
+ const [expanded, setExpanded] = React3.useState(() => {
3179
+ const { rootDirs } = buildTree(initialFiles);
3180
+ return collectAllDirPaths(rootDirs);
3181
+ });
3182
+ const [showAddFolder, setShowAddFolder] = React3.useState(false);
3183
+ const [newFolderName, setNewFolderName] = React3.useState("");
3184
+ const [addingFileInDir, setAddingFileInDir] = React3.useState(null);
3118
3185
  const [newFileName, setNewFileName] = React3.useState("");
3186
+ const [savedSnapshot, setSavedSnapshot] = React3.useState(() => JSON.stringify(initialFiles));
3187
+ React3.useEffect(() => {
3188
+ const snap = JSON.stringify(initialFiles);
3189
+ setSavedSnapshot(snap);
3190
+ setFiles(initialFiles);
3191
+ const { rootDirs } = buildTree(initialFiles);
3192
+ setExpanded(collectAllDirPaths(rootDirs));
3193
+ }, [initialFiles]);
3194
+ React3.useEffect(() => {
3195
+ if (savedVersion !== void 0 && savedVersion > 0) {
3196
+ setSavedSnapshot(JSON.stringify(files));
3197
+ }
3198
+ }, [savedVersion]);
3199
+ const onChangeRef = React3.useRef(onChange);
3200
+ onChangeRef.current = onChange;
3201
+ React3.useEffect(() => {
3202
+ if (onChangeRef.current && JSON.stringify(files) !== savedSnapshot) {
3203
+ onChangeRef.current(files);
3204
+ }
3205
+ }, [files, savedSnapshot]);
3119
3206
  const isDirty = React3.useMemo(
3120
- () => JSON.stringify(files) !== JSON.stringify(initialFiles),
3121
- [files, initialFiles]
3207
+ () => JSON.stringify(files) !== savedSnapshot,
3208
+ [files, savedSnapshot]
3209
+ );
3210
+ const tree = React3.useMemo(() => buildTree(files), [files]);
3211
+ const activeFile = React3.useMemo(
3212
+ () => selectedPath ? files.find((f) => f.path === selectedPath) ?? null : null,
3213
+ [files, selectedPath]
3122
3214
  );
3123
- const selectedFile = files.find((f) => f.path === selectedPath);
3124
- function updateFileContent(path, content) {
3125
- setFiles((prev) => prev.map((f) => f.path === path ? { ...f, content } : f));
3215
+ const handleEditorChange = React3.useCallback((value) => {
3216
+ if (readOnly || !selectedPath) return;
3217
+ setFiles((prev) => prev.map((f) => f.path === selectedPath ? { ...f, content: value } : f));
3218
+ }, [readOnly, selectedPath]);
3219
+ function toggleExpand(dirPath) {
3220
+ setExpanded((prev) => {
3221
+ const next = new Set(prev);
3222
+ if (next.has(dirPath)) next.delete(dirPath);
3223
+ else next.add(dirPath);
3224
+ return next;
3225
+ });
3226
+ }
3227
+ function addFolder() {
3228
+ const name = newFolderName.trim();
3229
+ if (!name) return;
3230
+ const filePath = `${name}/${newFileTemplate.filename}`;
3231
+ if (files.some((f) => f.path === filePath)) return;
3232
+ const content = newFileTemplate.content.replace("# New", `# ${name}`);
3233
+ setFiles((prev) => [...prev, { path: filePath, content }]);
3234
+ setSelectedPath(filePath);
3235
+ setExpanded((prev) => /* @__PURE__ */ new Set([...prev, name]));
3236
+ setNewFolderName("");
3237
+ setShowAddFolder(false);
3238
+ }
3239
+ function removeDir(dirPath) {
3240
+ const prefix = dirPath + "/";
3241
+ const affectedFiles = files.filter((f) => f.path.startsWith(prefix));
3242
+ if (affectedFiles.length === 0) return;
3243
+ if (!confirm(`Remove "${dirPath}" and all ${affectedFiles.length} file(s)?`)) return;
3244
+ setFiles((prev) => prev.filter((f) => !f.path.startsWith(prefix)));
3245
+ if (selectedPath && selectedPath.startsWith(prefix)) setSelectedPath(null);
3126
3246
  }
3127
- function addFile() {
3247
+ function removeFile(filePath) {
3248
+ const fileName = filePath.split("/").pop() ?? filePath;
3249
+ if (!confirm(`Remove file "${fileName}"?`)) return;
3250
+ setFiles((prev) => prev.filter((f) => f.path !== filePath));
3251
+ if (selectedPath === filePath) setSelectedPath(null);
3252
+ }
3253
+ function addFileInDir(dirPath) {
3128
3254
  const name = newFileName.trim();
3129
- if (!name || files.some((f) => f.path === name)) return;
3130
- const content = name.endsWith(".md") ? "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" : "";
3131
- setFiles((prev) => [...prev, { path: name, content }]);
3132
- setSelectedPath(name);
3133
- setAddingFile(false);
3255
+ if (!name) return;
3256
+ const filePath = dirPath ? `${dirPath}/${name}` : name;
3257
+ if (files.some((f) => f.path === filePath)) return;
3258
+ setFiles((prev) => [...prev, { path: filePath, content: "" }]);
3259
+ setSelectedPath(filePath);
3134
3260
  setNewFileName("");
3261
+ setAddingFileInDir(null);
3135
3262
  }
3136
- function removeFile(path) {
3137
- setFiles((prev) => prev.filter((f) => f.path !== path));
3138
- if (selectedPath === path) {
3139
- setSelectedPath(files.find((f) => f.path !== path)?.path ?? null);
3140
- }
3141
- }
3142
- const handleSave = React3.useCallback(async () => {
3263
+ async function handleSave() {
3143
3264
  setSaving(true);
3144
3265
  try {
3145
- const folderMap = /* @__PURE__ */ new Map();
3146
- for (const file of files) {
3147
- const slashIdx = file.path.lastIndexOf("/");
3148
- if (slashIdx === -1) {
3149
- const existing = folderMap.get("(root)") ?? [];
3150
- existing.push({ path: file.path, content: file.content });
3151
- folderMap.set("(root)", existing);
3152
- } else {
3153
- const folder = file.path.slice(0, slashIdx);
3154
- const fileName = file.path.slice(slashIdx + 1);
3155
- const existing = folderMap.get(folder) ?? [];
3156
- existing.push({ path: fileName, content: file.content });
3157
- folderMap.set(folder, existing);
3158
- }
3159
- }
3160
- const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3161
- await client.agents.update(agentId, { skills });
3162
- onSaved?.();
3266
+ await onSave(files);
3163
3267
  } finally {
3164
3268
  setSaving(false);
3165
3269
  }
3166
- }, [files, agentId, client, onSaved]);
3167
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
3168
- /* @__PURE__ */ jsxRuntime.jsx(SectionHeader, { title: "Skills", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3169
- isDirty && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved" }),
3170
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "outline", onClick: () => setAddingFile(true), children: "Add File" }),
3171
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: handleSave, disabled: saving || !isDirty, children: saving ? "Saving..." : "Save Skills" })
3172
- ] }) }),
3173
- addingFile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
3174
- /* @__PURE__ */ jsxRuntime.jsx(
3175
- Input,
3176
- {
3177
- value: newFileName,
3178
- onChange: (e) => setNewFileName(e.target.value),
3179
- placeholder: "folder/SKILL.md",
3180
- className: "max-w-xs text-sm",
3181
- onKeyDown: (e) => e.key === "Enter" && addFile()
3182
- }
3183
- ),
3184
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: addFile, children: "Add" }),
3185
- /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
3186
- setAddingFile(false);
3187
- setNewFileName("");
3188
- }, children: "Cancel" })
3189
- ] }),
3190
- files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: 'No skills defined. Click "Add File" to create a skill.' }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 min-h-[300px]", children: [
3191
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-48 shrink-0 border-r border-border pr-3 space-y-1", children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsxs(
3270
+ }
3271
+ function renderTreeNode(node, depth) {
3272
+ const isExpanded = expanded.has(node.fullPath);
3273
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3274
+ /* @__PURE__ */ jsxRuntime.jsxs(
3192
3275
  "div",
3193
3276
  {
3194
- className: `flex items-center justify-between group rounded px-2 py-1 text-xs cursor-pointer ${selectedPath === f.path ? "bg-accent text-accent-foreground" : "hover:bg-muted/50"}`,
3195
- onClick: () => setSelectedPath(f.path),
3277
+ className: "flex items-center justify-between cursor-pointer hover:bg-muted/50 py-1 pr-2",
3278
+ style: { paddingLeft: `${depth * 16 + 8}px` },
3279
+ onClick: () => toggleExpand(node.fullPath),
3196
3280
  children: [
3197
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: f.path }),
3198
- /* @__PURE__ */ jsxRuntime.jsx(
3281
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-xs truncate flex items-center gap-1", children: [
3282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: isExpanded ? "\u25BE" : "\u25B8" }),
3283
+ node.name,
3284
+ "/"
3285
+ ] }),
3286
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3199
3287
  "button",
3200
3288
  {
3201
- type: "button",
3202
3289
  onClick: (e) => {
3203
3290
  e.stopPropagation();
3204
- removeFile(f.path);
3291
+ removeDir(node.fullPath);
3205
3292
  },
3206
- className: "opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive ml-1",
3293
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3207
3294
  children: "\xD7"
3208
3295
  }
3209
3296
  )
3210
3297
  ]
3211
- },
3212
- f.path
3213
- )) }),
3214
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: selectedFile ? /* @__PURE__ */ jsxRuntime.jsx(React3.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[300px] animate-pulse bg-muted/50 rounded" }), children: /* @__PURE__ */ jsxRuntime.jsx(
3215
- CodeEditor,
3216
- {
3217
- value: selectedFile.content,
3218
- onChange: (val) => updateFileContent(selectedFile.path, val),
3219
- filename: selectedFile.path
3220
3298
  }
3221
- ) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file to edit" }) })
3299
+ ),
3300
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3301
+ node.children.map((child) => renderTreeNode(child, depth + 1)),
3302
+ node.files.map((file) => {
3303
+ const fileName = file.path.split("/").pop() ?? file.path;
3304
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3305
+ "div",
3306
+ {
3307
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3308
+ style: { paddingLeft: `${(depth + 1) * 16 + 8}px` },
3309
+ onClick: () => setSelectedPath(file.path),
3310
+ children: [
3311
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", children: fileName }),
3312
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3313
+ "button",
3314
+ {
3315
+ onClick: (e) => {
3316
+ e.stopPropagation();
3317
+ removeFile(file.path);
3318
+ },
3319
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3320
+ children: "\xD7"
3321
+ }
3322
+ )
3323
+ ]
3324
+ },
3325
+ file.path
3326
+ );
3327
+ }),
3328
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { paddingLeft: `${(depth + 1) * 16 + 8}px` }, className: "py-1 pr-2", children: addingFileInDir === node.fullPath ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
3329
+ /* @__PURE__ */ jsxRuntime.jsx(
3330
+ Input,
3331
+ {
3332
+ value: newFileName,
3333
+ onChange: (e) => setNewFileName(e.target.value),
3334
+ placeholder: "file.md",
3335
+ className: "h-6 text-xs",
3336
+ onKeyDown: (e) => e.key === "Enter" && addFileInDir(node.fullPath),
3337
+ autoFocus: true
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: () => addFileInDir(node.fullPath), size: "sm", className: "h-6 text-xs px-2", children: "+" })
3341
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
3342
+ "button",
3343
+ {
3344
+ onClick: () => {
3345
+ setAddingFileInDir(node.fullPath);
3346
+ setNewFileName("");
3347
+ },
3348
+ className: "text-xs text-primary hover:underline",
3349
+ children: "+ File"
3350
+ }
3351
+ ) })
3352
+ ] })
3353
+ ] }, node.fullPath);
3354
+ }
3355
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3356
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3357
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3358
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: title }),
3359
+ isDirty && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved changes" }),
3360
+ readOnly && /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "secondary", className: "text-xs", children: "Read-only" })
3361
+ ] }),
3362
+ !readOnly && !hideSave && /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: handleSave, disabled: saving || !isDirty, size: "sm", children: saving ? "Saving..." : saveLabel })
3363
+ ] }),
3364
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4 min-h-[500px]", children: [
3365
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-64 shrink-0 border border-border rounded-md overflow-hidden", children: [
3366
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 bg-muted/50 border-b border-border flex items-center justify-between", children: [
3367
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: title }),
3368
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsxs(
3369
+ "button",
3370
+ {
3371
+ onClick: () => setShowAddFolder(!showAddFolder),
3372
+ className: "text-xs text-primary hover:underline",
3373
+ children: [
3374
+ "+ ",
3375
+ addFolderLabel
3376
+ ]
3377
+ }
3378
+ )
3379
+ ] }),
3380
+ showAddFolder && !readOnly && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 border-b border-border flex gap-1", children: [
3381
+ /* @__PURE__ */ jsxRuntime.jsx(
3382
+ Input,
3383
+ {
3384
+ value: newFolderName,
3385
+ onChange: (e) => setNewFolderName(e.target.value),
3386
+ placeholder: "folder-name",
3387
+ className: "h-7 text-xs",
3388
+ onKeyDown: (e) => e.key === "Enter" && addFolder(),
3389
+ autoFocus: true
3390
+ }
3391
+ ),
3392
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: addFolder, size: "sm", className: "h-7 text-xs px-2", children: "Add" })
3393
+ ] }),
3394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm overflow-y-auto", children: [
3395
+ tree.rootFiles.map((file) => /* @__PURE__ */ jsxRuntime.jsxs(
3396
+ "div",
3397
+ {
3398
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3399
+ style: { paddingLeft: "8px" },
3400
+ onClick: () => setSelectedPath(file.path),
3401
+ children: [
3402
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs truncate", children: file.path }),
3403
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
3404
+ "button",
3405
+ {
3406
+ onClick: (e) => {
3407
+ e.stopPropagation();
3408
+ removeFile(file.path);
3409
+ },
3410
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3411
+ children: "\xD7"
3412
+ }
3413
+ )
3414
+ ]
3415
+ },
3416
+ file.path
3417
+ )),
3418
+ tree.rootDirs.map((node) => renderTreeNode(node, 0)),
3419
+ files.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-3 text-xs text-muted-foreground", children: readOnly ? "No files." : `No ${title.toLowerCase()} yet. Add a ${addFolderLabel.toLowerCase()} to get started.` })
3420
+ ] })
3421
+ ] }),
3422
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-border rounded-md overflow-hidden", children: activeFile ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full flex flex-col", children: [
3423
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-1.5 bg-muted/50 border-b border-border text-xs text-muted-foreground", children: activeFile.path }),
3424
+ /* @__PURE__ */ jsxRuntime.jsx(React3.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 animate-pulse bg-muted/50" }), children: /* @__PURE__ */ jsxRuntime.jsx(
3425
+ CodeEditor,
3426
+ {
3427
+ value: activeFile.content,
3428
+ onChange: handleEditorChange,
3429
+ filename: activeFile.path
3430
+ }
3431
+ ) })
3432
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground text-sm", children: [
3433
+ "Select a file to ",
3434
+ readOnly ? "view" : "edit"
3435
+ ] }) })
3222
3436
  ] })
3223
3437
  ] });
3224
3438
  }
3439
+ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3440
+ const client = useAgentPlaneClient();
3441
+ const initialFiles = React3.useMemo(
3442
+ () => initialSkills.flatMap(
3443
+ (s) => s.files.map((f) => ({
3444
+ path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3445
+ content: f.content
3446
+ }))
3447
+ ),
3448
+ [initialSkills]
3449
+ );
3450
+ const handleSave = React3.useCallback(async (files) => {
3451
+ const folderMap = /* @__PURE__ */ new Map();
3452
+ for (const file of files) {
3453
+ const slashIdx = file.path.lastIndexOf("/");
3454
+ if (slashIdx === -1) {
3455
+ const existing = folderMap.get("(root)") ?? [];
3456
+ existing.push({ path: file.path, content: file.content });
3457
+ folderMap.set("(root)", existing);
3458
+ } else {
3459
+ const folder = file.path.slice(0, slashIdx);
3460
+ const fileName = file.path.slice(slashIdx + 1);
3461
+ const existing = folderMap.get(folder) ?? [];
3462
+ existing.push({ path: fileName, content: file.content });
3463
+ folderMap.set(folder, existing);
3464
+ }
3465
+ }
3466
+ const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3467
+ await client.agents.update(agentId, { skills });
3468
+ onSaved?.();
3469
+ }, [agentId, client, onSaved]);
3470
+ return /* @__PURE__ */ jsxRuntime.jsx(
3471
+ FileTreeEditor,
3472
+ {
3473
+ initialFiles,
3474
+ onSave: handleSave,
3475
+ title: "Skills",
3476
+ saveLabel: "Save Skills",
3477
+ addFolderLabel: "Skill",
3478
+ newFileTemplate: {
3479
+ filename: "SKILL.md",
3480
+ content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
3481
+ }
3482
+ }
3483
+ );
3484
+ }
3225
3485
  function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
3226
3486
  const client = useAgentPlaneClient();
3227
3487
  const [plugins, setPlugins] = React3.useState(initialPlugins);
package/dist/index.d.cts CHANGED
@@ -566,10 +566,6 @@ interface Props$5 {
566
566
  initialSkills: AgentSkill[];
567
567
  onSaved?: () => void;
568
568
  }
569
- /**
570
- * Simplified skills editor that works without CodeMirror (which is a heavy
571
- * dependency). Uses plain textareas for editing skill content.
572
- */
573
569
  declare function AgentSkillManager({ agentId, initialSkills, onSaved }: Props$5): react_jsx_runtime.JSX.Element;
574
570
 
575
571
  interface AgentPlugin {
package/dist/index.d.ts CHANGED
@@ -566,10 +566,6 @@ interface Props$5 {
566
566
  initialSkills: AgentSkill[];
567
567
  onSaved?: () => void;
568
568
  }
569
- /**
570
- * Simplified skills editor that works without CodeMirror (which is a heavy
571
- * dependency). Uses plain textareas for editing skill content.
572
- */
573
569
  declare function AgentSkillManager({ agentId, initialSkills, onSaved }: Props$5): react_jsx_runtime.JSX.Element;
574
570
 
575
571
  interface AgentPlugin {
package/dist/index.js CHANGED
@@ -3074,128 +3074,388 @@ function AgentConnectorsManager({ agentId, toolkits: initialToolkits, composioAl
3074
3074
  ] });
3075
3075
  }
3076
3076
  var CodeEditor = lazy(() => import('./code-editor-E7L6Y3LM.js'));
3077
- function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3078
- const client = useAgentPlaneClient();
3079
- const initialFiles = useMemo(
3080
- () => initialSkills.flatMap(
3081
- (s) => s.files.map((f) => ({
3082
- path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3083
- content: f.content
3084
- }))
3085
- ),
3086
- [initialSkills]
3087
- );
3077
+ function buildTree(files) {
3078
+ const rootFiles = [];
3079
+ const dirMap = /* @__PURE__ */ new Map();
3080
+ function ensureDir(dirPath) {
3081
+ const existing = dirMap.get(dirPath);
3082
+ if (existing) return existing;
3083
+ const parts = dirPath.split("/");
3084
+ const node = {
3085
+ name: parts[parts.length - 1],
3086
+ fullPath: dirPath,
3087
+ children: [],
3088
+ files: []
3089
+ };
3090
+ dirMap.set(dirPath, node);
3091
+ if (parts.length > 1) {
3092
+ const parentPath = parts.slice(0, -1).join("/");
3093
+ const parent = ensureDir(parentPath);
3094
+ if (!parent.children.some((c) => c.fullPath === dirPath)) {
3095
+ parent.children.push(node);
3096
+ }
3097
+ }
3098
+ return node;
3099
+ }
3100
+ for (const file of files) {
3101
+ const slashIdx = file.path.lastIndexOf("/");
3102
+ if (slashIdx === -1) {
3103
+ rootFiles.push(file);
3104
+ } else {
3105
+ const dirPath = file.path.slice(0, slashIdx);
3106
+ const dir = ensureDir(dirPath);
3107
+ dir.files.push(file);
3108
+ }
3109
+ }
3110
+ const topLevel = [];
3111
+ for (const node of dirMap.values()) {
3112
+ if (!node.fullPath.includes("/")) {
3113
+ topLevel.push(node);
3114
+ }
3115
+ }
3116
+ function sortNode(node) {
3117
+ node.children.sort((a, b) => a.name.localeCompare(b.name));
3118
+ node.files.sort((a, b) => a.path.localeCompare(b.path));
3119
+ node.children.forEach(sortNode);
3120
+ }
3121
+ topLevel.forEach(sortNode);
3122
+ topLevel.sort((a, b) => a.name.localeCompare(b.name));
3123
+ rootFiles.sort((a, b) => a.path.localeCompare(b.path));
3124
+ return { rootFiles, rootDirs: topLevel };
3125
+ }
3126
+ function collectAllDirPaths(nodes) {
3127
+ const paths = /* @__PURE__ */ new Set();
3128
+ function walk(node) {
3129
+ paths.add(node.fullPath);
3130
+ node.children.forEach(walk);
3131
+ }
3132
+ nodes.forEach(walk);
3133
+ return paths;
3134
+ }
3135
+ function FileTreeEditor({
3136
+ initialFiles,
3137
+ onSave,
3138
+ onChange,
3139
+ readOnly = false,
3140
+ hideSave = false,
3141
+ title = "Files",
3142
+ saveLabel = "Save",
3143
+ addFolderLabel = "Folder",
3144
+ newFileTemplate = { filename: "SKILL.md", content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" },
3145
+ savedVersion
3146
+ }) {
3088
3147
  const [files, setFiles] = useState(initialFiles);
3089
- const [selectedPath, setSelectedPath] = useState(files[0]?.path ?? null);
3148
+ const [selectedPath, setSelectedPath] = useState(
3149
+ initialFiles.length > 0 ? initialFiles[0].path : null
3150
+ );
3090
3151
  const [saving, setSaving] = useState(false);
3091
- const [addingFile, setAddingFile] = useState(false);
3152
+ const [expanded, setExpanded] = useState(() => {
3153
+ const { rootDirs } = buildTree(initialFiles);
3154
+ return collectAllDirPaths(rootDirs);
3155
+ });
3156
+ const [showAddFolder, setShowAddFolder] = useState(false);
3157
+ const [newFolderName, setNewFolderName] = useState("");
3158
+ const [addingFileInDir, setAddingFileInDir] = useState(null);
3092
3159
  const [newFileName, setNewFileName] = useState("");
3160
+ const [savedSnapshot, setSavedSnapshot] = useState(() => JSON.stringify(initialFiles));
3161
+ useEffect(() => {
3162
+ const snap = JSON.stringify(initialFiles);
3163
+ setSavedSnapshot(snap);
3164
+ setFiles(initialFiles);
3165
+ const { rootDirs } = buildTree(initialFiles);
3166
+ setExpanded(collectAllDirPaths(rootDirs));
3167
+ }, [initialFiles]);
3168
+ useEffect(() => {
3169
+ if (savedVersion !== void 0 && savedVersion > 0) {
3170
+ setSavedSnapshot(JSON.stringify(files));
3171
+ }
3172
+ }, [savedVersion]);
3173
+ const onChangeRef = useRef(onChange);
3174
+ onChangeRef.current = onChange;
3175
+ useEffect(() => {
3176
+ if (onChangeRef.current && JSON.stringify(files) !== savedSnapshot) {
3177
+ onChangeRef.current(files);
3178
+ }
3179
+ }, [files, savedSnapshot]);
3093
3180
  const isDirty = useMemo(
3094
- () => JSON.stringify(files) !== JSON.stringify(initialFiles),
3095
- [files, initialFiles]
3181
+ () => JSON.stringify(files) !== savedSnapshot,
3182
+ [files, savedSnapshot]
3183
+ );
3184
+ const tree = useMemo(() => buildTree(files), [files]);
3185
+ const activeFile = useMemo(
3186
+ () => selectedPath ? files.find((f) => f.path === selectedPath) ?? null : null,
3187
+ [files, selectedPath]
3096
3188
  );
3097
- const selectedFile = files.find((f) => f.path === selectedPath);
3098
- function updateFileContent(path, content) {
3099
- setFiles((prev) => prev.map((f) => f.path === path ? { ...f, content } : f));
3189
+ const handleEditorChange = useCallback((value) => {
3190
+ if (readOnly || !selectedPath) return;
3191
+ setFiles((prev) => prev.map((f) => f.path === selectedPath ? { ...f, content: value } : f));
3192
+ }, [readOnly, selectedPath]);
3193
+ function toggleExpand(dirPath) {
3194
+ setExpanded((prev) => {
3195
+ const next = new Set(prev);
3196
+ if (next.has(dirPath)) next.delete(dirPath);
3197
+ else next.add(dirPath);
3198
+ return next;
3199
+ });
3200
+ }
3201
+ function addFolder() {
3202
+ const name = newFolderName.trim();
3203
+ if (!name) return;
3204
+ const filePath = `${name}/${newFileTemplate.filename}`;
3205
+ if (files.some((f) => f.path === filePath)) return;
3206
+ const content = newFileTemplate.content.replace("# New", `# ${name}`);
3207
+ setFiles((prev) => [...prev, { path: filePath, content }]);
3208
+ setSelectedPath(filePath);
3209
+ setExpanded((prev) => /* @__PURE__ */ new Set([...prev, name]));
3210
+ setNewFolderName("");
3211
+ setShowAddFolder(false);
3212
+ }
3213
+ function removeDir(dirPath) {
3214
+ const prefix = dirPath + "/";
3215
+ const affectedFiles = files.filter((f) => f.path.startsWith(prefix));
3216
+ if (affectedFiles.length === 0) return;
3217
+ if (!confirm(`Remove "${dirPath}" and all ${affectedFiles.length} file(s)?`)) return;
3218
+ setFiles((prev) => prev.filter((f) => !f.path.startsWith(prefix)));
3219
+ if (selectedPath && selectedPath.startsWith(prefix)) setSelectedPath(null);
3100
3220
  }
3101
- function addFile() {
3221
+ function removeFile(filePath) {
3222
+ const fileName = filePath.split("/").pop() ?? filePath;
3223
+ if (!confirm(`Remove file "${fileName}"?`)) return;
3224
+ setFiles((prev) => prev.filter((f) => f.path !== filePath));
3225
+ if (selectedPath === filePath) setSelectedPath(null);
3226
+ }
3227
+ function addFileInDir(dirPath) {
3102
3228
  const name = newFileName.trim();
3103
- if (!name || files.some((f) => f.path === name)) return;
3104
- const content = name.endsWith(".md") ? "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n" : "";
3105
- setFiles((prev) => [...prev, { path: name, content }]);
3106
- setSelectedPath(name);
3107
- setAddingFile(false);
3229
+ if (!name) return;
3230
+ const filePath = dirPath ? `${dirPath}/${name}` : name;
3231
+ if (files.some((f) => f.path === filePath)) return;
3232
+ setFiles((prev) => [...prev, { path: filePath, content: "" }]);
3233
+ setSelectedPath(filePath);
3108
3234
  setNewFileName("");
3235
+ setAddingFileInDir(null);
3109
3236
  }
3110
- function removeFile(path) {
3111
- setFiles((prev) => prev.filter((f) => f.path !== path));
3112
- if (selectedPath === path) {
3113
- setSelectedPath(files.find((f) => f.path !== path)?.path ?? null);
3114
- }
3115
- }
3116
- const handleSave = useCallback(async () => {
3237
+ async function handleSave() {
3117
3238
  setSaving(true);
3118
3239
  try {
3119
- const folderMap = /* @__PURE__ */ new Map();
3120
- for (const file of files) {
3121
- const slashIdx = file.path.lastIndexOf("/");
3122
- if (slashIdx === -1) {
3123
- const existing = folderMap.get("(root)") ?? [];
3124
- existing.push({ path: file.path, content: file.content });
3125
- folderMap.set("(root)", existing);
3126
- } else {
3127
- const folder = file.path.slice(0, slashIdx);
3128
- const fileName = file.path.slice(slashIdx + 1);
3129
- const existing = folderMap.get(folder) ?? [];
3130
- existing.push({ path: fileName, content: file.content });
3131
- folderMap.set(folder, existing);
3132
- }
3133
- }
3134
- const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3135
- await client.agents.update(agentId, { skills });
3136
- onSaved?.();
3240
+ await onSave(files);
3137
3241
  } finally {
3138
3242
  setSaving(false);
3139
3243
  }
3140
- }, [files, agentId, client, onSaved]);
3141
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-muted-foreground/25 p-5", children: [
3142
- /* @__PURE__ */ jsx(SectionHeader, { title: "Skills", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3143
- isDirty && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved" }),
3144
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: () => setAddingFile(true), children: "Add File" }),
3145
- /* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleSave, disabled: saving || !isDirty, children: saving ? "Saving..." : "Save Skills" })
3146
- ] }) }),
3147
- addingFile && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mb-3", children: [
3148
- /* @__PURE__ */ jsx(
3149
- Input,
3150
- {
3151
- value: newFileName,
3152
- onChange: (e) => setNewFileName(e.target.value),
3153
- placeholder: "folder/SKILL.md",
3154
- className: "max-w-xs text-sm",
3155
- onKeyDown: (e) => e.key === "Enter" && addFile()
3156
- }
3157
- ),
3158
- /* @__PURE__ */ jsx(Button, { size: "sm", onClick: addFile, children: "Add" }),
3159
- /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
3160
- setAddingFile(false);
3161
- setNewFileName("");
3162
- }, children: "Cancel" })
3163
- ] }),
3164
- files.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: 'No skills defined. Click "Add File" to create a skill.' }) : /* @__PURE__ */ jsxs("div", { className: "flex gap-4 min-h-[300px]", children: [
3165
- /* @__PURE__ */ jsx("div", { className: "w-48 shrink-0 border-r border-border pr-3 space-y-1", children: files.map((f) => /* @__PURE__ */ jsxs(
3244
+ }
3245
+ function renderTreeNode(node, depth) {
3246
+ const isExpanded = expanded.has(node.fullPath);
3247
+ return /* @__PURE__ */ jsxs("div", { children: [
3248
+ /* @__PURE__ */ jsxs(
3166
3249
  "div",
3167
3250
  {
3168
- className: `flex items-center justify-between group rounded px-2 py-1 text-xs cursor-pointer ${selectedPath === f.path ? "bg-accent text-accent-foreground" : "hover:bg-muted/50"}`,
3169
- onClick: () => setSelectedPath(f.path),
3251
+ className: "flex items-center justify-between cursor-pointer hover:bg-muted/50 py-1 pr-2",
3252
+ style: { paddingLeft: `${depth * 16 + 8}px` },
3253
+ onClick: () => toggleExpand(node.fullPath),
3170
3254
  children: [
3171
- /* @__PURE__ */ jsx("span", { className: "truncate", children: f.path }),
3172
- /* @__PURE__ */ jsx(
3255
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-xs truncate flex items-center gap-1", children: [
3256
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: isExpanded ? "\u25BE" : "\u25B8" }),
3257
+ node.name,
3258
+ "/"
3259
+ ] }),
3260
+ !readOnly && /* @__PURE__ */ jsx(
3173
3261
  "button",
3174
3262
  {
3175
- type: "button",
3176
3263
  onClick: (e) => {
3177
3264
  e.stopPropagation();
3178
- removeFile(f.path);
3265
+ removeDir(node.fullPath);
3179
3266
  },
3180
- className: "opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive ml-1",
3267
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3181
3268
  children: "\xD7"
3182
3269
  }
3183
3270
  )
3184
3271
  ]
3185
- },
3186
- f.path
3187
- )) }),
3188
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: selectedFile ? /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "h-[300px] animate-pulse bg-muted/50 rounded" }), children: /* @__PURE__ */ jsx(
3189
- CodeEditor,
3190
- {
3191
- value: selectedFile.content,
3192
- onChange: (val) => updateFileContent(selectedFile.path, val),
3193
- filename: selectedFile.path
3194
3272
  }
3195
- ) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file to edit" }) })
3273
+ ),
3274
+ isExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
3275
+ node.children.map((child) => renderTreeNode(child, depth + 1)),
3276
+ node.files.map((file) => {
3277
+ const fileName = file.path.split("/").pop() ?? file.path;
3278
+ return /* @__PURE__ */ jsxs(
3279
+ "div",
3280
+ {
3281
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3282
+ style: { paddingLeft: `${(depth + 1) * 16 + 8}px` },
3283
+ onClick: () => setSelectedPath(file.path),
3284
+ children: [
3285
+ /* @__PURE__ */ jsx("span", { className: "text-xs truncate", children: fileName }),
3286
+ !readOnly && /* @__PURE__ */ jsx(
3287
+ "button",
3288
+ {
3289
+ onClick: (e) => {
3290
+ e.stopPropagation();
3291
+ removeFile(file.path);
3292
+ },
3293
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3294
+ children: "\xD7"
3295
+ }
3296
+ )
3297
+ ]
3298
+ },
3299
+ file.path
3300
+ );
3301
+ }),
3302
+ !readOnly && /* @__PURE__ */ jsx("div", { style: { paddingLeft: `${(depth + 1) * 16 + 8}px` }, className: "py-1 pr-2", children: addingFileInDir === node.fullPath ? /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
3303
+ /* @__PURE__ */ jsx(
3304
+ Input,
3305
+ {
3306
+ value: newFileName,
3307
+ onChange: (e) => setNewFileName(e.target.value),
3308
+ placeholder: "file.md",
3309
+ className: "h-6 text-xs",
3310
+ onKeyDown: (e) => e.key === "Enter" && addFileInDir(node.fullPath),
3311
+ autoFocus: true
3312
+ }
3313
+ ),
3314
+ /* @__PURE__ */ jsx(Button, { onClick: () => addFileInDir(node.fullPath), size: "sm", className: "h-6 text-xs px-2", children: "+" })
3315
+ ] }) : /* @__PURE__ */ jsx(
3316
+ "button",
3317
+ {
3318
+ onClick: () => {
3319
+ setAddingFileInDir(node.fullPath);
3320
+ setNewFileName("");
3321
+ },
3322
+ className: "text-xs text-primary hover:underline",
3323
+ children: "+ File"
3324
+ }
3325
+ ) })
3326
+ ] })
3327
+ ] }, node.fullPath);
3328
+ }
3329
+ return /* @__PURE__ */ jsxs("div", { children: [
3330
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3331
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3332
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: title }),
3333
+ isDirty && !readOnly && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved changes" }),
3334
+ readOnly && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: "Read-only" })
3335
+ ] }),
3336
+ !readOnly && !hideSave && /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: saving || !isDirty, size: "sm", children: saving ? "Saving..." : saveLabel })
3337
+ ] }),
3338
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-4 min-h-[500px]", children: [
3339
+ /* @__PURE__ */ jsxs("div", { className: "w-64 shrink-0 border border-border rounded-md overflow-hidden", children: [
3340
+ /* @__PURE__ */ jsxs("div", { className: "p-2 bg-muted/50 border-b border-border flex items-center justify-between", children: [
3341
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: title }),
3342
+ !readOnly && /* @__PURE__ */ jsxs(
3343
+ "button",
3344
+ {
3345
+ onClick: () => setShowAddFolder(!showAddFolder),
3346
+ className: "text-xs text-primary hover:underline",
3347
+ children: [
3348
+ "+ ",
3349
+ addFolderLabel
3350
+ ]
3351
+ }
3352
+ )
3353
+ ] }),
3354
+ showAddFolder && !readOnly && /* @__PURE__ */ jsxs("div", { className: "p-2 border-b border-border flex gap-1", children: [
3355
+ /* @__PURE__ */ jsx(
3356
+ Input,
3357
+ {
3358
+ value: newFolderName,
3359
+ onChange: (e) => setNewFolderName(e.target.value),
3360
+ placeholder: "folder-name",
3361
+ className: "h-7 text-xs",
3362
+ onKeyDown: (e) => e.key === "Enter" && addFolder(),
3363
+ autoFocus: true
3364
+ }
3365
+ ),
3366
+ /* @__PURE__ */ jsx(Button, { onClick: addFolder, size: "sm", className: "h-7 text-xs px-2", children: "Add" })
3367
+ ] }),
3368
+ /* @__PURE__ */ jsxs("div", { className: "text-sm overflow-y-auto", children: [
3369
+ tree.rootFiles.map((file) => /* @__PURE__ */ jsxs(
3370
+ "div",
3371
+ {
3372
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3373
+ style: { paddingLeft: "8px" },
3374
+ onClick: () => setSelectedPath(file.path),
3375
+ children: [
3376
+ /* @__PURE__ */ jsx("span", { className: "text-xs truncate", children: file.path }),
3377
+ !readOnly && /* @__PURE__ */ jsx(
3378
+ "button",
3379
+ {
3380
+ onClick: (e) => {
3381
+ e.stopPropagation();
3382
+ removeFile(file.path);
3383
+ },
3384
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3385
+ children: "\xD7"
3386
+ }
3387
+ )
3388
+ ]
3389
+ },
3390
+ file.path
3391
+ )),
3392
+ tree.rootDirs.map((node) => renderTreeNode(node, 0)),
3393
+ files.length === 0 && /* @__PURE__ */ jsx("p", { className: "p-3 text-xs text-muted-foreground", children: readOnly ? "No files." : `No ${title.toLowerCase()} yet. Add a ${addFolderLabel.toLowerCase()} to get started.` })
3394
+ ] })
3395
+ ] }),
3396
+ /* @__PURE__ */ jsx("div", { className: "flex-1 border border-border rounded-md overflow-hidden", children: activeFile ? /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-col", children: [
3397
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 bg-muted/50 border-b border-border text-xs text-muted-foreground", children: activeFile.path }),
3398
+ /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex-1 animate-pulse bg-muted/50" }), children: /* @__PURE__ */ jsx(
3399
+ CodeEditor,
3400
+ {
3401
+ value: activeFile.content,
3402
+ onChange: handleEditorChange,
3403
+ filename: activeFile.path
3404
+ }
3405
+ ) })
3406
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground text-sm", children: [
3407
+ "Select a file to ",
3408
+ readOnly ? "view" : "edit"
3409
+ ] }) })
3196
3410
  ] })
3197
3411
  ] });
3198
3412
  }
3413
+ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3414
+ const client = useAgentPlaneClient();
3415
+ const initialFiles = useMemo(
3416
+ () => initialSkills.flatMap(
3417
+ (s) => s.files.map((f) => ({
3418
+ path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3419
+ content: f.content
3420
+ }))
3421
+ ),
3422
+ [initialSkills]
3423
+ );
3424
+ const handleSave = useCallback(async (files) => {
3425
+ const folderMap = /* @__PURE__ */ new Map();
3426
+ for (const file of files) {
3427
+ const slashIdx = file.path.lastIndexOf("/");
3428
+ if (slashIdx === -1) {
3429
+ const existing = folderMap.get("(root)") ?? [];
3430
+ existing.push({ path: file.path, content: file.content });
3431
+ folderMap.set("(root)", existing);
3432
+ } else {
3433
+ const folder = file.path.slice(0, slashIdx);
3434
+ const fileName = file.path.slice(slashIdx + 1);
3435
+ const existing = folderMap.get(folder) ?? [];
3436
+ existing.push({ path: fileName, content: file.content });
3437
+ folderMap.set(folder, existing);
3438
+ }
3439
+ }
3440
+ const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3441
+ await client.agents.update(agentId, { skills });
3442
+ onSaved?.();
3443
+ }, [agentId, client, onSaved]);
3444
+ return /* @__PURE__ */ jsx(
3445
+ FileTreeEditor,
3446
+ {
3447
+ initialFiles,
3448
+ onSave: handleSave,
3449
+ title: "Skills",
3450
+ saveLabel: "Save Skills",
3451
+ addFolderLabel: "Skill",
3452
+ newFileTemplate: {
3453
+ filename: "SKILL.md",
3454
+ content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
3455
+ }
3456
+ }
3457
+ );
3458
+ }
3199
3459
  function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
3200
3460
  const client = useAgentPlaneClient();
3201
3461
  const [plugins, setPlugins] = useState(initialPlugins);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getcatalystiq/agent-plane-ui",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Embeddable React component library for AgentPlane",
5
5
  "type": "module",
6
6
  "exports": {