@kendoo.agentdesk/agentdesk 0.15.6 → 0.16.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.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,20 @@ All user-facing changes to AgentDesk. Each entry is tagged:
8
8
 
9
9
  Internal refactors, infrastructure changes, and architectural notes are not listed here.
10
10
 
11
+ ## [0.16.1] — 2026-04-18
12
+
13
+ ### Added
14
+ - `[UI]` Project settings tracker section now includes a collapsible "dedicated AgentDesk user" recommendation with step-by-step setup per tracker — the same guidance the CLI wizard shows.
15
+ - `[UI]` The Verify-credentials result now flags personal-looking accounts with a note suggesting a dedicated tracker user for cleaner attribution, matching the CLI's post-verify nudge.
16
+
17
+ ## [0.16.0] — 2026-04-18
18
+
19
+ ### Added
20
+ - `[CLI]` `agentdesk init` now walks you through tracker setup as a guided wizard — numbered steps, a side-by-side "dedicated AgentDesk user vs. your own account" recommendation with per-tracker instructions (Linear, Jira, GitHub), and a final review screen before anything is written.
21
+ - `[CLI]` After credentials verify, the wizard prints the identity your team will post under ("✓ Posting as: …"). If it looks like a personal account, a gentle nudge suggests rotating to a dedicated user for cleaner ticket attribution.
22
+ - `[CLI]` Re-running `agentdesk init` in a project that already has config offers a "tracker only" shortcut instead of walking the whole wizard.
23
+ - `[CLI]` `agentdesk init --quick` skips the narrative copy for power users and scripted setups.
24
+
11
25
  ## [0.15.6] — 2026-04-18
12
26
 
13
27
  ### Changed
package/bin/agentdesk.mjs CHANGED
@@ -61,7 +61,7 @@ if (!command || command === "help" || command === "--help") {
61
61
  Commands:
62
62
  agentdesk login Sign in to AgentDesk
63
63
  agentdesk logout Sign out and remove credentials
64
- agentdesk init Set up project and configure tracker
64
+ agentdesk init [--quick] 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
67
  agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark, nora)
