apify tool, tools API + automation UI, integrations config, super admin restrictions

- apify tool: TOOL.md definition, index.ts implementation with auto-auth via OFFICER_APIFY_TOKEN, output_path for large datasets
- tools API: /tools routes (list, detail, chat, create, delete) mirroring tasks pattern
- automation UI: tools tab in sidebar, NewTool component, tool detail view
- apify integration: settings page for enterprise API key config, pi-bridge passes env var to containers
- tiktok-trends task: rewritten as agent instructions using apify tool with output_path, scripted report generation for 50KB read limit
- restrict edit/delete of native/global capabilities to Super Admin only (backend + frontend)
- tools authoring guide: TOOLS.md with full spec for TOOL.md frontmatter, index.ts execute signature, patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 20:00:45 +00:00
co-authored by Claude Opus 4.6
parent 776738a983
commit bd362dc586
19 changed files with 1059 additions and 149 deletions
@@ -8,6 +8,7 @@ import { ArrowLeft, Pencil, Plus, Check, X, Trash2, Search, ChevronRight } from
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { Card } from '@/components/Card';
import { usePiChat, EmbeddableChat } from 'officerdev';
type CapabilitySummary = {
@@ -249,6 +250,7 @@ export const CapabilityList = ({ kind, endpoint, queryKey, selected, onSelect, o
export const CapabilityPage = ({ kind, endpoint, queryKey }: CapabilityPageProps) => {
const client = useClient();
const qc = useQueryClient();
const { user } = useAuth();
const [selected, setSelected] = useState<string | null>(null);
const [editing, setEditing] = useState(false);
const [isNew, setIsNew] = useState(false);
@@ -330,7 +332,7 @@ export const CapabilityPage = ({ kind, endpoint, queryKey }: CapabilityPageProps
<span className="text-sm font-medium text-duck-dark/70 flex-1">
{detail?.name ?? `Select a ${kind.toLowerCase()}`}
</span>
{detail && (
{detail && (detail.scope === 'user' || user?.role === 'Super Admin') && (
<>
<button
onClick={() => setEditing((e) => !e)}