@docyrus/docyrus 0.0.75 → 0.0.77
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/README.md +17 -0
- package/agent-loader.js +14 -6
- package/agent-loader.js.map +3 -3
- package/main.js +221 -70
- package/main.js.map +4 -4
- package/package.json +1 -1
- package/resources/pi-agent/extensions/docyrus-auth-tool.ts +322 -0
- package/server-loader.js +85 -2
- package/server-loader.js.map +2 -2
package/package.json
CHANGED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docyrus authentication tool for the pi coding agent.
|
|
3
|
+
*
|
|
4
|
+
* Registers a single tool `authenticate_docyrus` that the agent can call to
|
|
5
|
+
* authenticate the Docyrus CLI in the current working directory.
|
|
6
|
+
*
|
|
7
|
+
* Two execution modes are supported, selected at call time:
|
|
8
|
+
*
|
|
9
|
+
* - Server desktop mode (DOCYRUS_DESKTOP_TOOLS=1):
|
|
10
|
+
* The execute handler routes the call to the desktop client via
|
|
11
|
+
* ctx.ui.input("authenticate_docyrus", paramsJson). The Electron host
|
|
12
|
+
* detects the tool name, runs the OAuth2 device flow on behalf of the
|
|
13
|
+
* user (in the same cwd) and replies with the authenticated profile.
|
|
14
|
+
*
|
|
15
|
+
* - TUI / non-desktop mode:
|
|
16
|
+
* Pi owns the active terminal, so we cannot run `docyrus auth login`
|
|
17
|
+
* inline. Instead the handler opens a new OS terminal window, runs
|
|
18
|
+
* `docyrus auth login` interactively, and polls `docyrus auth who --json`
|
|
19
|
+
* in the background until the user finishes (or times out).
|
|
20
|
+
*/
|
|
21
|
+
import { spawn } from "node:child_process";
|
|
22
|
+
import { promisify } from "node:util";
|
|
23
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
24
|
+
import { Type } from "typebox";
|
|
25
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
26
|
+
|
|
27
|
+
const execFile = promisify(execFileCb);
|
|
28
|
+
|
|
29
|
+
const TOOL_NAME = "authenticate_docyrus";
|
|
30
|
+
const DESKTOP_TIMEOUT_MS = 5 * 60_000;
|
|
31
|
+
const TUI_POLL_INTERVAL_MS = 2_000;
|
|
32
|
+
const TUI_TIMEOUT_MS = 10 * 60_000;
|
|
33
|
+
|
|
34
|
+
interface IAuthenticateInput {
|
|
35
|
+
scope?: string;
|
|
36
|
+
clientId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface IAuthenticateResult {
|
|
40
|
+
authenticated: boolean;
|
|
41
|
+
apiBaseUrl?: string;
|
|
42
|
+
userId?: string;
|
|
43
|
+
email?: string;
|
|
44
|
+
tenantId?: string;
|
|
45
|
+
tenantName?: string;
|
|
46
|
+
tenantNo?: number;
|
|
47
|
+
scope?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function toolResult(payload: unknown) {
|
|
51
|
+
const text = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
|
|
52
|
+
return { content: [{ type: "text" as const, text }] };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toolError(message: string) {
|
|
56
|
+
return { content: [{ type: "text" as const, text: message }], isError: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readDocyrusCliEnvironment(env: NodeJS.ProcessEnv = process.env): {
|
|
60
|
+
executable: string;
|
|
61
|
+
entryPath: string;
|
|
62
|
+
scope: "local" | "global";
|
|
63
|
+
} {
|
|
64
|
+
const executable = env.DOCYRUS_CLI_EXECUTABLE?.trim();
|
|
65
|
+
const entryPath = env.DOCYRUS_CLI_ENTRY?.trim();
|
|
66
|
+
const rawScope = env.DOCYRUS_CLI_SCOPE?.trim();
|
|
67
|
+
const scope = rawScope === "global" ? "global" : "local";
|
|
68
|
+
if (!executable || !entryPath) {
|
|
69
|
+
throw new Error("Missing Docyrus CLI runtime env. Expected DOCYRUS_CLI_EXECUTABLE and DOCYRUS_CLI_ENTRY.");
|
|
70
|
+
}
|
|
71
|
+
return { executable, entryPath, scope };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildAuthLoginArgs(input: IAuthenticateInput, scope: "local" | "global"): string[] {
|
|
75
|
+
const args: string[] = [];
|
|
76
|
+
if (scope === "global") {
|
|
77
|
+
args.push("-g");
|
|
78
|
+
}
|
|
79
|
+
args.push("auth", "login");
|
|
80
|
+
if (input.scope?.trim()) {
|
|
81
|
+
args.push("--scope", input.scope.trim());
|
|
82
|
+
}
|
|
83
|
+
if (input.clientId?.trim()) {
|
|
84
|
+
args.push("--clientId", input.clientId.trim());
|
|
85
|
+
}
|
|
86
|
+
return args;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildAuthWhoArgs(scope: "local" | "global"): string[] {
|
|
90
|
+
const args: string[] = [];
|
|
91
|
+
if (scope === "global") {
|
|
92
|
+
args.push("-g");
|
|
93
|
+
}
|
|
94
|
+
args.push("auth", "who", "--json");
|
|
95
|
+
return args;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function shellQuote(value: string): string {
|
|
99
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function spawnInteractiveTerminal(params: {
|
|
103
|
+
executable: string;
|
|
104
|
+
entryPath: string;
|
|
105
|
+
args: string[];
|
|
106
|
+
cwd: string;
|
|
107
|
+
}): void {
|
|
108
|
+
const platform = process.platform;
|
|
109
|
+
const fullCommand = [params.executable, params.entryPath, ...params.args]
|
|
110
|
+
.map(shellQuote)
|
|
111
|
+
.join(" ");
|
|
112
|
+
|
|
113
|
+
if (platform === "darwin") {
|
|
114
|
+
const script = `cd ${shellQuote(params.cwd)} && ${fullCommand}`;
|
|
115
|
+
const osa = `tell application "Terminal"
|
|
116
|
+
activate
|
|
117
|
+
do script ${JSON.stringify(script)}
|
|
118
|
+
end tell`;
|
|
119
|
+
spawn("osascript", ["-e", osa], { stdio: "ignore", detached: true }).unref();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (platform === "win32") {
|
|
124
|
+
const winCommand = `cd /d ${shellQuote(params.cwd)} && ${fullCommand}`;
|
|
125
|
+
spawn("cmd", ["/c", "start", "", "cmd", "/k", winCommand], {
|
|
126
|
+
stdio: "ignore",
|
|
127
|
+
detached: true,
|
|
128
|
+
windowsHide: false,
|
|
129
|
+
}).unref();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const candidates = ["x-terminal-emulator", "gnome-terminal", "konsole", "xterm"];
|
|
134
|
+
const linuxCommand = `cd ${shellQuote(params.cwd)} && ${fullCommand}; echo; read -n1 -rsp 'Press any key to close...'`;
|
|
135
|
+
for (const candidate of candidates) {
|
|
136
|
+
try {
|
|
137
|
+
spawn(candidate, ["-e", "bash", "-c", linuxCommand], { stdio: "ignore", detached: true }).unref();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// try next candidate
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
throw new Error("No supported terminal emulator was found. Run `docyrus auth login` manually in another terminal.");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function readAuthWho(params: {
|
|
148
|
+
executable: string;
|
|
149
|
+
entryPath: string;
|
|
150
|
+
scope: "local" | "global";
|
|
151
|
+
cwd: string;
|
|
152
|
+
}): Promise<IAuthenticateResult | null> {
|
|
153
|
+
try {
|
|
154
|
+
const { stdout } = await execFile(
|
|
155
|
+
params.executable,
|
|
156
|
+
[params.entryPath, ...buildAuthWhoArgs(params.scope)],
|
|
157
|
+
{ cwd: params.cwd },
|
|
158
|
+
);
|
|
159
|
+
const parsed = JSON.parse(stdout) as Record<string, unknown>;
|
|
160
|
+
const data = (parsed.data ?? parsed) as Record<string, unknown> | undefined;
|
|
161
|
+
const ctx = (parsed.context ?? null) as Record<string, unknown> | null;
|
|
162
|
+
if (!data || typeof data !== "object") {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
authenticated: true,
|
|
168
|
+
userId: typeof data.id === "string" ? data.id : undefined,
|
|
169
|
+
email: typeof data.email === "string" ? data.email : undefined,
|
|
170
|
+
tenantId: ctx && typeof ctx.tenantId === "string" ? ctx.tenantId : undefined,
|
|
171
|
+
tenantName: ctx && typeof ctx.tenantName === "string" ? ctx.tenantName : undefined,
|
|
172
|
+
tenantNo: ctx && typeof ctx.tenantNo === "number" ? ctx.tenantNo : undefined,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function pollUntilAuthenticated(params: {
|
|
181
|
+
executable: string;
|
|
182
|
+
entryPath: string;
|
|
183
|
+
scope: "local" | "global";
|
|
184
|
+
cwd: string;
|
|
185
|
+
signal?: AbortSignal;
|
|
186
|
+
baseline: IAuthenticateResult | null;
|
|
187
|
+
}): Promise<IAuthenticateResult | null> {
|
|
188
|
+
const deadline = Date.now() + TUI_TIMEOUT_MS;
|
|
189
|
+
while (Date.now() < deadline) {
|
|
190
|
+
if (params.signal?.aborted) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const profile = await readAuthWho(params);
|
|
194
|
+
if (profile && profile.userId
|
|
195
|
+
&& (!params.baseline || params.baseline.userId !== profile.userId || params.baseline.tenantId !== profile.tenantId)) {
|
|
196
|
+
return profile;
|
|
197
|
+
}
|
|
198
|
+
await new Promise<void>((resolve) => {
|
|
199
|
+
const timer = setTimeout(resolve, TUI_POLL_INTERVAL_MS);
|
|
200
|
+
params.signal?.addEventListener("abort", () => {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
resolve();
|
|
203
|
+
}, { once: true });
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function executeDesktop(params: {
|
|
210
|
+
input: IAuthenticateInput;
|
|
211
|
+
ctx: { ui: { input: (title: string, placeholder?: string, opts?: { signal?: AbortSignal; timeout?: number }) => Promise<string | undefined> } };
|
|
212
|
+
signal: AbortSignal | undefined;
|
|
213
|
+
}) {
|
|
214
|
+
const paramsJson = JSON.stringify(params.input ?? {});
|
|
215
|
+
const resultJson = await params.ctx.ui.input(TOOL_NAME, paramsJson, {
|
|
216
|
+
signal: params.signal,
|
|
217
|
+
timeout: DESKTOP_TIMEOUT_MS,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!resultJson) {
|
|
221
|
+
return toolError("Desktop authentication was cancelled or timed out.");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
return toolResult(JSON.parse(resultJson));
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
return toolResult(resultJson);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function executeTui(params: {
|
|
233
|
+
input: IAuthenticateInput;
|
|
234
|
+
ctx: {
|
|
235
|
+
cwd?: string;
|
|
236
|
+
hasUI: boolean;
|
|
237
|
+
ui: {
|
|
238
|
+
notify?: (message: string, level?: "info" | "warning" | "error") => void;
|
|
239
|
+
confirm?: (title: string, message: string, opts?: { signal?: AbortSignal; timeout?: number }) => Promise<boolean>;
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
signal: AbortSignal | undefined;
|
|
243
|
+
}) {
|
|
244
|
+
const env = readDocyrusCliEnvironment();
|
|
245
|
+
const cwd = params.ctx.cwd || process.cwd();
|
|
246
|
+
const baseline = await readAuthWho({
|
|
247
|
+
executable: env.executable,
|
|
248
|
+
entryPath: env.entryPath,
|
|
249
|
+
scope: env.scope,
|
|
250
|
+
cwd,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const launchArgs = buildAuthLoginArgs(params.input ?? {}, env.scope);
|
|
254
|
+
spawnInteractiveTerminal({
|
|
255
|
+
executable: env.executable,
|
|
256
|
+
entryPath: env.entryPath,
|
|
257
|
+
args: launchArgs,
|
|
258
|
+
cwd,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (params.ctx.hasUI && params.ctx.ui.notify) {
|
|
262
|
+
params.ctx.ui.notify(
|
|
263
|
+
"Opened a new terminal to run `docyrus auth login`. Complete the device flow there; this tool will detect when login succeeds.",
|
|
264
|
+
"info",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const profile = await pollUntilAuthenticated({
|
|
269
|
+
executable: env.executable,
|
|
270
|
+
entryPath: env.entryPath,
|
|
271
|
+
scope: env.scope,
|
|
272
|
+
cwd,
|
|
273
|
+
signal: params.signal,
|
|
274
|
+
baseline,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (!profile) {
|
|
278
|
+
return toolError("Timed out waiting for `docyrus auth login` to complete in the new terminal.");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return toolResult(profile);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default function docyrusAuthTool(pi: ExtensionAPI): void {
|
|
285
|
+
pi.registerTool({
|
|
286
|
+
name: TOOL_NAME,
|
|
287
|
+
label: "Docyrus Authenticate",
|
|
288
|
+
description:
|
|
289
|
+
"Authenticate the Docyrus CLI in the current working directory. " +
|
|
290
|
+
"In the desktop app, the host runs the OAuth2 device flow automatically and returns the resulting profile. " +
|
|
291
|
+
"In a terminal session, a new terminal window is opened to run `docyrus auth login` interactively and the tool waits for it to finish.",
|
|
292
|
+
parameters: Type.Object({
|
|
293
|
+
scope: Type.Optional(Type.String({ description: "Optional OAuth2 scopes; defaults to the CLI's standard login scope." })),
|
|
294
|
+
clientId: Type.Optional(Type.String({ description: "Optional OAuth2 client id override." })),
|
|
295
|
+
}),
|
|
296
|
+
execute: async(
|
|
297
|
+
_toolCallId: string,
|
|
298
|
+
input: IAuthenticateInput,
|
|
299
|
+
signal: AbortSignal | undefined,
|
|
300
|
+
_onUpdate: unknown,
|
|
301
|
+
ctx: {
|
|
302
|
+
cwd?: string;
|
|
303
|
+
hasUI: boolean;
|
|
304
|
+
ui: {
|
|
305
|
+
input: (title: string, placeholder?: string, opts?: { signal?: AbortSignal; timeout?: number }) => Promise<string | undefined>;
|
|
306
|
+
notify?: (message: string, level?: "info" | "warning" | "error") => void;
|
|
307
|
+
confirm?: (title: string, message: string, opts?: { signal?: AbortSignal; timeout?: number }) => Promise<boolean>;
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
) => {
|
|
311
|
+
try {
|
|
312
|
+
if (process.env.DOCYRUS_DESKTOP_TOOLS === "1") {
|
|
313
|
+
return await executeDesktop({ input: input ?? {}, ctx, signal });
|
|
314
|
+
}
|
|
315
|
+
return await executeTui({ input: input ?? {}, ctx, signal });
|
|
316
|
+
}
|
|
317
|
+
catch (error: unknown) {
|
|
318
|
+
return toolError(error instanceof Error ? error.message : String(error));
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
}
|
package/server-loader.js
CHANGED
|
@@ -44601,6 +44601,21 @@ async function listAllTools(params) {
|
|
|
44601
44601
|
|
|
44602
44602
|
// src/server/browserToolSchemas.ts
|
|
44603
44603
|
var BROWSER_TOOL_PREFIX = "docyrus_browser_";
|
|
44604
|
+
var DESKTOP_AUTHENTICATE_TOOL_NAME = "authenticate_docyrus";
|
|
44605
|
+
var DESKTOP_TOOL_SCHEMAS = [
|
|
44606
|
+
{
|
|
44607
|
+
name: DESKTOP_AUTHENTICATE_TOOL_NAME,
|
|
44608
|
+
description: "Authenticate the Docyrus CLI in the current working directory. The desktop app runs the OAuth2 device flow on behalf of the user and returns the authenticated profile (user, tenant, scope).",
|
|
44609
|
+
source: "built-in",
|
|
44610
|
+
inputSchema: {
|
|
44611
|
+
type: "object",
|
|
44612
|
+
properties: {
|
|
44613
|
+
scope: { type: "string", description: "Optional OAuth2 scopes; defaults to the CLI's standard login scope." },
|
|
44614
|
+
clientId: { type: "string", description: "Optional OAuth2 client id override." }
|
|
44615
|
+
}
|
|
44616
|
+
}
|
|
44617
|
+
}
|
|
44618
|
+
];
|
|
44604
44619
|
var BROWSER_TOOL_SCHEMAS = [
|
|
44605
44620
|
{
|
|
44606
44621
|
name: "docyrus_browser_navigate",
|
|
@@ -47028,6 +47043,67 @@ async function createAgentServer(params) {
|
|
|
47028
47043
|
return c.json({ error: message }, 500);
|
|
47029
47044
|
}
|
|
47030
47045
|
});
|
|
47046
|
+
app.post("/api/git/sync", async (c) => {
|
|
47047
|
+
const cwd = context.cwd;
|
|
47048
|
+
const body2 = await c.req.json().catch(() => ({}));
|
|
47049
|
+
try {
|
|
47050
|
+
let isRepo = false;
|
|
47051
|
+
try {
|
|
47052
|
+
const inside = (await gitExec(["rev-parse", "--is-inside-work-tree"], cwd)).trim();
|
|
47053
|
+
isRepo = inside === "true";
|
|
47054
|
+
} catch {
|
|
47055
|
+
isRepo = false;
|
|
47056
|
+
}
|
|
47057
|
+
if (!isRepo) {
|
|
47058
|
+
return c.json({ error: "Not a git repository", cwd }, 400);
|
|
47059
|
+
}
|
|
47060
|
+
const branch = body2.branch?.trim() || (await gitExec(["rev-parse", "--abbrev-ref", "HEAD"], cwd)).trim();
|
|
47061
|
+
if (!branch || branch === "HEAD") {
|
|
47062
|
+
return c.json({ error: "Cannot sync from a detached HEAD", cwd }, 409);
|
|
47063
|
+
}
|
|
47064
|
+
const remote = body2.remote?.trim() || "origin";
|
|
47065
|
+
await gitExec(["add", "-A"], cwd);
|
|
47066
|
+
const statusRaw = (await gitExec(["status", "--porcelain"], cwd)).trim();
|
|
47067
|
+
const hasStaged = (await gitExec(["diff", "--cached", "--name-only"], cwd)).trim().length > 0;
|
|
47068
|
+
let committed = false;
|
|
47069
|
+
let commitHash;
|
|
47070
|
+
const commitMessage = body2.message?.trim() || "chore: sync from docyrus coder";
|
|
47071
|
+
if (hasStaged) {
|
|
47072
|
+
await gitExec(["commit", "-m", commitMessage], cwd);
|
|
47073
|
+
commitHash = (await gitExec(["rev-parse", "HEAD"], cwd)).trim();
|
|
47074
|
+
committed = true;
|
|
47075
|
+
}
|
|
47076
|
+
let upstream;
|
|
47077
|
+
try {
|
|
47078
|
+
upstream = (await gitExec(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd)).trim();
|
|
47079
|
+
} catch {
|
|
47080
|
+
upstream = void 0;
|
|
47081
|
+
}
|
|
47082
|
+
const pushArgs = upstream ? ["push", remote, branch] : ["push", "--set-upstream", remote, branch];
|
|
47083
|
+
const pushOutput = await gitExec(pushArgs, cwd);
|
|
47084
|
+
return c.json({
|
|
47085
|
+
ok: true,
|
|
47086
|
+
cwd,
|
|
47087
|
+
branch,
|
|
47088
|
+
remote,
|
|
47089
|
+
committed,
|
|
47090
|
+
commit: committed ? { hash: commitHash, message: commitMessage } : null,
|
|
47091
|
+
pushed: true,
|
|
47092
|
+
upstreamSet: !upstream,
|
|
47093
|
+
statusBeforeSync: statusRaw,
|
|
47094
|
+
pushOutput
|
|
47095
|
+
});
|
|
47096
|
+
} catch (error48) {
|
|
47097
|
+
const errAny = error48;
|
|
47098
|
+
const stderr = errAny.stderr ? errAny.stderr.toString() : "";
|
|
47099
|
+
const stdout = errAny.stdout ? errAny.stdout.toString() : "";
|
|
47100
|
+
const message = errAny.message || String(error48);
|
|
47101
|
+
return c.json({
|
|
47102
|
+
error: stderr.trim() || message,
|
|
47103
|
+
stdout: stdout.trim() || void 0
|
|
47104
|
+
}, 500);
|
|
47105
|
+
}
|
|
47106
|
+
});
|
|
47031
47107
|
app.get("/api/mcp/servers", async (c) => {
|
|
47032
47108
|
try {
|
|
47033
47109
|
const [config2, cache, provenance] = await Promise.all([
|
|
@@ -47330,14 +47406,15 @@ async function createAgentServer(params) {
|
|
|
47330
47406
|
cwd: context.cwd
|
|
47331
47407
|
});
|
|
47332
47408
|
if (context.desktop) {
|
|
47333
|
-
tools.push(...BROWSER_TOOL_SCHEMAS);
|
|
47409
|
+
tools.push(...BROWSER_TOOL_SCHEMAS, ...DESKTOP_TOOL_SCHEMAS);
|
|
47334
47410
|
}
|
|
47335
47411
|
const builtInCount = tools.filter((t) => t.source === "built-in").length;
|
|
47336
47412
|
const mcpCount = tools.filter((t) => t.source !== "built-in").length;
|
|
47337
47413
|
return c.json({
|
|
47338
47414
|
tools,
|
|
47339
47415
|
summary: { builtIn: builtInCount, mcp: mcpCount, total: tools.length },
|
|
47340
|
-
clientSideToolPrefixes: context.desktop ? [BROWSER_TOOL_PREFIX] : []
|
|
47416
|
+
clientSideToolPrefixes: context.desktop ? [BROWSER_TOOL_PREFIX] : [],
|
|
47417
|
+
clientSideToolNames: context.desktop ? [DESKTOP_AUTHENTICATE_TOOL_NAME] : []
|
|
47341
47418
|
});
|
|
47342
47419
|
} catch (error48) {
|
|
47343
47420
|
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
@@ -47549,6 +47626,10 @@ async function createAgentServer(params) {
|
|
|
47549
47626
|
process.stderr.write(` POST /api/env/stop \u2014 stop dev server
|
|
47550
47627
|
`);
|
|
47551
47628
|
process.stderr.write(` GET /api/git/diff \u2014 uncommitted file diffs
|
|
47629
|
+
`);
|
|
47630
|
+
process.stderr.write(` GET /api/git/commits \u2014 recent commits
|
|
47631
|
+
`);
|
|
47632
|
+
process.stderr.write(` POST /api/git/sync \u2014 stage, commit, and push working tree
|
|
47552
47633
|
`);
|
|
47553
47634
|
process.stderr.write(` GET /api/mcp/servers \u2014 list MCP servers
|
|
47554
47635
|
`);
|
|
@@ -47584,6 +47665,8 @@ async function createAgentServer(params) {
|
|
|
47584
47665
|
process.stderr.write(` POST /api/extension-ui-response \u2014 submit extension UI response
|
|
47585
47666
|
`);
|
|
47586
47667
|
process.stderr.write(` Browser tools: docyrus_browser_* (client-side via extension_ui)
|
|
47668
|
+
`);
|
|
47669
|
+
process.stderr.write(` Auth tool: authenticate_docyrus (client-side via extension_ui)
|
|
47587
47670
|
`);
|
|
47588
47671
|
}
|
|
47589
47672
|
process.stderr.write(` * /api/cli/** \u2014 proxy any docyrus CLI command
|