@@ -142,7 +142,8 @@ else if (command === "logout") {
142
142
 
143
143
  else if (command === "init") {
144
144
  const { runInit } = await import("../cli/init.mjs");
145
- await runInit(process.cwd());
145
+ const quick = args.slice(1).includes("--quick");
146
+ await runInit(process.cwd(), { quick });
146
147
  }
147
148
 
148
149
  else if (command === "team") {
package/cli/init.mjs CHANGED
@@ -1,16 +1,94 @@
1
- // `agentdesk init` — interactive project setup with tracker configuration
1
+ // `agentdesk init` — interactive project setup with tracker configuration.
2
+ //
3
+ // Run modes:
4
+ // agentdesk init → full wizard with inline guidance
5
+ // agentdesk init --quick → skip narrative copy for power users / re-runs
6
+ // (automatic) re-run → when .agentdesk.json already exists, offer
7
+ // to edit only a specific section
2
8
 
3
9
  import { existsSync, readFileSync, writeFileSync } from "fs";
4
10
  import { join } from "path";
5
11
  import { createInterface } from "readline";
6
12
  import { detectProject } from "./detect.mjs";
7
- import { loadConfig } from "./config.mjs";
13
+ import { loadConfig, pushConfig } from "./config.mjs";
8
14
  import { getStoredApiKey } from "./login.mjs";
9
15
  import { registerLocalProject } from "./projects.mjs";
10
16
  import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
11
17
 
12
18
  const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
13
19
 
20
+ // Pretty dividers and tracker-specific "dedicated user" guidance.
21
+ const TRACKER_GUIDANCE = {
22
+ linear: {
23
+ name: "Linear",
24
+ dedicatedSteps: [
25
+ "1. Open https://linear.app/<your-workspace>/settings/members",
26
+ "2. Invite a new member named \"AgentDesk\" to an email you control",
27
+ "3. Accept the invite from that email, log in as the new user",
28
+ "4. Go to https://linear.app/settings/api → Create key → label it \"AgentDesk\"",
29
+ "5. Paste the key when prompted below",
30
+ ],
31
+ personalNote: "Your own Linear account. Comments on tickets will appear under your name.",
32
+ },
33
+ jira: {
34
+ name: "Jira",
35
+ dedicatedSteps: [
36
+ "1. Open https://admin.atlassian.com/ → Directory → Invite users",
37
+ "2. Invite an email you control (e.g. agentdesk@yourdomain.com) as \"AgentDesk\"",
38
+ "3. Accept the invite and sign in as the new user",
39
+ "4. Go to https://id.atlassian.com/manage-profile/security/api-tokens → Create API token",
40
+ "5. Paste the email + token when prompted below",
41
+ ],
42
+ personalNote: "Your own Jira account. Comments on tickets will appear under your name.",
43
+ },
44
+ github: {
45
+ name: "GitHub",
46
+ dedicatedSteps: [
47
+ "GitHub enforces one account per human, so a \"dedicated bot user\" must be",
48
+ "a GitHub App (first-class bot identity, shows as agentdesk[bot] on comments).",
49
+ "That's a deeper install — not available yet in this wizard. For now:",
50
+ " • Option A: Use gh CLI authenticated as yourself (quickest to get started)",
51
+ " • Option B: Generate a Personal Access Token scoped to the repo",
52
+ ],
53
+ personalNote: "Your own GitHub account via gh CLI or a PAT. Comments appear under your name.",
54
+ },
55
+ };
56
+
57
+ function printDedicatedUserChoice(tracker, quick) {
58
+ if (quick || !tracker) return;
59
+ const g = TRACKER_GUIDANCE[tracker];
60
+ if (!g) return;
61
+ console.log("");
62
+ console.log(` ${g.name} account — who will post the comments?`);
63
+ console.log(" ─────────────────────────────────────────────");
64
+ console.log("");
65
+ console.log(" Recommended: Dedicated \"AgentDesk\" user");
66
+ console.log(" Clean audit trail, survives team changes, clear attribution.");
67
+ for (const line of g.dedicatedSteps) console.log(` ${line}`);
68
+ console.log("");
69
+ console.log(" Quick start: Your own account");
70
+ console.log(` ${g.personalNote}`);
71
+ console.log("");
72
+ console.log(" (This wizard doesn't care which you pick — it only asks for the");
73
+ console.log(" credential. You're deciding whose credential to paste.)");
74
+ console.log("");
75
+ }
76
+
77
+ function printIdentityEcho(identity) {
78
+ if (!identity) return;
79
+ const label = identity.name || identity.login || identity.email || "(unknown)";
80
+ const extra = identity.email && identity.email !== identity.name ? ` <${identity.email}>` : "";
81
+ console.log(` ✓ Posting as: ${label}${extra}`);
82
+ const looksLikePerson =
83
+ identity.name && !/agent\s*desk|agentdesk|bot|service/i.test(identity.name) &&
84
+ !/(^|[.+])(agentdesk|bot|svc|service)(@|$|[.+])/i.test(identity.email || "");
85
+ if (looksLikePerson) {
86
+ console.log(" (Looks like a personal account — comments will be attributed to this user.");
87
+ console.log(" If you'd rather have a dedicated AgentDesk identity, rotate the credential");
88
+ console.log(" to one generated under a dedicated tracker user and re-run `agentdesk init`.)");
89
+ }
90
+ }
91
+
14
92
  function loadApiKey(dir) {
15
93
  const envPath = join(dir, ".env");
16
94
  if (!existsSync(envPath)) return getStoredApiKey();
@@ -76,7 +154,8 @@ async function fetchServerCreds(apiKey, projectKey) {
76
154
  return {};
77
155
  }
78
156
 
79
- export async function runInit(cwd) {
157
+ export async function runInit(cwd, opts = {}) {
158
+ const quick = !!opts.quick;
80
159
  const project = detectProject(cwd);
81
160
  const existingConfig = loadConfig(cwd);
82
161
  const projectId = project.name || cwd.split("/").pop();
@@ -85,6 +164,26 @@ export async function runInit(cwd) {
85
164
 
86
165
  const rl = createInterface({ input: process.stdin, output: process.stdout });
87
166
 
167
+ // Re-run mode: if config already exists (and we're not in --quick), let
168
+ // the user jump to a specific section instead of walking the full wizard.
169
+ let editScope = "full";
170
+ if (hasConfig && !quick) {
171
+ console.log("");
172
+ console.log(" AgentDesk — existing project detected");
173
+ console.log(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
174
+ console.log("");
175
+ const choice = await selectOption(rl, "What would you like to do?", [
176
+ { label: "Full setup (walk the whole wizard again)", value: "full" },
177
+ { label: "Tracker only (change tracker or credentials)", value: "tracker" },
178
+ { label: "Cancel", value: "cancel" },
179
+ ]);
180
+ if (choice.value === "cancel") {
181
+ rl.close();
182
+ return;
183
+ }
184
+ editScope = choice.value;
185
+ }
186
+
88
187
  console.log("");
89
188
  console.log(" AgentDesk — Project Setup");
90
189
  console.log(" ━━━━━━━━━━━━━━━━━━━━━━━━");
@@ -103,11 +202,20 @@ export async function runInit(cwd) {
103
202
 
104
203
  // --- Step 1: Project key ---
105
204
  const defaultKey = existingConfig.projectKey || projectId;
106
- const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
107
- const finalProjectKey = keyAnswer.trim() || defaultKey;
108
- console.log("");
205
+ let finalProjectKey;
206
+ if (editScope === "tracker") {
207
+ finalProjectKey = defaultKey;
208
+ } else {
209
+ console.log(" Step 1 of 4 — Project key");
210
+ console.log("");
211
+ const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
212
+ finalProjectKey = keyAnswer.trim() || defaultKey;
213
+ console.log("");
214
+ }
109
215
 
110
216
  // --- Step 2: Tracker selection (platform first) ---
217
+ if (!quick) console.log(editScope === "tracker" ? " Tracker setup" : " Step 2 of 4 — Task tracker");
218
+ console.log("");
111
219
  const trackerOptions = [
112
220
  { label: "Linear", value: "linear" },
113
221
  { label: "Jira", value: "jira" },
@@ -119,6 +227,9 @@ export async function runInit(cwd) {
119
227
  const tracker = selected.value;
120
228
  console.log("");
121
229
 
230
+ // Inline guidance: dedicated-user vs personal, only when a tracker is picked.
231
+ printDedicatedUserChoice(tracker, quick);
232
+
122
233
  // --- Step 2: Tracker configuration (details + credentials) ---
123
234
  const config = {};
124
235
  if (tracker) config.tracker = tracker;
@@ -166,6 +277,7 @@ export async function runInit(cwd) {
166
277
 
167
278
  if (check.ok) {
168
279
  console.log(" ✓ Tracker permissions verified (read, create, update)");
280
+ printIdentityEcho(check.identity);
169
281
  console.log("");
170
282
  verified = true;
171
283
  } else {
@@ -329,7 +441,7 @@ export async function runInit(cwd) {
329
441
  }
330
442
  }
331
443
 
332
- // --- Save .agentdesk.json ---
444
+ // --- Summary + confirm before write ---
333
445
  let merged = {};
334
446
  if (hasConfig) {
335
447
  try { merged = JSON.parse(readFileSync(configPath, "utf-8")); } catch {}
@@ -349,6 +461,24 @@ export async function runInit(cwd) {
349
461
  delete merged.github;
350
462
  }
351
463
 
464
+ if (!quick) {
465
+ console.log(" Review");
466
+ console.log(" ──────");
467
+ console.log(` Project key: ${merged.projectKey}`);
468
+ console.log(` Tracker: ${merged.tracker || "none"}`);
469
+ if (merged.linear) console.log(` Linear: workspace=${merged.linear.workspace || "-"}, team=${merged.linear.teamKey || "-"}`);
470
+ if (merged.jira) console.log(` Jira: ${merged.jira.baseUrl || "-"}, project=${merged.jira.project || "-"}`);
471
+ if (merged.github) console.log(` GitHub: ${merged.github.repo || "-"}`);
472
+ console.log("");
473
+ const confirm = (await ask(rl, " Save this config? [Y/n]: ")).trim().toLowerCase();
474
+ if (confirm && confirm !== "y" && confirm !== "yes") {
475
+ console.log(" Cancelled — nothing written.");
476
+ rl.close();
477
+ return;
478
+ }
479
+ console.log("");
480
+ }
481
+
352
482
  writeFileSync(configPath, JSON.stringify(merged, null, 2) + "\n");
353
483
  console.log(` Saved .agentdesk.json`);
354
484
 
@@ -385,14 +515,14 @@ export async function runInit(cwd) {
385
515
  if (res.ok) {
386
516
  console.log(" Registered with agentdesk.live");
387
517
  }
388
- // Push settings to server
518
+ // Push settings to server via the shared helper so error surfacing is consistent.
389
519
  const key = loadApiKey(cwd);
390
520
  if (key) {
391
- await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings`, {
392
- method: "PUT",
393
- headers: { "Content-Type": "application/json", "x-api-key": key },
394
- body: JSON.stringify(merged),
395
- }).catch(() => {});
521
+ const push = await pushConfig(key, SERVER, finalProjectKey, merged);
522
+ if (!push.ok && push.error !== "missing_auth") {
523
+ console.log(` ⚠ Could not push settings to agentdesk.live: ${push.error}`);
524
+ console.log(" Your local .agentdesk.json is saved; retry from the UI or re-run init.");
525
+ }
396
526
  }
397
527
  } catch {
398
528
  // Server not available
@@ -29,13 +29,16 @@ async function checkLinear({ teamKey, workspace }, creds) {
29
29
  if (!apiKey) return { ok: false, errors: ["Missing LINEAR_API_KEY — configure it in the AgentDesk dashboard or .env"] };
30
30
 
31
31
  const errors = [];
32
+ let identity = null;
32
33
 
33
34
  // Check read: fetch viewer and team info
34
35
  try {
35
- const viewerRes = await gql(apiKey, `{ viewer { id name } }`);
36
+ const viewerRes = await gql(apiKey, `{ viewer { id name email } }`);
36
37
  if (viewerRes.errors) {
37
38
  return { ok: false, errors: ["LINEAR_API_KEY is invalid or expired"] };
38
39
  }
40
+ const v = viewerRes.data?.viewer;
41
+ if (v) identity = { name: v.name, email: v.email, id: v.id };
39
42
  } catch (e) {
40
43
  return { ok: false, errors: [`Cannot reach Linear API: ${e.message}`] };
41
44
  }
@@ -77,7 +80,7 @@ async function checkLinear({ teamKey, workspace }, creds) {
77
80
  }
78
81
  }
79
82
 
80
- return { ok: errors.length === 0, errors };
83
+ return { ok: errors.length === 0, errors, identity };
81
84
  }
82
85
 
83
86
  async function checkJira({ baseUrl, project }, creds) {
@@ -88,8 +91,21 @@ async function checkJira({ baseUrl, project }, creds) {
88
91
  if (!baseUrl) return { ok: false, errors: ["Missing Jira base URL — run 'agentdesk init' to configure"] };
89
92
 
90
93
  const errors = [];
94
+ let identity = null;
91
95
  const auth = "Basic " + Buffer.from(`${email}:${token}`).toString("base64");
92
96
 
97
+ // Fetch the authenticated user so we can report "Posting as X".
98
+ try {
99
+ const meRes = await fetch(`${baseUrl}/rest/api/3/myself`, {
100
+ headers: { Authorization: auth, Accept: "application/json" },
101
+ signal: AbortSignal.timeout(10000),
102
+ });
103
+ if (meRes.ok) {
104
+ const me = await meRes.json();
105
+ identity = { name: me.displayName || email, email: me.emailAddress || email, id: me.accountId };
106
+ }
107
+ } catch {}
108
+
93
109
  // Use Jira's mypermissions endpoint to check all permissions at once
94
110
  const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
95
111
  try {
@@ -133,13 +149,14 @@ async function checkJira({ baseUrl, project }, creds) {
133
149
  return { ok: false, errors: [`Cannot reach Jira at ${baseUrl}: ${e.message}`] };
134
150
  }
135
151
 
136
- return { ok: errors.length === 0, errors };
152
+ return { ok: errors.length === 0, errors, identity };
137
153
  }
138
154
 
139
155
  async function checkGitHub({ repo }, creds) {
140
156
  if (!repo) return { ok: false, errors: ["Missing GitHub repo — run 'agentdesk init' to configure"] };
141
157
 
142
158
  const errors = [];
159
+ let identity = null;
143
160
 
144
161
  // Check if gh CLI is available
145
162
  try {
@@ -148,9 +165,14 @@ async function checkGitHub({ repo }, creds) {
148
165
  return { ok: false, errors: ["GitHub CLI (gh) is not installed — install it from https://cli.github.com"] };
149
166
  }
150
167
 
151
- // Check auth status
168
+ // Check auth status and grab the authenticated login
152
169
  try {
153
170
  execSync("gh auth status", { stdio: "pipe" });
171
+ try {
172
+ const who = execSync(`gh api user --jq '{login: .login, name: .name}'`, { stdio: "pipe", encoding: "utf-8" }).trim();
173
+ const j = JSON.parse(who);
174
+ identity = { name: j.name || j.login, login: j.login };
175
+ } catch {}
154
176
  } catch {
155
177
  return { ok: false, errors: ["GitHub CLI is not authenticated — run 'gh auth login'"] };
156
178
  }
@@ -175,7 +197,7 @@ async function checkGitHub({ repo }, creds) {
175
197
  }
176
198
  }
177
199
 
178
- return { ok: errors.length === 0, errors };
200
+ return { ok: errors.length === 0, errors, identity };
179
201
  }
180
202
 
181
203
  // Helper: execute a Linear GraphQL query
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.15.6",
3
+ "version": "0.16.1",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {