@kendoo.agentdesk/agentdesk 0.17.5 → 0.18.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/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ 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.18.0] — 2026-04-18
12
+
13
+ ### Changed
14
+ - `[CLI]` `agentdesk init` redesigned:
15
+ - Tracker first, then its location, then auth, then verify, then pick a project from a list fetched from the tracker API (Linear teams, Jira projects). No more asking for a project key before you've told the wizard what tracker you're using.
16
+ - Every credential step prints exact steps: the URL to open, the button to click, the label to use, the scopes to pick.
17
+ - Always asks for GitHub repo, account handle, and token — even when the tracker is Linear, Jira, or None. Agents push code via GitHub; this is no longer left to luck or a global `gh auth`.
18
+ - Always asks for an identity badge (optional display name).
19
+ - The agentdesk project key is asked last, defaulting to the tracker's project key so you don't type it twice.
20
+ - Verifies the GitHub token matches the account handle you typed. Warns on mismatch.
21
+
22
+ ## [0.17.6] — 2026-04-18
23
+
24
+ ### Fixed
25
+ - `[CLI]` Picking "Skip — configure later" on the GitHub auth prompt now actually skips the verification step. Previously it would still run the silent check against whichever gh account was globally logged in, which defeated the point of skipping.
26
+
11
27
  ## [0.17.5] — 2026-04-18
12
28
 
13
29
  ### Fixed
package/cli/config.mjs CHANGED
@@ -16,7 +16,7 @@ const DEFAULTS = {
16
16
  tracker: null,
17
17
  linear: { teamKey: null, workspace: null },
18
18
  jira: { baseUrl: null, project: null },
19
- github: { repo: null },
19
+ github: { repo: null, login: null },
20
20
  team: null,
21
21
  commands: { test: null, build: null, lint: null },
22
22
  projectAgents: [],
package/cli/init.mjs CHANGED
@@ -155,6 +155,131 @@ async function fetchServerCreds(apiKey, projectKey) {
155
155
  return {};
156
156
  }
157
157
 
158
+ // Render a numbered-step box of instructions with exact URLs and button names.
159
+ function printSteps(title, steps) {
160
+ const pad = " ";
161
+ console.log(`${pad}${title}`);
162
+ console.log(`${pad}${"─".repeat(title.length)}`);
163
+ for (let i = 0; i < steps.length; i++) {
164
+ console.log(`${pad} ${i + 1}. ${steps[i]}`);
165
+ }
166
+ console.log("");
167
+ }
168
+
169
+ // Try to detect the current git remote (origin) and parse owner/repo from
170
+ // known GitHub URL forms. Returns "owner/repo" or null. Used as a default
171
+ // suggestion in the GitHub step.
172
+ function detectGitRemote(dir) {
173
+ try {
174
+ const url = execSync("git remote get-url origin", { cwd: dir, stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
175
+ let m = url.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/);
176
+ if (m) return `${m[1]}/${m[2]}`;
177
+ } catch {}
178
+ return null;
179
+ }
180
+
181
+ // Fetch the set of "things to pick from" per tracker once the auth has been
182
+ // verified. For Linear this is teams; Jira is projects; GitHub Issues is
183
+ // repos (filtered to ones the token can access). Returns an array of
184
+ // { id, name, raw } or null if listing failed — caller falls back to a
185
+ // manual prompt.
186
+ async function listTrackerProjects({ tracker, creds, location }) {
187
+ if (tracker === "linear") {
188
+ if (!creds.LINEAR_API_KEY) return null;
189
+ try {
190
+ const res = await fetch("https://api.linear.app/graphql", {
191
+ method: "POST",
192
+ headers: { Authorization: creds.LINEAR_API_KEY, "Content-Type": "application/json" },
193
+ body: JSON.stringify({ query: "{ teams(first: 50) { nodes { id key name } } }" }),
194
+ signal: AbortSignal.timeout(8000),
195
+ });
196
+ if (!res.ok) return null;
197
+ const data = await res.json();
198
+ const nodes = data?.data?.teams?.nodes || [];
199
+ return nodes.map(t => ({ id: t.key, name: `${t.name} (${t.key})`, raw: t }));
200
+ } catch { return null; }
201
+ }
202
+ if (tracker === "jira") {
203
+ if (!creds.JIRA_EMAIL || !creds.JIRA_API_TOKEN || !location) return null;
204
+ try {
205
+ const auth = "Basic " + Buffer.from(`${creds.JIRA_EMAIL}:${creds.JIRA_API_TOKEN}`).toString("base64");
206
+ const res = await fetch(`${location.replace(/\/+$/, "")}/rest/api/3/project/search?maxResults=50`, {
207
+ headers: { Authorization: auth, Accept: "application/json" },
208
+ signal: AbortSignal.timeout(8000),
209
+ });
210
+ if (!res.ok) return null;
211
+ const data = await res.json();
212
+ const values = data?.values || [];
213
+ return values.map(p => ({ id: p.key, name: `${p.name} (${p.key})`, raw: p }));
214
+ } catch { return null; }
215
+ }
216
+ if (tracker === "github") {
217
+ // GitHub has potentially thousands of repos; don't try to list — rely on
218
+ // the explicit repo prompt (which has a git-remote default anyway).
219
+ return null;
220
+ }
221
+ return null;
222
+ }
223
+
224
+ async function pickFromList(rl, prompt, items, { allowManual = true } = {}) {
225
+ const options = items.map(it => ({ label: it.name, value: it.id }));
226
+ if (allowManual) options.push({ label: "Type it manually instead", value: "__manual__" });
227
+ const choice = await selectOption(rl, prompt, options);
228
+ if (choice.value === "__manual__") return null;
229
+ return choice.value;
230
+ }
231
+
232
+ // Prompt for a value, showing instructions first, looping until non-empty.
233
+ async function promptRequired(rl, label, instructions) {
234
+ if (instructions) printSteps(instructions.title, instructions.steps);
235
+ while (true) {
236
+ const v = (await ask(rl, ` ${label}: `)).trim();
237
+ if (v) return v;
238
+ console.log(` ${label} is required. Ctrl+C to abort.`);
239
+ }
240
+ }
241
+
242
+ // Verify tracker auth with what we have in .env + server, then fetch
243
+ // available teams/projects and let the user pick from a list.
244
+ async function verifyAndPickTrackerProject({ rl, cwd, finalProjectKey, tracker, config, location }) {
245
+ console.log(" Verifying tracker access...");
246
+ const dotEnv = loadDotEnv(cwd);
247
+ const apiKey = loadApiKey(cwd);
248
+ const serverCreds = await fetchServerCreds(apiKey, finalProjectKey);
249
+ const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
250
+ const check = await checkTrackerPermissions({ tracker, config, credentials });
251
+ if (!check.ok) {
252
+ console.log(" ⚠ " + (check.errors[0] || "Verification failed"));
253
+ for (const e of check.errors.slice(1)) console.log(` • ${e}`);
254
+ console.log("");
255
+ return { ok: false };
256
+ }
257
+ console.log(" ✓ Tracker access verified");
258
+ printIdentityEcho(check.identity);
259
+ console.log("");
260
+
261
+ // GitHub Issues — project = repo, already collected above. Skip the pick.
262
+ if (tracker === "github") return { ok: true, trackerProjectId: null };
263
+
264
+ // Fetch selectable items (Linear teams / Jira projects) and let user pick.
265
+ const items = await listTrackerProjects({ tracker, creds: credentials, location });
266
+ if (!items || items.length === 0) {
267
+ console.log(" Couldn't list projects automatically — type the key manually.");
268
+ const label = tracker === "linear" ? "Linear team key (e.g. KEN)" : "Jira project key (e.g. PROJ)";
269
+ const manual = await promptRequired(rl, label);
270
+ console.log("");
271
+ return { ok: true, trackerProjectId: manual };
272
+ }
273
+ console.log("");
274
+ const picked = await pickFromList(rl, `Pick the ${tracker === "linear" ? "team" : "project"} this agentdesk project maps to:`, items);
275
+ console.log("");
276
+ if (picked) return { ok: true, trackerProjectId: picked };
277
+ const label = tracker === "linear" ? "Linear team key" : "Jira project key";
278
+ const manual = await promptRequired(rl, label);
279
+ console.log("");
280
+ return { ok: true, trackerProjectId: manual };
281
+ }
282
+
158
283
  export async function runInit(cwd, opts = {}) {
159
284
  const quick = !!opts.quick;
160
285
  const project = detectProject(cwd);
@@ -201,21 +326,14 @@ export async function runInit(cwd, opts = {}) {
201
326
  if (project.lintCommand) console.log(` Lint: ${project.lintCommand}`);
202
327
  if (project.testCommand || project.buildCommand || project.lintCommand) console.log("");
203
328
 
204
- // --- Step 1: Project key ---
205
- const defaultKey = existingConfig.projectKey || projectId;
206
- let finalProjectKey;
207
- if (editScope === "tracker") {
208
- finalProjectKey = defaultKey;
209
- } else {
210
- console.log(" Step 1 of 4 — Project key");
211
- console.log("");
212
- const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
213
- finalProjectKey = keyAnswer.trim() || defaultKey;
214
- console.log("");
215
- }
329
+ // --- Tracker step: platform location → auth → verify → pick project ---
330
+ const config = {};
331
+ // Initial default for the agentdesk project key. Gets overwritten with the
332
+ // tracker's project/team key if we learn one during verification, and the
333
+ // user can override at the final review screen.
334
+ let finalProjectKey = existingConfig.projectKey || projectId;
216
335
 
217
- // --- Step 2: Tracker selection (platform first) ---
218
- if (!quick) console.log(editScope === "tracker" ? " Tracker setup" : " Step 2 of 4 — Task tracker");
336
+ if (!quick) console.log(editScope === "tracker" ? " Tracker" : " Step 1 of 3 — Task tracker");
219
337
  console.log("");
220
338
  const trackerOptions = [
221
339
  { label: "Linear", value: "linear" },
@@ -223,264 +341,144 @@ export async function runInit(cwd, opts = {}) {
223
341
  { label: "GitHub Issues", value: "github" },
224
342
  { label: "None (use descriptions only)", value: null },
225
343
  ];
226
-
227
- const selected = await selectOption(rl, "Task tracker:", trackerOptions);
228
- const tracker = selected.value;
344
+ const tracker = (await selectOption(rl, "Task tracker:", trackerOptions)).value;
229
345
  console.log("");
230
-
231
- // Inline guidance: dedicated-user vs personal, only when a tracker is picked.
232
- printDedicatedUserChoice(tracker, quick);
233
-
234
- // --- Step 2: Tracker configuration (details + credentials) ---
235
- const config = {};
236
346
  if (tracker) config.tracker = tracker;
237
347
 
348
+ if (tracker) printDedicatedUserChoice(tracker, quick);
349
+
350
+ // Tracker location + auth with exact instructions, then verify.
238
351
  if (tracker === "linear") {
239
- const currentWs = existingConfig.linear?.workspace || "";
240
- const wsAnswer = await ask(rl, ` Linear workspace slug${currentWs ? ` (${currentWs})` : ""}: `);
241
- const ws = wsAnswer.trim() || currentWs;
242
- const currentTeamKey = existingConfig.linear?.teamKey || "";
243
- const tkAnswer = await ask(rl, ` Linear team key${currentTeamKey ? ` (${currentTeamKey})` : ""} (e.g. KEN): `);
244
- const teamKey = tkAnswer.trim() || currentTeamKey;
245
- if (ws || teamKey) config.linear = { ...(ws && { workspace: ws }), ...(teamKey && { teamKey }) };
352
+ if (!quick) printSteps("Create a Linear API key", [
353
+ "Open https://linear.app/settings/api in your browser (log in first if needed).",
354
+ "Click \"Create key\" (top right of the API page).",
355
+ "Label the key: AgentDesk",
356
+ "Expiration: set a value matching your policy, or choose \"No expiration\".",
357
+ "Click \"Create\". Copy the key shown (starts with lin_api_). You will NOT see it again.",
358
+ ]);
359
+ const ws = await promptRequired(rl, "Linear workspace slug (the 'kendoo' in linear.app/kendoo)");
360
+ const key = await promptRequired(rl, "Linear API key you just copied");
361
+ saveEnvVar(cwd, "LINEAR_API_KEY", key);
362
+ console.log(" ✓ Saved LINEAR_API_KEY to .env");
246
363
  console.log("");
364
+ config.linear = { workspace: ws };
247
365
  }
248
366
 
249
367
  if (tracker === "jira") {
250
- const currentUrl = existingConfig.jira?.baseUrl || "";
251
- const urlAnswer = await ask(rl, ` Jira base URL${currentUrl ? ` (${currentUrl})` : ""}: `);
252
- const url = urlAnswer.trim() || currentUrl;
253
- const currentProj = existingConfig.jira?.project || "";
254
- const projAnswer = await ask(rl, ` Jira project key${currentProj ? ` (${currentProj})` : ""} (e.g. PROJ): `);
255
- const proj = projAnswer.trim() || currentProj;
256
- if (url || proj) config.jira = { ...(url && { baseUrl: url }), ...(proj && { project: proj }) };
368
+ if (!quick) printSteps("Create a Jira API token", [
369
+ "Open https://id.atlassian.com/manage-profile/security/api-tokens",
370
+ "Click \"Create API token\".",
371
+ "Label: AgentDesk",
372
+ "Click \"Create\". Copy the token (shown once).",
373
+ "You'll also need the email you sign into Atlassian with.",
374
+ ]);
375
+ const baseUrl = await promptRequired(rl, "Jira tenant URL (e.g. https://yourco.atlassian.net)");
376
+ const email = await promptRequired(rl, "Your Atlassian login email");
377
+ const token = await promptRequired(rl, "Jira API token you just created");
378
+ saveEnvVar(cwd, "JIRA_EMAIL", email);
379
+ saveEnvVar(cwd, "JIRA_API_TOKEN", token);
380
+ console.log(" ✓ Saved JIRA_EMAIL and JIRA_API_TOKEN to .env");
257
381
  console.log("");
382
+ config.jira = { baseUrl: baseUrl.replace(/\/+$/, "") };
258
383
  }
259
384
 
260
385
  if (tracker === "github") {
261
- const current = existingConfig.github?.repo || "";
262
- const answer = await ask(rl, ` GitHub repo${current ? ` (${current})` : ""} (owner/repo): `);
263
- const r = answer.trim() || current;
264
- if (r) config.github = { repo: r };
386
+ // The GitHub-issues credential doubles as the git-push credential below,
387
+ // so we collect a full repo/login/token block up front.
388
+ if (!quick) printSteps("Create a GitHub personal access token", [
389
+ "Open https://github.com/settings/tokens (classic tokens).",
390
+ "Click \"Generate new token\" → \"Generate new token (classic)\".",
391
+ "Name: AgentDesk",
392
+ "Expiration: match your policy.",
393
+ "Scopes: check \"repo\" (full control of private repositories).",
394
+ "Click \"Generate token\" at the bottom. Copy the token (shown once).",
395
+ ]);
396
+ const detected = detectGitRemote(cwd);
397
+ const repoDefault = existingConfig.github?.repo || detected || "";
398
+ const repo = (await ask(rl, ` GitHub repo (owner/repo)${repoDefault ? ` [${repoDefault}]` : ""}: `)).trim() || repoDefault;
399
+ const loginDefault = existingConfig.github?.login || "";
400
+ const login = (await ask(rl, ` GitHub username (the @handle that owns the token)${loginDefault ? ` [${loginDefault}]` : ""}: `)).trim() || loginDefault;
401
+ const token = await promptRequired(rl, "GitHub token");
402
+ saveEnvVar(cwd, "GITHUB_TOKEN", token);
403
+ console.log(" ✓ Saved GITHUB_TOKEN to .env");
265
404
  console.log("");
405
+ config.github = { repo, login };
406
+ }
266
407
 
267
- // Ask which identity to use for this project, regardless of whether
268
- // `gh auth` is already set up globally. Without this prompt the wizard
269
- // silently reuses whichever account the user last ran `gh auth login`
270
- // with, which is surprising on a machine with multiple GitHub identities.
271
- let ghLogin = null;
272
- try {
273
- const out = execSync("gh api user --jq .login", { stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
274
- if (out) ghLogin = out;
275
- } catch {}
276
- const ghOptionLabel = ghLogin
277
- ? `Use gh CLI currently authenticated as @${ghLogin}`
278
- : `Use gh CLI you'll run 'gh auth login' after this wizard`;
279
- const authChoice = await selectOption(rl, "GitHub authentication for this project:", [
280
- { label: ghOptionLabel, value: "gh" },
281
- { label: "Paste a personal access token (use a different account)", value: "pat" },
282
- { label: "Skip — configure later in the dashboard", value: "skip" },
408
+ // Verify tracker + fetch available projects to pick from.
409
+ let trackerLocation = null;
410
+ if (tracker === "jira") trackerLocation = config.jira?.baseUrl;
411
+ if (tracker) {
412
+ const verified = await verifyAndPickTrackerProject({ rl, cwd, finalProjectKey, tracker, config, location: trackerLocation });
413
+ if (!verified.ok) {
414
+ console.log(" Continuing without tracker verification.");
415
+ console.log(" You can re-run `agentdesk init` after fixing credentials.");
416
+ console.log("");
417
+ } else if (verified.trackerProjectId) {
418
+ if (tracker === "linear") config.linear = { ...(config.linear || {}), teamKey: verified.trackerProjectId };
419
+ if (tracker === "jira") config.jira = { ...(config.jira || {}), project: verified.trackerProjectId };
420
+ // Use the tracker's project key as the default agentdesk project key.
421
+ if (!existingConfig.projectKey) finalProjectKey = verified.trackerProjectId.toLowerCase();
422
+ }
423
+ }
424
+
425
+ // --- GitHub block (always — even when tracker is Linear/Jira/None) ---
426
+ // This configures the credential agents use for git push, PRs, and
427
+ // GitHub API calls. When tracker is already GitHub Issues the block
428
+ // above already collected repo+login+token, so skip the duplicate.
429
+ let githubLogin = config.github?.login || null;
430
+ if (tracker !== "github") {
431
+ if (!quick) {
432
+ console.log(" ─".repeat(30));
433
+ console.log(" GitHub access (required — agents push code and open PRs here)");
434
+ console.log("");
435
+ }
436
+ if (!quick) printSteps("Create a GitHub personal access token", [
437
+ "Open https://github.com/settings/tokens (classic tokens).",
438
+ "Click \"Generate new token\" → \"Generate new token (classic)\".",
439
+ "Name: AgentDesk",
440
+ "Expiration: match your policy.",
441
+ "Scopes: check \"repo\" (full control of private repositories).",
442
+ "Click \"Generate token\" at the bottom. Copy the token (shown once).",
283
443
  ]);
444
+ const detected = detectGitRemote(cwd);
445
+ const repoDefault = existingConfig.github?.repo || detected || "";
446
+ const repo = (await ask(rl, ` GitHub repo (owner/repo)${repoDefault ? ` [${repoDefault}]` : ""}: `)).trim() || repoDefault;
447
+ const loginDefault = existingConfig.github?.login || "";
448
+ const login = (await ask(rl, ` GitHub username (the @handle that owns the token)${loginDefault ? ` [${loginDefault}]` : ""}: `)).trim() || loginDefault;
449
+ const token = await promptRequired(rl, "GitHub token");
450
+ saveEnvVar(cwd, "GITHUB_TOKEN", token);
451
+ console.log(" ✓ Saved GITHUB_TOKEN to .env");
284
452
  console.log("");
453
+ config.github = { repo, login };
454
+ githubLogin = login;
285
455
 
286
- if (authChoice.value === "pat") {
287
- if (!quick) {
288
- console.log(" Create a token:");
289
- console.log(" 1. Open https://github.com/settings/tokens");
290
- console.log(" 2. Click \"Generate new token (classic)\"");
291
- console.log(" 3. Name: AgentDesk. Scope: repo. Click Generate.");
292
- console.log(" 4. Copy the token and paste below.");
293
- console.log("");
294
- }
295
- while (true) {
296
- const tokenInput = (await ask(rl, " GitHub token: ")).trim();
297
- if (!tokenInput) {
298
- console.log(" Token is required. Press Ctrl+C to abort.");
299
- continue;
300
- }
301
- saveEnvVar(cwd, "GITHUB_TOKEN", tokenInput);
302
- console.log(" ✓ Saved to .env (as GITHUB_TOKEN). This overrides any global gh CLI auth for this project.");
303
- console.log("");
304
- break;
456
+ // Verify token matches the user-stated login.
457
+ try {
458
+ const env = { ...process.env, GH_TOKEN: token };
459
+ const who = execSync("gh api user --jq .login", { env, stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
460
+ if (who && login && who.toLowerCase() !== login.toLowerCase()) {
461
+ console.log(` ⚠ Token is valid but authenticates as @${who}, not @${login}. Agents will post as @${who}.`);
462
+ } else if (who) {
463
+ console.log(` ✓ GitHub verified as @${who}`);
305
464
  }
465
+ } catch {
466
+ console.log(" ⚠ Could not verify the GitHub token (gh CLI missing or network unreachable). Saved anyway — verify later in the dashboard.");
306
467
  }
468
+ console.log("");
307
469
  }
308
470
 
309
- // --- Step 3: Verify tracker permissions (with re-prompt on failure) ---
310
- if (tracker) {
311
- let verified = false;
312
- while (!verified) {
313
- console.log(" Checking tracker permissions...");
314
- const dotEnv = loadDotEnv(cwd);
315
- const apiKey = loadApiKey(cwd);
316
- const serverCreds = await fetchServerCreds(apiKey, finalProjectKey);
317
- const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
318
- const check = await checkTrackerPermissions({ tracker, config, credentials });
319
-
320
- if (check.ok) {
321
- console.log(" ✓ Tracker permissions verified (read, create, update)");
322
- printIdentityEcho(check.identity);
323
- console.log("");
324
- verified = true;
325
- } else {
326
- console.log("");
327
- console.log(" ⚠ Tracker permission issues:");
328
- for (const err of check.errors) {
329
- console.log(` • ${err}`);
330
- }
331
- console.log("");
332
-
333
- // Determine what's missing and offer to fix it
334
- const missingCreds = check.errors.some(e =>
335
- e.includes("Missing") && (e.includes("API_KEY") || e.includes("TOKEN") || e.includes("EMAIL"))
336
- );
337
-
338
- if (missingCreds) {
339
- console.log(" Let's fix this now. I'll walk you through it.");
340
- console.log("");
341
-
342
- if (tracker === "linear") {
343
- console.log(" ┌─────────────────────────────────────────────┐");
344
- console.log(" │ How to get your Linear API key: │");
345
- console.log(" │ │");
346
- console.log(" │ 1. Open https://linear.app/settings/api │");
347
- console.log(" │ 2. Click \"Create new API key\" │");
348
- console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
349
- console.log(" │ 4. Copy the key │");
350
- console.log(" │ 5. Paste it below │");
351
- console.log(" └─────────────────────────────────────────────┘");
352
- console.log("");
353
-
354
- while (true) {
355
- const keyInput = await ask(rl, " Linear API key: ");
356
- const key = keyInput.trim();
357
- if (!key) {
358
- console.log(" The API key is required for the agents to read and update your Linear tasks.");
359
- console.log("");
360
- continue;
361
- }
362
- saveEnvVar(cwd, "LINEAR_API_KEY", key);
363
- console.log(" ✓ Saved to .env");
364
- console.log("");
365
- break;
366
- }
367
- continue; // Re-check with the new key
368
- }
369
-
370
- if (tracker === "jira") {
371
- console.log(" ┌──────────────────────────────────────────────────────────┐");
372
- console.log(" │ How to get your Jira API token: │");
373
- console.log(" │ │");
374
- console.log(" │ 1. Open https://id.atlassian.com/manage-profile/ │");
375
- console.log(" │ security/api-tokens │");
376
- console.log(" │ 2. Click \"Create API token\" │");
377
- console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
378
- console.log(" │ 4. Copy the token │");
379
- console.log(" │ │");
380
- console.log(" │ You'll also need the email address you use │");
381
- console.log(" │ to log into Jira (not your username). │");
382
- console.log(" └──────────────────────────────────────────────────────────┘");
383
- console.log("");
384
-
385
- if (!credentials.JIRA_EMAIL) {
386
- while (true) {
387
- const emailInput = (await ask(rl, " Your Jira email address: ")).trim();
388
- if (!emailInput || !emailInput.includes("@")) {
389
- console.log(" Please enter a valid email address (the one you use to log into Jira).");
390
- continue;
391
- }
392
- saveEnvVar(cwd, "JIRA_EMAIL", emailInput);
393
- console.log(" ✓ Email saved");
394
- break;
395
- }
396
- }
397
-
398
- while (true) {
399
- const tokenInput = (await ask(rl, " Jira API token: ")).trim();
400
- if (!tokenInput) {
401
- console.log(" The API token is required for the agents to read and update your Jira tasks.");
402
- console.log("");
403
- continue;
404
- }
405
- saveEnvVar(cwd, "JIRA_API_TOKEN", tokenInput);
406
- console.log(" ✓ Token saved to .env");
407
- console.log("");
408
- break;
409
- }
410
- continue; // Re-check
411
- }
412
-
413
- if (tracker === "github") {
414
- console.log(" ┌──────────────────────────────────────────────────────────┐");
415
- console.log(" │ Two ways to authenticate with GitHub: │");
416
- console.log(" │ │");
417
- console.log(" │ Option A — GitHub CLI (recommended): │");
418
- console.log(" │ Run: gh auth login │");
419
- console.log(" │ Then press Enter below. │");
420
- console.log(" │ │");
421
- console.log(" │ Option B — Personal Access Token: │");
422
- console.log(" │ 1. Open https://github.com/settings/tokens │");
423
- console.log(" │ 2. Click \"Generate new token (classic)\" │");
424
- console.log(" │ 3. Select the \"repo\" scope │");
425
- console.log(" │ 4. Generate and copy the token │");
426
- console.log(" │ 5. Paste it below │");
427
- console.log(" └──────────────────────────────────────────────────────────┘");
428
- console.log("");
429
-
430
- const tokenInput = await ask(rl, " GitHub token (or Enter if using gh CLI): ");
431
- const token = tokenInput.trim();
432
- if (token) {
433
- saveEnvVar(cwd, "GITHUB_TOKEN", token);
434
- console.log(" ✓ Token saved to .env");
435
- console.log("");
436
- } else {
437
- console.log(" OK, will check gh CLI authentication...");
438
- console.log("");
439
- }
440
- continue; // Re-check
441
- }
442
- }
443
-
444
- // Non-credential error (wrong team key, wrong URL, network issue, etc.)
445
- console.log("");
446
- const choice = await selectOption(rl, "What would you like to do?", [
447
- { label: "Fix it and try again", value: "retry" },
448
- { label: "Continue without tracker verification (fix later in dashboard)", value: "skip" },
449
- { label: "Choose a different tracker platform", value: "change" },
450
- ]);
451
-
452
- if (choice.value === "retry") {
453
- // Re-prompt for the tracker details that might be wrong
454
- if (tracker === "linear" && config.linear) {
455
- const wsAnswer = await ask(rl, ` Linear workspace slug (${config.linear.workspace || ""}): `);
456
- if (wsAnswer.trim()) config.linear.workspace = wsAnswer.trim();
457
- const tkAnswer = await ask(rl, ` Linear team key (${config.linear.teamKey || ""}): `);
458
- if (tkAnswer.trim()) config.linear.teamKey = tkAnswer.trim();
459
- }
460
- if (tracker === "jira" && config.jira) {
461
- const urlAnswer = await ask(rl, ` Jira base URL (${config.jira.baseUrl || ""}): `);
462
- if (urlAnswer.trim()) config.jira.baseUrl = urlAnswer.trim();
463
- const projAnswer = await ask(rl, ` Jira project key (${config.jira.project || ""}): `);
464
- if (projAnswer.trim()) config.jira.project = projAnswer.trim();
465
- }
466
- if (tracker === "github" && config.github) {
467
- const repoAnswer = await ask(rl, ` GitHub repo (${config.github.repo || ""}): `);
468
- if (repoAnswer.trim()) config.github.repo = repoAnswer.trim();
469
- }
470
- console.log("");
471
- continue;
472
- } else if (choice.value === "change") {
473
- rl.close();
474
- return runInit(cwd);
475
- } else {
476
- console.log("");
477
- console.log(" Continuing without tracker verification.");
478
- console.log(" You can configure credentials later at agentdesk.live or re-run 'agentdesk init'.");
479
- console.log("");
480
- verified = true;
481
- }
482
- }
483
- }
471
+ // Identity badge always ask (optional).
472
+ const badgeDefault = existingConfig.identityBadge || "";
473
+ const badge = (await ask(rl, ` Identity badge (display name for AgentDesk)${badgeDefault ? ` [${badgeDefault}]` : ""} [optional]: `)).trim() || badgeDefault;
474
+ if (badge) config.identityBadge = badge;
475
+ console.log("");
476
+
477
+ // --- Final step: agentdesk project key (with the tracker-derived default) ---
478
+ if (!quick) {
479
+ const keyAnswer = await ask(rl, ` AgentDesk project key [${finalProjectKey}]: `);
480
+ if (keyAnswer.trim()) finalProjectKey = keyAnswer.trim();
481
+ console.log("");
484
482
  }
485
483
 
486
484
  // --- Summary + confirm before write ---
@@ -491,26 +489,22 @@ export async function runInit(cwd, opts = {}) {
491
489
 
492
490
  merged.projectKey = finalProjectKey;
493
491
 
494
- if (tracker) {
495
- merged.tracker = tracker;
496
- if (config.linear) merged.linear = config.linear;
497
- if (config.jira) merged.jira = config.jira;
498
- if (config.github) merged.github = config.github;
499
- } else {
500
- delete merged.tracker;
501
- delete merged.linear;
502
- delete merged.jira;
503
- delete merged.github;
504
- }
492
+ if (tracker) merged.tracker = tracker;
493
+ else delete merged.tracker;
494
+ if (config.linear) merged.linear = config.linear; else if (!tracker) delete merged.linear;
495
+ if (config.jira) merged.jira = config.jira; else if (!tracker) delete merged.jira;
496
+ if (config.github) merged.github = config.github;
497
+ if (config.identityBadge) merged.identityBadge = config.identityBadge;
505
498
 
506
499
  if (!quick) {
507
500
  console.log(" Review");
508
501
  console.log(" ──────");
509
- console.log(` Project key: ${merged.projectKey}`);
510
- console.log(` Tracker: ${merged.tracker || "none"}`);
511
- if (merged.linear) console.log(` Linear: workspace=${merged.linear.workspace || "-"}, team=${merged.linear.teamKey || "-"}`);
512
- if (merged.jira) console.log(` Jira: ${merged.jira.baseUrl || "-"}, project=${merged.jira.project || "-"}`);
513
- if (merged.github) console.log(` GitHub: ${merged.github.repo || "-"}`);
502
+ console.log(` Project key: ${merged.projectKey}`);
503
+ console.log(` Tracker: ${merged.tracker || "none"}`);
504
+ if (merged.linear) console.log(` Linear: workspace=${merged.linear.workspace || "-"}, team=${merged.linear.teamKey || "-"}`);
505
+ if (merged.jira) console.log(` Jira: ${merged.jira.baseUrl || "-"}, project=${merged.jira.project || "-"}`);
506
+ if (merged.github) console.log(` GitHub: repo=${merged.github.repo || "-"}, user=@${merged.github.login || "-"}`);
507
+ if (merged.identityBadge) console.log(` Identity badge: ${merged.identityBadge}`);
514
508
  console.log("");
515
509
  const confirm = (await ask(rl, " Save this config? [Y/n]: ")).trim().toLowerCase();
516
510
  if (confirm && confirm !== "y" && confirm !== "yes") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.17.5",
3
+ "version": "0.18.0",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {