@kendoo.agentdesk/agentdesk 0.19.2 → 0.19.4
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 +10 -0
- package/README.md +3 -0
- package/bin/agentdesk.mjs +11 -0
- package/cli/bootstrap.mjs +287 -0
- package/cli/init.mjs +44 -6
- package/cli/login.mjs +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,16 @@ Internal refactors, infrastructure changes, and architectural notes are not list
|
|
|
13
13
|
### Added
|
|
14
14
|
- `[UI]` Private-session sharing flow. When someone visits a session URL they don't own, they now see a friendly "Shh… this one's private" page with a one-click "Request access" button instead of a blank error. The session owner sees pending requests in a new header inbox and can grant viewer access for just that session or the whole project. Viewer-granted sessions show up in the teammate's sidebar tagged as a viewer.
|
|
15
15
|
|
|
16
|
+
## [0.19.4] — 2026-04-19
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `[CLI]` `agentdesk bootstrap` — fresh-machine setup for repos already configured on another machine. Reads the committed `.agentdesk.json` for project identity, tracker type, team/project key, and GitHub repo, then only prompts for the per-machine secrets (Linear API key, Jira email + token, GitHub token) that belong in the local `.env`. Previously the only post-clone path was `agentdesk init`, which walked the full wizard and risked overwriting project-wide settings when the user just wanted to paste in their own tokens. Bootstrap never writes `.agentdesk.json` and never pushes to the server, so the first machine's setup stays untouched. `agentdesk login` now points at both `init` (new project) and `bootstrap` (cloned repo) as next steps.
|
|
20
|
+
|
|
21
|
+
## [0.19.3] — 2026-04-19
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- `[CLI]` `agentdesk init` no longer silently saves tracker credentials that fail verification. Previously a typo in the email, a revoked API token, or a wrong tenant URL would print a one-line warning and proceed — leaving the user with a "valid-looking" `.env` that then failed at `agentdesk team` time with a generic "authentication failed" message. Init now prompts to re-enter credentials, save anyway, or cancel, so broken config is caught at the moment it's typed instead of the next time a session is started.
|
|
25
|
+
|
|
16
26
|
## [0.19.2] — 2026-04-18
|
|
17
27
|
|
|
18
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -47,6 +47,8 @@ A guided wizard walks you through it: picks up your project type, asks which tas
|
|
|
47
47
|
|
|
48
48
|
Re-running `agentdesk init` in a project that already has config offers a "tracker only" shortcut. Use `agentdesk init --quick` to skip the narrative copy for scripted setups.
|
|
49
49
|
|
|
50
|
+
**On a second machine** (repo cloned from a teammate's setup): run `agentdesk bootstrap` instead of `init`. It reads the committed `.agentdesk.json` for the project-wide settings (tracker type, team/project key, repo) and only prompts for per-machine secrets (Linear API key, Jira email + token, GitHub token) that end up in `.env`. The other machine's `.env` and the server config are left untouched.
|
|
51
|
+
|
|
50
52
|
`.agentdesk.json` is kept in sync with the server: every CLI run fetches the authoritative config and rewrites the local file as a credential-free snapshot. Credentials live only on the server, encrypted. If a local edit disagreed with the server's value, a one-line warning lists the overridden fields.
|
|
51
53
|
|
|
52
54
|
### 4. Run a team session
|
|
@@ -72,6 +74,7 @@ Watch the session live at [agentdesk.live](https://agentdesk.live). Each session
|
|
|
72
74
|
agentdesk login Sign in to AgentDesk
|
|
73
75
|
agentdesk logout Sign out and remove credentials
|
|
74
76
|
agentdesk init Set up project and configure tracker
|
|
77
|
+
agentdesk bootstrap Fill in per-machine tokens on a cloned repo
|
|
75
78
|
agentdesk team <TASK-ID> Run a team session on an existing task
|
|
76
79
|
agentdesk team -d "..." Describe what you want — task created automatically
|
|
77
80
|
agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark, nora)
|
package/bin/agentdesk.mjs
CHANGED
|
@@ -58,10 +58,16 @@ if (!command || command === "help" || command === "--help") {
|
|
|
58
58
|
2. agentdesk init Set up your project (choose tracker, save config)
|
|
59
59
|
3. agentdesk team TASK-123 Run a team session
|
|
60
60
|
|
|
61
|
+
On a freshly cloned repo (already set up on another machine):
|
|
62
|
+
1. agentdesk login Sign in
|
|
63
|
+
2. agentdesk bootstrap Fill in per-machine secrets (tokens)
|
|
64
|
+
3. agentdesk team TASK-123 Run a team session
|
|
65
|
+
|
|
61
66
|
Commands:
|
|
62
67
|
agentdesk login Sign in to AgentDesk
|
|
63
68
|
agentdesk logout Sign out and remove credentials
|
|
64
69
|
agentdesk init [--quick] Set up project and configure tracker
|
|
70
|
+
agentdesk bootstrap Fill in per-machine tokens on a cloned repo
|
|
65
71
|
agentdesk team <TASK-ID> Run a team session on an existing task
|
|
66
72
|
agentdesk team -d "..." Create a task and run a session
|
|
67
73
|
agentdesk <agent> -d "..." Run a single agent (jane, dennis, sam, bart, vera, luna, mark, nora)
|
|
@@ -146,6 +152,11 @@ else if (command === "init") {
|
|
|
146
152
|
await runInit(process.cwd(), { quick });
|
|
147
153
|
}
|
|
148
154
|
|
|
155
|
+
else if (command === "bootstrap") {
|
|
156
|
+
const { runBootstrap } = await import("../cli/bootstrap.mjs");
|
|
157
|
+
await runBootstrap(process.cwd());
|
|
158
|
+
}
|
|
159
|
+
|
|
149
160
|
else if (command === "team") {
|
|
150
161
|
// Parse options first to find -d, --cwd, --child-strategy
|
|
151
162
|
let description = "";
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// `agentdesk bootstrap` — fill in per-machine secrets after cloning a repo
|
|
2
|
+
// that's already been set up on a different machine.
|
|
3
|
+
//
|
|
4
|
+
// Intent: on a fresh clone, `.agentdesk.json` carries the project identity,
|
|
5
|
+
// tracker binding, and GitHub repo — everything that's project-wide. What's
|
|
6
|
+
// missing are the per-machine secrets that live in `.env` (LINEAR_API_KEY,
|
|
7
|
+
// JIRA_EMAIL/JIRA_API_TOKEN, GITHUB_TOKEN). Bootstrap detects which are
|
|
8
|
+
// missing or invalid and prompts only for those.
|
|
9
|
+
//
|
|
10
|
+
// Hard constraints:
|
|
11
|
+
// - Never write `.agentdesk.json` (besides the cached-server-state rewrite
|
|
12
|
+
// that loadConfig already does on every CLI run — we don't add new ones).
|
|
13
|
+
// - Never push to the server. Project-wide config on machine 1 is untouched.
|
|
14
|
+
// - Only writes to `.env`.
|
|
15
|
+
// - Idempotent. Running on a fully-configured machine prints "all set" and
|
|
16
|
+
// exits 0.
|
|
17
|
+
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import { createInterface } from "readline";
|
|
21
|
+
import { loadConfig } from "./config.mjs";
|
|
22
|
+
import { getStoredApiKey } from "./login.mjs";
|
|
23
|
+
import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
|
|
24
|
+
import { assertPushable, PreflightError } from "./session-preflight.mjs";
|
|
25
|
+
|
|
26
|
+
const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
|
|
27
|
+
|
|
28
|
+
function ask(rl, question) {
|
|
29
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function promptRequired(rl, label) {
|
|
33
|
+
while (true) {
|
|
34
|
+
const v = (await ask(rl, ` ${label}: `)).trim();
|
|
35
|
+
if (v) return v;
|
|
36
|
+
console.log(` ${label} is required. Ctrl+C to abort.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function saveEnvVar(dir, key, value) {
|
|
41
|
+
const envPath = join(dir, ".env");
|
|
42
|
+
let content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
|
|
43
|
+
const re = new RegExp(`^${key}=.*$`, "m");
|
|
44
|
+
if (re.test(content)) {
|
|
45
|
+
content = content.replace(re, `${key}=${value}`);
|
|
46
|
+
} else {
|
|
47
|
+
content += `${content && !content.endsWith("\n") ? "\n" : ""}${key}=${value}\n`;
|
|
48
|
+
}
|
|
49
|
+
writeFileSync(envPath, content);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function loadDotEnvLocal(dir) {
|
|
53
|
+
const envPath = join(dir, ".env");
|
|
54
|
+
const out = {};
|
|
55
|
+
if (!existsSync(envPath)) return out;
|
|
56
|
+
for (const line of readFileSync(envPath, "utf-8").split("\n")) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
59
|
+
const eq = trimmed.indexOf("=");
|
|
60
|
+
if (eq !== -1) out[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readLocalConfig(cwd) {
|
|
66
|
+
const path = join(cwd, ".agentdesk.json");
|
|
67
|
+
if (!existsSync(path)) return null;
|
|
68
|
+
try { return JSON.parse(readFileSync(path, "utf-8")); } catch { return null; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Compact step list — the init wizard carries the full narrative; bootstrap
|
|
72
|
+
// assumes the user has already seen it on their first machine.
|
|
73
|
+
function printTrackerHint(tracker) {
|
|
74
|
+
if (tracker === "linear") {
|
|
75
|
+
console.log(" (Get a Linear API key at https://linear.app/settings/api)");
|
|
76
|
+
} else if (tracker === "jira") {
|
|
77
|
+
console.log(" (Create a Jira API token at https://id.atlassian.com/manage-profile/security/api-tokens)");
|
|
78
|
+
} else if (tracker === "github") {
|
|
79
|
+
console.log(" (Create a GitHub token at https://github.com/settings/tokens — scope: repo)");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Walk missing tracker credentials and prompt for each. Writes to .env.
|
|
84
|
+
// Returns the merged credentials map so the caller can verify.
|
|
85
|
+
async function promptTrackerCreds(rl, cwd, tracker, existing) {
|
|
86
|
+
const creds = { ...existing };
|
|
87
|
+
if (tracker === "linear") {
|
|
88
|
+
if (!creds.LINEAR_API_KEY) {
|
|
89
|
+
console.log("");
|
|
90
|
+
printTrackerHint("linear");
|
|
91
|
+
const v = await promptRequired(rl, "Linear API key");
|
|
92
|
+
saveEnvVar(cwd, "LINEAR_API_KEY", v);
|
|
93
|
+
creds.LINEAR_API_KEY = v;
|
|
94
|
+
console.log(" ✓ Saved LINEAR_API_KEY to .env");
|
|
95
|
+
}
|
|
96
|
+
} else if (tracker === "jira") {
|
|
97
|
+
if (!creds.JIRA_EMAIL) {
|
|
98
|
+
console.log("");
|
|
99
|
+
printTrackerHint("jira");
|
|
100
|
+
const v = await promptRequired(rl, "Your Atlassian login email");
|
|
101
|
+
saveEnvVar(cwd, "JIRA_EMAIL", v);
|
|
102
|
+
creds.JIRA_EMAIL = v;
|
|
103
|
+
console.log(" ✓ Saved JIRA_EMAIL to .env");
|
|
104
|
+
}
|
|
105
|
+
if (!creds.JIRA_API_TOKEN) {
|
|
106
|
+
if (creds.JIRA_EMAIL) console.log("");
|
|
107
|
+
const v = await promptRequired(rl, "Jira API token");
|
|
108
|
+
saveEnvVar(cwd, "JIRA_API_TOKEN", v);
|
|
109
|
+
creds.JIRA_API_TOKEN = v;
|
|
110
|
+
console.log(" ✓ Saved JIRA_API_TOKEN to .env");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return creds;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function promptGitHubToken(rl, cwd) {
|
|
117
|
+
console.log("");
|
|
118
|
+
printTrackerHint("github");
|
|
119
|
+
const v = await promptRequired(rl, "GitHub token");
|
|
120
|
+
saveEnvVar(cwd, "GITHUB_TOKEN", v);
|
|
121
|
+
console.log(" ✓ Saved GITHUB_TOKEN to .env");
|
|
122
|
+
return v;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function selectOption(rl, prompt, options) {
|
|
126
|
+
console.log(` ${prompt}`);
|
|
127
|
+
console.log("");
|
|
128
|
+
options.forEach((opt, i) => console.log(` ${i + 1}) ${opt.label}`));
|
|
129
|
+
console.log("");
|
|
130
|
+
while (true) {
|
|
131
|
+
const answer = await ask(rl, ` Choose (1-${options.length}): `);
|
|
132
|
+
const num = parseInt(answer.trim(), 10);
|
|
133
|
+
if (num >= 1 && num <= options.length) return options[num - 1];
|
|
134
|
+
console.log(` Please enter a number between 1 and ${options.length}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function runBootstrap(cwd = process.cwd()) {
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log(" AgentDesk — Bootstrap this machine");
|
|
141
|
+
console.log(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
142
|
+
console.log("");
|
|
143
|
+
|
|
144
|
+
// 1. Must be logged in.
|
|
145
|
+
const apiKey = getStoredApiKey();
|
|
146
|
+
if (!apiKey) {
|
|
147
|
+
console.log(" Not logged in. Run `agentdesk login` first.");
|
|
148
|
+
console.log("");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2. Must have a project config committed in this repo.
|
|
153
|
+
const localConfig = readLocalConfig(cwd);
|
|
154
|
+
if (!localConfig) {
|
|
155
|
+
console.log(" No .agentdesk.json in this directory.");
|
|
156
|
+
console.log(" This command is for repos that were already set up on another machine.");
|
|
157
|
+
console.log(" For a new project, run: agentdesk init");
|
|
158
|
+
console.log("");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const projectKey = localConfig.projectKey;
|
|
163
|
+
if (!projectKey) {
|
|
164
|
+
console.log(" .agentdesk.json has no projectKey — run `agentdesk init` to repair.");
|
|
165
|
+
console.log("");
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 3. Load merged config (server-authoritative when reachable). This is the
|
|
170
|
+
// same call `agentdesk team` makes, so we see the same view.
|
|
171
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
172
|
+
const config = await loadConfig(cwd, { apiKey, serverUrl: SERVER, projectName: projectKey, silent: true });
|
|
173
|
+
|
|
174
|
+
const tracker = config.tracker || null;
|
|
175
|
+
const repo = config.github?.repo || null;
|
|
176
|
+
const login = config.github?.login || null;
|
|
177
|
+
|
|
178
|
+
console.log(` Project: ${projectKey}`);
|
|
179
|
+
console.log(` Tracker: ${tracker || "none"}`);
|
|
180
|
+
if (tracker === "linear" && config.linear?.teamKey) console.log(` Team: ${config.linear.teamKey}`);
|
|
181
|
+
if (tracker === "jira" && config.jira?.project) console.log(` Project: ${config.jira.project}`);
|
|
182
|
+
if (repo) console.log(` Repo: ${repo}${login ? ` (@${login})` : ""}`);
|
|
183
|
+
console.log("");
|
|
184
|
+
|
|
185
|
+
// 4. Gap scan + prompt loop. We retry tracker + github verification up to
|
|
186
|
+
// three times, matching init's retry/skip/cancel UX.
|
|
187
|
+
let creds = resolveCredentialsFromEnv(loadDotEnvLocal(cwd));
|
|
188
|
+
let trackerOk = !tracker; // no tracker → nothing to verify
|
|
189
|
+
let githubOk = false;
|
|
190
|
+
let attempts = 0;
|
|
191
|
+
|
|
192
|
+
while (attempts < 5) {
|
|
193
|
+
attempts += 1;
|
|
194
|
+
|
|
195
|
+
// Tracker
|
|
196
|
+
if (tracker && !trackerOk) {
|
|
197
|
+
const missing =
|
|
198
|
+
(tracker === "linear" && !creds.LINEAR_API_KEY) ||
|
|
199
|
+
(tracker === "jira" && (!creds.JIRA_EMAIL || !creds.JIRA_API_TOKEN));
|
|
200
|
+
if (missing) {
|
|
201
|
+
creds = await promptTrackerCreds(rl, cwd, tracker, creds);
|
|
202
|
+
}
|
|
203
|
+
process.stdout.write(" Verifying tracker access... ");
|
|
204
|
+
const check = await checkTrackerPermissions({ tracker, config, credentials: creds });
|
|
205
|
+
if (check.ok) {
|
|
206
|
+
console.log("✓");
|
|
207
|
+
if (check.identity?.name || check.identity?.email) {
|
|
208
|
+
const label = check.identity.name || check.identity.login || check.identity.email;
|
|
209
|
+
console.log(` ✓ Posting as: ${label}`);
|
|
210
|
+
}
|
|
211
|
+
trackerOk = true;
|
|
212
|
+
} else {
|
|
213
|
+
console.log("failed");
|
|
214
|
+
for (const e of check.errors || []) console.log(` • ${e}`);
|
|
215
|
+
console.log("");
|
|
216
|
+
const next = await selectOption(rl, "What now?", [
|
|
217
|
+
{ label: "Re-enter credentials", value: "retry" },
|
|
218
|
+
{ label: "Skip tracker (fix later in .env)", value: "skip" },
|
|
219
|
+
{ label: "Cancel", value: "cancel" },
|
|
220
|
+
]);
|
|
221
|
+
console.log("");
|
|
222
|
+
if (next.value === "cancel") { rl.close(); process.exit(1); }
|
|
223
|
+
if (next.value === "skip") { trackerOk = true; break; }
|
|
224
|
+
// retry: clear the tracker-specific creds so the next loop re-prompts
|
|
225
|
+
if (tracker === "linear") delete creds.LINEAR_API_KEY;
|
|
226
|
+
if (tracker === "jira") { delete creds.JIRA_EMAIL; delete creds.JIRA_API_TOKEN; }
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// GitHub — always required (agents push code regardless of tracker)
|
|
232
|
+
if (!githubOk) {
|
|
233
|
+
if (!creds.GITHUB_TOKEN) {
|
|
234
|
+
creds.GITHUB_TOKEN = await promptGitHubToken(rl, cwd);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
assertPushable({ cwd, creds: { GITHUB_TOKEN: creds.GITHUB_TOKEN }, projectName: projectKey });
|
|
238
|
+
// Best-effort gh API check to confirm the token is valid and matches
|
|
239
|
+
// the configured login. Mirrors init.mjs:495-506.
|
|
240
|
+
try {
|
|
241
|
+
const { execSync } = await import("child_process");
|
|
242
|
+
const env = { ...process.env, GH_TOKEN: creds.GITHUB_TOKEN };
|
|
243
|
+
const who = execSync("gh api user --jq .login", { env, stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
|
|
244
|
+
if (who && login && who.toLowerCase() !== login.toLowerCase()) {
|
|
245
|
+
console.log(` ⚠ GitHub token authenticates as @${who}, not @${login} (from .agentdesk.json). Agents will post as @${who}.`);
|
|
246
|
+
} else if (who) {
|
|
247
|
+
console.log(` ✓ GitHub verified as @${who}`);
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
console.log(" ✓ GITHUB_TOKEN present (couldn't verify via gh — install gh CLI for identity check)");
|
|
251
|
+
}
|
|
252
|
+
githubOk = true;
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (err instanceof PreflightError) {
|
|
255
|
+
console.log(` ✗ ${err.message}`);
|
|
256
|
+
console.log("");
|
|
257
|
+
const next = await selectOption(rl, "What now?", [
|
|
258
|
+
{ label: "Re-enter GITHUB_TOKEN", value: "retry" },
|
|
259
|
+
{ label: "Skip (sessions will fail until fixed)", value: "skip" },
|
|
260
|
+
{ label: "Cancel", value: "cancel" },
|
|
261
|
+
]);
|
|
262
|
+
console.log("");
|
|
263
|
+
if (next.value === "cancel") { rl.close(); process.exit(1); }
|
|
264
|
+
if (next.value === "skip") { githubOk = true; break; }
|
|
265
|
+
delete creds.GITHUB_TOKEN;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (trackerOk && githubOk) break;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
rl.close();
|
|
276
|
+
|
|
277
|
+
console.log("");
|
|
278
|
+
if (trackerOk && githubOk) {
|
|
279
|
+
console.log(" Ready. Try:");
|
|
280
|
+
const prefix = config.linear?.teamKey || config.jira?.project || projectKey.toUpperCase();
|
|
281
|
+
console.log(` agentdesk team ${prefix}-123`);
|
|
282
|
+
console.log("");
|
|
283
|
+
} else {
|
|
284
|
+
console.log(" Bootstrap finished with missing pieces — edit .env directly or re-run.");
|
|
285
|
+
console.log("");
|
|
286
|
+
}
|
|
287
|
+
}
|
package/cli/init.mjs
CHANGED
|
@@ -406,16 +406,54 @@ export async function runInit(cwd, opts = {}) {
|
|
|
406
406
|
config.github = { repo, login };
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
// Verify tracker + fetch available projects to pick from.
|
|
409
|
+
// Verify tracker + fetch available projects to pick from. If verification
|
|
410
|
+
// fails (bad email, revoked token, typo in base URL), loop back and let the
|
|
411
|
+
// user re-enter credentials rather than silently saving broken config.
|
|
410
412
|
let trackerLocation = null;
|
|
411
413
|
if (tracker === "jira") trackerLocation = config.jira?.baseUrl;
|
|
414
|
+
let verified = { ok: true };
|
|
412
415
|
if (tracker) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
console.log(" You can re-run `agentdesk init` after fixing credentials.");
|
|
416
|
+
while (true) {
|
|
417
|
+
verified = await verifyAndPickTrackerProject({ rl, cwd, finalProjectKey, tracker, config, location: trackerLocation });
|
|
418
|
+
if (verified.ok) break;
|
|
417
419
|
console.log("");
|
|
418
|
-
|
|
420
|
+
const next = await selectOption(rl, "What now?", [
|
|
421
|
+
{ label: "Re-enter credentials and try again", value: "retry" },
|
|
422
|
+
{ label: "Save config anyway (will need to fix before `agentdesk team`)", value: "skip" },
|
|
423
|
+
{ label: "Cancel init", value: "cancel" },
|
|
424
|
+
]);
|
|
425
|
+
console.log("");
|
|
426
|
+
if (next.value === "cancel") {
|
|
427
|
+
rl.close();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (next.value === "skip") break;
|
|
431
|
+
// Retry: re-prompt only the tracker auth fields, not the whole wizard.
|
|
432
|
+
if (tracker === "linear") {
|
|
433
|
+
const key = await promptRequired(rl, "Linear API key");
|
|
434
|
+
saveEnvVar(cwd, "LINEAR_API_KEY", key);
|
|
435
|
+
console.log(" ✓ Updated LINEAR_API_KEY in .env");
|
|
436
|
+
console.log("");
|
|
437
|
+
} else if (tracker === "jira") {
|
|
438
|
+
const baseUrl = (await ask(rl, ` Jira tenant URL [${config.jira?.baseUrl || ""}]: `)).trim() || config.jira?.baseUrl;
|
|
439
|
+
const email = await promptRequired(rl, "Your Atlassian login email");
|
|
440
|
+
const token = await promptRequired(rl, "Jira API token");
|
|
441
|
+
saveEnvVar(cwd, "JIRA_EMAIL", email);
|
|
442
|
+
saveEnvVar(cwd, "JIRA_API_TOKEN", token);
|
|
443
|
+
if (baseUrl) {
|
|
444
|
+
config.jira = { ...(config.jira || {}), baseUrl: baseUrl.replace(/\/+$/, "") };
|
|
445
|
+
trackerLocation = config.jira.baseUrl;
|
|
446
|
+
}
|
|
447
|
+
console.log(" ✓ Updated JIRA_EMAIL and JIRA_API_TOKEN in .env");
|
|
448
|
+
console.log("");
|
|
449
|
+
} else if (tracker === "github") {
|
|
450
|
+
const token = await promptRequired(rl, "GitHub token");
|
|
451
|
+
saveEnvVar(cwd, "GITHUB_TOKEN", token);
|
|
452
|
+
console.log(" ✓ Updated GITHUB_TOKEN in .env");
|
|
453
|
+
console.log("");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (verified.ok && verified.trackerProjectId) {
|
|
419
457
|
if (tracker === "linear") config.linear = { ...(config.linear || {}), teamKey: verified.trackerProjectId };
|
|
420
458
|
if (tracker === "jira") config.jira = { ...(config.jira || {}), project: verified.trackerProjectId };
|
|
421
459
|
// Use the tracker's project key as the default agentdesk project key.
|
package/cli/login.mjs
CHANGED
|
@@ -114,7 +114,8 @@ export async function runLogin() {
|
|
|
114
114
|
console.log(` API key saved to ~/.agentdesk/credentials.json`);
|
|
115
115
|
console.log("");
|
|
116
116
|
console.log(" Next steps:");
|
|
117
|
-
console.log(" agentdesk init");
|
|
117
|
+
console.log(" agentdesk init (new project on this machine)");
|
|
118
|
+
console.log(" agentdesk bootstrap (repo cloned from another machine)");
|
|
118
119
|
console.log(" agentdesk team TASK-123");
|
|
119
120
|
console.log("");
|
|
120
121
|
|