@kendoo.agentdesk/agentdesk 0.9.20 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -69,6 +69,7 @@ agentdesk logout Sign out and remove credentials
69
69
  agentdesk init Set up project and configure tracker
70
70
  agentdesk team <TASK-ID> Run a team session on an existing task
71
71
  agentdesk team -d "..." Describe what you want — task created automatically
72
+ agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark)
72
73
  agentdesk daemon Start daemon for remote sessions
73
74
  agentdesk update Update to the latest version
74
75
  ```
@@ -113,6 +114,18 @@ You can also define them in `.agentdesk.json` as a local override if preferred.
113
114
 
114
115
  AgentDesk also auto-discovers agents from `.claude/agents/`, `.claude/commands/`, `.mcp.json`, GitHub Actions workflows, Dependabot, and Renovate configs.
115
116
 
117
+ ### Solo agent mode
118
+
119
+ Run a single agent instead of the full team:
120
+
121
+ ```bash
122
+ agentdesk jane -d "Create a customer journey plan for onboarding"
123
+ agentdesk dennis KEN-517
124
+ agentdesk vera -d "Add test coverage for the auth module"
125
+ ```
126
+
127
+ The agent runs independently with its own expertise and ground rules. Useful for focused tasks that don't need full team collaboration.
128
+
116
129
  ### Custom instructions
117
130
 
118
131
  Add project-specific rules that all agents follow. Set via Web UI or in `.agentdesk.json`:
package/bin/agentdesk.mjs CHANGED
@@ -64,6 +64,7 @@ if (!command || command === "help" || command === "--help") {
64
64
  agentdesk init Set up project and configure tracker
65
65
  agentdesk team <TASK-ID> Run a team session on an existing task
66
66
  agentdesk team -d "..." Create a task and run a session
67
+ agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, etc.)
67
68
  agentdesk daemon Start daemon for remote sessions
68
69
  agentdesk update Update to the latest version
69
70
 
@@ -94,7 +95,36 @@ if (!command || command === "help" || command === "--help") {
94
95
  process.exit(0);
95
96
  }
96
97
 
97
- if (command === "login") {
98
+ // Single-agent mode — agentdesk jane -d "...", agentdesk dennis TASK-123, etc.
99
+ const AGENT_NAMES = ["jane", "dennis", "sam", "bart", "vera", "luna", "mark"];
100
+ if (AGENT_NAMES.includes(command?.toLowerCase())) {
101
+ let description = "";
102
+ let cwd = process.cwd();
103
+ let taskId = null;
104
+ const remaining = args.slice(1);
105
+
106
+ for (let i = 0; i < remaining.length; i++) {
107
+ if ((remaining[i] === "--description" || remaining[i] === "-d") && remaining[i + 1]) {
108
+ description = remaining[++i];
109
+ } else if (remaining[i] === "--cwd" && remaining[i + 1]) {
110
+ cwd = remaining[++i];
111
+ } else if (!taskId && !remaining[i].startsWith("-")) {
112
+ taskId = remaining[i];
113
+ }
114
+ }
115
+
116
+ if (!taskId && !description) {
117
+ console.error(`Usage: agentdesk ${command} <TASK-ID>`);
118
+ console.error(` or: agentdesk ${command} -d "description"`);
119
+ process.exit(1);
120
+ }
121
+
122
+ const { runTeam } = await import("../cli/team.mjs");
123
+ const code = await runTeam(taskId, { description, cwd, soloAgent: command.charAt(0).toUpperCase() + command.slice(1).toLowerCase() });
124
+ process.exit(code);
125
+ }
126
+
127
+ else if (command === "login") {
98
128
  const { runLogin } = await import("../cli/login.mjs");
99
129
  await runLogin();
100
130
  }
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
5
5
  import { createInterface } from "readline";
6
6
  import { join, dirname } from "path";
7
7
  import { fileURLToPath } from "url";
8
- import { buildPrompt } from "./prompt.mjs";
8
+ import { buildPrompt, buildSoloPrompt } from "./prompt.mjs";
9
9
  import { createStreamParser } from "./stream-parser.mjs";
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -47,7 +47,7 @@ function timestamp() {
47
47
  export async function runOrchestrator({
48
48
  taskId, taskLink, description, createTask, tracker, config,
49
49
  project, team, teamSections, inboxUrl, sessionUrl, cwd,
50
- onEvent, apiKey, serverUrl,
50
+ onEvent, apiKey, serverUrl, soloAgent,
51
51
  }) {
52
52
  const trackerCreds = await fetchTrackerCredentials(project?.name, apiKey, serverUrl);
53
53
  const env = { ...process.env, ...loadDotEnv(cwd), ...trackerCreds };
@@ -63,10 +63,9 @@ export async function runOrchestrator({
63
63
  onEvent?.({ ...event, timestamp: timestamp() });
64
64
  }
65
65
 
66
- const fullPrompt = buildPrompt({
67
- taskId, taskLink, description, createTask, tracker, config,
68
- project, teamSections, inboxUrl, sessionUrl,
69
- });
66
+ const fullPrompt = soloAgent
67
+ ? buildSoloPrompt({ agentName: soloAgent, taskId, description, tracker, config, project, sessionUrl })
68
+ : buildPrompt({ taskId, taskLink, description, createTask, tracker, config, project, teamSections, inboxUrl, sessionUrl });
70
69
 
71
70
  emit({
72
71
  type: "session:start",
@@ -74,11 +73,11 @@ export async function runOrchestrator({
74
73
  title: description || taskId,
75
74
  project: project?.name || null,
76
75
  sessionNumber: 1,
77
- agents: teamSections.names,
76
+ agents: soloAgent ? [soloAgent] : teamSections.names,
78
77
  cliVersion: CLI_VERSION,
79
78
  });
80
79
 
81
- emit({ type: "phase:change", phase: "INTAKE" });
80
+ emit({ type: "phase:change", phase: soloAgent ? "EXECUTION" : "INTAKE" });
82
81
 
83
82
  const child = spawn(
84
83
  "claude",
package/cli/prompt.mjs CHANGED
@@ -4,6 +4,7 @@ import { readFileSync } from "fs";
4
4
  import { resolve, dirname } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { generateContext } from "./detect.mjs";
7
+ import { BUILT_IN_AGENTS } from "./agents.mjs";
7
8
 
8
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
10
  const PROMPT_PATH = resolve(__dirname, "../prompts/team.md");
@@ -100,3 +101,84 @@ export function buildPrompt({ taskId, taskLink, description, createTask, tracker
100
101
 
101
102
  return `${prompt}\n\n---\n\n## PROJECT CONTEXT\n\n${context}\n\n${timeInfo}`;
102
103
  }
104
+
105
+ export function buildSoloPrompt({ agentName, taskId, description, tracker, config, project, sessionUrl }) {
106
+ const agent = BUILT_IN_AGENTS[agentName];
107
+ if (!agent) throw new Error(`Unknown agent: ${agentName}`);
108
+
109
+ const lines = [
110
+ `# ${agentName} — ${agent.role} (Solo Mode)`,
111
+ ``,
112
+ `You are ${agentName}, ${agent.description}.`,
113
+ `You are working independently on this task — there is no team. You handle everything yourself.`,
114
+ ``,
115
+ agent.groundRules ? `## Ground Rules\n\n${agent.groundRules}` : "",
116
+ agent.codePrinciple ? `## Code Principles\n\n${agent.codePrinciple}` : "",
117
+ ``,
118
+ `## Task`,
119
+ ``,
120
+ taskId ? `Task ID: ${taskId}` : "",
121
+ description ? `Description: ${description}` : "",
122
+ sessionUrl ? `Session: ${sessionUrl}` : "",
123
+ ``,
124
+ `## Instructions`,
125
+ ``,
126
+ `Work on this task independently. Follow CLAUDE.md conventions if present.`,
127
+ `Read and understand the codebase before making changes.`,
128
+ ``,
129
+ ];
130
+
131
+ if (agent.execution?.tasks) {
132
+ lines.push(`## Your responsibilities`, ``);
133
+ for (const t of agent.execution.tasks) {
134
+ lines.push(`- ${t}`);
135
+ }
136
+ lines.push(``);
137
+ }
138
+
139
+ // Extract and include tracker integration from team prompt
140
+ if (tracker && taskId) {
141
+ let teamPrompt = readFileSync(PROMPT_PATH, "utf-8");
142
+
143
+ // Extract the matching tracker block
144
+ const trackerKey = tracker.toUpperCase();
145
+ const trackerRegex = new RegExp(`\\{\\{#${trackerKey}\\}\\}([\\s\\S]*?)\\{\\{\\/${trackerKey}\\}\\}`, "g");
146
+ let trackerSection = "";
147
+ let match;
148
+ while ((match = trackerRegex.exec(teamPrompt)) !== null) {
149
+ trackerSection += match[1] + "\n";
150
+ }
151
+
152
+ if (trackerSection) {
153
+ // Substitute variables
154
+ trackerSection = trackerSection.replace(/\{\{TASK_ID\}\}/g, taskId);
155
+ trackerSection = trackerSection.replace(/\{\{SESSION_URL\}\}/g, sessionUrl || "");
156
+ if (config.jira?.baseUrl) {
157
+ trackerSection = trackerSection.replace(/\{\{JIRA_BASE_URL\}\}/g, config.jira.baseUrl.replace(/\/+$/, ""));
158
+ }
159
+ lines.push(trackerSection);
160
+ }
161
+
162
+ lines.push(
163
+ `## Required tracker actions`,
164
+ ``,
165
+ `1. Fetch the task — read summary, description, status, comments, attachments.`,
166
+ `2. Post a comment: "${agentName} working on this task (solo mode). Session: ${sessionUrl || ""}"`,
167
+ `3. Do your work.`,
168
+ `4. Post a final comment summarizing what was done, what was omitted, and any manual steps required.`,
169
+ ``,
170
+ );
171
+ }
172
+
173
+ // Add custom instructions
174
+ if (config?.instructions) {
175
+ lines.push(`## Additional Instructions`, ``, config.instructions, ``);
176
+ }
177
+
178
+ // Add project context
179
+ const context = generateContext(project);
180
+ const now = new Date();
181
+ const timeInfo = `Current date/time: ${now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric" })} ${now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`;
182
+
183
+ return lines.filter(Boolean).join("\n") + `\n\n---\n\n## PROJECT CONTEXT\n\n${context}\n\n${timeInfo}`;
184
+ }
package/cli/team.mjs CHANGED
@@ -87,6 +87,10 @@ export async function runTeam(taskId, opts = {}) {
87
87
  console.log(`Jira: ${cyan}${config.jira.baseUrl}${reset}`);
88
88
  }
