@kendoo.agentdesk/agentdesk 0.9.8 → 0.9.10
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/cli/init.mjs +216 -51
- 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,13 @@ 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 ---
|
|
104
|
+
// --- Step 1: Project key ---
|
|
67
105
|
const defaultKey = existingConfig.projectKey || projectId;
|
|
68
106
|
const keyAnswer = await ask(rl, ` Project key (${defaultKey}): `);
|
|
69
107
|
const finalProjectKey = keyAnswer.trim() || defaultKey;
|
|
70
108
|
console.log("");
|
|
71
109
|
|
|
72
|
-
// --- Tracker selection ---
|
|
110
|
+
// --- Step 2: Tracker selection (platform first) ---
|
|
73
111
|
const trackerOptions = [
|
|
74
112
|
{ label: "Linear", value: "linear" },
|
|
75
113
|
{ label: "Jira", value: "jira" },
|
|
@@ -81,17 +119,17 @@ export async function runInit(cwd) {
|
|
|
81
119
|
const tracker = selected.value;
|
|
82
120
|
console.log("");
|
|
83
121
|
|
|
84
|
-
//
|
|
85
|
-
const config = {
|
|
122
|
+
// --- Step 2: Tracker configuration (details + credentials) ---
|
|
123
|
+
const config = {};
|
|
86
124
|
if (tracker) config.tracker = tracker;
|
|
87
125
|
|
|
88
126
|
if (tracker === "linear") {
|
|
89
127
|
const currentWs = existingConfig.linear?.workspace || "";
|
|
90
128
|
const wsAnswer = await ask(rl, ` Linear workspace slug${currentWs ? ` (${currentWs})` : ""}: `);
|
|
91
129
|
const ws = wsAnswer.trim() || currentWs;
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const teamKey =
|
|
130
|
+
const currentTeamKey = existingConfig.linear?.teamKey || "";
|
|
131
|
+
const tkAnswer = await ask(rl, ` Linear team key${currentTeamKey ? ` (${currentTeamKey})` : ""} (e.g. KEN): `);
|
|
132
|
+
const teamKey = tkAnswer.trim() || currentTeamKey;
|
|
95
133
|
if (ws || teamKey) config.linear = { ...(ws && { workspace: ws }), ...(teamKey && { teamKey }) };
|
|
96
134
|
console.log("");
|
|
97
135
|
}
|
|
@@ -115,53 +153,179 @@ export async function runInit(cwd) {
|
|
|
115
153
|
console.log("");
|
|
116
154
|
}
|
|
117
155
|
|
|
118
|
-
// --- Verify tracker permissions ---
|
|
156
|
+
// --- Step 3: Verify tracker permissions (with re-prompt on failure) ---
|
|
119
157
|
if (tracker) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (eq !== -1) dotEnv[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
158
|
+
let verified = false;
|
|
159
|
+
while (!verified) {
|
|
160
|
+
console.log(" Checking tracker permissions...");
|
|
161
|
+
const dotEnv = loadDotEnv(cwd);
|
|
162
|
+
const apiKey = loadApiKey(cwd);
|
|
163
|
+
const serverCreds = await fetchServerCreds(apiKey, finalProjectKey);
|
|
164
|
+
const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
|
|
165
|
+
const check = await checkTrackerPermissions({ tracker, config, credentials });
|
|
131
166
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
167
|
+
if (check.ok) {
|
|
168
|
+
console.log(" ✓ Tracker permissions verified (read, create, update)");
|
|
169
|
+
console.log("");
|
|
170
|
+
verified = true;
|
|
171
|
+
} else {
|
|
172
|
+
console.log("");
|
|
173
|
+
console.log(" ⚠ Tracker permission issues:");
|
|
174
|
+
for (const err of check.errors) {
|
|
175
|
+
console.log(` • ${err}`);
|
|
176
|
+
}
|
|
177
|
+
console.log("");
|
|
144
178
|
|
|
145
|
-
|
|
146
|
-
|
|
179
|
+
// Determine what's missing and offer to fix it
|
|
180
|
+
const missingCreds = check.errors.some(e =>
|
|
181
|
+
e.includes("Missing") && (e.includes("API_KEY") || e.includes("TOKEN") || e.includes("EMAIL"))
|
|
182
|
+
);
|
|
147
183
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
if (missingCreds) {
|
|
185
|
+
console.log(" Let's fix this now. I'll walk you through it.");
|
|
186
|
+
console.log("");
|
|
187
|
+
|
|
188
|
+
if (tracker === "linear") {
|
|
189
|
+
console.log(" ┌─────────────────────────────────────────────┐");
|
|
190
|
+
console.log(" │ How to get your Linear API key: │");
|
|
191
|
+
console.log(" │ │");
|
|
192
|
+
console.log(" │ 1. Open https://linear.app/settings/api │");
|
|
193
|
+
console.log(" │ 2. Click \"Create new API key\" │");
|
|
194
|
+
console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
|
|
195
|
+
console.log(" │ 4. Copy the key │");
|
|
196
|
+
console.log(" │ 5. Paste it below │");
|
|
197
|
+
console.log(" └─────────────────────────────────────────────┘");
|
|
198
|
+
console.log("");
|
|
199
|
+
|
|
200
|
+
while (true) {
|
|
201
|
+
const keyInput = await ask(rl, " Linear API key: ");
|
|
202
|
+
const key = keyInput.trim();
|
|
203
|
+
if (!key) {
|
|
204
|
+
console.log(" The API key is required for the agents to read and update your Linear tasks.");
|
|
205
|
+
console.log("");
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
saveEnvVar(cwd, "LINEAR_API_KEY", key);
|
|
209
|
+
console.log(" ✓ Saved to .env");
|
|
210
|
+
console.log("");
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
continue; // Re-check with the new key
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (tracker === "jira") {
|
|
217
|
+
console.log(" ┌──────────────────────────────────────────────────────────┐");
|
|
218
|
+
console.log(" │ How to get your Jira API token: │");
|
|
219
|
+
console.log(" │ │");
|
|
220
|
+
console.log(" │ 1. Open https://id.atlassian.com/manage-profile/ │");
|
|
221
|
+
console.log(" │ security/api-tokens │");
|
|
222
|
+
console.log(" │ 2. Click \"Create API token\" │");
|
|
223
|
+
console.log(" │ 3. Give it a label (e.g. \"AgentDesk\") │");
|
|
224
|
+
console.log(" │ 4. Copy the token │");
|
|
225
|
+
console.log(" │ │");
|
|
226
|
+
console.log(" │ You'll also need the email address you use │");
|
|
227
|
+
console.log(" │ to log into Jira (not your username). │");
|
|
228
|
+
console.log(" └──────────────────────────────────────────────────────────┘");
|
|
229
|
+
console.log("");
|
|
230
|
+
|
|
231
|
+
if (!credentials.JIRA_EMAIL) {
|
|
232
|
+
while (true) {
|
|
233
|
+
const emailInput = (await ask(rl, " Your Jira email address: ")).trim();
|
|
234
|
+
if (!emailInput || !emailInput.includes("@")) {
|
|
235
|
+
console.log(" Please enter a valid email address (the one you use to log into Jira).");
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
saveEnvVar(cwd, "JIRA_EMAIL", emailInput);
|
|
239
|
+
console.log(" ✓ Email saved");
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
while (true) {
|
|
245
|
+
const tokenInput = (await ask(rl, " Jira API token: ")).trim();
|
|
246
|
+
if (!tokenInput) {
|
|
247
|
+
console.log(" The API token is required for the agents to read and update your Jira tasks.");
|
|
248
|
+
console.log("");
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
saveEnvVar(cwd, "JIRA_API_TOKEN", tokenInput);
|
|
252
|
+
console.log(" ✓ Token saved to .env");
|
|
253
|
+
console.log("");
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
continue; // Re-check
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (tracker === "github") {
|
|
260
|
+
console.log(" ┌──────────────────────────────────────────────────────────┐");
|
|
261
|
+
console.log(" │ Two ways to authenticate with GitHub: │");
|
|
262
|
+
console.log(" │ │");
|
|
263
|
+
console.log(" │ Option A — GitHub CLI (recommended): │");
|
|
264
|
+
console.log(" │ Run: gh auth login │");
|
|
265
|
+
console.log(" │ Then press Enter below. │");
|
|
266
|
+
console.log(" │ │");
|
|
267
|
+
console.log(" │ Option B — Personal Access Token: │");
|
|
268
|
+
console.log(" │ 1. Open https://github.com/settings/tokens │");
|
|
269
|
+
console.log(" │ 2. Click \"Generate new token (classic)\" │");
|
|
270
|
+
console.log(" │ 3. Select the \"repo\" scope │");
|
|
271
|
+
console.log(" │ 4. Generate and copy the token │");
|
|
272
|
+
console.log(" │ 5. Paste it below │");
|
|
273
|
+
console.log(" └──────────────────────────────────────────────────────────┘");
|
|
274
|
+
console.log("");
|
|
275
|
+
|
|
276
|
+
const tokenInput = await ask(rl, " GitHub token (or Enter if using gh CLI): ");
|
|
277
|
+
const token = tokenInput.trim();
|
|
278
|
+
if (token) {
|
|
279
|
+
saveEnvVar(cwd, "GITHUB_TOKEN", token);
|
|
280
|
+
console.log(" ✓ Token saved to .env");
|
|
281
|
+
console.log("");
|
|
282
|
+
} else {
|
|
283
|
+
console.log(" OK, will check gh CLI authentication...");
|
|
284
|
+
console.log("");
|
|
285
|
+
}
|
|
286
|
+
continue; // Re-check
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Non-credential error (wrong team key, wrong URL, network issue, etc.)
|
|
291
|
+
console.log("");
|
|
292
|
+
const choice = await selectOption(rl, "What would you like to do?", [
|
|
293
|
+
{ label: "Fix it and try again", value: "retry" },
|
|
294
|
+
{ label: "Continue without tracker verification (fix later in dashboard)", value: "skip" },
|
|
295
|
+
{ label: "Choose a different tracker platform", value: "change" },
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
if (choice.value === "retry") {
|
|
299
|
+
// Re-prompt for the tracker details that might be wrong
|
|
300
|
+
if (tracker === "linear" && config.linear) {
|
|
301
|
+
const wsAnswer = await ask(rl, ` Linear workspace slug (${config.linear.workspace || ""}): `);
|
|
302
|
+
if (wsAnswer.trim()) config.linear.workspace = wsAnswer.trim();
|
|
303
|
+
const tkAnswer = await ask(rl, ` Linear team key (${config.linear.teamKey || ""}): `);
|
|
304
|
+
if (tkAnswer.trim()) config.linear.teamKey = tkAnswer.trim();
|
|
305
|
+
}
|
|
306
|
+
if (tracker === "jira" && config.jira) {
|
|
307
|
+
const urlAnswer = await ask(rl, ` Jira base URL (${config.jira.baseUrl || ""}): `);
|
|
308
|
+
if (urlAnswer.trim()) config.jira.baseUrl = urlAnswer.trim();
|
|
309
|
+
const projAnswer = await ask(rl, ` Jira project key (${config.jira.project || ""}): `);
|
|
310
|
+
if (projAnswer.trim()) config.jira.project = projAnswer.trim();
|
|
311
|
+
}
|
|
312
|
+
if (tracker === "github" && config.github) {
|
|
313
|
+
const repoAnswer = await ask(rl, ` GitHub repo (${config.github.repo || ""}): `);
|
|
314
|
+
if (repoAnswer.trim()) config.github.repo = repoAnswer.trim();
|
|
315
|
+
}
|
|
316
|
+
console.log("");
|
|
317
|
+
continue;
|
|
318
|
+
} else if (choice.value === "change") {
|
|
319
|
+
rl.close();
|
|
320
|
+
return runInit(cwd);
|
|
321
|
+
} else {
|
|
322
|
+
console.log("");
|
|
323
|
+
console.log(" Continuing without tracker verification.");
|
|
324
|
+
console.log(" You can configure credentials later at agentdesk.live or re-run 'agentdesk init'.");
|
|
325
|
+
console.log("");
|
|
326
|
+
verified = true;
|
|
327
|
+
}
|
|
160
328
|
}
|
|
161
|
-
console.log("");
|
|
162
|
-
} else {
|
|
163
|
-
console.log(" ✓ Tracker permissions verified (read, create, update)");
|
|
164
|
-
console.log("");
|
|
165
329
|
}
|
|
166
330
|
}
|
|
167
331
|
|
|
@@ -227,8 +391,9 @@ export async function runInit(cwd) {
|
|
|
227
391
|
console.log(" Ready! Run a team session:");
|
|
228
392
|
console.log("");
|
|
229
393
|
if (tracker) {
|
|
230
|
-
|
|
231
|
-
console.log(` agentdesk team
|
|
394
|
+
const prefix = config.linear?.teamKey || config.jira?.project || finalProjectKey.toUpperCase();
|
|
395
|
+
console.log(` agentdesk team ${prefix}-123`);
|
|
396
|
+
console.log(` agentdesk team ${prefix}-123 -d "Optional extra context"`);
|
|
232
397
|
} else {
|
|
233
398
|
console.log(` agentdesk team my-feature -d "Add dark mode support"`);
|
|
234
399
|
}
|