@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/setup.js
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { CLI_VERSION } from "./core.js";
|
|
5
|
+
const CURSOR_FRONTMATTER = "---\nalwaysApply: true\n---\n\n";
|
|
6
|
+
const SECTION_MARKER = "<!-- salestouch -->";
|
|
7
|
+
const skillDescriptions = {
|
|
8
|
+
mcp: "Use the SalesTouch MCP server for leads, missions, offers, imports, LinkedIn prospecting, and scoring workflows that live in SalesTouch.",
|
|
9
|
+
cli: "Use the SalesTouch CLI for leads, missions, offers, imports, LinkedIn prospecting, and scoring workflows that live in SalesTouch.",
|
|
10
|
+
};
|
|
11
|
+
const ruleBodies = {
|
|
12
|
+
mcp: `Use SalesTouch MCP whenever the user asks about SalesTouch data or workflows: leads, missions, offers, imports, LinkedIn outreach, prospecting inbox, or scoring.
|
|
13
|
+
|
|
14
|
+
Prefer this workflow:
|
|
15
|
+
1. Inspect the available SalesTouch tools and resources relevant to the task.
|
|
16
|
+
2. Read the input schema before making a write call.
|
|
17
|
+
3. Use read-only resources and tools first when context is missing.
|
|
18
|
+
4. Keep payloads deterministic and JSON-first.
|
|
19
|
+
5. Summarize the outcome briefly after the action completes.
|
|
20
|
+
|
|
21
|
+
Do not invent SalesTouch IDs, resource URIs, or mutation payload fields when the schema can be queried first.`,
|
|
22
|
+
cli: `Use the SalesTouch CLI whenever the user asks about SalesTouch data or workflows: leads, missions, offers, imports, LinkedIn outreach, prospecting inbox, or scoring.
|
|
23
|
+
|
|
24
|
+
Prefer this workflow:
|
|
25
|
+
1. Run \`salestouch commands list\` to discover commands.
|
|
26
|
+
2. Run \`salestouch commands schema <command_id>\` before preparing a payload.
|
|
27
|
+
3. Execute with \`salestouch commands run <command_id> --input-json ...\`.
|
|
28
|
+
4. Use \`salestouch resources list\` and \`salestouch resources read <uri>\` for read-only reference data.
|
|
29
|
+
5. Keep payloads deterministic and JSON-first.
|
|
30
|
+
|
|
31
|
+
Do not invent SalesTouch IDs, resource URIs, or mutation payload fields when the schema can be queried first.`,
|
|
32
|
+
};
|
|
33
|
+
function getSkillContent(mode) {
|
|
34
|
+
const body = mode === "mcp"
|
|
35
|
+
? `# SalesTouch
|
|
36
|
+
|
|
37
|
+
Use the SalesTouch MCP server for SalesTouch-native workflows.
|
|
38
|
+
|
|
39
|
+
## When To Use It
|
|
40
|
+
|
|
41
|
+
- Leads, missions, offers, imports, LinkedIn prospecting, scoring
|
|
42
|
+
- Workspace status and SalesTouch reference resources
|
|
43
|
+
- Multi-step prospecting tasks that still map to existing SalesTouch tools
|
|
44
|
+
|
|
45
|
+
## Workflow
|
|
46
|
+
|
|
47
|
+
1. Inspect the available SalesTouch tools and resources relevant to the task.
|
|
48
|
+
2. Read the tool schema before constructing inputs.
|
|
49
|
+
3. Prefer read operations when context is missing.
|
|
50
|
+
4. Execute the smallest deterministic action that satisfies the request.
|
|
51
|
+
5. Summarize the result briefly and accurately.
|
|
52
|
+
|
|
53
|
+
## Rules
|
|
54
|
+
|
|
55
|
+
- Use SalesTouch MCP tools as the default interface to SalesTouch.
|
|
56
|
+
- Prefer schema-first execution for write actions.
|
|
57
|
+
- Keep payloads JSON-first and deterministic.
|
|
58
|
+
- Do not invent SalesTouch IDs, handles, or hidden fields.
|
|
59
|
+
- If a required field is unclear, fetch more context before writing.`
|
|
60
|
+
: `# SalesTouch
|
|
61
|
+
|
|
62
|
+
Use the SalesTouch CLI for SalesTouch-native workflows.
|
|
63
|
+
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
- \`salestouch commands list\`
|
|
67
|
+
- \`salestouch commands schema <command_id>\`
|
|
68
|
+
- \`salestouch commands run <command_id> --input-json '<json>'\`
|
|
69
|
+
- \`salestouch resources list\`
|
|
70
|
+
- \`salestouch resources read <uri>\`
|
|
71
|
+
|
|
72
|
+
## Workflow
|
|
73
|
+
|
|
74
|
+
1. Discover the command with \`salestouch commands list\`.
|
|
75
|
+
2. Inspect its payload with \`salestouch commands schema\`.
|
|
76
|
+
3. Execute with \`salestouch commands run\`.
|
|
77
|
+
4. Use \`salestouch resources\` for read-only context.
|
|
78
|
+
|
|
79
|
+
## Rules
|
|
80
|
+
|
|
81
|
+
- Prefer schema-first execution for write actions.
|
|
82
|
+
- Keep payloads JSON-first and deterministic.
|
|
83
|
+
- Do not invent SalesTouch IDs, handles, or hidden fields.
|
|
84
|
+
- If a required field is unclear, fetch more context before writing.`;
|
|
85
|
+
return `---
|
|
86
|
+
name: salestouch
|
|
87
|
+
description: ${skillDescriptions[mode]}
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
${body}
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
function getRuleContent(agent, mode) {
|
|
94
|
+
const body = ruleBodies[mode];
|
|
95
|
+
return agent === "cursor" ? `${CURSOR_FRONTMATTER}${body}\n` : `${body}\n`;
|
|
96
|
+
}
|
|
97
|
+
function pathExists(targetPath) {
|
|
98
|
+
return access(targetPath).then(() => true, () => false);
|
|
99
|
+
}
|
|
100
|
+
function stripJsonComments(text) {
|
|
101
|
+
let result = "";
|
|
102
|
+
let index = 0;
|
|
103
|
+
while (index < text.length) {
|
|
104
|
+
if (text[index] === '"') {
|
|
105
|
+
const start = index++;
|
|
106
|
+
while (index < text.length && text[index] !== '"') {
|
|
107
|
+
if (text[index] === "\\") {
|
|
108
|
+
index += 1;
|
|
109
|
+
}
|
|
110
|
+
index += 1;
|
|
111
|
+
}
|
|
112
|
+
result += text.slice(start, ++index);
|
|
113
|
+
}
|
|
114
|
+
else if (text[index] === "/" && text[index + 1] === "/") {
|
|
115
|
+
index += 2;
|
|
116
|
+
while (index < text.length && text[index] !== "\n") {
|
|
117
|
+
index += 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (text[index] === "/" && text[index + 1] === "*") {
|
|
121
|
+
index += 2;
|
|
122
|
+
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
|
|
123
|
+
index += 1;
|
|
124
|
+
}
|
|
125
|
+
index += 2;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
result += text[index];
|
|
129
|
+
index += 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
async function readJsonConfig(filePath) {
|
|
135
|
+
try {
|
|
136
|
+
const raw = await readFile(filePath, "utf8");
|
|
137
|
+
const trimmed = raw.trim();
|
|
138
|
+
return trimmed ? JSON.parse(stripJsonComments(trimmed)) : {};
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function writeJsonConfig(filePath, config) {
|
|
145
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
146
|
+
await writeFile(filePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
147
|
+
}
|
|
148
|
+
async function writeJsonMcpConfig(filePath, configKey, entry) {
|
|
149
|
+
const existing = await readJsonConfig(filePath);
|
|
150
|
+
const section = existing[configKey] ?? {};
|
|
151
|
+
await writeJsonConfig(filePath, {
|
|
152
|
+
...existing,
|
|
153
|
+
[configKey]: {
|
|
154
|
+
...section,
|
|
155
|
+
salestouch: entry,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
return filePath;
|
|
159
|
+
}
|
|
160
|
+
function buildCodexTomlBlock(options) {
|
|
161
|
+
if (options.mcpTarget === "remote") {
|
|
162
|
+
return [
|
|
163
|
+
"[mcp_servers.salestouch]",
|
|
164
|
+
`url = "${options.mcpUrl}"`,
|
|
165
|
+
"startup_timeout_sec = 20.0",
|
|
166
|
+
"",
|
|
167
|
+
].join("\n");
|
|
168
|
+
}
|
|
169
|
+
return [
|
|
170
|
+
"[mcp_servers.salestouch]",
|
|
171
|
+
'command = "salestouch"',
|
|
172
|
+
'args = ["mcp", "serve"]',
|
|
173
|
+
"startup_timeout_sec = 20.0",
|
|
174
|
+
"",
|
|
175
|
+
].join("\n");
|
|
176
|
+
}
|
|
177
|
+
async function writeCodexTomlConfig(filePath, options) {
|
|
178
|
+
const block = buildCodexTomlBlock(options);
|
|
179
|
+
let existing = "";
|
|
180
|
+
try {
|
|
181
|
+
existing = await readFile(filePath, "utf8");
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// file created below
|
|
185
|
+
}
|
|
186
|
+
const sectionHeader = "[mcp_servers.salestouch]";
|
|
187
|
+
const alreadyExists = existing.includes(sectionHeader);
|
|
188
|
+
let next = existing;
|
|
189
|
+
if (alreadyExists) {
|
|
190
|
+
const sectionStart = existing.indexOf(sectionHeader);
|
|
191
|
+
const rest = existing.slice(sectionStart + sectionHeader.length);
|
|
192
|
+
const nextSectionMatch = /\n\[[^\n]+\]/.exec(rest);
|
|
193
|
+
const sectionEnd = nextSectionMatch
|
|
194
|
+
? sectionStart + sectionHeader.length + nextSectionMatch.index + 1
|
|
195
|
+
: existing.length;
|
|
196
|
+
const before = existing.slice(0, sectionStart).replace(/\n+$/, "");
|
|
197
|
+
const after = existing.slice(sectionEnd).replace(/^\n+/, "");
|
|
198
|
+
next = `${before}${before ? "\n\n" : ""}${block}${after ? `\n${after}` : ""}`;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
202
|
+
next = `${existing}${separator}${block}`;
|
|
203
|
+
}
|
|
204
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
205
|
+
await writeFile(filePath, next, "utf8");
|
|
206
|
+
return filePath;
|
|
207
|
+
}
|
|
208
|
+
async function writeRule(rule, scope, content) {
|
|
209
|
+
const filePath = rule.path(scope);
|
|
210
|
+
if (rule.kind === "file") {
|
|
211
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
212
|
+
await writeFile(filePath, content, "utf8");
|
|
213
|
+
return filePath;
|
|
214
|
+
}
|
|
215
|
+
const section = `${rule.sectionMarker}\n${content}${rule.sectionMarker}`;
|
|
216
|
+
let existing = "";
|
|
217
|
+
try {
|
|
218
|
+
existing = await readFile(filePath, "utf8");
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// file created below
|
|
222
|
+
}
|
|
223
|
+
if (existing.includes(rule.sectionMarker)) {
|
|
224
|
+
const escapedMarker = rule.sectionMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
225
|
+
const regex = new RegExp(`${escapedMarker}\\n[\\s\\S]*?${escapedMarker}`);
|
|
226
|
+
const updated = existing.replace(regex, section);
|
|
227
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
228
|
+
await writeFile(filePath, updated, "utf8");
|
|
229
|
+
return filePath;
|
|
230
|
+
}
|
|
231
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
|
|
232
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
233
|
+
await writeFile(filePath, `${existing}${separator}${section}\n`, "utf8");
|
|
234
|
+
return filePath;
|
|
235
|
+
}
|
|
236
|
+
async function writeSkill(skillDir, mode) {
|
|
237
|
+
const targetDir = join(skillDir, "salestouch");
|
|
238
|
+
await mkdir(targetDir, { recursive: true });
|
|
239
|
+
const skillPath = join(targetDir, "SKILL.md");
|
|
240
|
+
await writeFile(skillPath, getSkillContent(mode), "utf8");
|
|
241
|
+
return skillPath;
|
|
242
|
+
}
|
|
243
|
+
function getManifestPath(scope) {
|
|
244
|
+
return scope === "global"
|
|
245
|
+
? join(homedir(), ".salestouch", "setup.json")
|
|
246
|
+
: join(process.cwd(), ".salestouch", "setup.json");
|
|
247
|
+
}
|
|
248
|
+
async function writeSetupManifest(manifest) {
|
|
249
|
+
const manifestPath = getManifestPath(manifest.scope);
|
|
250
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
251
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
252
|
+
return manifestPath;
|
|
253
|
+
}
|
|
254
|
+
function buildClaudeEntry(options) {
|
|
255
|
+
if (options.mcpTarget === "remote") {
|
|
256
|
+
return {
|
|
257
|
+
type: "http",
|
|
258
|
+
url: options.mcpUrl,
|
|
259
|
+
...(options.authMode === "api_key" && options.apiKey
|
|
260
|
+
? { headers: { "X-API-Key": options.apiKey } }
|
|
261
|
+
: {}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
command: "salestouch",
|
|
266
|
+
args: ["mcp", "serve"],
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function buildCursorEntry(options) {
|
|
270
|
+
if (options.mcpTarget === "remote") {
|
|
271
|
+
return {
|
|
272
|
+
url: options.mcpUrl,
|
|
273
|
+
...(options.authMode === "api_key" && options.apiKey
|
|
274
|
+
? { headers: { "X-API-Key": options.apiKey } }
|
|
275
|
+
: {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
command: "salestouch",
|
|
280
|
+
args: ["mcp", "serve"],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function buildOpenCodeEntry(options) {
|
|
284
|
+
if (options.mcpTarget === "remote") {
|
|
285
|
+
return {
|
|
286
|
+
type: "remote",
|
|
287
|
+
url: options.mcpUrl,
|
|
288
|
+
enabled: true,
|
|
289
|
+
...(options.authMode === "api_key" && options.apiKey
|
|
290
|
+
? {
|
|
291
|
+
oauth: false,
|
|
292
|
+
headers: {
|
|
293
|
+
"X-API-Key": options.apiKey,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
: {}),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
type: "local",
|
|
301
|
+
command: ["salestouch", "mcp", "serve"],
|
|
302
|
+
enabled: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function assertCodexRemoteSupport(options) {
|
|
306
|
+
if (options.mcpTarget === "remote" && options.authMode === "api_key") {
|
|
307
|
+
throw new Error("Codex remote MCP setup supports OAuth only. Use `salestouch setup --codex --login` or `--local-mcp`.");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const agents = {
|
|
311
|
+
claude: {
|
|
312
|
+
name: "claude",
|
|
313
|
+
detect: {
|
|
314
|
+
projectPaths: [".mcp.json", ".claude"],
|
|
315
|
+
globalPaths: [join(homedir(), ".claude.json"), join(homedir(), ".claude")],
|
|
316
|
+
},
|
|
317
|
+
skillDir: (scope) => scope === "global" ? join(homedir(), ".claude", "skills") : join(process.cwd(), ".claude", "skills"),
|
|
318
|
+
rule: {
|
|
319
|
+
kind: "file",
|
|
320
|
+
path: (scope) => scope === "global"
|
|
321
|
+
? join(homedir(), ".claude", "rules", "salestouch.md")
|
|
322
|
+
: join(process.cwd(), ".claude", "rules", "salestouch.md"),
|
|
323
|
+
},
|
|
324
|
+
installMcp: (options) => writeJsonMcpConfig(options.scope === "global" ? join(homedir(), ".claude.json") : join(process.cwd(), ".mcp.json"), "mcpServers", buildClaudeEntry(options)),
|
|
325
|
+
},
|
|
326
|
+
cursor: {
|
|
327
|
+
name: "cursor",
|
|
328
|
+
detect: {
|
|
329
|
+
projectPaths: [".cursor"],
|
|
330
|
+
globalPaths: [join(homedir(), ".cursor")],
|
|
331
|
+
},
|
|
332
|
+
skillDir: (scope) => scope === "global" ? join(homedir(), ".cursor", "skills") : join(process.cwd(), ".cursor", "skills"),
|
|
333
|
+
rule: {
|
|
334
|
+
kind: "file",
|
|
335
|
+
path: (scope) => scope === "global"
|
|
336
|
+
? join(homedir(), ".cursor", "rules", "salestouch.mdc")
|
|
337
|
+
: join(process.cwd(), ".cursor", "rules", "salestouch.mdc"),
|
|
338
|
+
},
|
|
339
|
+
installMcp: (options) => writeJsonMcpConfig(options.scope === "global"
|
|
340
|
+
? join(homedir(), ".cursor", "mcp.json")
|
|
341
|
+
: join(process.cwd(), ".cursor", "mcp.json"), "mcpServers", buildCursorEntry(options)),
|
|
342
|
+
},
|
|
343
|
+
codex: {
|
|
344
|
+
name: "codex",
|
|
345
|
+
detect: {
|
|
346
|
+
projectPaths: [".codex", "AGENTS.md"],
|
|
347
|
+
globalPaths: [join(homedir(), ".codex")],
|
|
348
|
+
},
|
|
349
|
+
skillDir: (scope) => scope === "global" ? join(homedir(), ".agents", "skills") : join(process.cwd(), ".agents", "skills"),
|
|
350
|
+
rule: {
|
|
351
|
+
kind: "append",
|
|
352
|
+
path: (scope) => scope === "global" ? join(homedir(), ".codex", "AGENTS.md") : join(process.cwd(), "AGENTS.md"),
|
|
353
|
+
sectionMarker: SECTION_MARKER,
|
|
354
|
+
},
|
|
355
|
+
installMcp: async (options) => {
|
|
356
|
+
assertCodexRemoteSupport(options);
|
|
357
|
+
return writeCodexTomlConfig(options.scope === "global"
|
|
358
|
+
? join(homedir(), ".codex", "config.toml")
|
|
359
|
+
: join(process.cwd(), ".codex", "config.toml"), options);
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
opencode: {
|
|
363
|
+
name: "opencode",
|
|
364
|
+
detect: {
|
|
365
|
+
projectPaths: ["opencode.json", ".opencode.json", "AGENTS.md"],
|
|
366
|
+
globalPaths: [join(homedir(), ".config", "opencode")],
|
|
367
|
+
},
|
|
368
|
+
skillDir: (scope) => scope === "global" ? join(homedir(), ".agents", "skills") : join(process.cwd(), ".agents", "skills"),
|
|
369
|
+
rule: {
|
|
370
|
+
kind: "append",
|
|
371
|
+
path: (scope) => scope === "global"
|
|
372
|
+
? join(homedir(), ".config", "opencode", "AGENTS.md")
|
|
373
|
+
: join(process.cwd(), "AGENTS.md"),
|
|
374
|
+
sectionMarker: SECTION_MARKER,
|
|
375
|
+
},
|
|
376
|
+
installMcp: (options) => writeJsonMcpConfig(options.scope === "global"
|
|
377
|
+
? join(homedir(), ".config", "opencode", "opencode.json")
|
|
378
|
+
: join(process.cwd(), "opencode.json"), "mcp", buildOpenCodeEntry(options)),
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
async function detectAgents(scope) {
|
|
382
|
+
const detected = [];
|
|
383
|
+
for (const agent of Object.values(agents)) {
|
|
384
|
+
const candidates = scope === "global" ? agent.detect.globalPaths : agent.detect.projectPaths;
|
|
385
|
+
for (const candidate of candidates) {
|
|
386
|
+
if (await pathExists(candidate)) {
|
|
387
|
+
detected.push(agent.name);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return detected;
|
|
393
|
+
}
|
|
394
|
+
export async function resolveSetupAgents(requestedAgents, scope) {
|
|
395
|
+
if (requestedAgents.length > 0) {
|
|
396
|
+
return requestedAgents;
|
|
397
|
+
}
|
|
398
|
+
const detected = await detectAgents(scope);
|
|
399
|
+
if (detected.length > 0) {
|
|
400
|
+
return detected;
|
|
401
|
+
}
|
|
402
|
+
return ["codex"];
|
|
403
|
+
}
|
|
404
|
+
export async function readSetupManifest(scope) {
|
|
405
|
+
try {
|
|
406
|
+
const raw = await readFile(getManifestPath(scope), "utf8");
|
|
407
|
+
return JSON.parse(raw);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
export async function runSetup(options) {
|
|
414
|
+
const selectedAgents = await resolveSetupAgents(options.agents, options.scope);
|
|
415
|
+
const installSkills = options.installSkills ?? true;
|
|
416
|
+
const manifest = {
|
|
417
|
+
version: 1,
|
|
418
|
+
cli_version: CLI_VERSION,
|
|
419
|
+
generated_at: new Date().toISOString(),
|
|
420
|
+
scope: options.scope,
|
|
421
|
+
mode: options.mode,
|
|
422
|
+
mcp_target: options.mode === "mcp" ? options.mcpTarget : null,
|
|
423
|
+
auth_mode: options.mode === "mcp" ? options.authMode : null,
|
|
424
|
+
install_skills: installSkills,
|
|
425
|
+
base_url: options.baseUrl,
|
|
426
|
+
mcp_url: options.mode === "mcp" ? options.mcpUrl : null,
|
|
427
|
+
agents: selectedAgents,
|
|
428
|
+
};
|
|
429
|
+
const manifestPath = await writeSetupManifest(manifest);
|
|
430
|
+
const results = [];
|
|
431
|
+
for (const agentName of selectedAgents) {
|
|
432
|
+
const agent = agents[agentName];
|
|
433
|
+
let mcpConfigPath;
|
|
434
|
+
if (options.mode === "mcp" && agent.installMcp) {
|
|
435
|
+
mcpConfigPath = await agent.installMcp({
|
|
436
|
+
scope: options.scope,
|
|
437
|
+
authMode: options.authMode,
|
|
438
|
+
mcpTarget: options.mcpTarget,
|
|
439
|
+
mcpUrl: options.mcpUrl,
|
|
440
|
+
apiKey: options.apiKey,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
const rulePath = await writeRule(agent.rule, options.scope, getRuleContent(agent.name, options.mode));
|
|
444
|
+
const skillPath = installSkills
|
|
445
|
+
? await writeSkill(agent.skillDir(options.scope), options.mode)
|
|
446
|
+
: undefined;
|
|
447
|
+
results.push({
|
|
448
|
+
agent: agent.name,
|
|
449
|
+
mode: options.mode,
|
|
450
|
+
scope: options.scope,
|
|
451
|
+
mcpConfigPath,
|
|
452
|
+
rulePath,
|
|
453
|
+
skillPath,
|
|
454
|
+
manifestPath,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return results;
|
|
458
|
+
}
|
|
459
|
+
export async function runSkillsInstall(options) {
|
|
460
|
+
const selectedAgents = await resolveSetupAgents(options.agents, options.scope);
|
|
461
|
+
const results = [];
|
|
462
|
+
for (const agentName of selectedAgents) {
|
|
463
|
+
const agent = agents[agentName];
|
|
464
|
+
const skillPath = await writeSkill(agent.skillDir(options.scope), options.mode);
|
|
465
|
+
results.push({
|
|
466
|
+
agent: agent.name,
|
|
467
|
+
mode: options.mode,
|
|
468
|
+
scope: options.scope,
|
|
469
|
+
skillPath,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
return results;
|
|
473
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salestouch/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Public SalesTouch CLI for commands, resources, and developer workflows.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"salestouch": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"dev": "tsx src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@clack/prompts": "^1.2.0",
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
|
+
"dotenv": "^17.3.1",
|
|
24
|
+
"picocolors": "^1.1.1",
|
|
25
|
+
"zod": "^4.3.6"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|