@getcatalystiq/agent-plane-ui 0.1.5 → 0.1.7

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.js CHANGED
@@ -1065,6 +1065,7 @@ function CancelRunButton({ runId, onCancelled }) {
1065
1065
  ] }) })
1066
1066
  ] });
1067
1067
  }
1068
+ var emptyForm = { name: "", slug: "", description: "", base_url: "", mcp_endpoint_path: "/mcp" };
1068
1069
  function McpServerListPage({ initialData }) {
1069
1070
  const { mutate } = useSWRConfig();
1070
1071
  const client = useAgentPlaneClient();
@@ -1073,21 +1074,48 @@ function McpServerListPage({ initialData }) {
1073
1074
  (c) => c.customConnectors.listServers(),
1074
1075
  initialData ? { fallbackData: initialData } : void 0
1075
1076
  );
1076
- const [showAdd, setShowAdd] = useState(false);
1077
- const [adding, setAdding] = useState(false);
1078
- const [newServer, setNewServer] = useState({ name: "", slug: "", description: "", base_url: "", mcp_endpoint_path: "/mcp" });
1077
+ const [showCreate, setShowCreate] = useState(false);
1078
+ const [creating, setCreating] = useState(false);
1079
+ const [createForm, setCreateForm] = useState(emptyForm);
1080
+ const [createError, setCreateError] = useState("");
1081
+ const [editTarget, setEditTarget] = useState(null);
1082
+ const [editing, setEditing] = useState(false);
1083
+ const [editForm, setEditForm] = useState({ name: "", description: "" });
1084
+ const [editError, setEditError] = useState("");
1079
1085
  const [deleteTarget, setDeleteTarget] = useState(null);
1080
1086
  const [deleting, setDeleting] = useState(false);
1081
1087
  const [deleteError, setDeleteError] = useState("");
1082
- async function handleAdd() {
1083
- setAdding(true);
1088
+ async function handleCreate() {
1089
+ setCreating(true);
1090
+ setCreateError("");
1084
1091
  try {
1085
- await client.customConnectors.createServer(newServer);
1086
- setShowAdd(false);
1087
- setNewServer({ name: "", slug: "", description: "", base_url: "", mcp_endpoint_path: "/mcp" });
1092
+ await client.customConnectors.createServer(createForm);
1093
+ setShowCreate(false);
1094
+ setCreateForm(emptyForm);
1088
1095
  mutate("mcp-servers");
1096
+ } catch (err) {
1097
+ setCreateError(err instanceof Error ? err.message : "Failed to create");
1089
1098
  } finally {
1090
- setAdding(false);
1099
+ setCreating(false);
1100
+ }
1101
+ }
1102
+ function openEdit(server) {
1103
+ setEditTarget(server);
1104
+ setEditForm({ name: server.name, description: server.description });
1105
+ setEditError("");
1106
+ }
1107
+ async function handleEdit() {
1108
+ if (!editTarget) return;
1109
+ setEditing(true);
1110
+ setEditError("");
1111
+ try {
1112
+ await client.customConnectors.updateServer(editTarget.id, editForm);
1113
+ setEditTarget(null);
1114
+ mutate("mcp-servers");
1115
+ } catch (err) {
1116
+ setEditError(err instanceof Error ? err.message : "Failed to update");
1117
+ } finally {
1118
+ setEditing(false);
1091
1119
  }
1092
1120
  }
1093
1121
  async function handleDelete() {
@@ -1106,7 +1134,7 @@ function McpServerListPage({ initialData }) {
1106
1134
  }
1107
1135
  if (error) {
1108
1136
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[40vh]", children: /* @__PURE__ */ jsxs("p", { className: "text-destructive", children: [
1109
- "Failed to load MCP servers: ",
1137
+ "Failed to load connectors: ",
1110
1138
  error.message
1111
1139
  ] }) });
1112
1140
  }
@@ -1114,17 +1142,7 @@ function McpServerListPage({ initialData }) {
1114
1142
  return /* @__PURE__ */ jsx(Skeleton, { className: "h-96 rounded-lg" });
1115
1143
  }
