@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 +5 -0
- package/cli/bootstrap.mjs +125 -13
- package/package.json +1 -1
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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;
|