@kendoo.agentdesk/agentdesk 0.9.8 → 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.
- package/cli/init.mjs +219 -54
- 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
|
-
// ---
|
|
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
|
-
//
|
|
85
|
-
const config = {
|
|
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
|
|
93
|
-
const
|
|
94
|
-
const teamKey =
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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) {
|