1116
1144
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
1117
- /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setShowAdd(!showAdd), children: showAdd ? "Cancel" : "Register Connector" }) }),
1118
- showAdd && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border p-4 space-y-3", children: [
1119
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
1120
- /* @__PURE__ */ jsx(Input, { placeholder: "Name", value: newServer.name, onChange: (e) => setNewServer({ ...newServer, name: e.target.value }) }),
1121
- /* @__PURE__ */ jsx(Input, { placeholder: "Slug", value: newServer.slug, onChange: (e) => setNewServer({ ...newServer, slug: e.target.value }) }),
1122
- /* @__PURE__ */ jsx(Input, { placeholder: "Description", value: newServer.description, onChange: (e) => setNewServer({ ...newServer, description: e.target.value }) }),
1123
- /* @__PURE__ */ jsx(Input, { placeholder: "Base URL", value: newServer.base_url, onChange: (e) => setNewServer({ ...newServer, base_url: e.target.value }) }),
1124
- /* @__PURE__ */ jsx(Input, { placeholder: "MCP Endpoint Path", value: newServer.mcp_endpoint_path, onChange: (e) => setNewServer({ ...newServer, mcp_endpoint_path: e.target.value }) })
1125
- ] }),
1126
- /* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleAdd, disabled: adding || !newServer.name || !newServer.base_url, children: adding ? "Adding..." : "Add Server" })
1127
- ] }),
1145
+ /* @__PURE__ */ jsx("div", { className: "flex items-center", children: /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setShowCreate(true), children: "+ New Connector" }) }),
1128
1146
  /* @__PURE__ */ jsxs(AdminTable, { children: [
1129
1147
  /* @__PURE__ */ jsxs(AdminTableHead, { children: [
1130
1148
  /* @__PURE__ */ jsx(Th, { children: "Name" }),
@@ -1138,10 +1156,17 @@ function McpServerListPage({ initialData }) {
1138
1156
  ] }),
1139
1157
  /* @__PURE__ */ jsxs("tbody", { children: [
1140
1158
  servers.map((s) => /* @__PURE__ */ jsxs(AdminTableRow, { children: [
1141
- /* @__PURE__ */ jsx("td", { className: "p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1142
- s.logo_url && /* @__PURE__ */ jsx("img", { src: s.logo_url, alt: "", className: "w-5 h-5 rounded-sm object-contain" }),
1143
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: s.name })
1144
- ] }) }),
1159
+ /* @__PURE__ */ jsx("td", { className: "p-3", children: /* @__PURE__ */ jsxs(
1160
+ "button",
1161
+ {
1162
+ onClick: () => openEdit(s),
1163
+ className: "flex items-center gap-2 text-left hover:underline cursor-pointer",
1164
+ children: [
1165
+ s.logo_url && /* @__PURE__ */ jsx("img", { src: s.logo_url, alt: "", className: "w-5 h-5 rounded-sm object-contain" }),
1166
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-primary", children: s.name })
1167
+ ]
1168
+ }
1169
+ ) }),
1145
1170
  /* @__PURE__ */ jsx("td", { className: "p-3 font-mono text-xs text-muted-foreground", children: s.slug }),
1146
1171
  /* @__PURE__ */ jsx("td", { className: "p-3 font-mono text-xs text-muted-foreground truncate max-w-xs", title: s.base_url, children: s.base_url }),
1147
1172
  /* @__PURE__ */ jsx("td", { className: "p-3", children: /* @__PURE__ */ jsx(Badge, { variant: s.client_id ? "default" : "secondary", children: s.client_id ? "Registered" : "No DCR" }) }),
@@ -1159,9 +1184,55 @@ function McpServerListPage({ initialData }) {
1159
1184
  }
1160
1185
  ) })
1161
1186
  ] }, s.id)),
1162
- servers.length === 0 && /* @__PURE__ */ jsx(EmptyRow, { colSpan: 8, children: 'No custom connectors registered. Click "Register Connector" to add one.' })
1187
+ servers.length === 0 && /* @__PURE__ */ jsx(EmptyRow, { colSpan: 8, children: 'No custom connectors registered. Click "+ New Connector" to add one.' })
1163
1188
  ] })
1164
1189
  ] }),
1190
+ /* @__PURE__ */ jsx(Dialog, { open: showCreate, onOpenChange: setShowCreate, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
1191
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Register Connector" }) }),
1192
+ /* @__PURE__ */ jsxs(DialogBody, { className: "space-y-3", children: [
1193
+ createError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: createError }),
1194
+ /* @__PURE__ */ jsx(FormField, { label: "Name", children: /* @__PURE__ */ jsx(Input, { value: createForm.name, onChange: (e) => setCreateForm({ ...createForm, name: e.target.value }), placeholder: "My MCP Server" }) }),
1195
+ /* @__PURE__ */ jsx(FormField, { label: "Slug", children: /* @__PURE__ */ jsx(Input, { value: createForm.slug, onChange: (e) => setCreateForm({ ...createForm, slug: e.target.value }), placeholder: "my-mcp-server" }) }),
1196
+ /* @__PURE__ */ jsx(FormField, { label: "Description", children: /* @__PURE__ */ jsx(Input, { value: createForm.description, onChange: (e) => setCreateForm({ ...createForm, description: e.target.value }), placeholder: "What this connector does" }) }),
1197
+ /* @__PURE__ */ jsx(FormField, { label: "Base URL", children: /* @__PURE__ */ jsx(Input, { value: createForm.base_url, onChange: (e) => setCreateForm({ ...createForm, base_url: e.target.value }), placeholder: "https://my-server.example.com" }) }),
1198
+ /* @__PURE__ */ jsx(FormField, { label: "MCP Endpoint Path", children: /* @__PURE__ */ jsx(Input, { value: createForm.mcp_endpoint_path, onChange: (e) => setCreateForm({ ...createForm, mcp_endpoint_path: e.target.value }), placeholder: "/mcp" }) })
1199
+ ] }),
1200
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1201
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setShowCreate(false), children: "Cancel" }),
1202
+ /* @__PURE__ */ jsx(Button, { onClick: handleCreate, disabled: creating || !createForm.name || !createForm.base_url, children: creating ? "Creating..." : "Create" })
1203
+ ] })
1204
+ ] }) }),
1205
+ /* @__PURE__ */ jsx(Dialog, { open: !!editTarget, onOpenChange: (open) => {
1206
+ if (!open) setEditTarget(null);
1207
+ }, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
1208
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "Edit Connector" }) }),
1209
+ /* @__PURE__ */ jsxs(DialogBody, { className: "space-y-3", children: [
1210
+ editError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: editError }),
1211
+ /* @__PURE__ */ jsx(FormField, { label: "Name", children: /* @__PURE__ */ jsx(Input, { value: editForm.name, onChange: (e) => setEditForm({ ...editForm, name: e.target.value }) }) }),
1212
+ /* @__PURE__ */ jsx(FormField, { label: "Description", children: /* @__PURE__ */ jsx(Input, { value: editForm.description, onChange: (e) => setEditForm({ ...editForm, description: e.target.value }) }) }),
1213
+ editTarget && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground space-y-1", children: [
1214
+ /* @__PURE__ */ jsxs("div", { children: [
1215
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Slug:" }),
1216
+ " ",
1217
+ editTarget.slug
1218
+ ] }),
1219
+ /* @__PURE__ */ jsxs("div", { children: [
1220
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Base URL:" }),
1221
+ " ",
1222
+ editTarget.base_url
1223
+ ] }),
1224
+ /* @__PURE__ */ jsxs("div", { children: [
1225
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Endpoint:" }),
1226
+ " ",
1227
+ editTarget.mcp_endpoint_path
1228
+ ] })
1229
+ ] })
1230
+ ] }),
1231
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
1232
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setEditTarget(null), children: "Cancel" }),
1233
+ /* @__PURE__ */ jsx(Button, { onClick: handleEdit, disabled: editing || !editForm.name, children: editing ? "Saving..." : "Save" })
1234
+ ] })
1235
+ ] }) }),
1165
1236
  /* @__PURE__ */ jsxs(
1166
1237
  ConfirmDialog,
1167
1238
  {
@@ -1172,14 +1243,14 @@ function McpServerListPage({ initialData }) {
1172
1243
  setDeleteError("");
1173
1244
  }
1174
1245
  },
1175
- title: "Delete Server",
1246
+ title: "Delete Connector",
1176
1247
  confirmLabel: "Delete",
1177
1248
  loadingLabel: "Deleting...",
1178
1249
  loading: deleting,
1179
1250
  error: deleteError,
1180
1251
  onConfirm: handleDelete,
1181
1252
  children: [
1182
- "Delete MCP server ",
1253
+ "Delete connector ",
1183
1254
  /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: deleteTarget?.name }),
1184
1255
  "? This cannot be undone."
1185
1256
  ]
@@ -3074,128 +3145,388 @@ function AgentConnectorsManager({ agentId, toolkits: initialToolkits, composioAl
3074
3145
  ] });
3075
3146
  }
3076
3147
  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
- );
3148
+ function buildTree(files) {
3149
+ const rootFiles = [];
3150
+ const dirMap = /* @__PURE__ */ new Map();
3151
+ function ensureDir(dirPath) {
3152
+ const existing = dirMap.get(dirPath);
3153
+ if (existing) return existing;
3154
+ const parts = dirPath.split("/");
3155
+ const node = {
3156
+ name: parts[parts.length - 1],
3157
+ fullPath: dirPath,
3158
+ children: [],
3159
+ files: []
3160
+ };
3161
+ dirMap.set(dirPath, node);
3162
+ if (parts.length > 1) {
3163
+ const parentPath = parts.slice(0, -1).join("/");
3164
+ const parent = ensureDir(parentPath);
3165
+ if (!parent.children.some((c) => c.fullPath === dirPath)) {
3166
+ parent.children.push(node);
3167
+ }
3168
+ }
3169
+ return node;
3170
+ }
3171
+ for (const file of files) {
3172
+ const slashIdx = file.path.lastIndexOf("/");
3173
+ if (slashIdx === -1) {
3174
+ rootFiles.push(file);
3175
+ } else {
3176
+ const dirPath = file.path.slice(0, slashIdx);
3177
+ const dir = ensureDir(dirPath);
3178
+ dir.files.push(file);
3179
+ }
3180
+ }
3181
+ const topLevel = [];
3182
+ for (const node of dirMap.values()) {
3183
+ if (!node.fullPath.includes("/")) {
3184
+ topLevel.push(node);
3185
+ }
3186
+ }
3187
+ function sortNode(node) {
3188
+ node.children.sort((a, b) => a.name.localeCompare(b.name));
3189
+ node.files.sort((a, b) => a.path.localeCompare(b.path));
3190
+ node.children.forEach(sortNode);
3191
+ }
3192
+ topLevel.forEach(sortNode);
3193
+ topLevel.sort((a, b) => a.name.localeCompare(b.name));
3194
+ rootFiles.sort((a, b) => a.path.localeCompare(b.path));
3195
+ return { rootFiles, rootDirs: topLevel };
3196
+ }
3197
+ function collectAllDirPaths(nodes) {
3198
+ const paths = /* @__PURE__ */ new Set();
3199
+ function walk(node) {
3200
+ paths.add(node.fullPath);
3201
+ node.children.forEach(walk);
3202
+ }
3203
+ nodes.forEach(walk);
3204
+ return paths;
3205
+ }
3206
+ function FileTreeEditor({
3207
+ initialFiles,
3208
+ onSave,
3209
+ onChange,
3210
+ readOnly = false,
3211
+ hideSave = false,
3212
+ title = "Files",
3213
+ saveLabel = "Save",
3214
+ addFolderLabel = "Folder",
3215
+ 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" },
3216
+ savedVersion
3217
+ }) {
3088
3218
  const [files, setFiles] = useState(initialFiles);
3089
- const [selectedPath, setSelectedPath] = useState(files[0]?.path ?? null);
3219
+ const [selectedPath, setSelectedPath] = useState(
3220
+ initialFiles.length > 0 ? initialFiles[0].path : null
3221
+ );
3090
3222
  const [saving, setSaving] = useState(false);
3091
- const [addingFile, setAddingFile] = useState(false);
3223
+ const [expanded, setExpanded] = useState(() => {
3224
+ const { rootDirs } = buildTree(initialFiles);
3225
+ return collectAllDirPaths(rootDirs);
3226
+ });
3227
+ const [showAddFolder, setShowAddFolder] = useState(false);
3228
+ const [newFolderName, setNewFolderName] = useState("");
3229
+ const [addingFileInDir, setAddingFileInDir] = useState(null);
3092
3230
  const [newFileName, setNewFileName] = useState("");
3231
+ const [savedSnapshot, setSavedSnapshot] = useState(() => JSON.stringify(initialFiles));
3232
+ useEffect(() => {
3233
+ const snap = JSON.stringify(initialFiles);
3234
+ setSavedSnapshot(snap);
3235
+ setFiles(initialFiles);
3236
+ const { rootDirs } = buildTree(initialFiles);
3237
+ setExpanded(collectAllDirPaths(rootDirs));
3238
+ }, [initialFiles]);
3239
+ useEffect(() => {
3240
+ if (savedVersion !== void 0 && savedVersion > 0) {
3241
+ setSavedSnapshot(JSON.stringify(files));
3242
+ }
3243
+ }, [savedVersion]);
3244
+ const onChangeRef = useRef(onChange);
3245
+ onChangeRef.current = onChange;
3246
+ useEffect(() => {
3247
+ if (onChangeRef.current && JSON.stringify(files) !== savedSnapshot) {
3248
+ onChangeRef.current(files);
3249
+ }
3250
+ }, [files, savedSnapshot]);
3093
3251
  const isDirty = useMemo(
3094
- () => JSON.stringify(files) !== JSON.stringify(initialFiles),
3095
- [files, initialFiles]
3252
+ () => JSON.stringify(files) !== savedSnapshot,
3253
+ [files, savedSnapshot]
3254
+ );
3255
+ const tree = useMemo(() => buildTree(files), [files]);
3256
+ const activeFile = useMemo(
3257
+ () => selectedPath ? files.find((f) => f.path === selectedPath) ?? null : null,
3258
+ [files, selectedPath]
3096
3259
  );
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));
3260
+ const handleEditorChange = useCallback((value) => {
3261
+ if (readOnly || !selectedPath) return;
3262
+ setFiles((prev) => prev.map((f) => f.path === selectedPath ? { ...f, content: value } : f));
3263
+ }, [readOnly, selectedPath]);
3264
+ function toggleExpand(dirPath) {
3265
+ setExpanded((prev) => {
3266
+ const next = new Set(prev);
3267
+ if (next.has(dirPath)) next.delete(dirPath);
3268
+ else next.add(dirPath);
3269
+ return next;
3270
+ });
3271
+ }
3272
+ function addFolder() {
3273
+ const name = newFolderName.trim();
3274
+ if (!name) return;
3275
+ const filePath = `${name}/${newFileTemplate.filename}`;
3276
+ if (files.some((f) => f.path === filePath)) return;
3277
+ const content = newFileTemplate.content.replace("# New", `# ${name}`);
3278
+ setFiles((prev) => [...prev, { path: filePath, content }]);
3279
+ setSelectedPath(filePath);
3280
+ setExpanded((prev) => /* @__PURE__ */ new Set([...prev, name]));
3281
+ setNewFolderName("");
3282
+ setShowAddFolder(false);
3100
3283
  }
