@kendoo.agentdesk/agentdesk 0.9.5 → 0.9.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/README.md CHANGED
@@ -79,7 +79,6 @@ agentdesk update Update to the latest version
79
79
  |------|-------------|
80
80
  | `--description`, `-d` | Task description or requirements |
81
81
  | `--cwd` | Working directory (defaults to current) |
82
- | `--lite` | Lite mode — all agents share one process (faster, cheaper) |
83
82
 
84
83
  ## Configuration
85
84
 
@@ -116,22 +115,14 @@ AgentDesk also auto-discovers agents from `.claude/agents/`, `.claude/commands/`
116
115
 
117
116
  ## How It Works
118
117
 
119
- Each agent runs in its own Claude process — not one model role-playing multiple personas. This produces genuine disagreements and better output because each agent reasons separately.
118
+ All agents collaborate in a single Claude process — each with distinct roles, ground rules, and areas of expertise.
120
119
 
121
120
  1. You run `agentdesk team TASK-ID` in your project directory
122
121
  2. AgentDesk detects your project type, reads `CLAUDE.md`, and discovers existing agents
123
122
  3. The orchestrator manages 5 phases: **Intake** > **Brainstorm** > **Planning** > **Execution** > **Review**
124
- 4. In brainstorm/planning/review, all agents run **in parallel** each gets their own Claude session
125
- 5. In execution, agents run sequentially: Dennis implements > Sam audits > Luna+Mark review > Vera tests > Bart QA
126
- 6. The session streams live to [agentdesk.live](https://agentdesk.live) where you can watch and send messages to the team
127
- 7. Token usage is tracked and displayed per session
128
-
129
- ### Agent Modes
130
-
131
- | Mode | Command | Description |
132
- |------|---------|-------------|
133
- | Full (default) | `agentdesk team -d "..."` | Each agent runs in its own process. Better output. |
134
- | Lite | `agentdesk team -d "..." --lite` | All agents share one process. Faster and cheaper. |
123
+ 4. Agents discuss, disagree, and build on each other's ideas within a shared context
124
+ 5. The session streams live to [agentdesk.live](https://agentdesk.live) where you can watch and send messages to the team
125
+ 6. Token usage is tracked and displayed per session
135
126
 
136
127
  ## Daemon (Remote Sessions)
137
128
 
package/bin/agentdesk.mjs CHANGED
@@ -70,7 +70,7 @@ if (!command || command === "help" || command === "--help") {
70
70
  Options:
71
71
  --description, -d Task description or requirements
72
72
  --cwd Working directory (defaults to current)
73
- --lite Lite mode — all agents share one process (faster, cheaper)
73
+
74
74
 
75
75
  How it works:
76
76
  With a tracker (Linear, Jira, GitHub Issues):
@@ -110,7 +110,6 @@ else if (command === "team") {
110
110
  let description = "";
111
111
  let cwd = process.cwd();
112
112
  let taskId = null;
113
- let lite = false;
114
113
  const remaining = args.slice(1);
115
114
 
116
115
  for (let i = 0; i < remaining.length; i++) {
@@ -118,10 +117,6 @@ else if (command === "team") {
118
117
  description = remaining[++i];
119
118
  } else if (remaining[i] === "--cwd" && remaining[i + 1]) {
120
119
  cwd = remaining[++i];
121
- } else if (remaining[i] === "--lite" || remaining[i] === "--legacy") {
122
- lite = true;
123
- } else if (remaining[i] === "--full") {
124
- lite = false;
125
120
  } else if (!taskId && !remaining[i].startsWith("-")) {
126
121
  taskId = remaining[i];
127
122
  }
@@ -134,7 +129,7 @@ else if (command === "team") {
134
129
  }
135
130
 
136
131
  const { runTeam } = await import("../cli/team.mjs");
137
- const code = await runTeam(taskId, { description, cwd, lite });
132
+ const code = await runTeam(taskId, { description, cwd });
138
133
  process.exit(code);
139
134
  }
140
135
 
package/cli/agents.mjs CHANGED
@@ -4,34 +4,32 @@ export const BUILT_IN_AGENTS = {
4
4
  Jane: {
5
5
  badge: "●● JANE ●●",
6
6
  role: "Product Analyst / Team Lead",
7
- description: "facilitates discussion, clarifies requirements, keeps the team focused, evaluates team performance at the end",
8
- groundRules: "Jane resolves disagreements and keeps the discussion productive. She NEVER reads code, runs tools, or inspects files that's the developer and auditor's job. She focuses on requirements, user impact, scope, and coordination.",
9
- brainstorm: { tag: "SAY", focus: "facilitation, question, or summary" },
10
- planning: "Requirements Summary (what we're building, acceptance criteria, scope boundaries). If this task involves UI changes, Jane declares it a UI task and tells Luna to define screenshot requirements.",
7
+ description: "leads the session, clarifies requirements, coordinates the team, manages tracker status",
8
+ groundRules: "Jane focuses on requirements, scope, and coordinationshe does not read code. She creates tracker tasks when needed, manages status transitions, and posts the session start/end comments.",
9
+ planning: "Requirements: what we're building, acceptance criteria, scope. Flags UI tasks for Luna.",
11
10
  execution: {
12
11
  step: "Jane wraps up",
13
12
  tasks: [
14
- "Review the PR description. Ensure it explains what changed and why.",
15
- "Summarize the session.",
13
+ "Transition task to 'In Review'.",
14
+ "Verify PR description is clear.",
15
+ "Post final summary comment on tracker.",
16
16
  ],
17
- order: 99, // last
17
+ order: 99,
18
18
  },
19
19
  },
20
20
  Dennis: {
21
21
  badge: "■■ DENNIS ■■",
22
22
  role: "Senior Developer",
23
- description: "assesses technical feasibility, proposes architecture, implements the solution",
24
- groundRules: "Dennis must verify at least 1 assumption about the codebase before agreeing to any approach.",
25
- codePrinciple: "Never write logic inline inside components or UI templates. Extract all conditionals, transformations, calculations, and API calls into dedicated functions, hooks, or services.",
26
- brainstorm: { tag: "THINK", focus: "technical feasibility, existing patterns" },
27
- planning: "Implementation Plan (files to modify, approach, complexity S/M/L)",
23
+ description: "implements the solution, verifies technical feasibility",
24
+ groundRules: "Dennis verifies assumptions about the codebase with tools before committing to an approach.",
25
+ codePrinciple: "Extract logic out of components/templates into functions, hooks, or services.",
26
+ planning: "Implementation plan: files to modify, approach, complexity (S/M/L)",
28
27
  execution: {
29
28
  step: "Dennis implements",
30
29
  tasks: [
31
- "Create a branch following project conventions.",
32
- "Implement the changes according to the agreed plan.",
30
+ "Create branch, implement changes per the plan.",
33
31
  "Run linter and build to verify.",
34
- "Commit the implementation.",
32
+ "Commit.",
35
33
  ],
36
34
  order: 1,
37
35
  },
@@ -39,17 +37,15 @@ export const BUILT_IN_AGENTS = {
39
37
  Sam: {
40
38
  badge: "◆◆ SAM ◆◆",
41
39
  role: "Architecture Auditor",
42
- description: "scans for separation-of-concerns violations, guards code architecture",
43
- groundRules: "Sam must verify that the proposed approach does not introduce architecture violations. He always backs claims with a file reference or line number.",
44
- codePrinciple: "Actively guards separation of concerns throughout the session. Always cites file and line number.",
45
- brainstorm: { tag: "THINK", focus: "codebase patterns, architecture risks" },
46
- planning: "Architecture Review (existing violations, whether approach is clean)",
40
+ description: "guards code architecture and separation of concerns",
41
+ groundRules: "Sam backs claims with file:line references.",
42
+ codePrinciple: "Guards separation of concerns. Cites file and line number.",
43
+ planning: "Architecture review: existing patterns, whether approach is clean",
47
44
  execution: {
48
- step: "Sam scans for violations",
45
+ step: "Sam audits",
49
46
  tasks: [
50
- "Read all files the developer changed.",
51
- "Check for architecture violations.",
52
- "If violations found, the developer fixes them before proceeding.",
47
+ "Read changed files, check for architecture violations.",
48
+ "If violations found, developer fixes before proceeding.",
53
49
  ],
54
50
  order: 2,
55
51
  },
@@ -57,21 +53,18 @@ export const BUILT_IN_AGENTS = {
57
53
  Bart: {
58
54
  badge: "▲▲ BART ▲▲",
59
55
  role: "QA Engineer",
60
- description: "identifies edge cases, test plans, acceptance criteria, quality risks",
61
- groundRules: "Bart must identify at least 2 risks or edge cases before agreeing to any plan.",
62
- codePrinciple: "Flag any logic written directly inside a component or UI template.",
63
- brainstorm: { tag: "SAY", focus: "risks, edge cases, \"what happens when...\"" },
64
- planning: "Test Plan (acceptance criteria, key test cases, edge cases)",
56
+ description: "reviews quality, edge cases, creates PR, captures screenshots",
57
+ groundRules: "Bart identifies risks and edge cases before approving.",
58
+ codePrinciple: "Flag inline logic in components/templates.",
59
+ planning: "Test plan: acceptance criteria, key edge cases",
65
60
  execution: {
66
- step: "Bart reviews",
61
+ step: "Bart reviews & creates PR",
67
62
  tasks: [
68
- "Read EVERY file the developer changed.",
69
- "Check calculations, edge cases, error handling.",
63
+ "Read all changed files. Check edge cases, error handling.",
70
64
  "Run linter and build.",
71
- "If this is a UI task, capture screenshots following Luna's screenshot plan (see SCREENSHOTS section in prompt).",
72
- "If ALL criteria pass, push and create PR: `gh pr create --title \"...\" --body \"...\"`",
73
- "Post screenshots to the task tracker as a separate comment (not inside badge blocks).",
74
- "Approve the PR if clean.",
65
+ "Capture screenshots if UI task (per Luna's plan).",
66
+ "Push and create PR. Post PR link on tracker.",
67
+ "Post screenshots as separate tracker comment.",
75
68
  ],
76
69
  order: 4,
77
70
  },
@@ -79,17 +72,15 @@ export const BUILT_IN_AGENTS = {
79
72
  Vera: {
80
73
  badge: "◈◈ VERA ◈◈",
81
74
  role: "Test Engineer",
82
- description: "writes unit and regression tests for changed code, ensures test coverage",
83
- groundRules: "Vera must identify which functions need unit test coverage before agreeing to any plan.",
84
- codePrinciple: "Never mount or render UI to test a logic outcome. Test files must mirror the service/utility structure.",
85
- brainstorm: { tag: "SAY", focus: "test coverage gaps, which functions need tests" },
86
- planning: "Test Plan (which functions need tests, regression tests)",
75
+ description: "writes unit and regression tests for changed code",
76
+ groundRules: "Vera identifies which functions need test coverage.",
77
+ codePrinciple: "Test logic directly don't mount UI to test outcomes. Mirror service/utility structure.",
78
+ planning: "Test coverage: which functions need tests, regression cases",
87
79
  execution: {
88
80
  step: "Vera writes tests",
89
81
  tasks: [
90
- "Read every file the developer changed. Identify testable functions.",
91
- "Write unit tests following existing test patterns.",
92
- "Run tests to verify they pass.",
82
+ "Identify testable functions in changed files.",
83
+ "Write unit tests following existing patterns. Run and verify.",
93
84
  "Commit test files.",
94
85
  ],
95
86
  order: 3,
@@ -98,21 +89,16 @@ export const BUILT_IN_AGENTS = {
98
89
  Luna: {
99
90
  badge: "☾☾ LUNA ☾☾",
100
91
  role: "UX/UI Designer",
101
- description: "designs pixel-perfect interfaces, champions user experience, applies psychology of user behavior, ensures visual consistency, accessibility, and intuitive interaction patterns",
102
- groundRules: "Luna must review any UI changes for visual consistency, spacing, color harmony, accessibility (contrast, focus states), and intuitive interaction flow. She references specific components, screenshots, or design patterns.",
103
- codePrinciple: "Reviews all UI changes for visual hierarchy, whitespace balance, color consistency, typography, responsive behavior, and accessibility (WCAG). Pushes back on cluttered layouts, inconsistent spacing, poor contrast, or confusing interaction flows. Proposes specific CSS/styling improvements with exact values.",
104
- brainstorm: { tag: "THINK", focus: "UX/UI impact, visual consistency, interaction patterns, accessibility" },
105
- planning: "UX Review (visual impact, layout concerns, accessibility checklist, interaction improvements). For UI tasks: define which pages/views need screenshots and whether desktop, mobile, or both are relevant.",
92
+ description: "reviews UI changes for visual consistency, accessibility, and UX",
93
+ groundRules: "Luna references specific components and proposes exact CSS fixes.",
94
+ codePrinciple: "Reviews visual hierarchy, spacing, color, typography, responsive behavior, accessibility (WCAG).",
95
+ planning: "UX review: visual impact, accessibility. For UI tasks: define screenshot plan (pages, viewports).",
106
96
  execution: {
107
- step: "Luna reviews UI changes (if applicable)",
97
+ step: "Luna reviews UI (if applicable)",
108
98
  tasks: [
109
- "Read any changed component, page, or style files.",
110
- "Check visual hierarchy, spacing consistency, color harmony, typography.",
111
- "Verify accessibility: contrast ratios, focus states, keyboard navigation, screen reader labels.",
112
- "Check responsive behavior and interaction flow.",
113
- "If issues found, propose specific fixes (exact CSS values, spacing, colors). The developer implements them before proceeding.",
114
- "Define the screenshot plan: which pages/routes to capture and which viewports (desktop, mobile, or both). Hand this plan to Bart.",
115
- "Skip this step if the task has no UI impact.",
99
+ "Read changed UI files. Check visual consistency and accessibility.",
100
+ "Propose specific fixes if needed. Developer implements.",
101
+ "Hand screenshot plan to Bart. Skip if no UI impact.",
116
102
  ],
117
103
  order: 2.1,
118
104
  },
@@ -120,19 +106,16 @@ export const BUILT_IN_AGENTS = {
120
106
  Mark: {
121
107
  badge: "✦✦ MARK ✦✦",
122
108
  role: "Content Writer",
123
- description: "crafts precise, engaging copy for UI text, error messages, tooltips, onboarding flows, and documentation. Simplifies technical language into friendly, clear wording. Ensures consistent tone and voice across the product",
124
- groundRules: "Mark must review all user-facing text — labels, buttons, headings, error messages, tooltips, empty states, confirmation dialogs. He ensures the tone is friendly and consistent, wording is concise, and technical jargon is avoided unless the audience is technical.",
125
- codePrinciple: "Reviews all user-facing strings in changed files. Rewrites vague, wordy, or technical copy into clear, concise, human-friendly language. Ensures consistent voice — no mixing formal and casual tone. Checks empty states, error messages, and confirmation dialogs for helpfulness.",
126
- brainstorm: { tag: "THINK", focus: "copy clarity, tone, user-facing text quality" },
127
- planning: "Content Review (user-facing text audit, tone, clarity, empty states, error messages)",
109
+ description: "reviews user-facing text for clarity, tone, and consistency",
110
+ groundRules: "Mark reviews all labels, buttons, error messages, tooltips, and empty states.",
111
+ codePrinciple: "Rewrites vague or jargon-heavy copy into clear, concise language. Ensures consistent tone.",
112
+ planning: "Content review: user-facing text audit, tone consistency",
128
113
  execution: {
129
114
  step: "Mark reviews content (if applicable)",
130
115
  tasks: [
131
- "Read all changed files that contain user-facing text (components, templates, error handlers).",
132
- "Check every label, button, heading, tooltip, error message, empty state, and confirmation dialog.",
133
- "Rewrite anything vague, wordy, jargon-heavy, or inconsistent in tone.",
134
- "Propose exact replacement strings. The developer implements them.",
135
- "Skip this step if the task has no user-facing text changes.",
116
+ "Read changed files with user-facing text.",
117
+ "Propose exact replacement strings if needed. Developer implements.",
118
+ "Skip if no user-facing text changes.",
136
119
  ],
137
120
  order: 2.2,
138
121
  },
@@ -172,7 +155,6 @@ export function resolveTeam(config) {
172
155
  role: custom.role || "Team Member",
173
156
  description: custom.role || "Custom team member",
174
157
  groundRules: custom.when ? `${custom.name} is invoked: ${custom.when}. How to use: ${custom.how || "as needed"}.` : "",
175
- brainstorm: { tag: "SAY", focus: custom.role || "general input" },
176
158
  planning: `${custom.role || custom.name} Review`,
177
159
  execution: custom.when ? {
178
160
  step: `${custom.name} reviews`,
@@ -201,7 +183,6 @@ export function resolveTeam(config) {
201
183
  role: entry.role || "Team Member",
202
184
  description: entry.description || entry.role || "Custom team member",
203
185
  groundRules: entry.groundRules || "",
204
- brainstorm: entry.brainstorm || { tag: "SAY", focus: entry.role || "general input" },
205
186
  planning: entry.planning || `${entry.role || entry.name} Review`,
206
187
  execution: entry.execution || null,
207
188
  });
@@ -242,9 +223,9 @@ export function generateTeamPrompt(team) {
242
223
 
243
224
  // Ground rules
244
225
  const rules = [];
245
- rules.push(`1. Each agent speaks in turn, prefixed with their badge (e.g., "${team[0].badge} Starting.").`);
246
- rules.push(`2. ALL text output MUST be prefixed with the acting agent's badge. Never output unprefixed text.`);
247
- rules.push(`3. Agents should DISAGREE when they see problems — don't rubber-stamp each other.`);
226
+ rules.push(`1. Each agent speaks prefixed with their badge (e.g., "${team[0].badge} ...").`);
227
+ rules.push(`2. ALL text output MUST be prefixed with the acting agent's badge.`);
228
+ rules.push(`3. Raise concerns when you see problems — don't rubber-stamp.`);
248
229
  let ruleNum = 4;
249
230
  for (const a of team) {
250
231
  if (a.groundRules) {
@@ -252,7 +233,6 @@ export function generateTeamPrompt(team) {
252
233
  ruleNum++;
253
234
  }
254
235
  }
255
- rules.push(`${ruleNum}. Every statement should add value — no filler, no repeating what someone else already said.`);
256
236
  sections.groundRules = rules.join("\n");
257
237
 
258
238
  // Code principles
@@ -261,11 +241,6 @@ export function generateTeamPrompt(team) {
261
241
  .map(a => `- ${a.name}: ${a.codePrinciple}`);
262
242
  sections.codePrinciples = principles.length > 0 ? principles.join("\n") : "";
263
243
 
264
- // Brainstorm order
265
- sections.brainstormOrder = team.map((a, i) =>
266
- `${i + 1}. ${a.badge} [${a.brainstorm.tag}] ${a.brainstorm.focus}`
267
- ).join("\n");
268
-
269
244
  // Planning presentations
270
245
  sections.planningOrder = team.map(a =>
271
246
  `${a.badge} ${a.planning}`
package/cli/daemon.mjs CHANGED
@@ -313,7 +313,7 @@ export async function runDaemon() {
313
313
 
314
314
  // 4. Session handling
315
315
 
316
- async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt, mode }) {
316
+ async function handleStartSession({ sessionId, projectId, taskId: remoteTaskId, prompt }) {
317
317
  // Validate project against local allowlist
318
318
  const project = projects.find(p => p.id === projectId);
319
319
  if (!project) {
@@ -379,7 +379,6 @@ export async function runDaemon() {
379
379
  project: detected, team, teamSections,
380
380
  inboxUrl, sessionUrl,
381
381
  cwd: project.path,
382
- mode: mode || "full",
383
382
  apiKey,
384
383
  serverUrl: agentdeskServer,
385
384
  onEvent(event) {
package/cli/init.mjs CHANGED
@@ -7,6 +7,7 @@ import { detectProject } from "./detect.mjs";
7
7
  import { loadConfig } from "./config.mjs";
8
8
  import { getStoredApiKey } from "./login.mjs";
9
9
  import { registerLocalProject } from "./projects.mjs";
10
+ import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
10
11
 
11
12
  const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
12
13
 
@@ -62,6 +63,12 @@ export async function runInit(cwd) {
62
63
  if (project.lintCommand) console.log(` Lint: ${project.lintCommand}`);
63
64
  if (project.testCommand || project.buildCommand || project.lintCommand) console.log("");
64
65
 
66
+ // --- Project key ---
67
+ const defaultKey = existingConfig.projectKey || projectId;
68
+ const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
69
+ const finalProjectKey = keyAnswer.trim() || defaultKey;
70
+ console.log("");
71
+
65
72
  // --- Tracker selection ---
66
73
  const trackerOptions = [
67
74
  { label: "Linear", value: "linear" },
@@ -75,22 +82,28 @@ export async function runInit(cwd) {
75
82
  console.log("");
76
83
 
77
84
  // Build config
78
- const config = {};
85
+ const config = { projectKey: finalProjectKey };
79
86
  if (tracker) config.tracker = tracker;
80
87
 
81
88
  if (tracker === "linear") {
82
- const current = existingConfig.linear?.workspace || "";
83
- const answer = await ask(rl, ` Linear workspace slug${current ? ` (${current})` : ""}: `);
84
- const ws = answer.trim() || current;
85
- if (ws) config.linear = { workspace: ws };
89
+ const currentWs = existingConfig.linear?.workspace || "";
90
+ const wsAnswer = await ask(rl, ` Linear workspace slug${currentWs ? ` (${currentWs})` : ""}: `);
91
+ const ws = wsAnswer.trim() || currentWs;
92
+ const currentKey = existingConfig.linear?.teamKey || "";
93
+ const keyAnswer = await ask(rl, ` Linear team key${currentKey ? ` (${currentKey})` : ""} (e.g. KEN): `);
94
+ const teamKey = keyAnswer.trim() || currentKey;
95
+ if (ws || teamKey) config.linear = { ...(ws && { workspace: ws }), ...(teamKey && { teamKey }) };
86
96
  console.log("");
87
97
  }
88
98
 
89
99
  if (tracker === "jira") {
90
- const current = existingConfig.jira?.baseUrl || "";
91
- const answer = await ask(rl, ` Jira base URL${current ? ` (${current})` : ""}: `);
92
- const url = answer.trim() || current;
93
- if (url) config.jira = { baseUrl: url };
100
+ const currentUrl = existingConfig.jira?.baseUrl || "";
101
+ const urlAnswer = await ask(rl, ` Jira base URL${currentUrl ? ` (${currentUrl})` : ""}: `);
102
+ const url = urlAnswer.trim() || currentUrl;
103
+ const currentProj = existingConfig.jira?.project || "";
104
+ const projAnswer = await ask(rl, ` Jira project key${currentProj ? ` (${currentProj})` : ""} (e.g. PROJ): `);
105
+ const proj = projAnswer.trim() || currentProj;
106
+ if (url || proj) config.jira = { ...(url && { baseUrl: url }), ...(proj && { project: proj }) };
94
107
  console.log("");
95
108
  }
96
109
 
@@ -102,12 +115,64 @@ export async function runInit(cwd) {
102
115
  console.log("");
103
116
  }
104
117
 
118
+ // --- Verify tracker permissions ---
119
+ if (tracker) {
120
+ console.log(" Checking tracker permissions...");
121
+ const envPath = join(cwd, ".env");
122
+ let dotEnv = {};
123
+ if (existsSync(envPath)) {
124
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
125
+ const trimmed = line.trim();
126
+ if (!trimmed || trimmed.startsWith("#")) continue;
127
+ const eq = trimmed.indexOf("=");
128
+ if (eq !== -1) dotEnv[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
129
+ }
130
+ }
131
+
132
+ // Also try fetching credentials from server
133
+ const apiKey = loadApiKey(cwd);
134
+ let serverCreds = {};
135
+ if (apiKey) {
136
+ try {
137
+ const res = await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings/credentials`, {
138
+ headers: { "x-api-key": apiKey },
139
+ signal: AbortSignal.timeout(5000),
140
+ });
141
+ if (res.ok) serverCreds = await res.json();
142
+ } catch {}
143
+ }
144
+
145
+ const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
146
+ const check = await checkTrackerPermissions({ tracker, config, credentials });
147
+
148
+ if (!check.ok) {
149
+ console.log("");
150
+ console.log(" ⚠ Tracker permission issues:");
151
+ for (const err of check.errors) {
152
+ console.log(` • ${err}`);
153
+ }
154
+ console.log("");
155
+ const proceed = await ask(rl, " Continue anyway? (y/N): ");
156
+ if (proceed.trim().toLowerCase() !== "y") {
157
+ console.log(" Setup cancelled. Fix the issues above and run 'agentdesk init' again.");
158
+ rl.close();
159
+ return;
160
+ }
161
+ console.log("");
162
+ } else {
163
+ console.log(" ✓ Tracker permissions verified (read, create, update)");
164
+ console.log("");
165
+ }
166
+ }
167
+
105
168
  // --- Save .agentdesk.json ---
106
169
  let merged = {};
107
170
  if (hasConfig) {
108
171
  try { merged = JSON.parse(readFileSync(configPath, "utf-8")); } catch {}
109
172
  }
110
173
 
174
+ merged.projectKey = finalProjectKey;
175
+
111
176
  if (tracker) {
112
177
  merged.tracker = tracker;
113
178
  if (config.linear) merged.linear = config.linear;
@@ -124,7 +189,7 @@ export async function runInit(cwd) {
124
189
  console.log(` Saved .agentdesk.json`);
125
190
 
126
191
  // Register in local project index (for daemon discovery)
127
- registerLocalProject(projectId, project.name || projectId, project.dir);
192
+ registerLocalProject(finalProjectKey, project.name || finalProjectKey, project.dir);
128
193
 
129
194
  // --- Register with server ---
130
195
  try {
@@ -135,8 +200,8 @@ export async function runInit(cwd) {
135
200
  ...(loadApiKey(cwd) ? { "x-api-key": loadApiKey(cwd) } : {}),
136
201
  },
137
202
  body: JSON.stringify({
138
- id: projectId,
139
- name: project.name || projectId,
203
+ id: finalProjectKey,
204
+ name: project.name || finalProjectKey,
140
205
  path: project.dir,
141
206
  type: project.type,
142
207
  tracker,
@@ -148,7 +213,7 @@ export async function runInit(cwd) {
148
213
  // Push settings to server
149
214
  const key = loadApiKey(cwd);
150
215
  if (key) {
151
- await fetch(`${SERVER}/api/projects/${projectId}/settings`, {
216
+ await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings`, {
152
217
  method: "PUT",
153
218
  headers: { "Content-Type": "application/json", "x-api-key": key },
154
219
  body: JSON.stringify(merged),