@mentio-dev/cli 0.1.1 → 0.2.0
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 +2 -2
- package/dist/index.js +116 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install -g @mentio-dev/cli
|
|
7
|
-
mentio auth:
|
|
7
|
+
mentio auth:login
|
|
8
8
|
mentio keywords:create --term "acme" --kind brand
|
|
9
9
|
mentio mentions:search --relevant true --sentiment negative --limit 20
|
|
10
10
|
mentio mentions:update mm_7f3a... --status done
|
|
@@ -12,4 +12,4 @@ mentio analytics:summary --range 7d --compare true
|
|
|
12
12
|
mentio mentions:watch --platform reddit | jq -r '.post.url'
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
JSON out, compact when piped and indented on a terminal; `--table` for lists.
|
|
15
|
+
JSON out, compact when piped and indented on a terminal; `--table` for lists. `mentio auth:login` signs in through the browser; keys also come from `--api-key`, `MENTIO_API_KEY`, or `mentio auth:set`. Every command, by area, is at [docs.mentio.dev/cli](https://docs.mentio.dev/cli).
|
package/dist/index.js
CHANGED
|
@@ -8,11 +8,12 @@ var __export = (target, all) => {
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
import { Command, Option } from "commander";
|
|
10
10
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
11
|
+
import { hostname } from "os";
|
|
11
12
|
|
|
12
13
|
// package.json
|
|
13
14
|
var package_default = {
|
|
14
15
|
name: "@mentio-dev/cli",
|
|
15
|
-
version: "0.
|
|
16
|
+
version: "0.2.0",
|
|
16
17
|
description: "Command-line client for the Mentio API: one command per endpoint, plus watch and MCP helpers.",
|
|
17
18
|
license: "MIT",
|
|
18
19
|
homepage: "https://docs.mentio.dev/cli",
|
|
@@ -225,6 +226,94 @@ function buildRequest(op, positional, flags, jsonBody) {
|
|
|
225
226
|
return { path, query, body };
|
|
226
227
|
}
|
|
227
228
|
|
|
229
|
+
// src/login.ts
|
|
230
|
+
import { randomBytes } from "crypto";
|
|
231
|
+
import { spawn } from "child_process";
|
|
232
|
+
import { createServer } from "http";
|
|
233
|
+
var DEFAULT_APP_URL = "https://app.mentio.dev";
|
|
234
|
+
var LoginError = class extends Error {
|
|
235
|
+
constructor(message) {
|
|
236
|
+
super(message);
|
|
237
|
+
this.name = "LoginError";
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var newState = () => randomBytes(24).toString("base64url");
|
|
241
|
+
function loginUrl(appUrl, params) {
|
|
242
|
+
const url = new URL("/cli/authorize", `${appUrl.replace(/\/+$/, "")}/`);
|
|
243
|
+
url.searchParams.set("port", String(params.port));
|
|
244
|
+
url.searchParams.set("state", params.state);
|
|
245
|
+
url.searchParams.set("name", params.name);
|
|
246
|
+
url.searchParams.set("scope", params.scope);
|
|
247
|
+
return url.toString();
|
|
248
|
+
}
|
|
249
|
+
var page = (title, body) => `<!doctype html><meta charset="utf-8"><title>${title}</title><body style="font:16px system-ui;padding:3rem;max-width:36rem;margin:auto"><h1 style="font-size:1.25rem">${title}</h1><p>${body}</p></body>`;
|
|
250
|
+
function startCallbackServer(state, timeoutMs) {
|
|
251
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
252
|
+
let settle = null;
|
|
253
|
+
const key = new Promise((resolve, reject) => {
|
|
254
|
+
settle = { resolve, reject };
|
|
255
|
+
});
|
|
256
|
+
const server = createServer((req, res) => {
|
|
257
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
258
|
+
if (url.pathname !== "/callback") {
|
|
259
|
+
res.writeHead(404, { "content-type": "text/html; charset=utf-8" }).end(page("Not found", "Nothing here."));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const error = url.searchParams.get("error");
|
|
263
|
+
if (error) {
|
|
264
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Cancelled", "The CLI was not authorized. You can close this tab."));
|
|
265
|
+
finish(() => settle?.reject(new LoginError(error === "denied" ? "Authorization was declined in the browser." : `Authorization failed: ${error}`)));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (url.searchParams.get("state") !== state) {
|
|
269
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(page("State mismatch", "This callback does not belong to the running login. Run mentio auth:login again."));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const issued = url.searchParams.get("key");
|
|
273
|
+
if (!issued) {
|
|
274
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(page("Missing key", "The callback carried no key. Run mentio auth:login again."));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("Mentio CLI connected", "The key is stored on this computer. You can close this tab and go back to the terminal."));
|
|
278
|
+
finish(() => settle?.resolve(issued));
|
|
279
|
+
});
|
|
280
|
+
const timer = setTimeout(() => finish(() => settle?.reject(new LoginError(`No authorization arrived within ${Math.round(timeoutMs / 1e3)} seconds.`))), timeoutMs);
|
|
281
|
+
const close = () => {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
server.close();
|
|
284
|
+
};
|
|
285
|
+
const finish = (outcome) => {
|
|
286
|
+
setTimeout(() => {
|
|
287
|
+
outcome();
|
|
288
|
+
close();
|
|
289
|
+
}, 50);
|
|
290
|
+
};
|
|
291
|
+
server.on("error", (err) => rejectServer(err));
|
|
292
|
+
server.listen(0, "127.0.0.1", () => {
|
|
293
|
+
const address = server.address();
|
|
294
|
+
if (!address || typeof address === "string") {
|
|
295
|
+
rejectServer(new LoginError("Could not open a loopback port."));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
key.catch(() => {
|
|
299
|
+
});
|
|
300
|
+
resolveServer({ port: address.port, key, close });
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function openBrowser(url) {
|
|
305
|
+
try {
|
|
306
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
307
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
308
|
+
child.on("error", () => {
|
|
309
|
+
});
|
|
310
|
+
child.unref();
|
|
311
|
+
return true;
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
228
317
|
// src/mcp.ts
|
|
229
318
|
var DEFAULT_MCP_URL = "https://mcp.mentio.dev/mcp";
|
|
230
319
|
function mcpConfig(kind, url, apiKey) {
|
|
@@ -3663,8 +3752,8 @@ async function watchMentions(options) {
|
|
|
3663
3752
|
if (result.error !== void 0 || !result.response?.ok) {
|
|
3664
3753
|
options.warn(JSON.stringify({ error: result.error ?? { code: `http_${result.response?.status ?? 0}`, message: "poll failed" } }));
|
|
3665
3754
|
} else {
|
|
3666
|
-
const
|
|
3667
|
-
const fresh = takeNew(seen,
|
|
3755
|
+
const page2 = result.data?.data ?? [];
|
|
3756
|
+
const fresh = takeNew(seen, page2);
|
|
3668
3757
|
if (!first || options.fromStart) for (const item of fresh) options.write(JSON.stringify(item));
|
|
3669
3758
|
}
|
|
3670
3759
|
first = false;
|
|
@@ -3744,6 +3833,30 @@ async function readStdin() {
|
|
|
3744
3833
|
return Buffer.concat(chunks).toString("utf8");
|
|
3745
3834
|
}
|
|
3746
3835
|
function registerAuth() {
|
|
3836
|
+
program.command("auth:login").description("Sign in through the browser: the dashboard mints an API key and hands it to this terminal").option("--app-url <url>", "Dashboard URL", DEFAULT_APP_URL).option("--name <label>", "Name of the key the dashboard creates", `CLI on ${hostname()}`).addOption(new Option("--scope <scope>", "read: GET only. write: everything.").choices(["read", "write"]).default("write")).option("--timeout <seconds>", "How long to wait for the browser", "300").option("--no-open", "Print the URL instead of opening the browser").action(async (flags) => {
|
|
3837
|
+
const globals = program.opts();
|
|
3838
|
+
const seconds = Number(flags.timeout);
|
|
3839
|
+
if (!Number.isFinite(seconds) || seconds < 10) fail({ error: { code: "usage", message: "--timeout must be at least 10 seconds" } }, 2);
|
|
3840
|
+
const state = newState();
|
|
3841
|
+
const server = await startCallbackServer(state, seconds * 1e3);
|
|
3842
|
+
const url = loginUrl(flags.appUrl, { port: server.port, state, name: flags.name, scope: flags.scope });
|
|
3843
|
+
const opened = flags.open ? openBrowser(url) : false;
|
|
3844
|
+
process.stderr.write(`${opened ? "Opening your browser to authorize the CLI. If it does not open, visit:" : "Open this URL to authorize the CLI:"}
|
|
3845
|
+
${url}
|
|
3846
|
+
Waiting for the browser (${seconds}s)...
|
|
3847
|
+
`);
|
|
3848
|
+
let key;
|
|
3849
|
+
try {
|
|
3850
|
+
key = await server.key;
|
|
3851
|
+
} catch (err) {
|
|
3852
|
+
fail({ error: { code: "login_failed", message: err instanceof LoginError ? err.message : String(err) } });
|
|
3853
|
+
}
|
|
3854
|
+
const path = writeConfig({ apiKey: key, ...globals.apiUrl ? { apiUrl: globals.apiUrl } : {} });
|
|
3855
|
+
const settings = resolveSettings({ apiKey: key, apiUrl: globals.apiUrl });
|
|
3856
|
+
const result = await clientFor(settings).request({ method: "GET", url: "/v1/company", security: BEARER, throwOnError: false });
|
|
3857
|
+
const company = result.error === void 0 && result.response?.ok ? result.data : void 0;
|
|
3858
|
+
print({ ok: true, workspace: company?.name ?? null, key: keyPrefix(key), scope: flags.scope, file: path }, globals);
|
|
3859
|
+
});
|
|
3747
3860
|
program.command("auth:set").description("Store an API key (and optionally the API host) in ~/.mentio/config.json").requiredOption("--key <key>", "API key from the dashboard or POST /v1/api-keys (mk_live_...)").option("--url <url>", "API host for a self-hosted deployment").action((flags) => {
|
|
3748
3861
|
const path = writeConfig({ apiKey: flags.key, ...flags.url ? { apiUrl: flags.url } : {} });
|
|
3749
3862
|
print({ ok: true, file: path, key: keyPrefix(flags.key) }, program.opts());
|