@kendoo.agentdesk/agentdesk 0.9.7 → 0.9.9

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.
Files changed (2) hide show
  1. package/cli/init.mjs +219 -54
  2. package/package.json +1 -1
package/cli/init.mjs CHANGED
@@ -38,6 +38,44 @@ async function selectOption(rl, prompt, options) {
38
38
  }
39
39
  }
40
40
 
41
+ function saveEnvVar(dir, key, value) {
42
+ const envPath = join(dir, ".env");
43
+ let content = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
44
+ const re = new RegExp(`^${key}=.*$`, "m");
45
+ if (re.test(content)) {
46
+ content = content.replace(re, `${key}=${value}`);
47
+ } else {
48
+ content += `${content && !content.endsWith("\n") ? "\n" : ""}${key}=${value}\n`;
49
+ }
50
+ writeFileSync(envPath, content);
51
+ }
52
+
53
+ function loadDotEnv(dir) {
54
+ const envPath = join(dir, ".env");
55
+ const dotEnv = {};
56
+ if (existsSync(envPath)) {
57
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed || trimmed.startsWith("#")) continue;
60
+ const eq = trimmed.indexOf("=");
61
+ if (eq !== -1) dotEnv[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
62
+ }
63
+ }
64
+ return dotEnv;
65
+ }
66
+
67
+ async function fetchServerCreds(apiKey, projectKey) {
68
+ if (!apiKey) return {};
69
+ try {
70
+ const res = await fetch(`${SERVER}/api/projects/${projectKey}/settings/credentials`, {
71
+ headers: { "x-api-key": apiKey },
72
+ signal: AbortSignal.timeout(5000),
73
+ });
74
+ if (res.ok) return await res.json();
75
+ } catch {}
76
+ return {};
77
+ }
78
+
41
79
  export async function runInit(cwd) {
42
80
  const project = detectProject(cwd);
43
81
  const existingConfig = loadConfig(cwd);
@@ -63,13 +101,7 @@ export async function runInit(cwd) {
63
101
  if (project.lintCommand) console.log(` Lint: ${project.lintCommand}`);
64
102
  if (project.testCommand || project.buildCommand || project.lintCommand) console.log("");
65
103
 
66
- // --- Project key ---
67
- const defaultKey = existingConfig.projectKey || projectId;
68
- const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
69
- const finalProjectKey = keyAnswer.trim() || defaultKey;
70
- console.log("");
71
-
72
- // --- Tracker selection ---
104
+ // --- Step 1: Tracker selection (platform first) ---
73
105
  const trackerOptions = [
74
106
  { label: "Linear", value: "linear" },
75
107
  { label: "Jira", value: "jira" },
@@ -81,17 +113,17 @@ export async function runInit(cwd) {
81
113
  const tracker = selected.value;
82
114
  console.log("");
83
115
 
84
- // Build config
85
- const config = { projectKey: finalProjectKey };
116
+ // --- Step 2: Tracker configuration (details + credentials) ---
117
+ const config = {};
86
118
  if (tracker) config.tracker = tracker;
87
119
 
88
120
  if (tracker === "linear") {
89
121
  const currentWs = existingConfig.linear?.workspace || "";
90
122
  const wsAnswer = await ask(rl, ` Linear workspace slug${currentWs ? ` (${currentWs})` : ""}: `);
91
123
  const ws = wsAnswer.trim() || currentWs;
92
- const currentKey = existingConfig.linear?.teamKey || "";
93
- const keyAnswer = await ask(rl, ` Linear team key${currentKey ? ` (${currentKey})` : ""} (e.g. KEN): `);
94
- const teamKey = keyAnswer.trim() || currentKey;
124
+ const currentTeamKey = existingConfig.linear?.teamKey || "";
125
+ const tkAnswer = await ask(rl, ` Linear team key${currentTeamKey ? ` (${currentTeamKey})` : ""} (e.g. KEN): `);
126
+ const teamKey = tkAnswer.trim() || currentTeamKey;
95
127
  if (ws || teamKey) config.linear = { ...(ws && { workspace: ws }), ...(teamKey && { teamKey }) };
96
128
  console.log("");
97
129
  }
@@ -115,56 +147,189 @@ export async function runInit(cwd) {
115
147
  console.log("");
116
148
  }
117
149
 
118
- // --- Verify tracker permissions ---
150
+ // --- Step 3: Verify tracker permissions (with re-prompt on failure) ---
119
151
  if (tracker) {
120
- console.log(" Checking tracker permissions...");
121
- const envPath = join(cwd, ".env");
122
- let dotEnv = {};
123
- if (existsSync(envPath)) {
124
- for (const line of readFileSync(envPath, "utf-8").split("\n")) {
125
- const trimmed = line.trim();
126
- if (!trimmed || trimmed.startsWith("#")) continue;
127
- const eq = trimmed.indexOf("=");
128
- if (eq !== -1) dotEnv[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
129
- }
130
- }
152
+ let verified = false;
153
+ while (!verified) {
154
+ console.log(" Checking tracker permissions...");
155
+ const dotEnv = loadDotEnv(cwd);
156
+ const apiKey = loadApiKey(cwd);
157
+ const defaultKey = existingConfig.projectKey || projectId;
158
+ const serverCreds = await fetchServerCreds(apiKey, defaultKey);
159
+ const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
160
+ const check = await checkTrackerPermissions({ tracker, config, credentials });
131
161
 
132
- // Also try fetching credentials from server
133
- const apiKey = loadApiKey(cwd);
134
- let serverCreds = {};
135
- if (apiKey) {
136
- try {
137
- const res = await fetch(`${SERVER}/api/projects/${finalProjectKey}/settings/credentials`, {
138
- headers: { "x-api-key": apiKey },
139
- signal: AbortSignal.timeout(5000),
140
- });
141
- if (res.ok) serverCreds = await res.json();
142
- } catch {}
143
- }
162
+ if (check.ok) {
163
+ console.log(" ✓ Tracker permissions verified (read, create, update)");
164
+ console.log("");
165
+ verified = true;
166
+ } else {
167
+ console.log("");
168
+ console.log(" ⚠ Tracker permission issues:");
169
+ for (const err of check.errors) {
170
+ console.log(` • ${err}`);
171
+ }
172
+ console.log("");
144
173
 
145
- const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
146
- const check = await checkTrackerPermissions({ tracker, config, credentials });
174
+ // Determine what's missing and offer to fix it
175
+ const missingCreds = check.errors.some(e =>
176
+ e.includes("Missing") && (e.includes("API_KEY") || e.includes("TOKEN") || e.includes("EMAIL"))
177
+ );
147
178
 
148
- if (!check.ok) {
149
- console.log("");
150
- console.log(" ⚠ Tracker permission issues:");
151
- for (const err of check.errors) {
152
- console.log(` • ${err}`);
153
- }
154
- console.log("");
155
- const proceed = await ask(rl, " Continue anyway? (y/N): ");
156
- if (proceed.trim().toLowerCase() !== "y") {
157
- console.log(" Setup cancelled. Fix the issues above and run 'agentdesk init' again.");
158
- rl.close();
159
- return;
179
+ if (missingCreds) {
180
+ console.log(" Let's fix this now. I'll walk you through it.");
181
+ console.log("");
182
+
183
+ if (tracker === "linear") {
184
+ console.log(" ┌─────────────────────────────────────────────┐");
185
+ console.log(" │ How to get your Linear API key: │");
186
+ console.log(" │ │");
187
+ console.log(" │ 1. Open https://linear.app/settings/api │");
188
+ console.log(" │ 2. Click \"Create new API key\" │");
189
+ console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
190
+ console.log(" │ 4. Copy the key │");
191
+ console.log(" │ 5. Paste it below │");
192
+ console.log(" └─────────────────────────────────────────────┘");
193
+ console.log("");
194
+
195
+ while (true) {
196
+ const keyInput = await ask(rl, " Linear API key: ");
197
+ const key = keyInput.trim();
198
+ if (!key) {
199
+ console.log(" The API key is required for the agents to read and update your Linear tasks.");
200
+ console.log("");
201
+ continue;
202
+ }
203
+ saveEnvVar(cwd, "LINEAR_API_KEY", key);
204
+ console.log(" ✓ Saved to .env");
205
+ console.log("");
206
+ break;
207
+ }
208
+ continue; // Re-check with the new key
209
+ }
210
+
211
+ if (tracker === "jira") {
212
+ console.log(" ┌──────────────────────────────────────────────────────────┐");
213
+ console.log(" │ How to get your Jira API token: │");
214
+ console.log(" │ │");
215
+ console.log(" │ 1. Open https://id.atlassian.com/manage-profile/ │");
216
+ console.log(" │ security/api-tokens │");
217
+ console.log(" │ 2. Click \"Create API token\" │");
218
+ console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
219
+ console.log(" │ 4. Copy the token │");
220
+ console.log(" │ │");
221
+ console.log(" │ You'll also need the email address you use │");
222
+ console.log(" │ to log into Jira (not your username). │");
223
+ console.log(" └──────────────────────────────────────────────────────────┘");
224
+ console.log("");
225
+
226
+ if (!credentials.JIRA_EMAIL) {
227
+ while (true) {
228
+ const emailInput = (await ask(rl, " Your Jira email address: ")).trim();
229
+ if (!emailInput || !emailInput.includes("@")) {
230
+ console.log(" Please enter a valid email address (the one you use to log into Jira).");
231
+ continue;
232
+ }
233
+ saveEnvVar(cwd, "JIRA_EMAIL", emailInput);
234
+ console.log(" ✓ Email saved");
235
+ break;
236
+ }
237
+ }
238
+
239
+ while (true) {
240
+ const tokenInput = (await ask(rl, " Jira API token: ")).trim();
241
+ if (!tokenInput) {
242
+ console.log(" The API token is required for the agents to read and update your Jira tasks.");
243
+ console.log("");
244
+ continue;
245
+ }
246
+ saveEnvVar(cwd, "JIRA_API_TOKEN", tokenInput);
247
+ console.log(" ✓ Token saved to .env");
248
+ console.log("");
249
+ break;
250
+ }
251
+ continue; // Re-check
252
+ }
253
+
254
+ if (tracker === "github") {
255
+ console.log(" ┌──────────────────────────────────────────────────────────┐");
256
+ console.log(" │ Two ways to authenticate with GitHub: │");
257
+ console.log(" │ │");
258
+ console.log(" │ Option A — GitHub CLI (recommended): │");
259
+ console.log(" │ Run: gh auth login │");
260
+ console.log(" │ Then press Enter below. │");
261
+ console.log(" │ │");
262
+ console.log(" │ Option B — Personal Access Token: │");
263
+ console.log(" │ 1. Open https://github.com/settings/tokens │");
264
+ console.log(" │ 2. Click \"Generate new token (classic)\" │");
265
+ console.log(" │ 3. Select the \"repo\" scope │");
266
+ console.log(" │ 4. Generate and copy the token │");
267
+ console.log(" │ 5. Paste it below │");
268
+ console.log(" └──────────────────────────────────────────────────────────┘");
269
+ console.log("");
270
+
271
+ const tokenInput = await ask(rl, " GitHub token (or Enter if using gh CLI): ");
272
+ const token = tokenInput.trim();
273
+ if (token) {
274
+ saveEnvVar(cwd, "GITHUB_TOKEN", token);
275
+ console.log(" ✓ Token saved to .env");
276
+ console.log("");
277
+ } else {
278
+ console.log(" OK, will check gh CLI authentication...");
279
+ console.log("");
280
+ }
281
+ continue; // Re-check
282
+ }
283
+ }
284
+
285
+ // Non-credential error (wrong team key, wrong URL, network issue, etc.)
286
+ console.log("");
287
+ const choice = await selectOption(rl, "What would you like to do?", [
288
+ { label: "Fix it and try again", value: "retry" },
289
+ { label: "Continue without tracker verification (fix later in dashboard)", value: "skip" },
290
+ { label: "Choose a different tracker platform", value: "change" },
291
+ ]);
292
+
293
+ if (choice.value === "retry") {
294
+ // Re-prompt for the tracker details that might be wrong
295
+ if (tracker === "linear" && config.linear) {
296
+ const wsAnswer = await ask(rl, ` Linear workspace slug (${config.linear.workspace || ""}): `);
297
+ if (wsAnswer.trim()) config.linear.workspace = wsAnswer.trim();
298
+ const tkAnswer = await ask(rl, ` Linear team key (${config.linear.teamKey || ""}): `);
299
+ if (tkAnswer.trim()) config.linear.teamKey = tkAnswer.trim();
300
+ }
301
+ if (tracker === "jira" && config.jira) {
302
+ const urlAnswer = await ask(rl, ` Jira base URL (${config.jira.baseUrl || ""}): `);
303
+ if (urlAnswer.trim()) config.jira.baseUrl = urlAnswer.trim();
304
+ const projAnswer = await ask(rl, ` Jira project key (${config.jira.project || ""}): `);
305
+ if (projAnswer.trim()) config.jira.project = projAnswer.trim();
306
+ }
307
+ if (tracker === "github" && config.github) {
308
+ const repoAnswer = await ask(rl, ` GitHub repo (${config.github.repo || ""}): `);
309
+ if (repoAnswer.trim()) config.github.repo = repoAnswer.trim();
310
+ }
311
+ console.log("");
312
+ continue;
313
+ } else if (choice.value === "change") {
314
+ rl.close();
315
+ return runInit(cwd);
316
+ } else {
317
+ console.log("");
318
+ console.log(" Continuing without tracker verification.");
319
+ console.log(" You can configure credentials later at agentdesk.live or re-run 'agentdesk init'.");
320
+ console.log("");
321
+ verified = true;
322
+ }
160
323
  }
161
- console.log("");
162
- } else {
163
- console.log(" ✓ Tracker permissions verified (read, create, update)");
164
- console.log("");
165
324
  }
166
325
  }
167
326
 
327
+ // --- Step 4: Project key ---
328
+ const defaultKey = existingConfig.projectKey || projectId;
329
+ const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
330
+ const finalProjectKey = keyAnswer.trim() || defaultKey;
331
+ console.log("");
332
+
168
333
  // --- Save .agentdesk.json ---
169
334
  let merged = {};
170
335
  if (hasConfig) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {