@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.
@@ -0,0 +1,5 @@
1
+ export type CliLocale = "en" | "fr";
2
+ type CliEnv = Record<string, string | undefined>;
3
+ export declare function detectCliLocale(env?: CliEnv): CliLocale;
4
+ export declare function isFrenchCliLocale(env?: CliEnv): boolean;
5
+ export {};
package/dist/locale.js ADDED
@@ -0,0 +1,28 @@
1
+ const FRENCH_LOCALE_PATTERN = /^fr(?:[_\-.]|$)/i;
2
+ function splitLocaleCandidates(value) {
3
+ if (!value) {
4
+ return [];
5
+ }
6
+ return value
7
+ .split(":")
8
+ .map((candidate) => candidate.trim())
9
+ .filter((candidate) => candidate.length > 0);
10
+ }
11
+ export function detectCliLocale(env = process.env) {
12
+ const explicitCandidates = splitLocaleCandidates(env.SALESTOUCH_CLI_LANG);
13
+ if (explicitCandidates.length > 0) {
14
+ return explicitCandidates.some((candidate) => FRENCH_LOCALE_PATTERN.test(candidate))
15
+ ? "fr"
16
+ : "en";
17
+ }
18
+ const candidates = [
19
+ ...splitLocaleCandidates(env.LC_ALL),
20
+ ...splitLocaleCandidates(env.LC_MESSAGES),
21
+ ...splitLocaleCandidates(env.LANGUAGE),
22
+ ...splitLocaleCandidates(env.LANG),
23
+ ];
24
+ return candidates.some((candidate) => FRENCH_LOCALE_PATTERN.test(candidate)) ? "fr" : "en";
25
+ }
26
+ export function isFrenchCliLocale(env = process.env) {
27
+ return detectCliLocale(env) === "fr";
28
+ }
@@ -0,0 +1,2 @@
1
+ import { type ResolvedAuth } from "./core.js";
2
+ export declare function startMcpServer(auth: ResolvedAuth): Promise<void>;
@@ -0,0 +1,181 @@
1
+ import process from "node:process";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { CLI_VERSION, apiRequest } from "./core.js";
6
+ const fallbackResources = [
7
+ {
8
+ uri: "salestouch://status",
9
+ name: "SalesTouch Status",
10
+ description: "Workspace installation and API connectivity status.",
11
+ mimeType: "application/json",
12
+ },
13
+ {
14
+ uri: "salestouch://catalog",
15
+ name: "SalesTouch Catalog",
16
+ description: "Public automation catalog exposed by the SalesTouch runtime.",
17
+ mimeType: "application/json",
18
+ },
19
+ ];
20
+ const schemaCache = new Map();
21
+ function writeStderr(message) {
22
+ process.stderr.write(`[salestouch-mcp] ${message}\n`);
23
+ }
24
+ async function fetchCatalog(auth) {
25
+ const payload = await apiRequest(auth, "/api/v1/commands");
26
+ return (payload.data?.commands ?? []).flatMap((entry) => {
27
+ if (typeof entry.command_id !== "string") {
28
+ return [];
29
+ }
30
+ return [
31
+ {
32
+ id: entry.command_id,
33
+ title: entry.title ?? entry.command_id,
34
+ description: entry.summary ?? entry.description ?? entry.title ?? entry.command_id,
35
+ risk: entry.risk ?? "read",
36
+ category: entry.category ?? null,
37
+ },
38
+ ];
39
+ });
40
+ }
41
+ async function fetchCommandSchema(auth, commandId) {
42
+ const cached = schemaCache.get(commandId);
43
+ if (cached) {
44
+ return cached;
45
+ }
46
+ const payload = await apiRequest(auth, `/api/v1/commands/${encodeURIComponent(commandId)}/schema`);
47
+ const schema = payload.data?.command?.input_schema ?? {
48
+ type: "object",
49
+ properties: {},
50
+ additionalProperties: true,
51
+ };
52
+ schemaCache.set(commandId, schema);
53
+ return schema;
54
+ }
55
+ async function fetchResources(auth) {
56
+ const payload = await apiRequest(auth, "/api/v1/resources");
57
+ return payload.data?.resources ?? fallbackResources;
58
+ }
59
+ async function readResource(auth, uri) {
60
+ const payload = await apiRequest(auth, `/api/v1/resources/read?uri=${encodeURIComponent(uri)}`);
61
+ return payload.data?.resource ?? null;
62
+ }
63
+ function safeJsonStringify(value) {
64
+ try {
65
+ return JSON.stringify(value, null, 2);
66
+ }
67
+ catch {
68
+ return String(value);
69
+ }
70
+ }
71
+ function getToolAnnotations(risk) {
72
+ if (risk === "read") {
73
+ return {
74
+ readOnlyHint: true,
75
+ destructiveHint: false,
76
+ idempotentHint: true,
77
+ openWorldHint: false,
78
+ };
79
+ }
80
+ return {
81
+ readOnlyHint: false,
82
+ destructiveHint: risk === "approval",
83
+ idempotentHint: false,
84
+ openWorldHint: false,
85
+ };
86
+ }
87
+ export async function startMcpServer(auth) {
88
+ const server = new Server({
89
+ name: "salestouch",
90
+ version: CLI_VERSION,
91
+ }, {
92
+ capabilities: {
93
+ tools: {
94
+ listChanged: false,
95
+ },
96
+ resources: {
97
+ listChanged: false,
98
+ subscribe: false,
99
+ },
100
+ },
101
+ instructions: "Use SalesTouch when the user asks to inspect or act on leads, missions, offers, imports, LinkedIn prospecting workflows, or scoring data that lives in SalesTouch. Prefer reading tool schemas before writing payloads. Use resources for workspace status and reference data.",
102
+ });
103
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
104
+ const catalog = await fetchCatalog(auth);
105
+ const tools = await Promise.all(catalog.map(async (tool) => ({
106
+ name: tool.id,
107
+ title: tool.title,
108
+ description: tool.description,
109
+ inputSchema: await fetchCommandSchema(auth, tool.id),
110
+ annotations: getToolAnnotations(tool.risk),
111
+ })));
112
+ return { tools };
113
+ });
114
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
115
+ const toolName = request.params.name;
116
+ const toolArgs = request.params.arguments && typeof request.params.arguments === "object"
117
+ ? request.params.arguments
118
+ : {};
119
+ const catalog = await fetchCatalog(auth);
120
+ if (!catalog.some((entry) => entry.id === toolName)) {
121
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`);
122
+ }
123
+ const payload = await apiRequest(auth, `/api/v1/commands/${encodeURIComponent(toolName)}`, {
124
+ method: "POST",
125
+ body: JSON.stringify({
126
+ input: toolArgs,
127
+ }),
128
+ });
129
+ const resultBody = payload.ok
130
+ ? {
131
+ data: payload.data ?? null,
132
+ meta: payload.meta ?? null,
133
+ }
134
+ : {
135
+ error: payload.error ?? { message: "Unknown command failure" },
136
+ meta: payload.meta ?? null,
137
+ };
138
+ return {
139
+ content: [
140
+ {
141
+ type: "text",
142
+ text: safeJsonStringify(resultBody),
143
+ },
144
+ ],
145
+ structuredContent: resultBody,
146
+ isError: !payload.ok,
147
+ };
148
+ });
149
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
150
+ const resources = await fetchResources(auth);
151
+ return {
152
+ resources: resources.map((resource) => ({
153
+ uri: String(resource.uri ?? ""),
154
+ name: String(resource.name ?? resource.uri ?? ""),
155
+ title: String(resource.name ?? resource.uri ?? ""),
156
+ description: String(resource.description ?? ""),
157
+ mimeType: "application/json",
158
+ })),
159
+ };
160
+ });
161
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
162
+ const uri = request.params.uri;
163
+ const resource = await readResource(auth, uri);
164
+ if (!resource?.uri) {
165
+ throw new McpError(ErrorCode.InvalidParams, `Resource not found: ${uri}`);
166
+ }
167
+ return {
168
+ contents: [
169
+ {
170
+ uri: resource.uri,
171
+ mimeType: "application/json",
172
+ text: safeJsonStringify((resource.payload ?? {})),
173
+ },
174
+ ],
175
+ };
176
+ });
177
+ const transport = new StdioServerTransport();
178
+ await server.connect(transport);
179
+ writeStderr("ready");
180
+ process.stdin.resume();
181
+ }
@@ -0,0 +1,39 @@
1
+ import { type FlagValue, type ResolvedAuth } from "./core.js";
2
+ import { type CliLocale } from "./locale.js";
3
+ import { type SetupAgent, type SetupMcpTarget, type SetupMode, type SetupScope } from "./setup.js";
4
+ export type SetupWizardSelections = {
5
+ agents: SetupAgent[];
6
+ mode: SetupMode;
7
+ scope: SetupScope;
8
+ mcpTarget: SetupMcpTarget;
9
+ installSkills: boolean;
10
+ authChoice: "reuse" | "login" | "api_key";
11
+ apiKey: string | null;
12
+ };
13
+ type WizardChoice<T> = {
14
+ label: string;
15
+ value: T;
16
+ hint: string;
17
+ };
18
+ export declare function shouldRunSetupWizard(flags: Record<string, FlagValue>, isInteractive: boolean): boolean;
19
+ export declare function buildSetupWizardAuthChoices(params: {
20
+ agent?: SetupAgent;
21
+ agents?: SetupAgent[];
22
+ mode: SetupMode;
23
+ mcpTarget: SetupMcpTarget;
24
+ existingAuth: ResolvedAuth | null;
25
+ locale?: CliLocale;
26
+ }): WizardChoice<"api_key" | "reuse" | "login">[];
27
+ export declare function applySetupWizardSelections(flags: Record<string, FlagValue>, selections: SetupWizardSelections): Record<string, FlagValue>;
28
+ export declare function runInteractiveSetupWizard(params: {
29
+ existingAuth: ResolvedAuth | null;
30
+ }): Promise<{
31
+ agents: SetupAgent[];
32
+ mode: SetupMode;
33
+ scope: SetupScope;
34
+ mcpTarget: SetupMcpTarget;
35
+ installSkills: boolean;
36
+ authChoice: "api_key" | "reuse" | "login";
37
+ apiKey: string | null;
38
+ } | null>;
39
+ export {};