@kendoo.agentdesk/agentdesk 0.19.4 → 0.19.5

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
@@ -13,6 +13,11 @@ 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.5] — 2026-04-19
17
+
18
+ ### Changed
19
+ - `[CLI]` `agentdesk bootstrap` now works even when the cloned repo has no committed `.agentdesk.json`. If the project config file is absent, bootstrap fetches the user's project list from the server, tries to auto-match by the git remote (`origin`), and falls back to a numbered picker of the account's projects. Once picked, a minimal `.agentdesk.json` is written locally (prompting the user to commit it so future clones skip the picker) and the rest of the gap-scan continues unchanged. Previous behavior required the file to already be present.
20
+
16
21
  ## [0.19.4] — 2026-04-19
17
22
 
18
23
  ### Added
package/cli/bootstrap.mjs CHANGED
@@ -18,6 +18,7 @@
18
18
  import { existsSync, readFileSync, writeFileSync } from "fs";
19
19
  import { join } from "path";
20
20
  import { createInterface } from "readline";
21
+ import { execSync } from "child_process";
21
22
  import { loadConfig } from "./config.mjs";
22
23
  import { getStoredApiKey } from "./login.mjs";
23
24
  import { checkTrackerPermissions, resolveCredentialsFromEnv } from "./tracker-check.mjs";
@@ -25,6 +26,114 @@ import { assertPushable, PreflightError } from "./session-preflight.mjs";
25
26
 
26
27
  const SERVER = process.env.AGENTDESK_SERVER || "https://agentdesk.live";
27
28
 
29
+ function detectGitRemote(dir) {
30
+ try {
31
+ const url = execSync("git remote get-url origin", { cwd: dir, stdio: ["ignore", "pipe", "ignore"], encoding: "utf-8" }).trim();
32
+ const m = url.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/);
33
+ if (m) return `${m[1]}/${m[2]}`;
34
+ } catch {}
35
+ return null;
36
+ }
37
+
38
+ async function fetchProjects(apiKey) {
39
+ try {
40
+ const res = await fetch(`${SERVER}/api/projects`, {
41
+ headers: { "x-api-key": apiKey },
42
+ signal: AbortSignal.timeout(8000),
43
+ });
44
+ if (!res.ok) return null;
45
+ return await res.json();
46
+ } catch { return null; }
47
+ }
48
+
49
+ async function fetchProjectSettings(apiKey, projectId) {
50
+ try {
51
+ const res = await fetch(`${SERVER}/api/projects/${projectId}/settings`, {
52
+ headers: { "x-api-key": apiKey },
53
+ signal: AbortSignal.timeout(5000),
54
+ });
55
+ if (!res.ok) return {};
56
+ return await res.json();
57
+ } catch { return {}; }
58
+ }
59
+
60
+ // Discover the server-side project for this clone when .agentdesk.json is
61
+ // absent. Strategy:
62
+ // 1. Fetch the user's projects list.
63
+ // 2. For each, fetch settings so we can see github.repo.
64
+ // 3. If we can match the local git remote to exactly one project's repo,
65
+ // auto-select (confirmation prompt).
66
+ // 4. Otherwise show a numbered picker of all the user's projects.
67
+ // 5. Write a minimal `.agentdesk.json` locally so loadConfig takes over
68
+ // from here.
69
+ // Returns the chosen projectKey, or null if the user aborts.
70
+ async function discoverProject(rl, cwd, apiKey) {
71
+ const remote = detectGitRemote(cwd);
72
+ if (remote) console.log(` Detected git remote: ${remote}`);
73
+ console.log(" Fetching your projects from the server...");
74
+
75
+ const projects = await fetchProjects(apiKey);
76
+ if (!projects || projects.length === 0) {
77
+ console.log("");
78
+ console.log(" No projects found on your account.");
79
+ console.log(" Run `agentdesk init` on the primary machine first, then retry here.");
80
+ console.log("");
81
+ return null;
82
+ }
83
+
84
+ // Fetch settings in parallel so we can display/match the configured repo.
85
+ const settings = await Promise.all(projects.map(p => fetchProjectSettings(apiKey, p.id)));
86
+ const rows = projects.map((p, i) => {
87
+ const repo = settings[i]?.github?.repo || null;
88
+ const matches = remote && repo && repo.toLowerCase() === remote.toLowerCase();
89
+ return { project: p, settings: settings[i] || {}, repo, matches };
90
+ });
91
+
92
+ const exactMatches = rows.filter(r => r.matches);
93
+ let chosen = null;
94
+
95
+ if (exactMatches.length === 1) {
96
+ const r = exactMatches[0];
97
+ console.log(` ✓ Match: ${r.project.name} (${r.project.id}) — repo ${r.repo}`);
98
+ console.log("");
99
+ const ans = (await askInline(rl, ` Use this project? [Y/n]: `)).trim().toLowerCase();
100
+ if (ans === "" || ans === "y" || ans === "yes") chosen = r;
101
+ }
102
+
103
+ if (!chosen) {
104
+ console.log("");
105
+ console.log(" Pick the project this repo maps to:");
106
+ console.log("");
107
+ rows.forEach((r, i) => {
108
+ const tag = r.matches ? " ← matches git remote" : "";
109
+ const repoStr = r.repo ? ` [${r.repo}]` : "";
110
+ console.log(` ${i + 1}) ${r.project.name} (${r.project.id})${repoStr}${tag}`);
111
+ });
112
+ console.log("");
113
+ while (true) {
114
+ const answer = await askInline(rl, ` Choose (1-${rows.length}, or 'c' to cancel): `);
115
+ const trimmed = answer.trim().toLowerCase();
116
+ if (trimmed === "c" || trimmed === "cancel") return null;
117
+ const num = parseInt(trimmed, 10);
118
+ if (num >= 1 && num <= rows.length) { chosen = rows[num - 1]; break; }
119
+ console.log(` Please enter a number between 1 and ${rows.length}`);
120
+ }
121
+ }
122
+
123
+ // Write a minimal .agentdesk.json — loadConfig will merge server state
124
+ // into it on the next call, producing the full cached view.
125
+ const minimal = { projectKey: chosen.project.id };
126
+ writeFileSync(join(cwd, ".agentdesk.json"), JSON.stringify(minimal, null, 2) + "\n");
127
+ console.log("");
128
+ console.log(` ✓ Wrote .agentdesk.json (projectKey: ${chosen.project.id})`);
129
+ console.log(" (commit this file so future clones skip the picker)");
130
+ return chosen.project.id;
131
+ }
132
+
133
+ function askInline(rl, question) {
134
+ return new Promise(resolve => rl.question(question, resolve));
135
+ }
136
+
28
137
  function ask(rl, question) {
29
138
  return new Promise(resolve => rl.question(question, resolve));
30
139
  }
