@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 +16 -0
- package/cli/config.mjs +1 -1
- package/cli/init.mjs +261 -267
- package/package.json +1 -1
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
|
-
// ---
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
if
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
console.log(
|
|
292
|
-
|
|
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
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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:
|
|
510
|
-
console.log(` Tracker:
|
|
511
|
-
if (merged.linear) console.log(` Linear:
|
|
512
|
-
if (merged.jira) console.log(` Jira:
|
|
513
|
-
if (merged.github) console.log(` GitHub:
|
|
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") {
|