@salestouch/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 +80 -0
- package/dist/core.d.ts +91 -0
- package/dist/core.js +382 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +937 -0
- package/dist/locale.d.ts +5 -0
- package/dist/locale.js +28 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +181 -0
- package/dist/setup-wizard.d.ts +39 -0
- package/dist/setup-wizard.js +492 -0
- package/dist/setup.d.ts +51 -0
- package/dist/setup.js +473 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { log, note, outro, spinner } from "@clack/prompts";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { CLI_VERSION, apiRequest, clearStoredAuth, describeAuthMethod, getRemoteMcpUrl, getConfigPath, loadConfig, loginWithDeviceFlow, outputJson, persistAuth, printResourceStatus, printTable, readFlagBoolean, readFlagString, resolveAuth, } from "./core.js";
|
|
8
|
+
import { detectCliLocale } from "./locale.js";
|
|
9
|
+
import { startMcpServer } from "./mcp-server.js";
|
|
10
|
+
import { applySetupWizardSelections, runInteractiveSetupWizard, shouldRunSetupWizard, } from "./setup-wizard.js";
|
|
11
|
+
import { readSetupManifest, runSetup, runSkillsInstall, } from "./setup.js";
|
|
12
|
+
const cliCatalogCategoryOrder = [
|
|
13
|
+
"linkedin",
|
|
14
|
+
"imports",
|
|
15
|
+
"leads",
|
|
16
|
+
"missions",
|
|
17
|
+
"offers",
|
|
18
|
+
"scoring",
|
|
19
|
+
"other",
|
|
20
|
+
];
|
|
21
|
+
function isFrench(locale) {
|
|
22
|
+
return locale === "fr";
|
|
23
|
+
}
|
|
24
|
+
function getCliCatalogCategoryLabel(category, locale = detectCliLocale()) {
|
|
25
|
+
const labels = {
|
|
26
|
+
en: {
|
|
27
|
+
linkedin: "LinkedIn",
|
|
28
|
+
imports: "Imports",
|
|
29
|
+
leads: "Leads",
|
|
30
|
+
missions: "Missions",
|
|
31
|
+
offers: "Offers",
|
|
32
|
+
scoring: "Scoring",
|
|
33
|
+
other: "Other",
|
|
34
|
+
},
|
|
35
|
+
fr: {
|
|
36
|
+
linkedin: "LinkedIn",
|
|
37
|
+
imports: "Imports",
|
|
38
|
+
leads: "Leads",
|
|
39
|
+
missions: "Missions",
|
|
40
|
+
offers: "Offres",
|
|
41
|
+
scoring: "Scoring",
|
|
42
|
+
other: "Autres",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return labels[isFrench(locale) ? "fr" : "en"][category];
|
|
46
|
+
}
|
|
47
|
+
function getAgentDisplayName(agent) {
|
|
48
|
+
switch (agent) {
|
|
49
|
+
case "claude":
|
|
50
|
+
return "Claude";
|
|
51
|
+
case "cursor":
|
|
52
|
+
return "Cursor";
|
|
53
|
+
case "codex":
|
|
54
|
+
return "Codex";
|
|
55
|
+
case "opencode":
|
|
56
|
+
return "OpenCode";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function formatAgentDisplayList(agents, locale = detectCliLocale()) {
|
|
60
|
+
const labels = agents.map((agent) => getAgentDisplayName(agent));
|
|
61
|
+
if (labels.length <= 1) {
|
|
62
|
+
return labels[0] ?? "";
|
|
63
|
+
}
|
|
64
|
+
if (labels.length === 2) {
|
|
65
|
+
return isFrench(locale) ? `${labels[0]} et ${labels[1]}` : `${labels[0]} and ${labels[1]}`;
|
|
66
|
+
}
|
|
67
|
+
const head = labels.slice(0, -1).join(", ");
|
|
68
|
+
const tail = labels[labels.length - 1];
|
|
69
|
+
return isFrench(locale) ? `${head} et ${tail}` : `${head}, and ${tail}`;
|
|
70
|
+
}
|
|
71
|
+
function buildMcpOutro(agents, locale = detectCliLocale()) {
|
|
72
|
+
return isFrench(locale)
|
|
73
|
+
? agents.length > 1
|
|
74
|
+
? `Ouvrez vos agents IA. SalesTouch est connecté dans ${formatAgentDisplayList(agents, locale)} et prêt à travailler à partir du vrai contexte.`
|
|
75
|
+
: "Ouvrez votre agent IA. SalesTouch est connecté, en direct, et prêt à travailler à partir du vrai contexte."
|
|
76
|
+
: agents.length > 1
|
|
77
|
+
? `Open your AI agents. SalesTouch is live in ${formatAgentDisplayList(agents, locale)} and ready to work from real context.`
|
|
78
|
+
: "Open your AI agent. SalesTouch is live, connected, and ready to work from real context.";
|
|
79
|
+
}
|
|
80
|
+
function buildCliOutro(agents, locale = detectCliLocale()) {
|
|
81
|
+
return isFrench(locale)
|
|
82
|
+
? agents.length > 1
|
|
83
|
+
? `Ouvrez vos agents IA. SalesTouch est prêt dans ${formatAgentDisplayList(agents, locale)} pour rechercher, qualifier et préparer des brouillons avec vous.`
|
|
84
|
+
: "Ouvrez votre agent IA. SalesTouch est prêt à rechercher, qualifier et préparer des brouillons avec vous."
|
|
85
|
+
: agents.length > 1
|
|
86
|
+
? `Open your AI agents. SalesTouch is ready in ${formatAgentDisplayList(agents, locale)} to research, qualify, and draft with you.`
|
|
87
|
+
: "Open your AI agent. SalesTouch is ready to research, qualify, and draft with you.";
|
|
88
|
+
}
|
|
89
|
+
function getSetupModeDisplay(mode, mcpTarget, locale = detectCliLocale()) {
|
|
90
|
+
if (mode === "cli") {
|
|
91
|
+
return "CLI";
|
|
92
|
+
}
|
|
93
|
+
if (isFrench(locale)) {
|
|
94
|
+
return mcpTarget === "remote" ? "MCP distant" : "MCP local";
|
|
95
|
+
}
|
|
96
|
+
return mcpTarget === "remote" ? "Remote MCP" : "Local MCP";
|
|
97
|
+
}
|
|
98
|
+
function getSetupAuthDisplay(authMode, authMethod, locale = detectCliLocale()) {
|
|
99
|
+
if (authMode === "local") {
|
|
100
|
+
if (isFrench(locale)) {
|
|
101
|
+
return authMethod === "api_key" ? "Clé API" : "Connexion SalesTouch";
|
|
102
|
+
}
|
|
103
|
+
return authMethod === "api_key" ? "API key" : "SalesTouch login";
|
|
104
|
+
}
|
|
105
|
+
if (authMode === "oauth") {
|
|
106
|
+
return isFrench(locale) ? "Connexion navigateur" : "Browser login";
|
|
107
|
+
}
|
|
108
|
+
return isFrench(locale) ? "Clé API" : "API key";
|
|
109
|
+
}
|
|
110
|
+
function buildWizardInstallSummary(params) {
|
|
111
|
+
const { agents, mode, mcpTarget, authMode, authMethod, baseUrl, mcpUrl, results, locale = detectCliLocale(), } = params;
|
|
112
|
+
const fileLines = results.flatMap((result) => {
|
|
113
|
+
const lines = [
|
|
114
|
+
`${pc.bold(getAgentDisplayName(result.agent))}`,
|
|
115
|
+
` rule ${result.rulePath}`,
|
|
116
|
+
];
|
|
117
|
+
if (result.skillPath) {
|
|
118
|
+
lines.push(` skill ${result.skillPath}`);
|
|
119
|
+
}
|
|
120
|
+
if (result.mcpConfigPath) {
|
|
121
|
+
lines.push(` mcp ${result.mcpConfigPath}`);
|
|
122
|
+
}
|
|
123
|
+
return lines;
|
|
124
|
+
});
|
|
125
|
+
const installedSkills = results.some((result) => Boolean(result.skillPath));
|
|
126
|
+
const nextSteps = mode === "mcp"
|
|
127
|
+
? [
|
|
128
|
+
isFrench(locale)
|
|
129
|
+
? `SalesTouch est prêt dans ${agents.length === 1 ? getAgentDisplayName(agents[0]) : "votre agent IA préféré"}.`
|
|
130
|
+
: `SalesTouch is ready inside ${agents.length === 1 ? getAgentDisplayName(agents[0]) : "your favorite AI agent"}.`,
|
|
131
|
+
isFrench(locale)
|
|
132
|
+
? "Demandez-lui d'explorer le contexte live, de rechercher les bonnes personnes et de préparer un premier brouillon à valider."
|
|
133
|
+
: "Ask it to explore live context, research the right people, and prepare a first draft you can approve.",
|
|
134
|
+
]
|
|
135
|
+
: [
|
|
136
|
+
isFrench(locale)
|
|
137
|
+
? `SalesTouch est prêt dans ${agents.length === 1 ? getAgentDisplayName(agents[0]) : "votre agent IA préféré"}.`
|
|
138
|
+
: `SalesTouch is ready inside ${agents.length === 1 ? getAgentDisplayName(agents[0]) : "your favorite AI agent"}.`,
|
|
139
|
+
isFrench(locale)
|
|
140
|
+
? "Lancez une première recherche : pnpm salestouch commands run lead.search --input-json '{\"query\":\"vp sales\"}' --json"
|
|
141
|
+
: "Try a first search: pnpm salestouch commands run lead.search --input-json '{\"query\":\"vp sales\"}' --json",
|
|
142
|
+
isFrench(locale)
|
|
143
|
+
? "Parcourez ce que SalesTouch sait faire : pnpm salestouch commands list"
|
|
144
|
+
: "Browse what SalesTouch can do: pnpm salestouch commands list",
|
|
145
|
+
];
|
|
146
|
+
return [
|
|
147
|
+
`${pc.bold(isFrench(locale) ? (agents.length > 1 ? "Agents IA" : "Agent IA") : (agents.length > 1 ? "AI agents" : "AI agent"))} ${agents.map((agent) => getAgentDisplayName(agent)).join(", ")}`,
|
|
148
|
+
`${pc.bold(isFrench(locale) ? "Connexion" : "Connection")} ${getSetupModeDisplay(mode, mcpTarget, locale)}`,
|
|
149
|
+
`${pc.bold(isFrench(locale) ? "Accès" : "Sign-in")} ${getSetupAuthDisplay(authMode, authMethod === "api_key" ? "api_key" : "salestouch_login", locale)}`,
|
|
150
|
+
`${pc.bold("Skills")} ${installedSkills ? (isFrench(locale) ? "Installés" : "Installed") : isFrench(locale) ? "Ignorés" : "Skipped"}`,
|
|
151
|
+
`${pc.bold(isFrench(locale) ? "URL de base" : "Base URL")} ${baseUrl}`,
|
|
152
|
+
...(mode === "mcp"
|
|
153
|
+
? [`${pc.bold(isFrench(locale) ? "URL MCP" : "MCP URL")} ${mcpUrl}`]
|
|
154
|
+
: []),
|
|
155
|
+
"",
|
|
156
|
+
`${pc.bold(isFrench(locale) ? "Installé pour vous" : "Installed for you")}`,
|
|
157
|
+
...fileLines,
|
|
158
|
+
"",
|
|
159
|
+
`${pc.bold(isFrench(locale) ? "Commencer ici" : "Start here")}`,
|
|
160
|
+
...nextSteps.map((step) => ` ${step}`),
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
function printUsage() {
|
|
164
|
+
console.log(`SalesTouch CLI ${CLI_VERSION}
|
|
165
|
+
|
|
166
|
+
Usage:
|
|
167
|
+
salestouch auth login [--base-url <url>] [--json]
|
|
168
|
+
salestouch auth logout [--json]
|
|
169
|
+
salestouch auth whoami [--json]
|
|
170
|
+
salestouch auth use-key <api_key> [--base-url <url>] [--json]
|
|
171
|
+
salestouch auth create-key [--name <name>] [--expires-in <seconds>] [--base-url <url>] [--json]
|
|
172
|
+
salestouch doctor [--json]
|
|
173
|
+
salestouch commands list [--json]
|
|
174
|
+
salestouch commands schema <command_id> [--json]
|
|
175
|
+
salestouch commands run <command_id> [--input-json <json>] [--input-file <path>] [--compact|--full] [--json]
|
|
176
|
+
salestouch resources list [--json]
|
|
177
|
+
salestouch resources read <uri> [--json]
|
|
178
|
+
salestouch setup [--claude] [--cursor] [--codex] [--opencode] [--global] [--mcp|--cli] [--skills|--no-skills] [--local-mcp] [--login|--api-key <key>] [--base-url <url>] [--json]
|
|
179
|
+
salestouch skills reinstall [--claude] [--cursor] [--codex] [--opencode] [--global] [--mcp|--cli] [--json]
|
|
180
|
+
salestouch mcp serve [--api-key <key>] [--access-token <token>] [--base-url <url>]
|
|
181
|
+
|
|
182
|
+
Auth resolution:
|
|
183
|
+
--api-key > SALESTOUCH_API_KEY > --access-token > SALESTOUCH_ACCESS_TOKEN > stored CLI login > stored API key
|
|
184
|
+
--base-url > SALESTOUCH_API_BASE_URL > stored config > workspace .env > https://www.salestouch.io
|
|
185
|
+
|
|
186
|
+
Notes:
|
|
187
|
+
\`salestouch commands run\` defaults to \`full\` responses.
|
|
188
|
+
Use \`--compact\` to save tokens when needed, but it can noticeably reduce prospecting quality.
|
|
189
|
+
Running \`salestouch setup\` with no setup flags starts the interactive setup wizard.
|
|
190
|
+
\`salestouch setup\` installs skills by default; pass \`--no-skills\` to skip them.
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
function parseArgs(argv) {
|
|
194
|
+
const positionals = [];
|
|
195
|
+
const flags = {};
|
|
196
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
197
|
+
const token = argv[index];
|
|
198
|
+
if (!token)
|
|
199
|
+
continue;
|
|
200
|
+
if (!token.startsWith("--")) {
|
|
201
|
+
positionals.push(token);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const [rawName, inlineValue] = token.slice(2).split("=", 2);
|
|
205
|
+
const name = rawName.trim();
|
|
206
|
+
if (!name) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (inlineValue !== undefined) {
|
|
210
|
+
flags[name] = inlineValue;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const next = argv[index + 1];
|
|
214
|
+
if (next && !next.startsWith("--")) {
|
|
215
|
+
flags[name] = next;
|
|
216
|
+
index += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
flags[name] = true;
|
|
220
|
+
}
|
|
221
|
+
return { positionals, flags };
|
|
222
|
+
}
|
|
223
|
+
function parseJsonInput(value) {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(value);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
throw new Error(error instanceof Error ? `Invalid JSON input: ${error.message}` : "Invalid JSON input");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function readFlagInt(flags, name) {
|
|
232
|
+
const value = readFlagString(flags, name);
|
|
233
|
+
if (!value) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const parsed = Number.parseInt(value, 10);
|
|
237
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
238
|
+
throw new Error(`Invalid value for --${name}. Expected a positive integer.`);
|
|
239
|
+
}
|
|
240
|
+
return parsed;
|
|
241
|
+
}
|
|
242
|
+
async function readJsonInput(flags) {
|
|
243
|
+
const inline = readFlagString(flags, "input-json");
|
|
244
|
+
const filePath = readFlagString(flags, "input-file");
|
|
245
|
+
if (inline && filePath) {
|
|
246
|
+
throw new Error("Use either --input-json or --input-file, not both.");
|
|
247
|
+
}
|
|
248
|
+
if (inline) {
|
|
249
|
+
return parseJsonInput(inline);
|
|
250
|
+
}
|
|
251
|
+
if (filePath) {
|
|
252
|
+
const raw = await readFile(path.resolve(process.cwd(), filePath), "utf8");
|
|
253
|
+
return parseJsonInput(raw);
|
|
254
|
+
}
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
async function readStatusResource(options) {
|
|
258
|
+
const auth = await resolveAuth({
|
|
259
|
+
apiKey: options?.apiKey ?? null,
|
|
260
|
+
accessToken: options?.accessToken ?? null,
|
|
261
|
+
baseUrl: options?.baseUrl ?? null,
|
|
262
|
+
});
|
|
263
|
+
const payload = await apiRequest(auth, `/api/v1/resources/read?uri=${encodeURIComponent("salestouch://status")}`);
|
|
264
|
+
return {
|
|
265
|
+
auth,
|
|
266
|
+
resource: payload.data?.resource ?? {},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function ensureSetupAuth(parsed) {
|
|
270
|
+
const apiKey = readFlagString(parsed.flags, "api-key");
|
|
271
|
+
const accessToken = readFlagString(parsed.flags, "access-token");
|
|
272
|
+
const baseUrl = readFlagString(parsed.flags, "base-url");
|
|
273
|
+
const forceLogin = readFlagBoolean(parsed.flags, "login");
|
|
274
|
+
if (apiKey) {
|
|
275
|
+
const { auth, resource } = await readStatusResource({ apiKey, baseUrl });
|
|
276
|
+
await persistAuth(auth);
|
|
277
|
+
return { auth, resource };
|
|
278
|
+
}
|
|
279
|
+
if (accessToken) {
|
|
280
|
+
const { auth, resource } = await readStatusResource({ accessToken, baseUrl });
|
|
281
|
+
await persistAuth(auth);
|
|
282
|
+
return { auth, resource };
|
|
283
|
+
}
|
|
284
|
+
if (forceLogin) {
|
|
285
|
+
const login = await loginWithDeviceFlow({
|
|
286
|
+
baseUrl,
|
|
287
|
+
quiet: readFlagBoolean(parsed.flags, "json"),
|
|
288
|
+
});
|
|
289
|
+
const payload = await apiRequest(login.auth, `/api/v1/resources/read?uri=${encodeURIComponent("salestouch://status")}`);
|
|
290
|
+
return {
|
|
291
|
+
auth: login.auth,
|
|
292
|
+
resource: payload.data?.resource ?? {},
|
|
293
|
+
userCode: login.userCode,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
const { auth, resource } = await readStatusResource({ baseUrl });
|
|
298
|
+
if (auth.credentialSource === "env" || auth.baseUrlSource === "env" || auth.baseUrlSource === "flag") {
|
|
299
|
+
await persistAuth(auth);
|
|
300
|
+
}
|
|
301
|
+
return { auth, resource };
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
try {
|
|
305
|
+
const resolved = await resolveAuth({ baseUrl });
|
|
306
|
+
if (resolved.credentialSource === "env") {
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (resolveError) {
|
|
311
|
+
if (!(resolveError instanceof Error) || !resolveError.message.includes("Missing authentication")) {
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const login = await loginWithDeviceFlow({
|
|
316
|
+
baseUrl,
|
|
317
|
+
quiet: readFlagBoolean(parsed.flags, "json"),
|
|
318
|
+
});
|
|
319
|
+
const payload = await apiRequest(login.auth, `/api/v1/resources/read?uri=${encodeURIComponent("salestouch://status")}`);
|
|
320
|
+
return {
|
|
321
|
+
auth: login.auth,
|
|
322
|
+
resource: payload.data?.resource ?? {},
|
|
323
|
+
userCode: login.userCode,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function getRequestedSetupAgents(flags) {
|
|
328
|
+
const agents = [];
|
|
329
|
+
if (readFlagBoolean(flags, "claude"))
|
|
330
|
+
agents.push("claude");
|
|
331
|
+
if (readFlagBoolean(flags, "cursor"))
|
|
332
|
+
agents.push("cursor");
|
|
333
|
+
if (readFlagBoolean(flags, "codex"))
|
|
334
|
+
agents.push("codex");
|
|
335
|
+
if (readFlagBoolean(flags, "opencode"))
|
|
336
|
+
agents.push("opencode");
|
|
337
|
+
return agents;
|
|
338
|
+
}
|
|
339
|
+
function shouldInstallSetupSkills(flags) {
|
|
340
|
+
return !readFlagBoolean(flags, "no-skills");
|
|
341
|
+
}
|
|
342
|
+
async function handleAuthLogin(parsed) {
|
|
343
|
+
const login = await loginWithDeviceFlow({
|
|
344
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
345
|
+
quiet: readFlagBoolean(parsed.flags, "json"),
|
|
346
|
+
});
|
|
347
|
+
const payload = await apiRequest(login.auth, `/api/v1/resources/read?uri=${encodeURIComponent("salestouch://status")}`);
|
|
348
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
349
|
+
outputJson({
|
|
350
|
+
ok: true,
|
|
351
|
+
auth_method: describeAuthMethod(login.auth),
|
|
352
|
+
base_url: login.auth.baseUrl,
|
|
353
|
+
config_path: getConfigPath(),
|
|
354
|
+
status: payload.data?.resource?.payload ?? null,
|
|
355
|
+
user_code: login.userCode,
|
|
356
|
+
});
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
console.log("SalesTouch login saved.");
|
|
360
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
361
|
+
console.log(`Base URL: ${login.auth.baseUrl}`);
|
|
362
|
+
printResourceStatus(payload.data?.resource ?? {});
|
|
363
|
+
}
|
|
364
|
+
async function handleAuthLogout(parsed) {
|
|
365
|
+
const config = await loadConfig();
|
|
366
|
+
if (!config.accessToken && !config.apiKey) {
|
|
367
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
368
|
+
outputJson({ ok: true, removed: false, config_path: getConfigPath() });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
console.log("No stored SalesTouch login found.");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
await clearStoredAuth();
|
|
375
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
376
|
+
outputJson({ ok: true, removed: true, config_path: getConfigPath() });
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
console.log("Stored SalesTouch credentials removed.");
|
|
380
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
381
|
+
}
|
|
382
|
+
async function handleAuthSetKey(parsed) {
|
|
383
|
+
const apiKey = parsed.positionals[2]?.trim() || readFlagString(parsed.flags, "api-key");
|
|
384
|
+
if (!apiKey) {
|
|
385
|
+
throw new Error("Missing API key. Usage: salestouch auth use-key <api_key> [--base-url <url>]");
|
|
386
|
+
}
|
|
387
|
+
const { auth, resource } = await readStatusResource({
|
|
388
|
+
apiKey,
|
|
389
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
390
|
+
});
|
|
391
|
+
await persistAuth(auth);
|
|
392
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
393
|
+
outputJson({
|
|
394
|
+
ok: true,
|
|
395
|
+
auth_method: describeAuthMethod(auth),
|
|
396
|
+
base_url: auth.baseUrl,
|
|
397
|
+
config_path: getConfigPath(),
|
|
398
|
+
status: resource.payload ?? null,
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.log("API key saved.");
|
|
403
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
404
|
+
console.log(`Base URL: ${auth.baseUrl}`);
|
|
405
|
+
printResourceStatus(resource);
|
|
406
|
+
}
|
|
407
|
+
async function handleAuthIssueKey(parsed) {
|
|
408
|
+
const auth = await resolveAuth({
|
|
409
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
410
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
411
|
+
});
|
|
412
|
+
if (auth.authMethod !== "session") {
|
|
413
|
+
throw new Error("`salestouch auth create-key` requires a SalesTouch login session. Run `salestouch auth login` first.");
|
|
414
|
+
}
|
|
415
|
+
const payload = await apiRequest(auth, "/api/v1/auth/api-keys", {
|
|
416
|
+
method: "POST",
|
|
417
|
+
body: JSON.stringify({
|
|
418
|
+
name: readFlagString(parsed.flags, "name") ?? undefined,
|
|
419
|
+
expires_in: readFlagInt(parsed.flags, "expires-in") ?? undefined,
|
|
420
|
+
}),
|
|
421
|
+
});
|
|
422
|
+
const apiKey = payload.data?.api_key?.trim();
|
|
423
|
+
if (!apiKey) {
|
|
424
|
+
throw new Error("SalesTouch did not return an API key.");
|
|
425
|
+
}
|
|
426
|
+
await persistAuth({
|
|
427
|
+
authMethod: "api_key",
|
|
428
|
+
credentialSource: "config",
|
|
429
|
+
apiKey,
|
|
430
|
+
baseUrl: auth.baseUrl,
|
|
431
|
+
baseUrlSource: auth.baseUrlSource,
|
|
432
|
+
});
|
|
433
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
434
|
+
outputJson({
|
|
435
|
+
ok: true,
|
|
436
|
+
api_key: apiKey,
|
|
437
|
+
api_key_id: payload.data?.api_key_id ?? null,
|
|
438
|
+
organization_id: payload.data?.organization_id ?? null,
|
|
439
|
+
name: payload.data?.name ?? null,
|
|
440
|
+
expires_at: payload.data?.expires_at ?? null,
|
|
441
|
+
config_path: getConfigPath(),
|
|
442
|
+
base_url: auth.baseUrl,
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
console.log("API key issued and saved.");
|
|
447
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
448
|
+
console.log(`Base URL: ${auth.baseUrl}`);
|
|
449
|
+
console.log(`Key ID: ${payload.data?.api_key_id ?? "unknown"}`);
|
|
450
|
+
console.log(`Org: ${payload.data?.organization_id ?? "unknown"}`);
|
|
451
|
+
console.log(`API Key: ${apiKey}`);
|
|
452
|
+
}
|
|
453
|
+
async function handleAuthWhoAmI(parsed) {
|
|
454
|
+
const { auth, resource } = await readStatusResource({
|
|
455
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
456
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
457
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
458
|
+
});
|
|
459
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
460
|
+
outputJson({
|
|
461
|
+
auth_method: describeAuthMethod(auth),
|
|
462
|
+
auth_source: auth.credentialSource,
|
|
463
|
+
base_url: auth.baseUrl,
|
|
464
|
+
resource,
|
|
465
|
+
});
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
console.log(`Auth method: ${describeAuthMethod(auth)}`);
|
|
469
|
+
console.log(`Auth source: ${auth.credentialSource}`);
|
|
470
|
+
printResourceStatus(resource);
|
|
471
|
+
}
|
|
472
|
+
async function handleDoctor(parsed) {
|
|
473
|
+
const auth = await resolveAuth({
|
|
474
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
475
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
476
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
477
|
+
});
|
|
478
|
+
const setupManifest = (await readSetupManifest("project")) ??
|
|
479
|
+
(readFlagBoolean(parsed.flags, "global") ? await readSetupManifest("global") : null);
|
|
480
|
+
const [statusPayload, catalogPayload] = await Promise.all([
|
|
481
|
+
apiRequest(auth, `/api/v1/resources/read?uri=${encodeURIComponent("salestouch://status")}`),
|
|
482
|
+
apiRequest(auth, "/api/v1/commands"),
|
|
483
|
+
]);
|
|
484
|
+
const status = statusPayload.data?.resource ?? {};
|
|
485
|
+
const commands = catalogPayload.data?.commands ?? [];
|
|
486
|
+
const warnings = [];
|
|
487
|
+
if (setupManifest) {
|
|
488
|
+
if (setupManifest.cli_version !== CLI_VERSION) {
|
|
489
|
+
warnings.push(`Setup manifest was generated with CLI ${setupManifest.cli_version}; current CLI is ${CLI_VERSION}. Re-run \`salestouch setup\`.`);
|
|
490
|
+
}
|
|
491
|
+
if (setupManifest.base_url !== auth.baseUrl) {
|
|
492
|
+
warnings.push(`Setup manifest base URL is ${setupManifest.base_url}; current auth uses ${auth.baseUrl}. Re-run \`salestouch setup\` if this changed intentionally.`);
|
|
493
|
+
}
|
|
494
|
+
if (setupManifest.mode === "mcp" && setupManifest.mcp_target === "remote") {
|
|
495
|
+
const expectedMcpUrl = getRemoteMcpUrl(auth.baseUrl);
|
|
496
|
+
if (setupManifest.mcp_url !== expectedMcpUrl) {
|
|
497
|
+
warnings.push(`Setup manifest MCP URL is ${setupManifest.mcp_url}; expected ${expectedMcpUrl}. Re-run \`salestouch setup\`.`);
|
|
498
|
+
}
|
|
499
|
+
const expectedAuthMode = auth.authMethod === "api_key" ? "api_key" : "oauth";
|
|
500
|
+
if (setupManifest.auth_mode && setupManifest.auth_mode !== expectedAuthMode) {
|
|
501
|
+
warnings.push(`Setup manifest auth mode is ${setupManifest.auth_mode}; current auth resolves to ${expectedAuthMode}. Re-run \`salestouch setup\` if you changed auth strategy.`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
506
|
+
outputJson({
|
|
507
|
+
base_url: auth.baseUrl,
|
|
508
|
+
base_url_source: auth.baseUrlSource,
|
|
509
|
+
auth_method: describeAuthMethod(auth),
|
|
510
|
+
auth_source: auth.credentialSource,
|
|
511
|
+
command_count: commands.length,
|
|
512
|
+
status,
|
|
513
|
+
warnings,
|
|
514
|
+
setup_manifest: setupManifest,
|
|
515
|
+
});
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
console.log(`Base URL: ${auth.baseUrl}`);
|
|
519
|
+
console.log(`Base URL source: ${auth.baseUrlSource}`);
|
|
520
|
+
console.log(`Auth method: ${describeAuthMethod(auth)}`);
|
|
521
|
+
console.log(`Auth source: ${auth.credentialSource}`);
|
|
522
|
+
console.log(`Commands: ${commands.length}`);
|
|
523
|
+
printResourceStatus(status);
|
|
524
|
+
if (warnings.length > 0) {
|
|
525
|
+
console.log("");
|
|
526
|
+
console.log("Warnings:");
|
|
527
|
+
for (const warning of warnings) {
|
|
528
|
+
console.log(`- ${warning}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function handleCatalog(parsed) {
|
|
533
|
+
const locale = detectCliLocale();
|
|
534
|
+
const auth = await resolveAuth({
|
|
535
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
536
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
537
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
538
|
+
});
|
|
539
|
+
const payload = await apiRequest(auth, "/api/v1/commands");
|
|
540
|
+
const commands = payload.data?.commands ?? [];
|
|
541
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
542
|
+
outputJson(commands);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const groupedCommands = new Map();
|
|
546
|
+
for (const command of commands) {
|
|
547
|
+
const category = cliCatalogCategoryOrder.includes(command.category)
|
|
548
|
+
? command.category
|
|
549
|
+
: "other";
|
|
550
|
+
const group = groupedCommands.get(category) ?? [];
|
|
551
|
+
group.push(command);
|
|
552
|
+
groupedCommands.set(category, group);
|
|
553
|
+
}
|
|
554
|
+
const orderedCategories = cliCatalogCategoryOrder.filter((category) => {
|
|
555
|
+
const group = groupedCommands.get(category);
|
|
556
|
+
return Boolean(group && group.length > 0);
|
|
557
|
+
});
|
|
558
|
+
for (const [index, category] of orderedCategories.entries()) {
|
|
559
|
+
const categoryCommands = groupedCommands.get(category)?.slice().sort((left, right) => String(left.command_id ?? "").localeCompare(String(right.command_id ?? ""))) ?? [];
|
|
560
|
+
console.log(pc.bold(`${getCliCatalogCategoryLabel(category, locale)} (${categoryCommands.length})`));
|
|
561
|
+
printTable([
|
|
562
|
+
["COMMAND", "RISK", "DESCRIPTION"],
|
|
563
|
+
...categoryCommands.map((command) => [
|
|
564
|
+
String(command.command_id ?? ""),
|
|
565
|
+
String(command.risk ?? ""),
|
|
566
|
+
String(command.description ?? ""),
|
|
567
|
+
]),
|
|
568
|
+
]);
|
|
569
|
+
if (index < orderedCategories.length - 1) {
|
|
570
|
+
console.log("");
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async function handleSchema(parsed) {
|
|
575
|
+
const auth = await resolveAuth({
|
|
576
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
577
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
578
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
579
|
+
});
|
|
580
|
+
const commandId = parsed.positionals[2];
|
|
581
|
+
if (!commandId) {
|
|
582
|
+
throw new Error("Missing command id. Usage: salestouch commands schema <command_id>");
|
|
583
|
+
}
|
|
584
|
+
const payload = await apiRequest(auth, `/api/v1/commands/${encodeURIComponent(commandId)}/schema`);
|
|
585
|
+
outputJson(payload.data?.command ?? null);
|
|
586
|
+
}
|
|
587
|
+
async function handleRun(parsed) {
|
|
588
|
+
const auth = await resolveAuth({
|
|
589
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
590
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
591
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
592
|
+
});
|
|
593
|
+
const commandId = parsed.positionals[2];
|
|
594
|
+
if (!commandId) {
|
|
595
|
+
throw new Error("Missing command id. Usage: salestouch commands run <command_id>");
|
|
596
|
+
}
|
|
597
|
+
const input = await readJsonInput(parsed.flags);
|
|
598
|
+
const responseProfile = readFlagBoolean(parsed.flags, "compact")
|
|
599
|
+
? "compact"
|
|
600
|
+
: readFlagBoolean(parsed.flags, "full")
|
|
601
|
+
? "full"
|
|
602
|
+
: "full";
|
|
603
|
+
const payload = await apiRequest(auth, `/api/v1/commands/${encodeURIComponent(commandId)}`, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
body: JSON.stringify({
|
|
606
|
+
input,
|
|
607
|
+
response_profile: responseProfile,
|
|
608
|
+
}),
|
|
609
|
+
});
|
|
610
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
611
|
+
outputJson(payload);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
console.log(`Command: ${commandId}`);
|
|
615
|
+
outputJson(payload);
|
|
616
|
+
}
|
|
617
|
+
async function handleResourcesList(parsed) {
|
|
618
|
+
const auth = await resolveAuth({
|
|
619
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
620
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
621
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
622
|
+
});
|
|
623
|
+
const payload = await apiRequest(auth, "/api/v1/resources");
|
|
624
|
+
const resources = payload.data?.resources ?? [];
|
|
625
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
626
|
+
outputJson(resources);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
printTable([
|
|
630
|
+
["URI", "NAME", "DESCRIPTION"],
|
|
631
|
+
...resources.map((resource) => [
|
|
632
|
+
String(resource.uri ?? ""),
|
|
633
|
+
String(resource.name ?? ""),
|
|
634
|
+
String(resource.description ?? ""),
|
|
635
|
+
]),
|
|
636
|
+
]);
|
|
637
|
+
}
|
|
638
|
+
async function handleResourcesRead(parsed) {
|
|
639
|
+
const auth = await resolveAuth({
|
|
640
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
641
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
642
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
643
|
+
});
|
|
644
|
+
const uri = parsed.positionals[2];
|
|
645
|
+
if (!uri) {
|
|
646
|
+
throw new Error("Missing resource uri. Usage: salestouch resources read <uri>");
|
|
647
|
+
}
|
|
648
|
+
const payload = await apiRequest(auth, `/api/v1/resources/read?uri=${encodeURIComponent(uri)}`);
|
|
649
|
+
outputJson(payload.data?.resource ?? null);
|
|
650
|
+
}
|
|
651
|
+
async function handleSetup(parsed) {
|
|
652
|
+
const locale = detectCliLocale();
|
|
653
|
+
let effectiveParsed = parsed;
|
|
654
|
+
let usedWizard = false;
|
|
655
|
+
if (shouldRunSetupWizard(parsed.flags, process.stdin.isTTY && process.stdout.isTTY)) {
|
|
656
|
+
usedWizard = true;
|
|
657
|
+
let existingAuth = null;
|
|
658
|
+
try {
|
|
659
|
+
existingAuth = await resolveAuth({
|
|
660
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
existingAuth = null;
|
|
665
|
+
}
|
|
666
|
+
const selections = await runInteractiveSetupWizard({
|
|
667
|
+
existingAuth,
|
|
668
|
+
});
|
|
669
|
+
if (!selections) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
effectiveParsed = {
|
|
673
|
+
...parsed,
|
|
674
|
+
flags: applySetupWizardSelections(parsed.flags, selections),
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
const hasCliMode = readFlagBoolean(effectiveParsed.flags, "cli");
|
|
678
|
+
const hasMcpMode = readFlagBoolean(effectiveParsed.flags, "mcp");
|
|
679
|
+
if (hasCliMode && hasMcpMode) {
|
|
680
|
+
throw new Error("Choose either --cli or --mcp, not both.");
|
|
681
|
+
}
|
|
682
|
+
if (readFlagBoolean(effectiveParsed.flags, "skills") && readFlagBoolean(effectiveParsed.flags, "no-skills")) {
|
|
683
|
+
throw new Error("Choose either --skills or --no-skills, not both.");
|
|
684
|
+
}
|
|
685
|
+
const mode = hasMcpMode ? "mcp" : "cli";
|
|
686
|
+
if (mode !== "mcp" && readFlagBoolean(effectiveParsed.flags, "local-mcp")) {
|
|
687
|
+
throw new Error("`--local-mcp` is only valid with `--mcp`.");
|
|
688
|
+
}
|
|
689
|
+
const scope = readFlagBoolean(effectiveParsed.flags, "global") ? "global" : "project";
|
|
690
|
+
const mcpTarget = readFlagBoolean(effectiveParsed.flags, "local-mcp") ? "local" : "remote";
|
|
691
|
+
const installSkills = shouldInstallSetupSkills(effectiveParsed.flags);
|
|
692
|
+
const requestedAgents = getRequestedSetupAgents(effectiveParsed.flags);
|
|
693
|
+
let setupAuth;
|
|
694
|
+
if (usedWizard && !readFlagBoolean(effectiveParsed.flags, "json")) {
|
|
695
|
+
if (readFlagBoolean(effectiveParsed.flags, "login")) {
|
|
696
|
+
log.step(isFrench(locale)
|
|
697
|
+
? "Ouverture de la connexion SalesTouch dans votre navigateur."
|
|
698
|
+
: "Opening SalesTouch login in your browser.");
|
|
699
|
+
setupAuth = await ensureSetupAuth(effectiveParsed);
|
|
700
|
+
log.success(isFrench(locale) ? "Connexion SalesTouch approuvée." : "SalesTouch login approved.");
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
const authSpinner = spinner();
|
|
704
|
+
authSpinner.start(isFrench(locale)
|
|
705
|
+
? "Vérification de vos identifiants SalesTouch"
|
|
706
|
+
: "Checking your SalesTouch credentials");
|
|
707
|
+
setupAuth = await ensureSetupAuth(effectiveParsed);
|
|
708
|
+
authSpinner.stop(isFrench(locale)
|
|
709
|
+
? `Authentifié via ${getSetupAuthDisplay(setupAuth.auth.authMethod === "api_key" ? "api_key" : "local", setupAuth.auth.authMethod === "api_key" ? "api_key" : "salestouch_login", locale)} sur ${setupAuth.auth.baseUrl}`
|
|
710
|
+
: `Authenticated with ${describeAuthMethod(setupAuth.auth)} on ${setupAuth.auth.baseUrl}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
setupAuth = await ensureSetupAuth(effectiveParsed);
|
|
715
|
+
}
|
|
716
|
+
const authMode = mode !== "mcp"
|
|
717
|
+
? "local"
|
|
718
|
+
: mcpTarget === "local"
|
|
719
|
+
? "local"
|
|
720
|
+
: setupAuth.auth.authMethod === "api_key"
|
|
721
|
+
? "api_key"
|
|
722
|
+
: "oauth";
|
|
723
|
+
const mcpUrl = getRemoteMcpUrl(setupAuth.auth.baseUrl);
|
|
724
|
+
let results;
|
|
725
|
+
const markDashboardSetupCompleted = async () => {
|
|
726
|
+
try {
|
|
727
|
+
await apiRequest(setupAuth.auth, "/api/v1/auth/setup-complete", {
|
|
728
|
+
method: "POST",
|
|
729
|
+
body: "{}",
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch {
|
|
733
|
+
// Keep setup successful even if the dashboard checklist marker fails.
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
if (usedWizard && !readFlagBoolean(effectiveParsed.flags, "json")) {
|
|
737
|
+
const installSpinner = spinner();
|
|
738
|
+
installSpinner.start(isFrench(locale) ? "Activation de SalesTouch" : "Bringing SalesTouch online");
|
|
739
|
+
results = await runSetup({
|
|
740
|
+
agents: requestedAgents,
|
|
741
|
+
mode,
|
|
742
|
+
scope,
|
|
743
|
+
mcpTarget,
|
|
744
|
+
authMode,
|
|
745
|
+
installSkills,
|
|
746
|
+
baseUrl: setupAuth.auth.baseUrl,
|
|
747
|
+
mcpUrl,
|
|
748
|
+
apiKey: setupAuth.auth.authMethod === "api_key" ? setupAuth.auth.apiKey : null,
|
|
749
|
+
});
|
|
750
|
+
await markDashboardSetupCompleted();
|
|
751
|
+
installSpinner.stop(isFrench(locale)
|
|
752
|
+
? `SalesTouch est prêt dans ${results.map((result) => getAgentDisplayName(result.agent)).join(", ")}`
|
|
753
|
+
: `SalesTouch is now ready in ${results.map((result) => getAgentDisplayName(result.agent)).join(", ")}`);
|
|
754
|
+
note(buildWizardInstallSummary({
|
|
755
|
+
agents: results.map((result) => result.agent),
|
|
756
|
+
mode,
|
|
757
|
+
mcpTarget,
|
|
758
|
+
authMode,
|
|
759
|
+
authMethod: setupAuth.auth.authMethod,
|
|
760
|
+
baseUrl: setupAuth.auth.baseUrl,
|
|
761
|
+
mcpUrl,
|
|
762
|
+
results,
|
|
763
|
+
locale,
|
|
764
|
+
}), isFrench(locale) ? "Votre agent IA est prêt" : "Your AI agent is ready");
|
|
765
|
+
const installedAgents = results.map((result) => result.agent);
|
|
766
|
+
outro(mode === "mcp" ? buildMcpOutro(installedAgents, locale) : buildCliOutro(installedAgents, locale));
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
results = await runSetup({
|
|
770
|
+
agents: requestedAgents,
|
|
771
|
+
mode,
|
|
772
|
+
scope,
|
|
773
|
+
mcpTarget,
|
|
774
|
+
authMode,
|
|
775
|
+
installSkills,
|
|
776
|
+
baseUrl: setupAuth.auth.baseUrl,
|
|
777
|
+
mcpUrl,
|
|
778
|
+
apiKey: setupAuth.auth.authMethod === "api_key" ? setupAuth.auth.apiKey : null,
|
|
779
|
+
});
|
|
780
|
+
await markDashboardSetupCompleted();
|
|
781
|
+
if (readFlagBoolean(effectiveParsed.flags, "json")) {
|
|
782
|
+
outputJson({
|
|
783
|
+
ok: true,
|
|
784
|
+
mode,
|
|
785
|
+
scope,
|
|
786
|
+
mcp_target: mode === "mcp" ? mcpTarget : null,
|
|
787
|
+
setup_auth_mode: mode === "mcp" ? authMode : null,
|
|
788
|
+
install_skills: installSkills,
|
|
789
|
+
auth_method: describeAuthMethod(setupAuth.auth),
|
|
790
|
+
auth_source: setupAuth.auth.credentialSource,
|
|
791
|
+
base_url: setupAuth.auth.baseUrl,
|
|
792
|
+
mcp_url: mode === "mcp" ? mcpUrl : null,
|
|
793
|
+
status: setupAuth.resource.payload ?? null,
|
|
794
|
+
setup: results,
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
console.log(`Mode: ${mode}`);
|
|
799
|
+
console.log(`Scope: ${scope}`);
|
|
800
|
+
if (mode === "mcp") {
|
|
801
|
+
console.log(`Target: ${mcpTarget}`);
|
|
802
|
+
console.log(`Setup auth:${authMode}`);
|
|
803
|
+
console.log(`MCP URL: ${mcpUrl}`);
|
|
804
|
+
}
|
|
805
|
+
console.log(`Skills: ${installSkills ? "installed" : "skipped"}`);
|
|
806
|
+
console.log(`Auth: ${describeAuthMethod(setupAuth.auth)} (${setupAuth.auth.credentialSource})`);
|
|
807
|
+
console.log(`Base URL: ${setupAuth.auth.baseUrl}`);
|
|
808
|
+
console.log("");
|
|
809
|
+
for (const result of results) {
|
|
810
|
+
console.log(`${result.agent}:`);
|
|
811
|
+
if (result.mcpConfigPath) {
|
|
812
|
+
console.log(` MCP: ${result.mcpConfigPath}`);
|
|
813
|
+
}
|
|
814
|
+
console.log(` Rule: ${result.rulePath}`);
|
|
815
|
+
if (result.skillPath) {
|
|
816
|
+
console.log(` Skill: ${result.skillPath}`);
|
|
817
|
+
}
|
|
818
|
+
console.log(` Meta: ${result.manifestPath}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function handleSkillsReinstall(parsed) {
|
|
822
|
+
const hasCliMode = readFlagBoolean(parsed.flags, "cli");
|
|
823
|
+
const hasMcpMode = readFlagBoolean(parsed.flags, "mcp");
|
|
824
|
+
if (hasCliMode && hasMcpMode) {
|
|
825
|
+
throw new Error("Choose either --cli or --mcp, not both.");
|
|
826
|
+
}
|
|
827
|
+
const scope = readFlagBoolean(parsed.flags, "global") ? "global" : "project";
|
|
828
|
+
const setupManifest = await readSetupManifest(scope);
|
|
829
|
+
const requestedAgents = getRequestedSetupAgents(parsed.flags);
|
|
830
|
+
const usingManifestDefaults = requestedAgents.length === 0 &&
|
|
831
|
+
!hasCliMode &&
|
|
832
|
+
!hasMcpMode &&
|
|
833
|
+
Boolean(setupManifest);
|
|
834
|
+
const mode = hasMcpMode ? "mcp" : hasCliMode ? "cli" : setupManifest?.mode ?? "cli";
|
|
835
|
+
const results = await runSkillsInstall({
|
|
836
|
+
agents: requestedAgents.length > 0 ? requestedAgents : setupManifest?.agents ?? [],
|
|
837
|
+
mode,
|
|
838
|
+
scope,
|
|
839
|
+
});
|
|
840
|
+
if (readFlagBoolean(parsed.flags, "json")) {
|
|
841
|
+
outputJson({
|
|
842
|
+
ok: true,
|
|
843
|
+
scope,
|
|
844
|
+
mode,
|
|
845
|
+
setup_manifest: setupManifest,
|
|
846
|
+
skills: results,
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
console.log(`Mode: ${mode}`);
|
|
851
|
+
console.log(`Scope: ${scope}`);
|
|
852
|
+
if (usingManifestDefaults) {
|
|
853
|
+
console.log("Defaults: setup manifest");
|
|
854
|
+
}
|
|
855
|
+
console.log("");
|
|
856
|
+
for (const result of results) {
|
|
857
|
+
console.log(`${result.agent}:`);
|
|
858
|
+
console.log(` Skill: ${result.skillPath}`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function handleMcpServe(parsed) {
|
|
862
|
+
const auth = await resolveAuth({
|
|
863
|
+
apiKey: readFlagString(parsed.flags, "api-key"),
|
|
864
|
+
accessToken: readFlagString(parsed.flags, "access-token"),
|
|
865
|
+
baseUrl: readFlagString(parsed.flags, "base-url"),
|
|
866
|
+
});
|
|
867
|
+
await startMcpServer(auth);
|
|
868
|
+
}
|
|
869
|
+
async function main() {
|
|
870
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
871
|
+
const [command, subcommand] = parsed.positionals;
|
|
872
|
+
if (!command || command === "help" || readFlagBoolean(parsed.flags, "help")) {
|
|
873
|
+
printUsage();
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (command === "auth" && subcommand === "login") {
|
|
877
|
+
await handleAuthLogin(parsed);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (command === "auth" && subcommand === "logout") {
|
|
881
|
+
await handleAuthLogout(parsed);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (command === "auth" && subcommand === "use-key") {
|
|
885
|
+
await handleAuthSetKey(parsed);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (command === "auth" && subcommand === "create-key") {
|
|
889
|
+
await handleAuthIssueKey(parsed);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (command === "auth" && subcommand === "whoami") {
|
|
893
|
+
await handleAuthWhoAmI(parsed);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
if (command === "doctor") {
|
|
897
|
+
await handleDoctor(parsed);
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (command === "commands" && subcommand === "list") {
|
|
901
|
+
await handleCatalog(parsed);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
if (command === "commands" && subcommand === "schema") {
|
|
905
|
+
await handleSchema(parsed);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
if (command === "commands" && subcommand === "run") {
|
|
909
|
+
await handleRun(parsed);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
if (command === "resources" && subcommand === "list") {
|
|
913
|
+
await handleResourcesList(parsed);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (command === "resources" && subcommand === "read") {
|
|
917
|
+
await handleResourcesRead(parsed);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (command === "setup") {
|
|
921
|
+
await handleSetup(parsed);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (command === "skills" && subcommand === "reinstall") {
|
|
925
|
+
await handleSkillsReinstall(parsed);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (command === "mcp" && subcommand === "serve") {
|
|
929
|
+
await handleMcpServe(parsed);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
throw new Error(`Unknown command: ${parsed.positionals.join(" ")}`);
|
|
933
|
+
}
|
|
934
|
+
main().catch((error) => {
|
|
935
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
936
|
+
process.exit(1);
|
|
937
|
+
});
|