@dosu/cli 0.16.0 → 0.16.2
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/bin/dosu.js +297 -248
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -2082,6 +2082,260 @@ var init_esm = __esm(() => {
|
|
|
2082
2082
|
} = import__.default);
|
|
2083
2083
|
});
|
|
2084
2084
|
|
|
2085
|
+
// src/config/config.ts
|
|
2086
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2087
|
+
import { join } from "node:path";
|
|
2088
|
+
function getConfigDir() {
|
|
2089
|
+
const dirName = process.env.DOSU_DEV === "true" ? "dosu-cli-dev" : "dosu-cli";
|
|
2090
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
2091
|
+
if (xdgConfig)
|
|
2092
|
+
return join(xdgConfig, dirName);
|
|
2093
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
2094
|
+
return join(home, ".config", dirName);
|
|
2095
|
+
}
|
|
2096
|
+
function getConfigPath() {
|
|
2097
|
+
const dir = getConfigDir();
|
|
2098
|
+
if (!existsSync(dir)) {
|
|
2099
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2100
|
+
}
|
|
2101
|
+
return join(dir, "config.json");
|
|
2102
|
+
}
|
|
2103
|
+
function loadConfig() {
|
|
2104
|
+
const path = getConfigPath();
|
|
2105
|
+
if (!existsSync(path)) {
|
|
2106
|
+
return emptyConfig();
|
|
2107
|
+
}
|
|
2108
|
+
try {
|
|
2109
|
+
const data = readFileSync(path, "utf-8");
|
|
2110
|
+
return JSON.parse(data);
|
|
2111
|
+
} catch {
|
|
2112
|
+
return emptyConfig();
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
function saveConfig(cfg) {
|
|
2116
|
+
const path = getConfigPath();
|
|
2117
|
+
const dir = getConfigDir();
|
|
2118
|
+
if (!existsSync(dir)) {
|
|
2119
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2120
|
+
}
|
|
2121
|
+
writeFileSync(path, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
2122
|
+
}
|
|
2123
|
+
function isAuthenticated(cfg) {
|
|
2124
|
+
return cfg.access_token !== "";
|
|
2125
|
+
}
|
|
2126
|
+
function isTokenExpired(cfg) {
|
|
2127
|
+
if (cfg.expires_at === 0)
|
|
2128
|
+
return false;
|
|
2129
|
+
return Math.floor(Date.now() / 1000) > cfg.expires_at - 300;
|
|
2130
|
+
}
|
|
2131
|
+
function emptyConfig() {
|
|
2132
|
+
return {
|
|
2133
|
+
access_token: "",
|
|
2134
|
+
refresh_token: "",
|
|
2135
|
+
expires_at: 0
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
var MODE_OSS = "oss";
|
|
2139
|
+
var init_config = () => {};
|
|
2140
|
+
|
|
2141
|
+
// src/config/constants.ts
|
|
2142
|
+
function getWebAppURL() {
|
|
2143
|
+
return process.env.DOSU_WEB_APP_URL_OVERRIDE ?? "https://app.dosu.dev" ?? "";
|
|
2144
|
+
}
|
|
2145
|
+
function getBackendURL() {
|
|
2146
|
+
return process.env.DOSU_BACKEND_URL_OVERRIDE ?? "https://api.dosu.dev" ?? "";
|
|
2147
|
+
}
|
|
2148
|
+
function getSupabaseURL() {
|
|
2149
|
+
return process.env.SUPABASE_URL_OVERRIDE ?? "https://wldmetsoicvieidlsqrb.supabase.co" ?? "";
|
|
2150
|
+
}
|
|
2151
|
+
function getSupabaseAnonKey() {
|
|
2152
|
+
return process.env.SUPABASE_ANON_KEY_OVERRIDE ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndsbG1ldHNvaWN2aWVpZGxzcXJiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ0NjA1NjUsImV4cCI6MjA1MDAzNjU2NX0.15LIxqSMVRnxsvLrk0GPjL1aSYPfOeaeFMdJXqgc5Xs" ?? "";
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/client/client.ts
|
|
2156
|
+
var exports_client = {};
|
|
2157
|
+
__export(exports_client, {
|
|
2158
|
+
SessionExpiredError: () => SessionExpiredError,
|
|
2159
|
+
Client: () => Client
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
class Client {
|
|
2163
|
+
baseURL;
|
|
2164
|
+
config;
|
|
2165
|
+
constructor(cfg) {
|
|
2166
|
+
this.baseURL = getBackendURL();
|
|
2167
|
+
this.config = cfg;
|
|
2168
|
+
}
|
|
2169
|
+
async doRequest(method, path, body) {
|
|
2170
|
+
if (!isAuthenticated(this.config)) {
|
|
2171
|
+
throw new Error("not authenticated - please run setup first");
|
|
2172
|
+
}
|
|
2173
|
+
if (isTokenExpired(this.config)) {
|
|
2174
|
+
await this.refreshToken();
|
|
2175
|
+
}
|
|
2176
|
+
let resp = await this.doRequestOnce(method, path, body);
|
|
2177
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
2178
|
+
try {
|
|
2179
|
+
await this.refreshToken();
|
|
2180
|
+
} catch {
|
|
2181
|
+
throw new SessionExpiredError;
|
|
2182
|
+
}
|
|
2183
|
+
resp = await this.doRequestOnce(method, path, body);
|
|
2184
|
+
}
|
|
2185
|
+
return resp;
|
|
2186
|
+
}
|
|
2187
|
+
async doRequestRaw(method, path) {
|
|
2188
|
+
return this.doRequestOnce(method, path);
|
|
2189
|
+
}
|
|
2190
|
+
async doRequestOnce(method, path, body) {
|
|
2191
|
+
const url = this.baseURL + path;
|
|
2192
|
+
const headers = {
|
|
2193
|
+
"Content-Type": "application/json",
|
|
2194
|
+
"Supabase-Access-Token": this.config.access_token
|
|
2195
|
+
};
|
|
2196
|
+
const options = { method, headers };
|
|
2197
|
+
if (body !== undefined) {
|
|
2198
|
+
options.body = JSON.stringify(body);
|
|
2199
|
+
}
|
|
2200
|
+
const controller = new AbortController;
|
|
2201
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2202
|
+
options.signal = controller.signal;
|
|
2203
|
+
try {
|
|
2204
|
+
return await fetch(url, options);
|
|
2205
|
+
} finally {
|
|
2206
|
+
clearTimeout(timeout);
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
async get(path) {
|
|
2210
|
+
return this.doRequest("GET", path);
|
|
2211
|
+
}
|
|
2212
|
+
async post(path, body) {
|
|
2213
|
+
return this.doRequest("POST", path, body);
|
|
2214
|
+
}
|
|
2215
|
+
async put(path, body) {
|
|
2216
|
+
return this.doRequest("PUT", path, body);
|
|
2217
|
+
}
|
|
2218
|
+
async delete(path) {
|
|
2219
|
+
return this.doRequest("DELETE", path);
|
|
2220
|
+
}
|
|
2221
|
+
async getWithApiKey(path) {
|
|
2222
|
+
return this.apiKeyRequest("GET", path);
|
|
2223
|
+
}
|
|
2224
|
+
async postWithApiKey(path, body) {
|
|
2225
|
+
return this.apiKeyRequest("POST", path, body);
|
|
2226
|
+
}
|
|
2227
|
+
async apiKeyRequest(method, path, body) {
|
|
2228
|
+
if (!this.config.api_key) {
|
|
2229
|
+
throw new Error("no API key available");
|
|
2230
|
+
}
|
|
2231
|
+
const url = this.baseURL + path;
|
|
2232
|
+
const headers = {
|
|
2233
|
+
"Content-Type": "application/json",
|
|
2234
|
+
"X-Dosu-API-Key": this.config.api_key
|
|
2235
|
+
};
|
|
2236
|
+
const options = { method, headers };
|
|
2237
|
+
if (body !== undefined) {
|
|
2238
|
+
options.body = JSON.stringify(body);
|
|
2239
|
+
}
|
|
2240
|
+
const controller = new AbortController;
|
|
2241
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2242
|
+
options.signal = controller.signal;
|
|
2243
|
+
try {
|
|
2244
|
+
return await fetch(url, options);
|
|
2245
|
+
} finally {
|
|
2246
|
+
clearTimeout(timeout);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
async refreshToken() {
|
|
2250
|
+
if (!this.config.refresh_token) {
|
|
2251
|
+
throw new Error("no refresh token available");
|
|
2252
|
+
}
|
|
2253
|
+
const supabaseURL = getSupabaseURL();
|
|
2254
|
+
const endpoint = `${supabaseURL}/auth/v1/token?grant_type=refresh_token`;
|
|
2255
|
+
const resp = await fetch(endpoint, {
|
|
2256
|
+
method: "POST",
|
|
2257
|
+
headers: {
|
|
2258
|
+
"Content-Type": "application/json",
|
|
2259
|
+
apikey: getSupabaseAnonKey()
|
|
2260
|
+
},
|
|
2261
|
+
body: JSON.stringify({ refresh_token: this.config.refresh_token })
|
|
2262
|
+
});
|
|
2263
|
+
if (resp.status !== 200) {
|
|
2264
|
+
throw new Error(`refresh failed with status ${resp.status}`);
|
|
2265
|
+
}
|
|
2266
|
+
const data = await resp.json();
|
|
2267
|
+
this.config.access_token = data.access_token;
|
|
2268
|
+
this.config.refresh_token = data.refresh_token;
|
|
2269
|
+
this.config.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
|
|
2270
|
+
saveConfig(this.config);
|
|
2271
|
+
}
|
|
2272
|
+
async getDeployments() {
|
|
2273
|
+
const resp = await this.get("/v1/mcp/deployments");
|
|
2274
|
+
if (resp.status !== 200) {
|
|
2275
|
+
let detail = await readErrorBody(resp);
|
|
2276
|
+
if (!detail || detail === "Internal Server Error") {
|
|
2277
|
+
detail = "check backend logs for details";
|
|
2278
|
+
}
|
|
2279
|
+
throw new Error(`failed to fetch deployments (status ${resp.status}): ${detail}`);
|
|
2280
|
+
}
|
|
2281
|
+
return resp.json();
|
|
2282
|
+
}
|
|
2283
|
+
async getOrgs() {
|
|
2284
|
+
const resp = await this.get("/v1/mcp/orgs");
|
|
2285
|
+
if (resp.status !== 200) {
|
|
2286
|
+
const detail = await readErrorBody(resp);
|
|
2287
|
+
throw new Error(`failed to fetch orgs (status ${resp.status}): ${detail}`);
|
|
2288
|
+
}
|
|
2289
|
+
return resp.json();
|
|
2290
|
+
}
|
|
2291
|
+
async validateAPIKey(apiKey, deploymentID) {
|
|
2292
|
+
try {
|
|
2293
|
+
const endpoint = `${this.baseURL}/v1/mcp/deployments/${encodeURIComponent(deploymentID)}`;
|
|
2294
|
+
const controller = new AbortController;
|
|
2295
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
2296
|
+
try {
|
|
2297
|
+
const resp = await fetch(endpoint, {
|
|
2298
|
+
method: "GET",
|
|
2299
|
+
headers: { "X-Dosu-API-Key": apiKey },
|
|
2300
|
+
signal: controller.signal
|
|
2301
|
+
});
|
|
2302
|
+
return resp.status !== 401 && resp.status !== 403;
|
|
2303
|
+
} finally {
|
|
2304
|
+
clearTimeout(timeout);
|
|
2305
|
+
}
|
|
2306
|
+
} catch {
|
|
2307
|
+
return true;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
async createAPIKey(deploymentID, name) {
|
|
2311
|
+
const path = `/v1/mcp/deployments/${deploymentID}/api-keys`;
|
|
2312
|
+
const resp = await this.post(path, { name });
|
|
2313
|
+
if (resp.status !== 200 && resp.status !== 201) {
|
|
2314
|
+
const detail = await readErrorBody(resp);
|
|
2315
|
+
throw new Error(`failed to create API key (status ${resp.status}): ${detail}`);
|
|
2316
|
+
}
|
|
2317
|
+
return resp.json();
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
async function readErrorBody(resp) {
|
|
2321
|
+
try {
|
|
2322
|
+
const text = await resp.text();
|
|
2323
|
+
return text.slice(0, 1024);
|
|
2324
|
+
} catch {
|
|
2325
|
+
return "";
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
var SessionExpiredError;
|
|
2329
|
+
var init_client = __esm(() => {
|
|
2330
|
+
init_config();
|
|
2331
|
+
SessionExpiredError = class SessionExpiredError extends Error {
|
|
2332
|
+
constructor() {
|
|
2333
|
+
super("session expired");
|
|
2334
|
+
this.name = "SessionExpiredError";
|
|
2335
|
+
}
|
|
2336
|
+
};
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2085
2339
|
// node_modules/picocolors/picocolors.js
|
|
2086
2340
|
var require_picocolors = __commonJS((exports, module) => {
|
|
2087
2341
|
var p = process || {};
|
|
@@ -4267,81 +4521,11 @@ var init_dist4 = __esm(() => {
|
|
|
4267
4521
|
allowErrorProps = SuperJSON.allowErrorProps;
|
|
4268
4522
|
});
|
|
4269
4523
|
|
|
4270
|
-
// src/config/config.ts
|
|
4271
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4272
|
-
import { join } from "node:path";
|
|
4273
|
-
function getConfigDir() {
|
|
4274
|
-
const dirName = process.env.DOSU_DEV === "true" ? "dosu-cli-dev" : "dosu-cli";
|
|
4275
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
4276
|
-
if (xdgConfig)
|
|
4277
|
-
return join(xdgConfig, dirName);
|
|
4278
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4279
|
-
return join(home, ".config", dirName);
|
|
4280
|
-
}
|
|
4281
|
-
function getConfigPath() {
|
|
4282
|
-
const dir = getConfigDir();
|
|
4283
|
-
if (!existsSync(dir)) {
|
|
4284
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4285
|
-
}
|
|
4286
|
-
return join(dir, "config.json");
|
|
4287
|
-
}
|
|
4288
|
-
function loadConfig() {
|
|
4289
|
-
const path = getConfigPath();
|
|
4290
|
-
if (!existsSync(path)) {
|
|
4291
|
-
return emptyConfig();
|
|
4292
|
-
}
|
|
4293
|
-
try {
|
|
4294
|
-
const data = readFileSync(path, "utf-8");
|
|
4295
|
-
return JSON.parse(data);
|
|
4296
|
-
} catch {
|
|
4297
|
-
return emptyConfig();
|
|
4298
|
-
}
|
|
4299
|
-
}
|
|
4300
|
-
function saveConfig(cfg) {
|
|
4301
|
-
const path = getConfigPath();
|
|
4302
|
-
const dir = getConfigDir();
|
|
4303
|
-
if (!existsSync(dir)) {
|
|
4304
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4305
|
-
}
|
|
4306
|
-
writeFileSync(path, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
4307
|
-
}
|
|
4308
|
-
function isAuthenticated(cfg) {
|
|
4309
|
-
return cfg.access_token !== "";
|
|
4310
|
-
}
|
|
4311
|
-
function isTokenExpired(cfg) {
|
|
4312
|
-
if (cfg.expires_at === 0)
|
|
4313
|
-
return false;
|
|
4314
|
-
return Math.floor(Date.now() / 1000) > cfg.expires_at - 300;
|
|
4315
|
-
}
|
|
4316
|
-
function emptyConfig() {
|
|
4317
|
-
return {
|
|
4318
|
-
access_token: "",
|
|
4319
|
-
refresh_token: "",
|
|
4320
|
-
expires_at: 0
|
|
4321
|
-
};
|
|
4322
|
-
}
|
|
4323
|
-
var MODE_OSS = "oss";
|
|
4324
|
-
var init_config = () => {};
|
|
4325
|
-
|
|
4326
|
-
// src/config/constants.ts
|
|
4327
|
-
function getWebAppURL() {
|
|
4328
|
-
return process.env.DOSU_WEB_APP_URL_OVERRIDE ?? "https://app.dosu.dev" ?? "";
|
|
4329
|
-
}
|
|
4330
|
-
function getBackendURL() {
|
|
4331
|
-
return process.env.DOSU_BACKEND_URL_OVERRIDE ?? "https://api.dosu.dev" ?? "";
|
|
4332
|
-
}
|
|
4333
|
-
function getSupabaseURL() {
|
|
4334
|
-
return process.env.SUPABASE_URL_OVERRIDE ?? "https://wldmetsoicvieidlsqrb.supabase.co" ?? "";
|
|
4335
|
-
}
|
|
4336
|
-
function getSupabaseAnonKey() {
|
|
4337
|
-
return process.env.SUPABASE_ANON_KEY_OVERRIDE ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndsbG1ldHNvaWN2aWVpZGxzcXJiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ0NjA1NjUsImV4cCI6MjA1MDAzNjU2NX0.15LIxqSMVRnxsvLrk0GPjL1aSYPfOeaeFMdJXqgc5Xs" ?? "";
|
|
4338
|
-
}
|
|
4339
|
-
|
|
4340
4524
|
// src/version/version.ts
|
|
4341
4525
|
function getVersionString() {
|
|
4342
4526
|
return `v${VERSION}`;
|
|
4343
4527
|
}
|
|
4344
|
-
var VERSION = "0.16.
|
|
4528
|
+
var VERSION = "0.16.2";
|
|
4345
4529
|
|
|
4346
4530
|
// src/debug/logger.ts
|
|
4347
4531
|
import {
|
|
@@ -4474,162 +4658,6 @@ var init_logger = __esm(() => {
|
|
|
4474
4658
|
};
|
|
4475
4659
|
});
|
|
4476
4660
|
|
|
4477
|
-
// src/client/client.ts
|
|
4478
|
-
var exports_client = {};
|
|
4479
|
-
__export(exports_client, {
|
|
4480
|
-
SessionExpiredError: () => SessionExpiredError,
|
|
4481
|
-
Client: () => Client
|
|
4482
|
-
});
|
|
4483
|
-
|
|
4484
|
-
class Client {
|
|
4485
|
-
baseURL;
|
|
4486
|
-
config;
|
|
4487
|
-
constructor(cfg) {
|
|
4488
|
-
this.baseURL = getBackendURL();
|
|
4489
|
-
this.config = cfg;
|
|
4490
|
-
}
|
|
4491
|
-
async doRequest(method, path, body) {
|
|
4492
|
-
if (!isAuthenticated(this.config)) {
|
|
4493
|
-
throw new Error("not authenticated - please run setup first");
|
|
4494
|
-
}
|
|
4495
|
-
if (isTokenExpired(this.config)) {
|
|
4496
|
-
await this.refreshToken();
|
|
4497
|
-
}
|
|
4498
|
-
let resp = await this.doRequestOnce(method, path, body);
|
|
4499
|
-
if (resp.status === 401 || resp.status === 403) {
|
|
4500
|
-
try {
|
|
4501
|
-
await this.refreshToken();
|
|
4502
|
-
} catch {
|
|
4503
|
-
throw new SessionExpiredError;
|
|
4504
|
-
}
|
|
4505
|
-
resp = await this.doRequestOnce(method, path, body);
|
|
4506
|
-
}
|
|
4507
|
-
return resp;
|
|
4508
|
-
}
|
|
4509
|
-
async doRequestRaw(method, path) {
|
|
4510
|
-
return this.doRequestOnce(method, path);
|
|
4511
|
-
}
|
|
4512
|
-
async doRequestOnce(method, path, body) {
|
|
4513
|
-
const url = this.baseURL + path;
|
|
4514
|
-
const headers = {
|
|
4515
|
-
"Content-Type": "application/json",
|
|
4516
|
-
"Supabase-Access-Token": this.config.access_token
|
|
4517
|
-
};
|
|
4518
|
-
const options = { method, headers };
|
|
4519
|
-
if (body !== undefined) {
|
|
4520
|
-
options.body = JSON.stringify(body);
|
|
4521
|
-
}
|
|
4522
|
-
const controller = new AbortController;
|
|
4523
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
4524
|
-
options.signal = controller.signal;
|
|
4525
|
-
try {
|
|
4526
|
-
return await fetch(url, options);
|
|
4527
|
-
} finally {
|
|
4528
|
-
clearTimeout(timeout);
|
|
4529
|
-
}
|
|
4530
|
-
}
|
|
4531
|
-
async get(path) {
|
|
4532
|
-
return this.doRequest("GET", path);
|
|
4533
|
-
}
|
|
4534
|
-
async post(path, body) {
|
|
4535
|
-
return this.doRequest("POST", path, body);
|
|
4536
|
-
}
|
|
4537
|
-
async put(path, body) {
|
|
4538
|
-
return this.doRequest("PUT", path, body);
|
|
4539
|
-
}
|
|
4540
|
-
async delete(path) {
|
|
4541
|
-
return this.doRequest("DELETE", path);
|
|
4542
|
-
}
|
|
4543
|
-
async refreshToken() {
|
|
4544
|
-
if (!this.config.refresh_token) {
|
|
4545
|
-
throw new Error("no refresh token available");
|
|
4546
|
-
}
|
|
4547
|
-
const supabaseURL = getSupabaseURL();
|
|
4548
|
-
const endpoint = `${supabaseURL}/auth/v1/token?grant_type=refresh_token`;
|
|
4549
|
-
const resp = await fetch(endpoint, {
|
|
4550
|
-
method: "POST",
|
|
4551
|
-
headers: {
|
|
4552
|
-
"Content-Type": "application/json",
|
|
4553
|
-
apikey: getSupabaseAnonKey()
|
|
4554
|
-
},
|
|
4555
|
-
body: JSON.stringify({ refresh_token: this.config.refresh_token })
|
|
4556
|
-
});
|
|
4557
|
-
if (resp.status !== 200) {
|
|
4558
|
-
throw new Error(`refresh failed with status ${resp.status}`);
|
|
4559
|
-
}
|
|
4560
|
-
const data = await resp.json();
|
|
4561
|
-
this.config.access_token = data.access_token;
|
|
4562
|
-
this.config.refresh_token = data.refresh_token;
|
|
4563
|
-
this.config.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
|
|
4564
|
-
saveConfig(this.config);
|
|
4565
|
-
}
|
|
4566
|
-
async getDeployments() {
|
|
4567
|
-
const resp = await this.get("/v1/mcp/deployments");
|
|
4568
|
-
if (resp.status !== 200) {
|
|
4569
|
-
let detail = await readErrorBody(resp);
|
|
4570
|
-
if (!detail || detail === "Internal Server Error") {
|
|
4571
|
-
detail = "check backend logs for details";
|
|
4572
|
-
}
|
|
4573
|
-
throw new Error(`failed to fetch deployments (status ${resp.status}): ${detail}`);
|
|
4574
|
-
}
|
|
4575
|
-
return resp.json();
|
|
4576
|
-
}
|
|
4577
|
-
async getOrgs() {
|
|
4578
|
-
const resp = await this.get("/v1/mcp/orgs");
|
|
4579
|
-
if (resp.status !== 200) {
|
|
4580
|
-
const detail = await readErrorBody(resp);
|
|
4581
|
-
throw new Error(`failed to fetch orgs (status ${resp.status}): ${detail}`);
|
|
4582
|
-
}
|
|
4583
|
-
return resp.json();
|
|
4584
|
-
}
|
|
4585
|
-
async validateAPIKey(apiKey, deploymentID) {
|
|
4586
|
-
try {
|
|
4587
|
-
const endpoint = `${this.baseURL}/v1/mcp/deployments/${encodeURIComponent(deploymentID)}`;
|
|
4588
|
-
const controller = new AbortController;
|
|
4589
|
-
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
4590
|
-
try {
|
|
4591
|
-
const resp = await fetch(endpoint, {
|
|
4592
|
-
method: "GET",
|
|
4593
|
-
headers: { "X-Dosu-API-Key": apiKey },
|
|
4594
|
-
signal: controller.signal
|
|
4595
|
-
});
|
|
4596
|
-
return resp.status !== 401 && resp.status !== 403;
|
|
4597
|
-
} finally {
|
|
4598
|
-
clearTimeout(timeout);
|
|
4599
|
-
}
|
|
4600
|
-
} catch {
|
|
4601
|
-
return true;
|
|
4602
|
-
}
|
|
4603
|
-
}
|
|
4604
|
-
async createAPIKey(deploymentID, name) {
|
|
4605
|
-
const path = `/v1/mcp/deployments/${deploymentID}/api-keys`;
|
|
4606
|
-
const resp = await this.post(path, { name });
|
|
4607
|
-
if (resp.status !== 200 && resp.status !== 201) {
|
|
4608
|
-
const detail = await readErrorBody(resp);
|
|
4609
|
-
throw new Error(`failed to create API key (status ${resp.status}): ${detail}`);
|
|
4610
|
-
}
|
|
4611
|
-
return resp.json();
|
|
4612
|
-
}
|
|
4613
|
-
}
|
|
4614
|
-
async function readErrorBody(resp) {
|
|
4615
|
-
try {
|
|
4616
|
-
const text = await resp.text();
|
|
4617
|
-
return text.slice(0, 1024);
|
|
4618
|
-
} catch {
|
|
4619
|
-
return "";
|
|
4620
|
-
}
|
|
4621
|
-
}
|
|
4622
|
-
var SessionExpiredError;
|
|
4623
|
-
var init_client = __esm(() => {
|
|
4624
|
-
init_config();
|
|
4625
|
-
SessionExpiredError = class SessionExpiredError extends Error {
|
|
4626
|
-
constructor() {
|
|
4627
|
-
super("session expired");
|
|
4628
|
-
this.name = "SessionExpiredError";
|
|
4629
|
-
}
|
|
4630
|
-
};
|
|
4631
|
-
});
|
|
4632
|
-
|
|
4633
4661
|
// src/client/trpc.ts
|
|
4634
4662
|
var exports_trpc = {};
|
|
4635
4663
|
__export(exports_trpc, {
|
|
@@ -5408,7 +5436,7 @@ function isDefinitiveError(err) {
|
|
|
5408
5436
|
}
|
|
5409
5437
|
async function requestCreateTicket(cfg, req) {
|
|
5410
5438
|
const client = new Client(cfg);
|
|
5411
|
-
const resp = await client.
|
|
5439
|
+
const resp = await client.postWithApiKey(TICKETS_PATH, req);
|
|
5412
5440
|
if (resp.status !== 202 && resp.status !== 200 && resp.status !== 201) {
|
|
5413
5441
|
throw new TicketHttpError(resp.status);
|
|
5414
5442
|
}
|
|
@@ -5416,7 +5444,7 @@ async function requestCreateTicket(cfg, req) {
|
|
|
5416
5444
|
}
|
|
5417
5445
|
async function requestGetTicket(cfg, ticketId) {
|
|
5418
5446
|
const client = new Client(cfg);
|
|
5419
|
-
const resp = await client.
|
|
5447
|
+
const resp = await client.getWithApiKey(`${TICKETS_PATH}/${encodeURIComponent(ticketId)}`);
|
|
5420
5448
|
if (resp.status !== 200 && resp.status !== 202) {
|
|
5421
5449
|
throw new TicketHttpError(resp.status);
|
|
5422
5450
|
}
|
|
@@ -6224,7 +6252,8 @@ function installClaudeHooks(configPath, opts = {}) {
|
|
|
6224
6252
|
if (typeof cfg.hooks !== "object" || cfg.hooks === null) {
|
|
6225
6253
|
cfg.hooks = {};
|
|
6226
6254
|
}
|
|
6227
|
-
const
|
|
6255
|
+
const includeStop = opts.stop !== false;
|
|
6256
|
+
const events = DEFAULT_HOOK_EVENTS.filter((event) => event !== "Stop" || includeStop);
|
|
6228
6257
|
for (const event of events) {
|
|
6229
6258
|
const existing = Array.isArray(cfg.hooks[event]) ? cfg.hooks[event] : [];
|
|
6230
6259
|
const preserved = existing.filter((g2) => !isDosuGroup(g2));
|
|
@@ -6284,7 +6313,7 @@ function inspectClaudeHooks(configPath) {
|
|
|
6284
6313
|
var DEFAULT_HOOK_EVENTS, MARKER = "__dosu", EVENT_SUBCOMMAND;
|
|
6285
6314
|
var init_claude_code = __esm(() => {
|
|
6286
6315
|
init_config_helpers();
|
|
6287
|
-
DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse"];
|
|
6316
|
+
DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse", "Stop"];
|
|
6288
6317
|
EVENT_SUBCOMMAND = {
|
|
6289
6318
|
UserPromptSubmit: "user-prompt-submit",
|
|
6290
6319
|
PostToolUse: "post-tool-use",
|
|
@@ -12239,6 +12268,7 @@ var init_flow3 = __esm(() => {
|
|
|
12239
12268
|
|
|
12240
12269
|
// src/cli/cli.ts
|
|
12241
12270
|
init_esm();
|
|
12271
|
+
init_client();
|
|
12242
12272
|
import { readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "node:fs";
|
|
12243
12273
|
|
|
12244
12274
|
// src/commands/analytics.ts
|
|
@@ -12998,7 +13028,7 @@ async function runUserPromptSubmit(input, now = Date.now()) {
|
|
|
12998
13028
|
return;
|
|
12999
13029
|
}
|
|
13000
13030
|
const cfg = loadConfig();
|
|
13001
|
-
if (!
|
|
13031
|
+
if (!cfg.api_key || !cfg.deployment_id) {
|
|
13002
13032
|
logger.debug("hooks", "submit skipped reason=not-configured");
|
|
13003
13033
|
return;
|
|
13004
13034
|
}
|
|
@@ -13052,7 +13082,7 @@ async function runPostToolUse(input, now = Date.now()) {
|
|
|
13052
13082
|
return;
|
|
13053
13083
|
}
|
|
13054
13084
|
const cfg = loadConfig();
|
|
13055
|
-
if (!
|
|
13085
|
+
if (!cfg.api_key || !cfg.deployment_id) {
|
|
13056
13086
|
saveState({ ...state, lastCheckedAt: now });
|
|
13057
13087
|
return;
|
|
13058
13088
|
}
|
|
@@ -13109,7 +13139,7 @@ async function runStop(input, now = Date.now()) {
|
|
|
13109
13139
|
return printContinue();
|
|
13110
13140
|
}
|
|
13111
13141
|
const cfg = loadConfig();
|
|
13112
|
-
if (!
|
|
13142
|
+
if (!cfg.api_key || !cfg.deployment_id)
|
|
13113
13143
|
return printContinue();
|
|
13114
13144
|
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
13115
13145
|
let resp;
|
|
@@ -13209,7 +13239,7 @@ async function runInstall(agent, opts) {
|
|
|
13209
13239
|
const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
|
|
13210
13240
|
const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
|
|
13211
13241
|
try {
|
|
13212
|
-
const { events } = installClaudeHooks2(configPath, {
|
|
13242
|
+
const { events } = installClaudeHooks2(configPath, { stop: opts.stop });
|
|
13213
13243
|
if (opts.json) {
|
|
13214
13244
|
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
13215
13245
|
emitStep2({ step: "hooks-install", path: configPath, events });
|
|
@@ -13299,7 +13329,7 @@ async function collectDoctorChecks(opts) {
|
|
|
13299
13329
|
detail: "no deployment selected (run 'dosu setup')"
|
|
13300
13330
|
});
|
|
13301
13331
|
}
|
|
13302
|
-
if (cfg.api_key && cfg.deployment_id
|
|
13332
|
+
if (cfg.api_key && cfg.deployment_id) {
|
|
13303
13333
|
const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
13304
13334
|
const ok = await new Client2(cfg).validateAPIKey(cfg.api_key, cfg.deployment_id);
|
|
13305
13335
|
checks.push({
|
|
@@ -13311,7 +13341,7 @@ async function collectDoctorChecks(opts) {
|
|
|
13311
13341
|
checks.push({
|
|
13312
13342
|
name: "backend",
|
|
13313
13343
|
status: "warn",
|
|
13314
|
-
detail: "skipped (
|
|
13344
|
+
detail: "skipped (no API key / no deployment)"
|
|
13315
13345
|
});
|
|
13316
13346
|
}
|
|
13317
13347
|
return checks;
|
|
@@ -13341,7 +13371,7 @@ function hooksCommand() {
|
|
|
13341
13371
|
cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").action(async () => {
|
|
13342
13372
|
await runHookEntrypoint("post-tool-use", readStdinRaw());
|
|
13343
13373
|
});
|
|
13344
|
-
cmd.command("stop").description("Hook entrypoint
|
|
13374
|
+
cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").action(async () => {
|
|
13345
13375
|
await runHookEntrypoint("stop", readStdinRaw());
|
|
13346
13376
|
});
|
|
13347
13377
|
cmd.command("status").description("Show the active Dosu knowledge ticket for this session").option("--json", "Output JSON", false).action((opts) => {
|
|
@@ -13354,13 +13384,13 @@ function hooksCommand() {
|
|
|
13354
13384
|
}
|
|
13355
13385
|
runStatus(input, opts);
|
|
13356
13386
|
});
|
|
13357
|
-
cmd.command("install").description("Install Dosu hooks into a coding agent's local config").addArgument(new Argument("<agent>", "coding agent to configure").choices(SUPPORTED_AGENTS)).option("--scope <scope>", "Config scope (local only)", "local").option("--dir <path>", "Project root (defaults to current directory)").option("--
|
|
13387
|
+
cmd.command("install").description("Install Dosu hooks into a coding agent's local config").addArgument(new Argument("<agent>", "coding agent to configure").choices(SUPPORTED_AGENTS)).option("--scope <scope>", "Config scope (local only)", "local").option("--dir <path>", "Project root (defaults to current directory)").option("--no-stop", "Skip the Stop hook (knowledge then delivers mid-session only, less reliably)").option("--json", "Emit machine-readable JSON", false).addHelpText("after", [
|
|
13358
13388
|
"",
|
|
13359
13389
|
`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
|
|
13360
13390
|
"",
|
|
13361
13391
|
"Examples:",
|
|
13362
13392
|
" $ dosu hooks install claude-code",
|
|
13363
|
-
" $ dosu hooks install claude-code --
|
|
13393
|
+
" $ dosu hooks install claude-code --no-stop",
|
|
13364
13394
|
" $ dosu hooks install claude-code --dir ./my-project"
|
|
13365
13395
|
].join(`
|
|
13366
13396
|
`)).action((agent, opts) => runInstall(agent, opts));
|
|
@@ -14341,10 +14371,17 @@ function createProgram() {
|
|
|
14341
14371
|
return;
|
|
14342
14372
|
}
|
|
14343
14373
|
const cfg = loadConfig();
|
|
14344
|
-
if (isAuthenticated(cfg)
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14374
|
+
if (isAuthenticated(cfg)) {
|
|
14375
|
+
if (!isTokenExpired(cfg)) {
|
|
14376
|
+
console.log("You are already logged in.");
|
|
14377
|
+
console.log("Run 'dosu logout' first to re-authenticate.");
|
|
14378
|
+
return;
|
|
14379
|
+
}
|
|
14380
|
+
if (await ensureFreshSession(cfg)) {
|
|
14381
|
+
console.log("Session refreshed.");
|
|
14382
|
+
console.log(`Credentials saved to ${getConfigPath()}`);
|
|
14383
|
+
return;
|
|
14384
|
+
}
|
|
14348
14385
|
}
|
|
14349
14386
|
console.log("Opening browser for authentication...");
|
|
14350
14387
|
const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
|
|
@@ -14384,14 +14421,14 @@ function createProgram() {
|
|
|
14384
14421
|
saveConfig(cfg);
|
|
14385
14422
|
console.log("Successfully logged out.");
|
|
14386
14423
|
});
|
|
14387
|
-
program2.command("status").description("Show current authentication and MCP status").action(() => {
|
|
14424
|
+
program2.command("status").description("Show current authentication and MCP status").action(async () => {
|
|
14388
14425
|
const cfg = loadConfig();
|
|
14389
14426
|
if (!isAuthenticated(cfg)) {
|
|
14390
14427
|
console.log("Status: Not logged in");
|
|
14391
14428
|
console.log("Run 'dosu login' to authenticate.");
|
|
14392
14429
|
return;
|
|
14393
14430
|
}
|
|
14394
|
-
if (isTokenExpired(cfg)) {
|
|
14431
|
+
if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
|
|
14395
14432
|
console.log("Status: Token expired");
|
|
14396
14433
|
console.log("Run 'dosu login' to re-authenticate.");
|
|
14397
14434
|
} else {
|
|
@@ -14409,7 +14446,7 @@ function createProgram() {
|
|
|
14409
14446
|
}
|
|
14410
14447
|
});
|
|
14411
14448
|
const mcp = program2.command("mcp").description("Manage MCP server integrations");
|
|
14412
|
-
mcp.command("add <agent>").description("Add Dosu MCP to an AI tool").option("-g, --global", "Add globally (all projects) instead of project-local", false).option("--show-secret", "Print full manual configuration secrets", false).action((toolId, opts) => {
|
|
14449
|
+
mcp.command("add <agent>").description("Add Dosu MCP to an AI tool").option("-g, --global", "Add globally (all projects) instead of project-local", false).option("--show-secret", "Print full manual configuration secrets", false).action(async (toolId, opts) => {
|
|
14413
14450
|
let provider;
|
|
14414
14451
|
try {
|
|
14415
14452
|
provider = getProvider(toolId.toLowerCase());
|
|
@@ -14420,7 +14457,7 @@ function createProgram() {
|
|
|
14420
14457
|
if (!isAuthenticated(cfg)) {
|
|
14421
14458
|
throw new Error("not logged in. Run 'dosu login' first");
|
|
14422
14459
|
}
|
|
14423
|
-
if (isTokenExpired(cfg)) {
|
|
14460
|
+
if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
|
|
14424
14461
|
throw new Error("session expired. Run 'dosu login' to re-authenticate");
|
|
14425
14462
|
}
|
|
14426
14463
|
if (cfg.mode !== MODE_OSS && !cfg.deployment_id) {
|
|
@@ -14545,6 +14582,18 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
14545
14582
|
});
|
|
14546
14583
|
return program2;
|
|
14547
14584
|
}
|
|
14585
|
+
async function ensureFreshSession(cfg) {
|
|
14586
|
+
if (!isTokenExpired(cfg))
|
|
14587
|
+
return true;
|
|
14588
|
+
try {
|
|
14589
|
+
logger.debug("cli", "token expired, attempting refresh");
|
|
14590
|
+
await new Client(cfg).refreshToken();
|
|
14591
|
+
return true;
|
|
14592
|
+
} catch (err) {
|
|
14593
|
+
logger.debug("cli", `token refresh failed: ${err instanceof Error ? err.message : err}`);
|
|
14594
|
+
return false;
|
|
14595
|
+
}
|
|
14596
|
+
}
|
|
14548
14597
|
async function execute() {
|
|
14549
14598
|
const program2 = createProgram();
|
|
14550
14599
|
await program2.parseAsync(process.argv);
|