89
89
 
90
+ if (opts.soloAgent) {
91
+ console.log(`Agent: ${opts.soloAgent} (solo mode)`);
92
+ }
93
+
90
94
  // Resolve team and generate dynamic prompt sections
91
95
  const team = resolveTeam(config);
92
96
  const teamSections = generateTeamPrompt(team, { tracker, config });
@@ -206,6 +210,7 @@ export async function runTeam(taskId, opts = {}) {
206
210
  onEvent: vizSend,
207
211
  apiKey,
208
212
  serverUrl: agentdeskServer,
213
+ soloAgent: opts.soloAgent || null,
209
214
  });
210
215
 
211
216
  clearInterval(heartbeatInterval);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.9.20",
3
+ "version": "0.10.0",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,10 +24,26 @@
24
24
  "test": "node --test tests/server.test.mjs tests/agents.test.mjs tests/homepage.test.mjs"
25
25
  },
26
26
  "dependencies": {
27
+ "@radix-ui/react-avatar": "^1.1.11",
28
+ "@radix-ui/react-dialog": "^1.1.15",
29
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
30
+ "@radix-ui/react-label": "^2.1.8",
31
+ "@radix-ui/react-scroll-area": "^1.2.10",
32
+ "@radix-ui/react-select": "^2.2.6",
33
+ "@radix-ui/react-separator": "^1.1.8",
34
+ "@radix-ui/react-slot": "^1.2.4",
35
+ "@radix-ui/react-switch": "^1.2.6",
36
+ "@radix-ui/react-tabs": "^1.1.13",
37
+ "@radix-ui/react-tooltip": "^1.2.8",
27
38
  "bcryptjs": "^3.0.3",
39
+ "class-variance-authority": "^0.7.1",
40
+ "clsx": "^2.1.1",
28
41
  "express": "^5.1.0",
29
42
  "jsonwebtoken": "^9.0.3",
43
+ "next-themes": "^0.4.6",
44
+ "sonner": "^2.0.7",
30
45
  "sql.js": "^1.14.1",
46
+ "tailwind-merge": "^3.5.0",
31
47
  "ws": "^8.18.0"
32
48
  },
33
49
  "devDependencies": {
@@ -39,6 +55,7 @@
39
55
  "react": "^19.1.0",
40
56
  "react-dom": "^19.1.0",
41
57
  "tailwindcss": "^3.4.17",
58
+ "typescript": "^6.0.2",
42
59
  "vite": "^6.3.5"
43
60
  },
44
61
  "keywords": [