@@ -149,26 +258,29 @@ export async function runBootstrap(cwd = process.cwd()) {
149
258
  process.exit(1);
150
259
  }
151
260
 
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
- }
261
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
262
+
263
+ // 2. Find the server-side project for this clone. Prefer a committed
264
+ // .agentdesk.json (clean path); fall back to discovering from the
265
+ // user's server-side project list when the file is missing.
266
+ let localConfig = readLocalConfig(cwd);
267
+ let projectKey = localConfig?.projectKey || null;
161
268
 
162
- const projectKey = localConfig.projectKey;
163
269
  if (!projectKey) {
164
- console.log(" .agentdesk.json has no projectKey — run `agentdesk init` to repair.");
270
+ if (localConfig) {
271
+ console.log(" .agentdesk.json has no projectKey — discovering project from server.");
272
+ } else {
273
+ console.log(" No .agentdesk.json in this directory — discovering project from server.");
274
+ }
275
+ console.log("");
276
+ projectKey = await discoverProject(rl, cwd, apiKey);
277
+ if (!projectKey) { rl.close(); process.exit(1); }
278
+ localConfig = readLocalConfig(cwd);
165
279
  console.log("");
166
- process.exit(1);
167
280
  }
168
281
 
169
282
  // 3. Load merged config (server-authoritative when reachable). This is the
170
283
  // same call `agentdesk team` makes, so we see the same view.
171
- const rl = createInterface({ input: process.stdin, output: process.stdout });
172
284
  const config = await loadConfig(cwd, { apiKey, serverUrl: SERVER, projectName: projectKey, silent: true });
173
285
 
174
286
  const tracker = config.tracker || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.19.4",
3
+ "version": "0.19.5",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {