@agentforge-ai/cli 0.3.2 → 0.4.1

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.
Files changed (89) hide show
  1. package/dist/default/.env.example +46 -6
  2. package/dist/default/README.md +89 -9
  3. package/dist/default/convex/schema.ts +248 -4
  4. package/dist/default/dashboard/app/components/DashboardLayout.tsx +245 -0
  5. package/dist/default/dashboard/app/components/ui/badge.tsx +26 -0
  6. package/dist/default/dashboard/app/components/ui/button.tsx +41 -0
  7. package/dist/default/dashboard/app/components/ui/card.tsx +44 -0
  8. package/dist/default/dashboard/app/components/ui/dialog.tsx +66 -0
  9. package/dist/default/dashboard/app/components/ui/input.tsx +21 -0
  10. package/dist/default/dashboard/app/components/ui/label.tsx +18 -0
  11. package/dist/default/dashboard/app/components/ui/select.tsx +75 -0
  12. package/dist/default/dashboard/app/components/ui/sheet.tsx +73 -0
  13. package/dist/default/dashboard/app/components/ui/switch.tsx +34 -0
  14. package/dist/default/dashboard/app/components/ui/table.tsx +60 -0
  15. package/dist/default/dashboard/app/components/ui/tabs.tsx +50 -0
  16. package/dist/default/dashboard/app/components/ui/tooltip.tsx +23 -0
  17. package/dist/default/dashboard/app/lib/utils.ts +6 -0
  18. package/dist/default/dashboard/app/main.tsx +35 -0
  19. package/dist/default/dashboard/app/routeTree.gen.ts +352 -0
  20. package/dist/default/dashboard/app/routes/__root.tsx +10 -0
  21. package/dist/default/dashboard/app/routes/agents.tsx +255 -0
  22. package/dist/default/dashboard/app/routes/chat.tsx +427 -0
  23. package/dist/default/dashboard/app/routes/connections.tsx +413 -0
  24. package/dist/default/dashboard/app/routes/cron.tsx +322 -0
  25. package/dist/default/dashboard/app/routes/files.tsx +203 -0
  26. package/dist/default/dashboard/app/routes/index.tsx +141 -0
  27. package/dist/default/dashboard/app/routes/projects.tsx +254 -0
  28. package/dist/default/dashboard/app/routes/sessions.tsx +272 -0
  29. package/dist/default/dashboard/app/routes/settings.tsx +583 -0
  30. package/dist/default/dashboard/app/routes/skills.tsx +252 -0
  31. package/dist/default/dashboard/app/routes/usage.tsx +181 -0
  32. package/dist/default/dashboard/app/styles/globals.css +93 -0
  33. package/dist/default/dashboard/index.html +13 -0
  34. package/dist/default/dashboard/package.json +36 -0
  35. package/dist/default/dashboard/postcss.config.js +6 -0
  36. package/dist/default/dashboard/tailwind.config.js +50 -0
  37. package/dist/default/dashboard/tsconfig.json +24 -0
  38. package/dist/default/dashboard/vite.config.ts +16 -0
  39. package/dist/default/package.json +8 -3
  40. package/dist/default/skills/skill-creator/SKILL.md +270 -0
  41. package/dist/default/skills/skill-creator/config.json +11 -0
  42. package/dist/default/skills/skill-creator/index.ts +392 -0
  43. package/dist/default/src/agent.ts +85 -5
  44. package/dist/index.js +1574 -10
  45. package/dist/index.js.map +1 -1
  46. package/package.json +2 -1
  47. package/templates/default/.env.example +46 -6
  48. package/templates/default/README.md +89 -9
  49. package/templates/default/convex/schema.ts +248 -4
  50. package/templates/default/dashboard/app/components/DashboardLayout.tsx +245 -0
  51. package/templates/default/dashboard/app/components/ui/badge.tsx +26 -0
  52. package/templates/default/dashboard/app/components/ui/button.tsx +41 -0
  53. package/templates/default/dashboard/app/components/ui/card.tsx +44 -0
  54. package/templates/default/dashboard/app/components/ui/dialog.tsx +66 -0
  55. package/templates/default/dashboard/app/components/ui/input.tsx +21 -0
  56. package/templates/default/dashboard/app/components/ui/label.tsx +18 -0
  57. package/templates/default/dashboard/app/components/ui/select.tsx +75 -0
  58. package/templates/default/dashboard/app/components/ui/sheet.tsx +73 -0
  59. package/templates/default/dashboard/app/components/ui/switch.tsx +34 -0
  60. package/templates/default/dashboard/app/components/ui/table.tsx +60 -0
  61. package/templates/default/dashboard/app/components/ui/tabs.tsx +50 -0
  62. package/templates/default/dashboard/app/components/ui/tooltip.tsx +23 -0
  63. package/templates/default/dashboard/app/lib/utils.ts +6 -0
  64. package/templates/default/dashboard/app/main.tsx +35 -0
  65. package/templates/default/dashboard/app/routeTree.gen.ts +352 -0
  66. package/templates/default/dashboard/app/routes/__root.tsx +10 -0
  67. package/templates/default/dashboard/app/routes/agents.tsx +255 -0
  68. package/templates/default/dashboard/app/routes/chat.tsx +427 -0
  69. package/templates/default/dashboard/app/routes/connections.tsx +413 -0
  70. package/templates/default/dashboard/app/routes/cron.tsx +322 -0
  71. package/templates/default/dashboard/app/routes/files.tsx +203 -0
  72. package/templates/default/dashboard/app/routes/index.tsx +141 -0
  73. package/templates/default/dashboard/app/routes/projects.tsx +254 -0
  74. package/templates/default/dashboard/app/routes/sessions.tsx +272 -0
  75. package/templates/default/dashboard/app/routes/settings.tsx +583 -0
  76. package/templates/default/dashboard/app/routes/skills.tsx +252 -0
  77. package/templates/default/dashboard/app/routes/usage.tsx +181 -0
  78. package/templates/default/dashboard/app/styles/globals.css +93 -0
  79. package/templates/default/dashboard/index.html +13 -0
  80. package/templates/default/dashboard/package.json +36 -0
  81. package/templates/default/dashboard/postcss.config.js +6 -0
  82. package/templates/default/dashboard/tailwind.config.js +50 -0
  83. package/templates/default/dashboard/tsconfig.json +24 -0
  84. package/templates/default/dashboard/vite.config.ts +16 -0
  85. package/templates/default/package.json +8 -3
  86. package/templates/default/skills/skill-creator/SKILL.md +270 -0
  87. package/templates/default/skills/skill-creator/config.json +11 -0
  88. package/templates/default/skills/skill-creator/index.ts +392 -0
  89. package/templates/default/src/agent.ts +85 -5
@@ -1,8 +1,48 @@
1
- # OpenAI API Key (required for default model)
2
- OPENAI_API_KEY=sk-your-key-here
1
+ # ─── Convex ─────────────────────────────────────────────────────────────
2
+ # Your Convex deployment URL (set automatically by `npx convex dev`)
3
+ CONVEX_URL=
3
4
 
4
- # E2B API Key (required for sandbox execution)
5
- E2B_API_KEY=e2b_your-key-here
5
+ # ─── LLM Providers ─────────────────────────────────────────────────────
6
+ # Set the API key for your preferred provider(s).
7
+ # You only need ONE provider to get started.
6
8
 
7
- # Convex deployment URL (set automatically by `npx convex dev`)
8
- # CONVEX_URL=https://your-deployment.convex.cloud
9
+ # OpenAI (https://platform.openai.com/api-keys)
10
+ OPENAI_API_KEY=
11
+
12
+ # OpenRouter — access 100+ models with one key (https://openrouter.ai/keys)
13
+ OPENROUTER_API_KEY=
14
+
15
+ # Anthropic (https://console.anthropic.com/)
16
+ ANTHROPIC_API_KEY=
17
+
18
+ # Google Gemini (https://aistudio.google.com/apikey)
19
+ GOOGLE_API_KEY=
20
+
21
+ # xAI / Grok (https://console.x.ai/)
22
+ XAI_API_KEY=
23
+
24
+ # ─── AgentForge Defaults ───────────────────────────────────────────────
25
+ # Default provider: openai | openrouter | anthropic | google | xai
26
+ AGENTFORGE_DEFAULT_PROVIDER=openai
27
+
28
+ # Default model (provider-specific)
29
+ AGENTFORGE_DEFAULT_MODEL=gpt-4o-mini
30
+
31
+ # ─── Sandbox (Code Execution) ──────────────────────────────────────────
32
+ # E2B API key for secure code execution (https://e2b.dev/)
33
+ E2B_API_KEY=
34
+
35
+ # ─── Workspace ──────────────────────────────────────────────────────────
36
+ # Workspace base path for local development (default: ./workspace)
37
+ AGENTFORGE_WORKSPACE_PATH=./workspace
38
+
39
+ # ─── Cloudflare R2 (Cloud Workspace) ────────────────────────────────────
40
+ # Required for cloud workspace deployment on Cloudflare
41
+ R2_ENDPOINT=
42
+ R2_ACCESS_KEY_ID=
43
+ R2_SECRET_ACCESS_KEY=
44
+ R2_BUCKET=
45
+
46
+ # ─── Dashboard ──────────────────────────────────────────────────────────
47
+ # Port for the web dashboard (default: 3000)
48
+ AGENTFORGE_DASHBOARD_PORT=3000
@@ -1,30 +1,110 @@
1
1
  # My AgentForge Project
2
2
 
3
- Built with [AgentForge](https://github.com/Agentic-Engineering-Agency/agentforge) - The Minimalist Framework for Collaborative AI Agents.
3
+ Built with [AgentForge](https://github.com/Agentic-Engineering-Agency/agentforge) a NanoClaw made with Mastra.
4
4
 
5
- ## Getting Started
5
+ ## Quick Start
6
6
 
7
7
  ```bash
8
- # Start the development server
9
- agentforge run
8
+ # 1. Install dependencies
9
+ npm install
10
10
 
11
- # Or use pnpm directly
12
- pnpm dev
11
+ # 2. Set up your environment
12
+ cp .env.example .env
13
+ # Edit .env and add your API key (OpenAI, OpenRouter, Anthropic, etc.)
14
+
15
+ # 3. Start the Convex dev server
16
+ npx convex dev
17
+
18
+ # 4. Start building!
19
+ npm run dev
13
20
  ```
14
21
 
15
22
  ## Project Structure
16
23
 
17
24
  ```
18
25
  ├── convex/ # Convex schema and functions
19
- │ └── schema.ts # Database schema (agents, threads, messages)
26
+ │ └── schema.ts # Database schema (agents, threads, messages, etc.)
20
27
  ├── src/
21
28
  │ └── agent.ts # Your agent definition
29
+ ├── skills/ # Custom skills directory
30
+ ├── .env.example # Environment variable template
22
31
  ├── package.json
23
32
  └── tsconfig.json
24
33
  ```
25
34
 
35
+ ## CLI Commands
36
+
37
+ ```bash
38
+ # Agent Management
39
+ agentforge agents list # List all agents
40
+ agentforge agents create # Create a new agent (interactive)
41
+ agentforge agents inspect <id> # Show agent details
42
+ agentforge agents edit <id> # Edit an agent
43
+ agentforge agents delete <id> # Delete an agent
44
+
45
+ # Chat
46
+ agentforge chat <agent-id> # Start chatting with an agent
47
+ agentforge chat --session <id> # Resume a session
48
+
49
+ # Sessions & Threads
50
+ agentforge sessions list # List all sessions
51
+ agentforge threads list # List all threads
52
+
53
+ # Skills
54
+ agentforge skills list # List installed skills
55
+ agentforge skills install <name> # Install a skill
56
+ agentforge skills search <query> # Search available skills
57
+
58
+ # Cron Jobs
59
+ agentforge cron list # List cron jobs
60
+ agentforge cron create # Create a cron job (interactive)
61
+
62
+ # MCP Connections
63
+ agentforge mcp list # List MCP connections
64
+ agentforge mcp add # Add a connection (interactive)
65
+ agentforge mcp test <id> # Test connection health
66
+
67
+ # Files & Projects
68
+ agentforge files list # List files
69
+ agentforge files upload <path> # Upload a file
70
+ agentforge projects list # List projects
71
+ agentforge projects create <name> # Create a project
72
+
73
+ # Configuration & Vault
74
+ agentforge config list # List all config
75
+ agentforge vault list # List secrets (masked)
76
+ agentforge vault add <name> <value> # Store a secret
77
+
78
+ # Utilities
79
+ agentforge status # Show system health
80
+ agentforge logs # Tail recent logs
81
+ agentforge dashboard # Open the web dashboard
82
+ agentforge deploy # Deploy to production
83
+ ```
84
+
85
+ ## Providers
86
+
87
+ AgentForge supports multiple LLM providers. Set your preferred provider in `.env`:
88
+
89
+ | Provider | Model Format | API Key Variable |
90
+ |----------|-------------|-----------------|
91
+ | OpenAI | `openai:gpt-4o-mini` | `OPENAI_API_KEY` |
92
+ | OpenRouter | `openrouter:anthropic/claude-3.5-sonnet` | `OPENROUTER_API_KEY` |
93
+ | Anthropic | `anthropic:claude-3-5-sonnet-20241022` | `ANTHROPIC_API_KEY` |
94
+ | Google | `google:gemini-2.0-flash` | `GOOGLE_API_KEY` |
95
+ | xAI | `xai:grok-2` | `XAI_API_KEY` |
96
+
97
+ ## Web Dashboard
98
+
99
+ Launch the dashboard for a visual interface:
100
+
101
+ ```bash
102
+ agentforge dashboard
103
+ ```
104
+
26
105
  ## Learn More
27
106
 
28
107
  - [AgentForge Documentation](https://github.com/Agentic-Engineering-Agency/agentforge)
29
- - [Mastra Documentation](https://mastra.ai/docs)
30
- - [Convex Documentation](https://docs.convex.dev)
108
+ - [CLI Reference](https://github.com/Agentic-Engineering-Agency/agentforge/blob/main/docs/cli-reference.md)
109
+ - [Convex Docs](https://docs.convex.dev)
110
+ - [Mastra Docs](https://mastra.ai/docs)
@@ -1,19 +1,53 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
2
  import { v } from "convex/values";
3
3
 
4
+ /**
5
+ * AgentForge Database Schema
6
+ *
7
+ * This schema defines all the tables needed for your AgentForge project.
8
+ * Customize it to fit your needs — add new tables, fields, or indexes.
9
+ *
10
+ * IMPORTANT: Index names cannot be "by_id" or "by_creation_time" (reserved by Convex).
11
+ * Use camelCase names like "byAgentId", "byUserId", etc.
12
+ */
4
13
  export default defineSchema({
14
+ // ─── Agent Definitions ───────────────────────────────────────────────
5
15
  agents: defineTable({
6
16
  id: v.string(),
7
17
  name: v.string(),
18
+ description: v.optional(v.string()),
8
19
  instructions: v.string(),
9
20
  model: v.string(),
21
+ provider: v.string(),
10
22
  tools: v.optional(v.any()),
11
- }).index("by_id", ["id"]),
23
+ temperature: v.optional(v.number()),
24
+ maxTokens: v.optional(v.number()),
25
+ topP: v.optional(v.number()),
26
+ isActive: v.boolean(),
27
+ createdAt: v.number(),
28
+ updatedAt: v.number(),
29
+ userId: v.optional(v.string()),
30
+ })
31
+ .index("byAgentId", ["id"])
32
+ .index("byUserId", ["userId"])
33
+ .index("byIsActive", ["isActive"]),
12
34
 
35
+ // ─── Conversation Threads ────────────────────────────────────────────
13
36
  threads: defineTable({
14
37
  name: v.optional(v.string()),
15
- }),
38
+ agentId: v.string(),
39
+ userId: v.optional(v.string()),
40
+ projectId: v.optional(v.string()),
41
+ status: v.string(),
42
+ metadata: v.optional(v.any()),
43
+ createdAt: v.number(),
44
+ updatedAt: v.number(),
45
+ })
46
+ .index("byAgentId", ["agentId"])
47
+ .index("byUserId", ["userId"])
48
+ .index("byStatus", ["status"]),
16
49
 
50
+ // ─── Messages ────────────────────────────────────────────────────────
17
51
  messages: defineTable({
18
52
  threadId: v.id("threads"),
19
53
  role: v.union(
@@ -23,6 +57,216 @@ export default defineSchema({
23
57
  v.literal("tool")
24
58
  ),
25
59
  content: v.string(),
26
- tool_calls: v.optional(v.any()),
27
- }).index("by_thread", ["threadId"]),
60
+ toolCalls: v.optional(v.any()),
61
+ toolResults: v.optional(v.any()),
62
+ tokenUsage: v.optional(v.any()),
63
+ model: v.optional(v.string()),
64
+ provider: v.optional(v.string()),
65
+ timestamp: v.number(),
66
+ })
67
+ .index("byThreadId", ["threadId"])
68
+ .index("byTimestamp", ["timestamp"]),
69
+
70
+ // ─── Sessions ────────────────────────────────────────────────────────
71
+ sessions: defineTable({
72
+ name: v.string(),
73
+ agentId: v.string(),
74
+ threadId: v.optional(v.id("threads")),
75
+ status: v.string(),
76
+ userId: v.optional(v.string()),
77
+ startedAt: v.number(),
78
+ lastActivityAt: v.number(),
79
+ metadata: v.optional(v.any()),
80
+ })
81
+ .index("byAgentId", ["agentId"])
82
+ .index("byUserId", ["userId"])
83
+ .index("byStatus", ["status"]),
84
+
85
+ // ─── Files ───────────────────────────────────────────────────────────
86
+ files: defineTable({
87
+ name: v.string(),
88
+ folderId: v.optional(v.string()),
89
+ mimeType: v.string(),
90
+ size: v.number(),
91
+ storageId: v.optional(v.string()),
92
+ url: v.optional(v.string()),
93
+ userId: v.optional(v.string()),
94
+ projectId: v.optional(v.string()),
95
+ createdAt: v.number(),
96
+ })
97
+ .index("byFolderId", ["folderId"])
98
+ .index("byUserId", ["userId"])
99
+ .index("byProjectId", ["projectId"]),
100
+
101
+ // ─── Folders ─────────────────────────────────────────────────────────
102
+ folders: defineTable({
103
+ name: v.string(),
104
+ parentId: v.optional(v.string()),
105
+ userId: v.optional(v.string()),
106
+ projectId: v.optional(v.string()),
107
+ createdAt: v.number(),
108
+ })
109
+ .index("byParentId", ["parentId"])
110
+ .index("byUserId", ["userId"]),
111
+
112
+ // ─── Projects / Workspaces ───────────────────────────────────────────
113
+ projects: defineTable({
114
+ name: v.string(),
115
+ description: v.optional(v.string()),
116
+ status: v.string(),
117
+ userId: v.optional(v.string()),
118
+ settings: v.optional(v.any()),
119
+ createdAt: v.number(),
120
+ updatedAt: v.number(),
121
+ })
122
+ .index("byUserId", ["userId"])
123
+ .index("byStatus", ["status"]),
124
+
125
+ // ─── Skills ──────────────────────────────────────────────────────────
126
+ skills: defineTable({
127
+ name: v.string(),
128
+ description: v.optional(v.string()),
129
+ category: v.string(),
130
+ version: v.string(),
131
+ isInstalled: v.boolean(),
132
+ configuration: v.optional(v.any()),
133
+ agentId: v.optional(v.string()),
134
+ userId: v.optional(v.string()),
135
+ createdAt: v.number(),
136
+ updatedAt: v.number(),
137
+ })
138
+ .index("byAgentId", ["agentId"])
139
+ .index("byCategory", ["category"])
140
+ .index("byIsInstalled", ["isInstalled"]),
141
+
142
+ // ─── Cron Jobs ───────────────────────────────────────────────────────
143
+ cronJobs: defineTable({
144
+ name: v.string(),
145
+ schedule: v.string(),
146
+ agentId: v.string(),
147
+ action: v.string(),
148
+ isEnabled: v.boolean(),
149
+ lastRunAt: v.optional(v.number()),
150
+ nextRunAt: v.optional(v.number()),
151
+ userId: v.optional(v.string()),
152
+ createdAt: v.number(),
153
+ updatedAt: v.number(),
154
+ })
155
+ .index("byAgentId", ["agentId"])
156
+ .index("byIsEnabled", ["isEnabled"])
157
+ .index("byUserId", ["userId"]),
158
+
159
+ // ─── MCP Connections ─────────────────────────────────────────────────
160
+ mcpConnections: defineTable({
161
+ name: v.string(),
162
+ type: v.string(),
163
+ endpoint: v.string(),
164
+ isConnected: v.boolean(),
165
+ isEnabled: v.boolean(),
166
+ credentials: v.optional(v.any()),
167
+ capabilities: v.optional(v.any()),
168
+ userId: v.optional(v.string()),
169
+ lastConnectedAt: v.optional(v.number()),
170
+ createdAt: v.number(),
171
+ updatedAt: v.number(),
172
+ })
173
+ .index("byUserId", ["userId"])
174
+ .index("byIsEnabled", ["isEnabled"]),
175
+
176
+ // ─── API Keys ────────────────────────────────────────────────────────
177
+ apiKeys: defineTable({
178
+ provider: v.string(),
179
+ keyName: v.string(),
180
+ encryptedKey: v.string(),
181
+ isActive: v.boolean(),
182
+ userId: v.optional(v.string()),
183
+ createdAt: v.number(),
184
+ lastUsedAt: v.optional(v.number()),
185
+ })
186
+ .index("byProvider", ["provider"])
187
+ .index("byUserId", ["userId"])
188
+ .index("byIsActive", ["isActive"]),
189
+
190
+ // ─── Usage Tracking ──────────────────────────────────────────────────
191
+ usage: defineTable({
192
+ agentId: v.string(),
193
+ sessionId: v.optional(v.string()),
194
+ provider: v.string(),
195
+ model: v.string(),
196
+ promptTokens: v.number(),
197
+ completionTokens: v.number(),
198
+ totalTokens: v.number(),
199
+ cost: v.optional(v.number()),
200
+ userId: v.optional(v.string()),
201
+ timestamp: v.number(),
202
+ })
203
+ .index("byAgentId", ["agentId"])
204
+ .index("byUserId", ["userId"])
205
+ .index("byTimestamp", ["timestamp"])
206
+ .index("byProvider", ["provider"]),
207
+
208
+ // ─── Settings ────────────────────────────────────────────────────────
209
+ settings: defineTable({
210
+ userId: v.string(),
211
+ key: v.string(),
212
+ value: v.any(),
213
+ updatedAt: v.number(),
214
+ })
215
+ .index("byUserId", ["userId"])
216
+ .index("byUserIdAndKey", ["userId", "key"]),
217
+
218
+ // ─── System Logs ─────────────────────────────────────────────────────
219
+ logs: defineTable({
220
+ level: v.union(
221
+ v.literal("debug"),
222
+ v.literal("info"),
223
+ v.literal("warn"),
224
+ v.literal("error")
225
+ ),
226
+ source: v.string(),
227
+ message: v.string(),
228
+ metadata: v.optional(v.any()),
229
+ userId: v.optional(v.string()),
230
+ timestamp: v.number(),
231
+ })
232
+ .index("byLevel", ["level"])
233
+ .index("bySource", ["source"])
234
+ .index("byTimestamp", ["timestamp"]),
235
+
236
+ // ─── Heartbeat (Task Continuation) ───────────────────────────────────
237
+ heartbeats: defineTable({
238
+ agentId: v.string(),
239
+ threadId: v.optional(v.id("threads")),
240
+ status: v.string(),
241
+ currentTask: v.optional(v.string()),
242
+ pendingTasks: v.array(v.string()),
243
+ context: v.string(),
244
+ lastCheck: v.number(),
245
+ nextCheck: v.number(),
246
+ metadata: v.optional(v.any()),
247
+ })
248
+ .index("byAgentId", ["agentId"])
249
+ .index("byStatus", ["status"])
250
+ .index("byNextCheck", ["nextCheck"]),
251
+
252
+ // ─── Secure Vault ────────────────────────────────────────────────────
253
+ vault: defineTable({
254
+ name: v.string(),
255
+ category: v.string(),
256
+ provider: v.optional(v.string()),
257
+ encryptedValue: v.string(),
258
+ iv: v.string(),
259
+ maskedValue: v.string(),
260
+ isActive: v.boolean(),
261
+ expiresAt: v.optional(v.number()),
262
+ lastAccessedAt: v.optional(v.number()),
263
+ accessCount: v.number(),
264
+ userId: v.optional(v.string()),
265
+ createdAt: v.number(),
266
+ updatedAt: v.number(),
267
+ })
268
+ .index("byUserId", ["userId"])
269
+ .index("byCategory", ["category"])
270
+ .index("byProvider", ["provider"])
271
+ .index("byIsActive", ["isActive"]),
28
272
  });
@@ -0,0 +1,245 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { Link, useRouterState } from "@tanstack/react-router";
3
+ import {
4
+ MessageSquare, LayoutDashboard, Radio, Server, Activity, Clock,
5
+ Bot, Sparkles, Network, Settings, Bug, FileText, Menu, X,
6
+ ChevronLeft, ChevronRight, User, Heart, FolderKanban, Folder,
7
+ } from "lucide-react";
8
+
9
+ const navItems = [
10
+ {
11
+ section: "Chat",
12
+ items: [
13
+ { href: "/chat", label: "Chat", icon: MessageSquare },
14
+ ],
15
+ },
16
+ {
17
+ section: "Control",
18
+ items: [
19
+ { href: "/", label: "Overview", icon: LayoutDashboard },
20
+ { href: "/sessions", label: "Sessions", icon: Activity },
21
+ { href: "/usage", label: "Usage", icon: Clock },
22
+ { href: "/cron", label: "Cron Jobs", icon: Clock },
23
+ ],
24
+ },
25
+ {
26
+ section: "Agent",
27
+ items: [
28
+ { href: "/agents", label: "Agents", icon: Bot },
29
+ { href: "/skills", label: "Skills", icon: Sparkles },
30
+ { href: "/connections", label: "Connections", icon: Network },
31
+ ],
32
+ },
33
+ {
34
+ section: "Workspace",
35
+ items: [
36
+ { href: "/projects", label: "Projects", icon: FolderKanban },
37
+ { href: "/files", label: "Files", icon: Folder },
38
+ ],
39
+ },
40
+ {
41
+ section: "Settings",
42
+ items: [
43
+ { href: "/settings", label: "Config", icon: Settings },
44
+ ],
45
+ },
46
+ ];
47
+
48
+ const HealthStatus = () => {
49
+ const [isOnline, setIsOnline] = useState(true);
50
+
51
+ useEffect(() => {
52
+ const interval = setInterval(() => {
53
+ // In production, poll the Convex backend heartbeat
54
+ }, 30000);
55
+ return () => clearInterval(interval);
56
+ }, []);
57
+
58
+ return (
59
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
60
+ <span className="relative flex h-2 w-2">
61
+ <span
62
+ className={`animate-ping absolute inline-flex h-full w-full rounded-full ${
63
+ isOnline ? "bg-green-400" : "bg-red-400"
64
+ } opacity-75`}
65
+ ></span>
66
+ <span
67
+ className={`relative inline-flex rounded-full h-2 w-2 ${
68
+ isOnline ? "bg-green-500" : "bg-red-500"
69
+ }`}
70
+ ></span>
71
+ </span>
72
+ {isOnline ? "Online" : "Offline"}
73
+ </div>
74
+ );
75
+ };
76
+
77
+ const Breadcrumb = () => {
78
+ const { location } = useRouterState();
79
+ const pathnames = location.pathname.split("/").filter((x) => x);
80
+
81
+ if (pathnames.length === 0) {
82
+ return <span className="text-sm font-medium">Overview</span>;
83
+ }
84
+
85
+ return (
86
+ <nav className="flex" aria-label="Breadcrumb">
87
+ <ol className="inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse">
88
+ <li className="inline-flex items-center">
89
+ <Link
90
+ to="/"
91
+ className="inline-flex items-center text-sm font-medium text-muted-foreground hover:text-foreground"
92
+ >
93
+ <LayoutDashboard className="w-4 h-4 me-2.5" />
94
+ Home
95
+ </Link>
96
+ </li>
97
+ {pathnames.map((value, index) => {
98
+ const to = `/${pathnames.slice(0, index + 1).join("/")}`;
99
+ const isLast = index === pathnames.length - 1;
100
+ return (
101
+ <li key={to}>
102
+ <div className="flex items-center">
103
+ <ChevronRight className="w-4 h-4 text-muted-foreground" />
104
+ <Link
105
+ to={to}
106
+ className={`ms-1 text-sm font-medium ${
107
+ isLast
108
+ ? "text-foreground"
109
+ : "text-muted-foreground hover:text-foreground"
110
+ } md:ms-2`}
111
+ >
112
+ {value.charAt(0).toUpperCase() + value.slice(1)}
113
+ </Link>
114
+ </div>
115
+ </li>
116
+ );
117
+ })}
118
+ </ol>
119
+ </nav>
120
+ );
121
+ };
122
+
123
+ export function DashboardLayout({ children }: { children: React.ReactNode }) {
124
+ const [isSidebarOpen, setIsSidebarOpen] = useState(true);
125
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
126
+ const { location } = useRouterState();
127
+
128
+ const NavLink = ({ item }: { item: any }) => {
129
+ const isActive = location.pathname === item.href;
130
+ return (
131
+ <Link
132
+ to={item.href}
133
+ className={`flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors ${
134
+ isActive
135
+ ? "bg-primary text-primary-foreground"
136
+ : "text-muted-foreground hover:bg-muted hover:text-foreground"
137
+ }`}
138
+ onClick={() => setIsMobileMenuOpen(false)}
139
+ >
140
+ <item.icon className="w-5 h-5 mr-3" />
141
+ <span>{item.label}</span>
142
+ </Link>
143
+ );
144
+ };
145
+
146
+ const sidebarContent = (
147
+ <div className="flex flex-col h-full">
148
+ <div className="flex items-center justify-between h-16 px-4 border-b border-border">
149
+ <Link to="/" className="flex items-center gap-2 text-lg font-bold">
150
+ <Bot className="w-7 h-7 text-primary" />
151
+ <span>AgentForge</span>
152
+ </Link>
153
+ </div>
154
+ <nav className="flex-1 px-2 py-4 space-y-4 overflow-y-auto">
155
+ {navItems.map((section) => (
156
+ <div key={section.section}>
157
+ <h3 className="px-3 mb-2 text-xs font-semibold tracking-wider text-muted-foreground/80 uppercase">
158
+ {section.section}
159
+ </h3>
160
+ <div className="space-y-1">
161
+ {section.items.map((item) => (
162
+ <NavLink key={item.href} item={item} />
163
+ ))}
164
+ </div>
165
+ </div>
166
+ ))}
167
+ </nav>
168
+ <div className="p-4 border-t border-border">
169
+ <HealthStatus />
170
+ </div>
171
+ </div>
172
+ );
173
+
174
+ return (
175
+ <div className="flex h-screen bg-background text-foreground">
176
+ {/* Desktop Sidebar */}
177
+ <aside
178
+ className={`hidden md:block bg-card border-r border-border transition-all duration-300 ease-in-out ${
179
+ isSidebarOpen ? "w-64" : "w-0 overflow-hidden"
180
+ }`}
181
+ >
182
+ {sidebarContent}
183
+ </aside>
184
+
185
+ {/* Mobile Sidebar Overlay */}
186
+ {isMobileMenuOpen && (
187
+ <div
188
+ className="fixed inset-0 z-40 flex md:hidden"
189
+ role="dialog"
190
+ aria-modal="true"
191
+ >
192
+ <div
193
+ className="fixed inset-0 bg-black/60"
194
+ aria-hidden="true"
195
+ onClick={() => setIsMobileMenuOpen(false)}
196
+ ></div>
197
+ <div className="relative flex flex-col flex-1 w-full max-w-xs bg-card">
198
+ <div className="absolute top-0 right-0 pt-2 -mr-12">
199
+ <button
200
+ type="button"
201
+ className="flex items-center justify-center w-10 h-10 ml-1 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
202
+ onClick={() => setIsMobileMenuOpen(false)}
203
+ >
204
+ <span className="sr-only">Close sidebar</span>
205
+ <X className="w-6 h-6 text-white" />
206
+ </button>
207
+ </div>
208
+ {sidebarContent}
209
+ </div>
210
+ </div>
211
+ )}
212
+
213
+ <div className="flex flex-col flex-1 min-w-0">
214
+ <header className="flex items-center justify-between h-16 px-4 bg-card border-b border-border md:px-6">
215
+ <div className="flex items-center gap-4">
216
+ <button
217
+ onClick={() => setIsSidebarOpen(!isSidebarOpen)}
218
+ className="hidden p-2 rounded-md md:block hover:bg-muted"
219
+ >
220
+ {isSidebarOpen ? (
221
+ <ChevronLeft className="w-5 h-5" />
222
+ ) : (
223
+ <ChevronRight className="w-5 h-5" />
224
+ )}
225
+ </button>
226
+ <button
227
+ onClick={() => setIsMobileMenuOpen(true)}
228
+ className="p-2 rounded-md md:hidden hover:bg-muted"
229
+ >
230
+ <Menu className="w-5 h-5" />
231
+ </button>
232
+ <Breadcrumb />
233
+ </div>
234
+ <div className="flex items-center gap-4">
235
+ <HealthStatus />
236
+ <div className="p-2 rounded-full bg-muted">
237
+ <User className="w-5 h-5 text-muted-foreground" />
238
+ </div>
239
+ </div>
240
+ </header>
241
+ <main className="flex-1 p-4 overflow-y-auto md:p-6">{children}</main>
242
+ </div>
243
+ </div>
244
+ );
245
+ }