3101
- function addFile() {
3284
+ function removeDir(dirPath) {
3285
+ const prefix = dirPath + "/";
3286
+ const affectedFiles = files.filter((f) => f.path.startsWith(prefix));
3287
+ if (affectedFiles.length === 0) return;
3288
+ if (!confirm(`Remove "${dirPath}" and all ${affectedFiles.length} file(s)?`)) return;
3289
+ setFiles((prev) => prev.filter((f) => !f.path.startsWith(prefix)));
3290
+ if (selectedPath && selectedPath.startsWith(prefix)) setSelectedPath(null);
3291
+ }
3292
+ function removeFile(filePath) {
3293
+ const fileName = filePath.split("/").pop() ?? filePath;
3294
+ if (!confirm(`Remove file "${fileName}"?`)) return;
3295
+ setFiles((prev) => prev.filter((f) => f.path !== filePath));
3296
+ if (selectedPath === filePath) setSelectedPath(null);
3297
+ }
3298
+ function addFileInDir(dirPath) {
3102
3299
  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);
3300
+ if (!name) return;
3301
+ const filePath = dirPath ? `${dirPath}/${name}` : name;
3302
+ if (files.some((f) => f.path === filePath)) return;
3303
+ setFiles((prev) => [...prev, { path: filePath, content: "" }]);
3304
+ setSelectedPath(filePath);
3108
3305
  setNewFileName("");
3306
+ setAddingFileInDir(null);
3109
3307
  }
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 () => {
3308
+ async function handleSave() {
3117
3309
  setSaving(true);
3118
3310
  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?.();
3311
+ await onSave(files);
3137
3312
  } finally {
3138
3313
  setSaving(false);
3139
3314
  }
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(
3315
+ }
3316
+ function renderTreeNode(node, depth) {
3317
+ const isExpanded = expanded.has(node.fullPath);
3318
+ return /* @__PURE__ */ jsxs("div", { children: [
3319
+ /* @__PURE__ */ jsxs(
3166
3320
  "div",
3167
3321
  {
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),
3322
+ className: "flex items-center justify-between cursor-pointer hover:bg-muted/50 py-1 pr-2",
3323
+ style: { paddingLeft: `${depth * 16 + 8}px` },
3324
+ onClick: () => toggleExpand(node.fullPath),
3170
3325
  children: [
3171
- /* @__PURE__ */ jsx("span", { className: "truncate", children: f.path }),
3172
- /* @__PURE__ */ jsx(
3326
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-xs truncate flex items-center gap-1", children: [
3327
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: isExpanded ? "\u25BE" : "\u25B8" }),
3328
+ node.name,
3329
+ "/"
3330
+ ] }),
3331
+ !readOnly && /* @__PURE__ */ jsx(
3173
3332
  "button",
3174
3333
  {
3175
- type: "button",
3176
3334
  onClick: (e) => {
3177
3335
  e.stopPropagation();
3178
- removeFile(f.path);
3336
+ removeDir(node.fullPath);
3179
3337
  },
3180
- className: "opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive ml-1",
3338
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3181
3339
  children: "\xD7"
3182
3340
  }
3183
3341
  )
3184
3342
  ]
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
3343
  }
3195
- ) }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Select a file to edit" }) })
3344
+ ),
3345
+ isExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
3346
+ node.children.map((child) => renderTreeNode(child, depth + 1)),
3347
+ node.files.map((file) => {
3348
+ const fileName = file.path.split("/").pop() ?? file.path;
3349
+ return /* @__PURE__ */ jsxs(
3350
+ "div",
3351
+ {
3352
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3353
+ style: { paddingLeft: `${(depth + 1) * 16 + 8}px` },
3354
+ onClick: () => setSelectedPath(file.path),
3355
+ children: [
3356
+ /* @__PURE__ */ jsx("span", { className: "text-xs truncate", children: fileName }),
3357
+ !readOnly && /* @__PURE__ */ jsx(
3358
+ "button",
3359
+ {
3360
+ onClick: (e) => {
3361
+ e.stopPropagation();
3362
+ removeFile(file.path);
3363
+ },
3364
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3365
+ children: "\xD7"
3366
+ }
3367
+ )
3368
+ ]
3369
+ },
3370
+ file.path
3371
+ );
3372
+ }),
3373
+ !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: [
3374
+ /* @__PURE__ */ jsx(
3375
+ Input,
3376
+ {
3377
+ value: newFileName,
3378
+ onChange: (e) => setNewFileName(e.target.value),
3379
+ placeholder: "file.md",
3380
+ className: "h-6 text-xs",
3381
+ onKeyDown: (e) => e.key === "Enter" && addFileInDir(node.fullPath),
3382
+ autoFocus: true
3383
+ }
3384
+ ),
3385
+ /* @__PURE__ */ jsx(Button, { onClick: () => addFileInDir(node.fullPath), size: "sm", className: "h-6 text-xs px-2", children: "+" })
3386
+ ] }) : /* @__PURE__ */ jsx(
3387
+ "button",
3388
+ {
3389
+ onClick: () => {
3390
+ setAddingFileInDir(node.fullPath);
3391
+ setNewFileName("");
3392
+ },
3393
+ className: "text-xs text-primary hover:underline",
3394
+ children: "+ File"
3395
+ }
3396
+ ) })
3397
+ ] })
3398
+ ] }, node.fullPath);
3399
+ }
3400
+ return /* @__PURE__ */ jsxs("div", { children: [
3401
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3402
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3403
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: title }),
3404
+ isDirty && !readOnly && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "text-xs", children: "Unsaved changes" }),
3405
+ readOnly && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: "Read-only" })
3406
+ ] }),
3407
+ !readOnly && !hideSave && /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: saving || !isDirty, size: "sm", children: saving ? "Saving..." : saveLabel })
3408
+ ] }),
3409
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-4 min-h-[500px]", children: [
3410
+ /* @__PURE__ */ jsxs("div", { className: "w-64 shrink-0 border border-border rounded-md overflow-hidden", children: [
3411
+ /* @__PURE__ */ jsxs("div", { className: "p-2 bg-muted/50 border-b border-border flex items-center justify-between", children: [
3412
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: title }),
3413
+ !readOnly && /* @__PURE__ */ jsxs(
3414
+ "button",
3415
+ {
3416
+ onClick: () => setShowAddFolder(!showAddFolder),
3417
+ className: "text-xs text-primary hover:underline",
3418
+ children: [
3419
+ "+ ",
3420
+ addFolderLabel
3421
+ ]
3422
+ }
3423
+ )
3424
+ ] }),
3425
+ showAddFolder && !readOnly && /* @__PURE__ */ jsxs("div", { className: "p-2 border-b border-border flex gap-1", children: [
3426
+ /* @__PURE__ */ jsx(
3427
+ Input,
3428
+ {
3429
+ value: newFolderName,
3430
+ onChange: (e) => setNewFolderName(e.target.value),
3431
+ placeholder: "folder-name",
3432
+ className: "h-7 text-xs",
3433
+ onKeyDown: (e) => e.key === "Enter" && addFolder(),
3434
+ autoFocus: true
3435
+ }
3436
+ ),
3437
+ /* @__PURE__ */ jsx(Button, { onClick: addFolder, size: "sm", className: "h-7 text-xs px-2", children: "Add" })
3438
+ ] }),
3439
+ /* @__PURE__ */ jsxs("div", { className: "text-sm overflow-y-auto", children: [
3440
+ tree.rootFiles.map((file) => /* @__PURE__ */ jsxs(
3441
+ "div",
3442
+ {
3443
+ className: `flex items-center justify-between cursor-pointer hover:bg-muted/30 py-1 pr-2 ${selectedPath === file.path ? "bg-primary/10 text-primary" : ""}`,
3444
+ style: { paddingLeft: "8px" },
3445
+ onClick: () => setSelectedPath(file.path),
3446
+ children: [
3447
+ /* @__PURE__ */ jsx("span", { className: "text-xs truncate", children: file.path }),
3448
+ !readOnly && /* @__PURE__ */ jsx(
3449
+ "button",
3450
+ {
3451
+ onClick: (e) => {
3452
+ e.stopPropagation();
3453
+ removeFile(file.path);
3454
+ },
3455
+ className: "text-muted-foreground hover:text-destructive text-xs ml-1 shrink-0",
3456
+ children: "\xD7"
3457
+ }
3458
+ )
3459
+ ]
3460
+ },
3461
+ file.path
3462
+ )),
3463
+ tree.rootDirs.map((node) => renderTreeNode(node, 0)),
3464
+ 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.` })
3465
+ ] })
3466
+ ] }),
3467
+ /* @__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: [
3468
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 bg-muted/50 border-b border-border text-xs text-muted-foreground", children: activeFile.path }),
3469
+ /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "flex-1 animate-pulse bg-muted/50" }), children: /* @__PURE__ */ jsx(
3470
+ CodeEditor,
3471
+ {
3472
+ value: activeFile.content,
3473
+ onChange: handleEditorChange,
3474
+ filename: activeFile.path
3475
+ }
3476
+ ) })
3477
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground text-sm", children: [
3478
+ "Select a file to ",
3479
+ readOnly ? "view" : "edit"
3480
+ ] }) })
3196
3481
  ] })
3197
3482
  ] });
3198
3483
  }
3484
+ function AgentSkillManager({ agentId, initialSkills, onSaved }) {
3485
+ const client = useAgentPlaneClient();
3486
+ const initialFiles = useMemo(
3487
+ () => initialSkills.flatMap(
3488
+ (s) => s.files.map((f) => ({
3489
+ path: s.folder === "(root)" ? f.path : `${s.folder}/${f.path}`,
3490
+ content: f.content
3491
+ }))
3492
+ ),
3493
+ [initialSkills]
3494
+ );
3495
+ const handleSave = useCallback(async (files) => {
3496
+ const folderMap = /* @__PURE__ */ new Map();
3497
+ for (const file of files) {
3498
+ const slashIdx = file.path.lastIndexOf("/");
3499
+ if (slashIdx === -1) {
3500
+ const existing = folderMap.get("(root)") ?? [];
3501
+ existing.push({ path: file.path, content: file.content });
3502
+ folderMap.set("(root)", existing);
3503
+ } else {
3504
+ const folder = file.path.slice(0, slashIdx);
3505
+ const fileName = file.path.slice(slashIdx + 1);
3506
+ const existing = folderMap.get(folder) ?? [];
3507
+ existing.push({ path: fileName, content: file.content });
3508
+ folderMap.set(folder, existing);
3509
+ }
3510
+ }
3511
+ const skills = Array.from(folderMap.entries()).map(([folder, files2]) => ({ folder, files: files2 }));
3512
+ await client.agents.update(agentId, { skills });
3513
+ onSaved?.();
3514
+ }, [agentId, client, onSaved]);
3515
+ return /* @__PURE__ */ jsx(
3516
+ FileTreeEditor,
3517
+ {
3518
+ initialFiles,
3519
+ onSave: handleSave,
3520
+ title: "Skills",
3521
+ saveLabel: "Save Skills",
3522
+ addFolderLabel: "Skill",
3523
+ newFileTemplate: {
3524
+ filename: "SKILL.md",
3525
+ content: "---\nname: New Skill\ndescription: Describe when this skill should be triggered\n---\n\n# Instructions\n\nDescribe what this skill does...\n"
3526
+ }
3527
+ }
3528
+ );
3529
+ }
3199
3530
  function AgentPluginManager({ agentId, initialPlugins, onSaved }) {
3200
3531
  const client = useAgentPlaneClient();
3201
3532
  const [plugins, setPlugins] = useState(initialPlugins);