@kendoo.agentdesk/agentdesk 0.18.1 → 0.18.3

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 CHANGED
@@ -8,6 +8,16 @@ 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.3] — 2026-04-18
12
+
13
+ ### Fixed
14
+ - `[CLI]` Tracker credentials: if a project has credentials stored both locally (`.env`) and on the server, the local value now wins. Previously the server's copy overrode `.env`, which meant that rotating a Jira / Linear / GitHub token locally didn't take effect while the server still held the old one.
15
+
16
+ ## [0.18.2] — 2026-04-18
17
+
18
+ ### Fixed
19
+ - `[CLI]` Jira tracker check no longer blocks on a 404 from the `mypermissions` endpoint (some tenants don't expose it). Auth verification now lives in the `/myself` call; `mypermissions` is best-effort and only reports missing permissions if the tenant returns them. A 404 on `/myself` itself still fails loudly — that's a real configuration problem.
20
+
11
21
  ## [0.18.1] — 2026-04-18
12
22
 
13
23
  ### Fixed
package/cli/init.mjs CHANGED
@@ -246,7 +246,8 @@ async function verifyAndPickTrackerProject({ rl, cwd, finalProjectKey, tracker,
246
246
  const dotEnv = loadDotEnv(cwd);
247
247
  const apiKey = loadApiKey(cwd);
248
248
  const serverCreds = await fetchServerCreds(apiKey, finalProjectKey);
249
- const credentials = resolveCredentialsFromEnv({ ...dotEnv, ...serverCreds });
249
+ // .env wins over server credentials fresh local rotation beats stale server state.
250
+ const credentials = resolveCredentialsFromEnv({ ...serverCreds, ...dotEnv });
250
251
  const check = await checkTrackerPermissions({ tracker, config, credentials });
251
252
  if (!check.ok) {
252
253
  console.log(" ⚠ " + (check.errors[0] || "Verification failed"));
package/cli/team.mjs CHANGED
@@ -112,7 +112,9 @@ export async function runTeam(taskId, opts = {}) {
112
112
  } catch {}
113
113
  }
114
114
 
115
- const credentials = resolveCredentialsFromEnv({ ...projectEnvVars, ...serverCreds });
115
+ // .env wins over server credentials a freshly-rotated token in the
116
+ // user's project should beat a stale one left behind on the server.
117
+ const credentials = resolveCredentialsFromEnv({ ...serverCreds, ...projectEnvVars });
116
118
  const check = await checkTrackerPermissions({ tracker, config, credentials });
117
119
 
118
120
  if (!check.ok) {
@@ -98,68 +98,79 @@ async function checkJira({ baseUrl, project }, creds) {
98
98
  // produce `...net//rest/...` which some Atlassian paths reject.
99
99
  const cleanBase = String(baseUrl || "").replace(/\/+$/, "");
100
100
 
101
- // Fetch the authenticated user so we can report "Posting as X".
102
- try {
103
- const meRes = await fetch(`${cleanBase}/rest/api/3/myself`, {
104
- headers: { Authorization: auth, Accept: "application/json" },
105
- signal: AbortSignal.timeout(10000),
106
- });
107
- if (meRes.ok) {
108
- const me = await meRes.json();
109
- identity = { name: me.displayName || email, email: me.emailAddress || email, id: me.accountId };
101
+ // Primary check: /myself. If this works, the tenant + auth are fine.
102
+ // Try v3 first, fall back to v2 on 404. This is what we report to the
103
+ // user as "auth works"; mypermissions is now a best-effort follow-up.
104
+ let myselfRes = null;
105
+ let lastUrl = "";
106
+ for (const v of ["3", "2"]) {
107
+ lastUrl = `${cleanBase}/rest/api/${v}/myself`;
108
+ try {
109
+ myselfRes = await fetch(lastUrl, {
110
+ headers: { Authorization: auth, Accept: "application/json" },
111
+ signal: AbortSignal.timeout(10000),
112
+ });
113
+ if (myselfRes.status !== 404) break;
114
+ } catch (e) {
115
+ if (e.name === "AbortError" || e.name === "TimeoutError") {
116
+ return { ok: false, errors: [`Cannot reach Jira at ${cleanBase} — request timed out`] };
117
+ }
118
+ return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}: ${e.message}`] };
110
119
  }
120
+ }
121
+ if (!myselfRes) return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}`] };
122
+ if (myselfRes.status === 401) {
123
+ return { ok: false, errors: ["Jira authentication failed — check JIRA_EMAIL and JIRA_API_TOKEN"] };
124
+ }
125
+ if (myselfRes.status === 403) {
126
+ return { ok: false, errors: ["Jira access forbidden — your account may lack access to this tenant"] };
127
+ }
128
+ if (myselfRes.status === 404) {
129
+ return { ok: false, errors: [`Jira API not found at ${cleanBase} (tried /rest/api/3/myself and /rest/api/2/myself). Confirm the base URL is the Atlassian tenant root and Jira is enabled on this tenant.`] };
130
+ }
131
+ if (!myselfRes.ok) {
132
+ return { ok: false, errors: [`Jira API error (${myselfRes.status}) calling ${lastUrl}`] };
133
+ }
134
+ try {
135
+ const me = await myselfRes.json();
136
+ identity = { name: me.displayName || email, email: me.emailAddress || email, id: me.accountId };
111
137
  } catch {}
112
138
 
113
- // Use Jira's mypermissions endpoint to check all permissions at once.
114
- // Try REST v3 first (Jira Cloud), fall back to v2 on 404 (older tenants /
115
- // Data Center installs where v3 isn't exposed).
116
- const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
117
- const tryVersions = ["3", "2"];
118
- let res = null;
119
- let lastUrl = "";
139
+ // Best-effort permission check. If it 404s or otherwise fails, we log the
140
+ // specific perms we COULD verify and return ok the user's auth works;
141
+ // any missing permission will surface when the agent tries the operation.
120
142
  try {
121
- for (const v of tryVersions) {
143
+ const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
144
+ let permRes = null;
145
+ for (const v of ["3", "2"]) {
122
146
  const url = `${cleanBase}/rest/api/${v}/mypermissions?permissions=${permissionsToCheck}` +
123
147
  (project ? `&projectKey=${project}` : "");
124
- lastUrl = url;
125
- res = await fetch(url, {
148
+ permRes = await fetch(url, {
126
149
  headers: { Authorization: auth, Accept: "application/json" },
127
150
  signal: AbortSignal.timeout(10000),
128
151
  });
129
- if (res.status !== 404) break;
130
- }
131
-
132
- if (res.status === 401) {
133
- return { ok: false, errors: ["Jira authentication failed — check JIRA_EMAIL and JIRA_API_TOKEN"] };
134
- }
135
- if (res.status === 403) {
136
- return { ok: false, errors: ["Jira access forbidden — your account may lack access to this project"] };
137
- }
138
- if (!res.ok) {
139
- return { ok: false, errors: [`Jira API error (${res.status}) calling ${lastUrl} — check that the base URL is the Atlassian tenant root (e.g. https://yourco.atlassian.net) without a path.`] };
152
+ if (permRes.status !== 404) break;
140
153
  }
141
-
142
- const data = await res.json();
143
- const perms = data.permissions || {};
144
-
145
- const permMap = {
146
- BROWSE_PROJECTS: "read tasks",
147
- CREATE_ISSUES: "create tasks",
148
- EDIT_ISSUES: "update tasks",
149
- ADD_COMMENTS: "add comments",
150
- TRANSITION_ISSUES: "change task status",
151
- };
152
-
153
- for (const [key, label] of Object.entries(permMap)) {
154
- if (perms[key] && !perms[key].havePermission) {
155
- errors.push(`Missing permission: ${label} (${key})`);
154
+ if (permRes && permRes.ok) {
155
+ const data = await permRes.json();
156
+ const perms = data.permissions || {};
157
+ const permMap = {
158
+ BROWSE_PROJECTS: "read tasks",
159
+ CREATE_ISSUES: "create tasks",
160
+ EDIT_ISSUES: "update tasks",
161
+ ADD_COMMENTS: "add comments",
162
+ TRANSITION_ISSUES: "change task status",
163
+ };
164
+ for (const [key, label] of Object.entries(permMap)) {
165
+ if (perms[key] && !perms[key].havePermission) {
166
+ errors.push(`Missing permission: ${label} (${key})`);
167
+ }
156
168
  }
157
169
  }
158
- } catch (e) {
159
- if (e.name === "AbortError" || e.name === "TimeoutError") {
160
- return { ok: false, errors: [`Cannot reach Jira at ${cleanBase} — request timed out`] };
161
- }
162
- return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}: ${e.message}`] };
170
+ // If mypermissions failed, silently skip — agent will hit real errors
171
+ // when it tries an operation, which is more accurate anyway.
172
+ } catch {
173
+ // Ignore — auth already verified via /myself above.
163
174
  }
164
175
 
165
176
  return { ok: errors.length === 0, errors, identity };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {