@mentio_website/cli 0.1.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 +57 -0
- package/bin/mentio.js +334 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @mentio/cli
|
|
2
|
+
|
|
3
|
+
Mentio from your terminal — see how AI answer engines (ChatGPT, Claude, Perplexity, Google AI) mention and cite your brand.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @mentio/cli
|
|
9
|
+
# or run without installing:
|
|
10
|
+
npx @mentio/cli --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node ≥ 18.
|
|
14
|
+
|
|
15
|
+
## Login
|
|
16
|
+
|
|
17
|
+
You need a Mentio **API key** (`gtk_…`).
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
mentio login # prompts for API base URL + API key
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Credentials are stored in `~/.mentio/config.json` (chmod 600). You can also pass `--key` per command or set `MENTIO_API_KEY`.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
mentio projects # list your projects
|
|
29
|
+
mentio add example.com --desc "What your brand does" --competitors a.com,b.com
|
|
30
|
+
|
|
31
|
+
mentio visibility <id> # visibility, citation rate, share of voice
|
|
32
|
+
mentio gaps <id> # prompts where you're absent + who's cited instead
|
|
33
|
+
mentio sources <id> # domains the engines cite for your topics
|
|
34
|
+
mentio timeseries <id> # visibility over time
|
|
35
|
+
mentio recommend <id> --lang en # actions + ready-to-use optimization prompts
|
|
36
|
+
mentio probe <id> # trigger a new analysis run
|
|
37
|
+
|
|
38
|
+
mentio audit <id> # on-page SEO audit + PageSpeed (free)
|
|
39
|
+
mentio queries <id> # real Google Search Console queries (GSC connected)
|
|
40
|
+
|
|
41
|
+
mentio account # plan + credit balance
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add `--json` to any command for scriptable output:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mentio visibility 2 --json | jq '.overall.visibility_rate'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Flags
|
|
51
|
+
|
|
52
|
+
- `--json` — raw JSON output
|
|
53
|
+
- `--base <url>` — override API base URL (self-host)
|
|
54
|
+
- `--key <apiKey>` — override API key (or `MENTIO_API_KEY`)
|
|
55
|
+
- `-h, --help` · `-v, --version`
|
|
56
|
+
|
|
57
|
+
MIT · https://usementio.eu
|
package/bin/mentio.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Mentio CLI — talk to the Mentio API from your terminal.
|
|
3
|
+
// Zero dependencies: Node >= 18 (global fetch). Config in ~/.mentio/config.json.
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import readline from "node:readline";
|
|
8
|
+
|
|
9
|
+
const VERSION = "0.1.0";
|
|
10
|
+
const DEFAULT_BASE = "https://api-production-c8ce.up.railway.app";
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), ".mentio");
|
|
12
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
13
|
+
|
|
14
|
+
// ── colors (respect NO_COLOR / non-TTY) ─────────────────────────────────────
|
|
15
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
16
|
+
const c = (code, s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
17
|
+
const orange = (s) => c("38;5;208", s);
|
|
18
|
+
const dim = (s) => c("2", s);
|
|
19
|
+
const bold = (s) => c("1", s);
|
|
20
|
+
const green = (s) => c("32", s);
|
|
21
|
+
const red = (s) => c("31", s);
|
|
22
|
+
|
|
23
|
+
// ── config ──────────────────────────────────────────────────────────────────
|
|
24
|
+
function loadConfig() {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function saveConfig(cfg) {
|
|
32
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
33
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
34
|
+
try {
|
|
35
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── args ─────────────────────────────────────────────────────────────────────
|
|
40
|
+
const argv = process.argv.slice(2);
|
|
41
|
+
const flags = {};
|
|
42
|
+
const positional = [];
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const a = argv[i];
|
|
45
|
+
if (a.startsWith("--")) {
|
|
46
|
+
const key = a.slice(2);
|
|
47
|
+
const next = argv[i + 1];
|
|
48
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
49
|
+
flags[key] = next;
|
|
50
|
+
i++;
|
|
51
|
+
} else flags[key] = true;
|
|
52
|
+
} else positional.push(a);
|
|
53
|
+
}
|
|
54
|
+
const cmd = positional[0];
|
|
55
|
+
|
|
56
|
+
function die(msg) {
|
|
57
|
+
console.error(red("✖ ") + msg);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── HTTP ─────────────────────────────────────────────────────────────────────
|
|
62
|
+
async function api(pathname, { method = "GET", body } = {}) {
|
|
63
|
+
const cfg = loadConfig();
|
|
64
|
+
const key = flags.key || process.env.MENTIO_API_KEY || cfg.key;
|
|
65
|
+
if (!key) die("Not logged in. Run: " + bold("mentio login") + " (or pass --key / MENTIO_API_KEY)");
|
|
66
|
+
const base = flags.base || cfg.base || DEFAULT_BASE;
|
|
67
|
+
let res;
|
|
68
|
+
try {
|
|
69
|
+
res = await fetch(base + pathname, {
|
|
70
|
+
method,
|
|
71
|
+
headers: {
|
|
72
|
+
Authorization: "Bearer " + key,
|
|
73
|
+
...(body ? { "Content-Type": "application/json" } : {}),
|
|
74
|
+
},
|
|
75
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
76
|
+
});
|
|
77
|
+
} catch (e) {
|
|
78
|
+
die("Network error: " + e.message);
|
|
79
|
+
}
|
|
80
|
+
const text = await res.text();
|
|
81
|
+
let data;
|
|
82
|
+
try {
|
|
83
|
+
data = JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
data = text;
|
|
86
|
+
}
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
const detail = data && data.error ? data.error : typeof data === "string" ? data : JSON.stringify(data);
|
|
89
|
+
if (res.status === 401) die("Unauthorized (" + res.status + "). Check your API key: mentio login");
|
|
90
|
+
die(`HTTP ${res.status}: ${detail}`);
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── formatting ────────────────────────────────────────────────────────────────
|
|
96
|
+
function print(data) {
|
|
97
|
+
if (flags.json) console.log(JSON.stringify(data, null, 2));
|
|
98
|
+
return flags.json;
|
|
99
|
+
}
|
|
100
|
+
const pct = (v) => (v == null ? "—" : (Math.round(v * 1000) / 10).toFixed(1) + "%");
|
|
101
|
+
function table(rows, headers) {
|
|
102
|
+
if (!rows.length) return console.log(dim("(none)"));
|
|
103
|
+
const cols = headers.map((h) => h.label);
|
|
104
|
+
const widths = headers.map((h, i) =>
|
|
105
|
+
Math.max(cols[i].length, ...rows.map((r) => String(h.get(r) ?? "").length))
|
|
106
|
+
);
|
|
107
|
+
const line = (cells) => cells.map((cell, i) => String(cell ?? "").padEnd(widths[i])).join(" ");
|
|
108
|
+
console.log(dim(line(cols)));
|
|
109
|
+
for (const r of rows) console.log(line(headers.map((h) => h.get(r))));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── commands ──────────────────────────────────────────────────────────────────
|
|
113
|
+
async function login() {
|
|
114
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
115
|
+
const ask = (q, def) => new Promise((r) => rl.question(q, (a) => r(a.trim() || def)));
|
|
116
|
+
const cfg = loadConfig();
|
|
117
|
+
const base = flags.base || (await ask(`API base URL [${cfg.base || DEFAULT_BASE}]: `, cfg.base || DEFAULT_BASE));
|
|
118
|
+
const key = flags.key || (await ask("API key (gtk_…): ", cfg.key));
|
|
119
|
+
rl.close();
|
|
120
|
+
if (!key) die("No API key provided.");
|
|
121
|
+
saveConfig({ ...cfg, base, key });
|
|
122
|
+
// verify
|
|
123
|
+
try {
|
|
124
|
+
const acc = await apiWith(base, key, "/api/v1/billing/account");
|
|
125
|
+
console.log(green("✓ ") + `Logged in. Plan: ${bold(acc.plan || "?")} · credits: ${acc.credits ?? "?"}`);
|
|
126
|
+
} catch {
|
|
127
|
+
console.log(green("✓ ") + "Saved credentials to " + dim(CONFIG_FILE));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function apiWith(base, key, pathname) {
|
|
131
|
+
const res = await fetch(base + pathname, { headers: { Authorization: "Bearer " + key } });
|
|
132
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
133
|
+
return res.json();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function logout() {
|
|
137
|
+
try {
|
|
138
|
+
fs.rmSync(CONFIG_FILE);
|
|
139
|
+
} catch {}
|
|
140
|
+
console.log(green("✓ ") + "Logged out.");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function account() {
|
|
144
|
+
const d = await api("/api/v1/billing/account");
|
|
145
|
+
if (print(d)) return;
|
|
146
|
+
console.log(`${bold("Plan")} ${d.plan}`);
|
|
147
|
+
console.log(`${bold("Credits")} ${d.credits}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function projects() {
|
|
151
|
+
const { projects } = await api("/api/v1/projects");
|
|
152
|
+
if (print({ projects })) return;
|
|
153
|
+
table(projects, [
|
|
154
|
+
{ label: "ID", get: (p) => p.id },
|
|
155
|
+
{ label: "DOMAIN", get: (p) => p.domain },
|
|
156
|
+
{ label: "NAME", get: (p) => p.name },
|
|
157
|
+
{ label: "COMPETITORS", get: (p) => (p.competitors || []).join(", ") },
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function add() {
|
|
162
|
+
const domain = positional[1];
|
|
163
|
+
if (!domain) die("Usage: mentio add <domain> [--desc \"...\"] [--competitors a.com,b.com] [--no-run]");
|
|
164
|
+
const body = {
|
|
165
|
+
domain,
|
|
166
|
+
description: flags.desc || flags.description || "",
|
|
167
|
+
competitors: (flags.competitors || "").split(",").map((s) => s.trim()).filter(Boolean),
|
|
168
|
+
generate_prompts: true,
|
|
169
|
+
run: !flags["no-run"],
|
|
170
|
+
};
|
|
171
|
+
const d = await api("/api/v1/projects", { method: "POST", body });
|
|
172
|
+
if (print(d)) return;
|
|
173
|
+
console.log(green("✓ ") + `Created project ${bold(d.id)} (${d.domain}).`);
|
|
174
|
+
if (d.run) console.log(dim(` run ${d.run.run_id} — ${d.run.jobs} probes across ${(d.run.engines || []).join(", ")}`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function reqId() {
|
|
178
|
+
const id = positional[1];
|
|
179
|
+
if (!id) die(`Usage: mentio ${cmd} <projectId>`);
|
|
180
|
+
return id;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function visibility() {
|
|
184
|
+
const d = await api(`/api/v1/projects/${reqId()}/visibility`);
|
|
185
|
+
if (print(d)) return;
|
|
186
|
+
const o = d.overall || {};
|
|
187
|
+
console.log(bold("Overall"));
|
|
188
|
+
console.log(` visibility ${orange(pct(o.visibility_rate))} citation ${pct(o.citation_rate)} SoV ${pct(o.share_of_voice)} (${o.probes || 0} probes)`);
|
|
189
|
+
if ((d.per_engine || []).length) {
|
|
190
|
+
console.log(bold("\nPer engine"));
|
|
191
|
+
table(d.per_engine, [
|
|
192
|
+
{ label: "ENGINE", get: (e) => e.engine },
|
|
193
|
+
{ label: "VISIBILITY", get: (e) => pct(e.visibility_rate) },
|
|
194
|
+
{ label: "CITATION", get: (e) => pct(e.citation_rate) },
|
|
195
|
+
{ label: "PROBES", get: (e) => e.probes },
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function gaps() {
|
|
201
|
+
const { gaps } = await api(`/api/v1/projects/${reqId()}/gaps`);
|
|
202
|
+
if (print({ gaps })) return;
|
|
203
|
+
table(gaps || [], [
|
|
204
|
+
{ label: "PROMPT", get: (g) => (g.prompt || "").slice(0, 60) },
|
|
205
|
+
{ label: "ENGINE", get: (g) => g.engine },
|
|
206
|
+
{ label: "AI CITED", get: (g) => (g.domains || []).slice(0, 3).join(", ") },
|
|
207
|
+
]);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function sources() {
|
|
211
|
+
const { cited_domains } = await api(`/api/v1/projects/${reqId()}/cited-domains`);
|
|
212
|
+
if (print({ cited_domains })) return;
|
|
213
|
+
table(cited_domains || [], [
|
|
214
|
+
{ label: "DOMAIN", get: (s) => s.domain },
|
|
215
|
+
{ label: "CITATIONS", get: (s) => s.count },
|
|
216
|
+
]);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function recommend() {
|
|
220
|
+
const lang = flags.lang || "en";
|
|
221
|
+
const d = await api(`/api/v1/projects/${reqId()}/recommendations?lang=${lang}`);
|
|
222
|
+
if (print(d)) return;
|
|
223
|
+
if (d.summary) console.log(dim(d.summary) + "\n");
|
|
224
|
+
console.log(bold("Actions"));
|
|
225
|
+
for (const r of d.recommendations || []) console.log(" " + orange("—") + " " + r);
|
|
226
|
+
if ((d.optimization_prompts || []).length) {
|
|
227
|
+
console.log(bold("\nOptimization prompts"));
|
|
228
|
+
for (const p of d.optimization_prompts) console.log(" " + dim("›") + " " + p);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function audit() {
|
|
233
|
+
const d = await api(`/api/v1/projects/${reqId()}/seo/audit`);
|
|
234
|
+
if (print(d)) return;
|
|
235
|
+
console.log(bold(d.url || "") + " " + (d.fetched ? green(`HTTP ${d.status}`) : red("unreachable")));
|
|
236
|
+
if (d.pagespeed) console.log(dim(`PageSpeed — perf ${d.pagespeed.performance} · seo ${d.pagespeed.seo}`));
|
|
237
|
+
const icon = (s) => (s === "ok" ? green("✓") : s === "fail" ? red("✗") : orange("!"));
|
|
238
|
+
for (const ch of d.checks || []) console.log(` ${icon(ch.status)} ${ch.message}`);
|
|
239
|
+
console.log(dim(` robots.txt ${d.robots_txt ? "✓" : "—"} sitemap ${d.sitemap ? "✓" : "—"}`));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function queries() {
|
|
243
|
+
const d = await api(`/api/v1/projects/${reqId()}/seo/queries`);
|
|
244
|
+
if (print(d)) return;
|
|
245
|
+
table(d.queries || [], [
|
|
246
|
+
{ label: "QUERY", get: (q) => (q.keys || []).join(" ") },
|
|
247
|
+
{ label: "CLICKS", get: (q) => q.clicks },
|
|
248
|
+
{ label: "IMPR", get: (q) => q.impressions },
|
|
249
|
+
{ label: "CTR", get: (q) => ((q.ctr || 0) * 100).toFixed(1) + "%" },
|
|
250
|
+
{ label: "POS", get: (q) => (q.position || 0).toFixed(1) },
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function timeseries() {
|
|
255
|
+
const d = await api(`/api/v1/projects/${reqId()}/timeseries`);
|
|
256
|
+
if (print(d)) return;
|
|
257
|
+
table(d.timeseries || [], [
|
|
258
|
+
{ label: "DATE", get: (t) => t.date || t.day },
|
|
259
|
+
{ label: "VISIBILITY", get: (t) => pct(t.visibility_rate) },
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function probe() {
|
|
264
|
+
const d = await api(`/api/v1/projects/${reqId()}/probe`, { method: "POST" });
|
|
265
|
+
if (print(d)) return;
|
|
266
|
+
console.log(green("✓ ") + `Run started: ${d.run_id} — ${d.jobs} probes (${(d.engines || []).join(", ")}).`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function help() {
|
|
270
|
+
console.log(`
|
|
271
|
+
${orange("mentio")} — see how AI answer engines cite your brand. ${dim("v" + VERSION)}
|
|
272
|
+
|
|
273
|
+
${bold("Usage")}
|
|
274
|
+
mentio <command> [args] [--json] [--base <url>] [--key <apiKey>]
|
|
275
|
+
|
|
276
|
+
${bold("Auth")}
|
|
277
|
+
login Save API base URL + API key to ~/.mentio/config.json
|
|
278
|
+
logout Remove stored credentials
|
|
279
|
+
account Show plan and credit balance
|
|
280
|
+
|
|
281
|
+
${bold("Projects")}
|
|
282
|
+
projects List your projects
|
|
283
|
+
add <domain> Add a project [--desc "..."] [--competitors a,b] [--no-run]
|
|
284
|
+
|
|
285
|
+
${bold("GEO metrics")} ${dim("(need a project id — see: mentio projects)")}
|
|
286
|
+
visibility <id> Visibility, citation rate, share of voice (overall + per engine)
|
|
287
|
+
gaps <id> Prompt × engine where you're absent, + who's cited instead
|
|
288
|
+
sources <id> Domains the engines cite for your topics
|
|
289
|
+
timeseries <id> Visibility over time
|
|
290
|
+
recommend <id> Actions + optimization prompts [--lang en|it]
|
|
291
|
+
probe <id> Trigger a new analysis run
|
|
292
|
+
|
|
293
|
+
${bold("SEO (free)")}
|
|
294
|
+
audit <id> On-page audit + PageSpeed
|
|
295
|
+
queries <id> Real Google Search Console queries (needs GSC connected)
|
|
296
|
+
|
|
297
|
+
${bold("Global flags")}
|
|
298
|
+
--json Raw JSON output (scriptable)
|
|
299
|
+
--base <url> Override API base URL
|
|
300
|
+
--key <apiKey> Override API key (or set MENTIO_API_KEY)
|
|
301
|
+
-h, --help This help · -v, --version
|
|
302
|
+
|
|
303
|
+
${dim("Docs: https://usementio.eu")}
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── dispatch ──────────────────────────────────────────────────────────────────
|
|
308
|
+
const commands = {
|
|
309
|
+
login,
|
|
310
|
+
logout,
|
|
311
|
+
account,
|
|
312
|
+
whoami: account,
|
|
313
|
+
projects,
|
|
314
|
+
add,
|
|
315
|
+
visibility,
|
|
316
|
+
gaps,
|
|
317
|
+
sources,
|
|
318
|
+
recommend,
|
|
319
|
+
recommendations: recommend,
|
|
320
|
+
audit,
|
|
321
|
+
queries,
|
|
322
|
+
timeseries,
|
|
323
|
+
probe,
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
async function main() {
|
|
327
|
+
if (flags.version || flags.v || cmd === "version") return console.log(VERSION);
|
|
328
|
+
if (!cmd || flags.help || flags.h || cmd === "help") return help();
|
|
329
|
+
const fn = commands[cmd];
|
|
330
|
+
if (!fn) die(`Unknown command: ${cmd}\nRun ${bold("mentio --help")}`);
|
|
331
|
+
await fn();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
main().catch((e) => die(e.message || String(e)));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mentio_website/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Mentio CLI — see how AI answer engines cite your brand, from the terminal.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mentio": "bin/mentio.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mentio",
|
|
18
|
+
"geo",
|
|
19
|
+
"generative-engine-optimization",
|
|
20
|
+
"ai-search",
|
|
21
|
+
"seo",
|
|
22
|
+
"cli"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://usementio.eu",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/giacomocavalcabo/mentio.git"
|
|
29
|
+
}
|
|
30
|
+
}
|