@alfe.ai/salesforce-mcp 0.0.2
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/dist/server.d.ts +29 -0
- package/dist/server.js +336 -0
- package/package.json +32 -0
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/server.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Salesforce MCP Server (Pattern A multi-org)
|
|
4
|
+
*
|
|
5
|
+
* Custom MCP server implementing the Salesforce REST/SOQL API directly
|
|
6
|
+
* (like the MYOB server; unlike the Xero proxy that wraps a child MCP).
|
|
7
|
+
* Every tool that touches credentials requires the LLM to pass `orgId`
|
|
8
|
+
* explicitly — the server keeps one `SalesforceClient` per connected org
|
|
9
|
+
* and dispatches per-call.
|
|
10
|
+
*
|
|
11
|
+
* Salesforce access tokens are per-org and NOT interchangeable, so token
|
|
12
|
+
* refresh is per-org (via the connect per-account refresh endpoint), unlike
|
|
13
|
+
* MYOB's interchangeable fan-out.
|
|
14
|
+
*
|
|
15
|
+
* Architecture:
|
|
16
|
+
* OpenClaw ←(stdio)→ this server ←(https)→ Salesforce REST API
|
|
17
|
+
*/
|
|
18
|
+
interface SalesforceOrgSummary {
|
|
19
|
+
orgId: string;
|
|
20
|
+
instanceUrl: string;
|
|
21
|
+
displayName: string | null;
|
|
22
|
+
connectedAt: string;
|
|
23
|
+
/** Whether the server has a live client for this org (false = init skipped). */
|
|
24
|
+
connected: boolean;
|
|
25
|
+
/** Why init was skipped, when `connected` is false. */
|
|
26
|
+
reason?: string;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { SalesforceOrgSummary };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { resolveConfig } from "@alfe.ai/config";
|
|
6
|
+
import { AgentApiClient } from "@alfe.ai/agent-api-client";
|
|
7
|
+
//#region src/salesforce-client.ts
|
|
8
|
+
var SalesforceClient = class {
|
|
9
|
+
accessToken;
|
|
10
|
+
instanceUrl;
|
|
11
|
+
apiVersion;
|
|
12
|
+
onRefresh;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.accessToken = config.accessToken;
|
|
15
|
+
this.instanceUrl = config.instanceUrl.replace(/\/+$/, "");
|
|
16
|
+
this.apiVersion = config.apiVersion;
|
|
17
|
+
this.onRefresh = config.onRefresh;
|
|
18
|
+
}
|
|
19
|
+
updateToken(accessToken) {
|
|
20
|
+
this.accessToken = accessToken;
|
|
21
|
+
}
|
|
22
|
+
get baseUrl() {
|
|
23
|
+
return `${this.instanceUrl}/services/data/v${this.apiVersion}`;
|
|
24
|
+
}
|
|
25
|
+
/** Run a SOQL query. Returns the standard `{ totalSize, done, records }`. */
|
|
26
|
+
async query(soql) {
|
|
27
|
+
return this.request("GET", `${this.baseUrl}/query?q=${encodeURIComponent(soql)}`);
|
|
28
|
+
}
|
|
29
|
+
/** Run a SOSL search. Returns the standard `{ searchRecords }`. */
|
|
30
|
+
async search(sosl) {
|
|
31
|
+
return this.request("GET", `${this.baseUrl}/search?q=${encodeURIComponent(sosl)}`);
|
|
32
|
+
}
|
|
33
|
+
/** Fetch one record by id, optionally projecting a subset of fields. */
|
|
34
|
+
async getRecord(sobject, id, fields) {
|
|
35
|
+
let url = `${this.baseUrl}/sobjects/${encodeURIComponent(sobject)}/${encodeURIComponent(id)}`;
|
|
36
|
+
if (fields?.length) url += `?fields=${encodeURIComponent(fields.join(","))}`;
|
|
37
|
+
return this.request("GET", url);
|
|
38
|
+
}
|
|
39
|
+
async createRecord(sobject, fields) {
|
|
40
|
+
return this.request("POST", `${this.baseUrl}/sobjects/${encodeURIComponent(sobject)}`, fields);
|
|
41
|
+
}
|
|
42
|
+
/** Update is a PATCH that returns 204 No Content on success. */
|
|
43
|
+
async updateRecord(sobject, id, fields) {
|
|
44
|
+
return this.request("PATCH", `${this.baseUrl}/sobjects/${encodeURIComponent(sobject)}/${encodeURIComponent(id)}`, fields);
|
|
45
|
+
}
|
|
46
|
+
/** Delete returns 204 No Content on success. */
|
|
47
|
+
async deleteRecord(sobject, id) {
|
|
48
|
+
return this.request("DELETE", `${this.baseUrl}/sobjects/${encodeURIComponent(sobject)}/${encodeURIComponent(id)}`);
|
|
49
|
+
}
|
|
50
|
+
/** Object metadata (fields, child relationships, picklist values, ...). */
|
|
51
|
+
async describe(sobject) {
|
|
52
|
+
return this.request("GET", `${this.baseUrl}/sobjects/${encodeURIComponent(sobject)}/describe`);
|
|
53
|
+
}
|
|
54
|
+
headers() {
|
|
55
|
+
return {
|
|
56
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async request(method, url, body) {
|
|
62
|
+
const doFetch = async () => {
|
|
63
|
+
const init = {
|
|
64
|
+
method,
|
|
65
|
+
headers: this.headers()
|
|
66
|
+
};
|
|
67
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
68
|
+
return fetch(url, init);
|
|
69
|
+
};
|
|
70
|
+
let response = await doFetch();
|
|
71
|
+
if (response.status === 401) {
|
|
72
|
+
this.accessToken = (await this.onRefresh()).accessToken;
|
|
73
|
+
response = await doFetch();
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorBody = await response.text();
|
|
77
|
+
throw new Error(`Salesforce API ${method} ${url} failed (${String(response.status)}): ${errorBody}`);
|
|
78
|
+
}
|
|
79
|
+
const text = await response.text();
|
|
80
|
+
if (!text) return {};
|
|
81
|
+
return JSON.parse(text);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/tools/shared.ts
|
|
86
|
+
/**
|
|
87
|
+
* Shared `orgId` selector field. Every credential-touching tool requires it
|
|
88
|
+
* (Pattern A per-call dispatch) — one Salesforce OAuth grant maps to one org,
|
|
89
|
+
* so the org id is the stable account selector. `McpServer.registerTool`
|
|
90
|
+
* expects a raw Zod shape (`{ field: z.string() }`), NOT a JSON-Schema object.
|
|
91
|
+
*/
|
|
92
|
+
const orgIdField = z.string().describe("Salesforce org id — use the value from salesforce_list_orgs to pick which connected org this call should target.");
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/tools/data.ts
|
|
95
|
+
/**
|
|
96
|
+
* Generic REST/SOQL core. These tools cover every standard AND custom object
|
|
97
|
+
* (Account, Contact, Opportunity, Lead, Case, `MyObject__c`, ...) without a
|
|
98
|
+
* per-object tool explosion — the LLM names the sObject as an argument.
|
|
99
|
+
*/
|
|
100
|
+
function registerDataTools(server, resolveClient) {
|
|
101
|
+
const register = server.registerTool.bind(server);
|
|
102
|
+
const ok = (result) => ({ content: [{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify(result, null, 2)
|
|
105
|
+
}] });
|
|
106
|
+
register("salesforce_query", {
|
|
107
|
+
description: "Run a SOQL query against a connected Salesforce org. Works for any standard or custom object, e.g. \"SELECT Id, Name FROM Account WHERE Industry = 'Technology' LIMIT 50\".",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
orgId: orgIdField,
|
|
110
|
+
soql: z.string().describe("The SOQL query string.")
|
|
111
|
+
}
|
|
112
|
+
}, async (args) => {
|
|
113
|
+
return ok(await resolveClient(args.orgId).query(args.soql));
|
|
114
|
+
});
|
|
115
|
+
register("salesforce_search", {
|
|
116
|
+
description: "Run a SOSL search across a connected Salesforce org, e.g. \"FIND {Acme} IN ALL FIELDS RETURNING Account(Id, Name), Contact(Id, Name)\".",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
orgId: orgIdField,
|
|
119
|
+
sosl: z.string().describe("The SOSL search string.")
|
|
120
|
+
}
|
|
121
|
+
}, async (args) => {
|
|
122
|
+
return ok(await resolveClient(args.orgId).search(args.sosl));
|
|
123
|
+
});
|
|
124
|
+
register("salesforce_get_record", {
|
|
125
|
+
description: "Fetch a single record by id from a connected Salesforce org.",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
orgId: orgIdField,
|
|
128
|
+
sobject: z.string().describe("The sObject API name, e.g. Account, Contact, Opportunity, or MyObject__c."),
|
|
129
|
+
id: z.string().describe("The 15- or 18-character record id."),
|
|
130
|
+
fields: z.array(z.string()).optional().describe("Optional subset of field API names to return. Omit for all accessible fields.")
|
|
131
|
+
}
|
|
132
|
+
}, async (args) => {
|
|
133
|
+
return ok(await resolveClient(args.orgId).getRecord(args.sobject, args.id, args.fields));
|
|
134
|
+
});
|
|
135
|
+
register("salesforce_create_record", {
|
|
136
|
+
description: "Create a record of any sObject type in a connected Salesforce org.",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
orgId: orgIdField,
|
|
139
|
+
sobject: z.string().describe("The sObject API name, e.g. Account, Contact, Lead, Case, or MyObject__c."),
|
|
140
|
+
fields: z.record(z.string(), z.unknown()).describe("Field API name → value map, e.g. { \"Name\": \"Acme Inc\", \"Industry\": \"Technology\" }.")
|
|
141
|
+
}
|
|
142
|
+
}, async (args) => {
|
|
143
|
+
return ok(await resolveClient(args.orgId).createRecord(args.sobject, args.fields));
|
|
144
|
+
});
|
|
145
|
+
register("salesforce_update_record", {
|
|
146
|
+
description: "Update fields on an existing record in a connected Salesforce org. Returns no body on success.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
orgId: orgIdField,
|
|
149
|
+
sobject: z.string().describe("The sObject API name."),
|
|
150
|
+
id: z.string().describe("The record id to update."),
|
|
151
|
+
fields: z.record(z.string(), z.unknown()).describe("Field API name → new value map. Only the supplied fields are changed.")
|
|
152
|
+
}
|
|
153
|
+
}, async (args) => {
|
|
154
|
+
await resolveClient(args.orgId).updateRecord(args.sobject, args.id, args.fields);
|
|
155
|
+
return ok({
|
|
156
|
+
id: args.id,
|
|
157
|
+
success: true
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
register("salesforce_delete_record", {
|
|
161
|
+
description: "Delete a record by id from a connected Salesforce org. Returns no body on success.",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
orgId: orgIdField,
|
|
164
|
+
sobject: z.string().describe("The sObject API name."),
|
|
165
|
+
id: z.string().describe("The record id to delete.")
|
|
166
|
+
}
|
|
167
|
+
}, async (args) => {
|
|
168
|
+
await resolveClient(args.orgId).deleteRecord(args.sobject, args.id);
|
|
169
|
+
return ok({
|
|
170
|
+
id: args.id,
|
|
171
|
+
deleted: true
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
register("salesforce_describe", {
|
|
175
|
+
description: "Describe an sObject's metadata (fields, types, picklist values, relationships) for a connected Salesforce org. Use this to discover field API names before querying or creating records.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
orgId: orgIdField,
|
|
178
|
+
sobject: z.string().describe("The sObject API name to describe, e.g. Account or MyObject__c.")
|
|
179
|
+
}
|
|
180
|
+
}, async (args) => {
|
|
181
|
+
return ok(await resolveClient(args.orgId).describe(args.sobject));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/server.ts
|
|
186
|
+
/**
|
|
187
|
+
* Salesforce MCP Server (Pattern A multi-org)
|
|
188
|
+
*
|
|
189
|
+
* Custom MCP server implementing the Salesforce REST/SOQL API directly
|
|
190
|
+
* (like the MYOB server; unlike the Xero proxy that wraps a child MCP).
|
|
191
|
+
* Every tool that touches credentials requires the LLM to pass `orgId`
|
|
192
|
+
* explicitly — the server keeps one `SalesforceClient` per connected org
|
|
193
|
+
* and dispatches per-call.
|
|
194
|
+
*
|
|
195
|
+
* Salesforce access tokens are per-org and NOT interchangeable, so token
|
|
196
|
+
* refresh is per-org (via the connect per-account refresh endpoint), unlike
|
|
197
|
+
* MYOB's interchangeable fan-out.
|
|
198
|
+
*
|
|
199
|
+
* Architecture:
|
|
200
|
+
* OpenClaw ←(stdio)→ this server ←(https)→ Salesforce REST API
|
|
201
|
+
*/
|
|
202
|
+
/**
|
|
203
|
+
* Salesforce REST API version. The SOQL / sObject CRUD / describe surface is
|
|
204
|
+
* version-stable, so this defaults to a widely-available version and can be
|
|
205
|
+
* bumped per-deploy via `SALESFORCE_API_VERSION` without a code change.
|
|
206
|
+
*/
|
|
207
|
+
const DEFAULT_API_VERSION = "61.0";
|
|
208
|
+
const clients = /* @__PURE__ */ new Map();
|
|
209
|
+
const inFlightRefreshByOrg = /* @__PURE__ */ new Map();
|
|
210
|
+
const orgSnapshot = [];
|
|
211
|
+
function log(msg) {
|
|
212
|
+
process.stderr.write(`[salesforce-mcp-server] ${msg}\n`);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Resolve an `orgId` selector to its `SalesforceClient`. Throws if unknown —
|
|
216
|
+
* the LLM should call `salesforce_list_orgs` to discover the valid set. If the
|
|
217
|
+
* org is connected on the agent but couldn't be initialised, the error tells
|
|
218
|
+
* the LLM to ask the user to reconnect rather than retrying blindly.
|
|
219
|
+
*/
|
|
220
|
+
function resolveClient(orgId) {
|
|
221
|
+
const c = clients.get(orgId);
|
|
222
|
+
if (c) return c;
|
|
223
|
+
const known = orgSnapshot.find((o) => o.orgId === orgId);
|
|
224
|
+
if (known && !known.connected) throw new Error(`orgId ${orgId} is connected on this agent but the server could not initialise a client for it (reason: ${known.reason ?? "unknown"}). Ask the user to reconnect this Salesforce org from the dashboard.`);
|
|
225
|
+
throw new Error(`Unknown orgId: ${orgId}. Call salesforce_list_orgs to see the connected Salesforce orgs on this agent.`);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Refresh a single org's access token (Salesforce tokens are per-org).
|
|
229
|
+
* Concurrent callers for the same org share one in-flight request; the
|
|
230
|
+
* refreshed token is pushed into the cached client.
|
|
231
|
+
*/
|
|
232
|
+
async function refreshOrg(apiClient, orgId) {
|
|
233
|
+
const existing = inFlightRefreshByOrg.get(orgId);
|
|
234
|
+
if (existing) return existing;
|
|
235
|
+
const promise = (async () => {
|
|
236
|
+
try {
|
|
237
|
+
const { accessToken } = await apiClient.refreshSalesforceAccountToken(orgId);
|
|
238
|
+
clients.get(orgId)?.updateToken(accessToken);
|
|
239
|
+
return accessToken;
|
|
240
|
+
} finally {
|
|
241
|
+
inFlightRefreshByOrg.delete(orgId);
|
|
242
|
+
}
|
|
243
|
+
})();
|
|
244
|
+
inFlightRefreshByOrg.set(orgId, promise);
|
|
245
|
+
return promise;
|
|
246
|
+
}
|
|
247
|
+
async function main() {
|
|
248
|
+
const config = resolveConfig();
|
|
249
|
+
const apiClient = new AgentApiClient({
|
|
250
|
+
apiKey: config.apiKey,
|
|
251
|
+
apiUrl: config.apiUrl
|
|
252
|
+
});
|
|
253
|
+
const apiVersion = process.env.SALESFORCE_API_VERSION ?? DEFAULT_API_VERSION;
|
|
254
|
+
const { accounts } = await apiClient.getSalesforceAccounts();
|
|
255
|
+
if (accounts.length === 0) log("No Salesforce orgs connected — server will start with salesforce_list_orgs + salesforce_refresh_token only");
|
|
256
|
+
for (const acct of accounts) {
|
|
257
|
+
if (!acct.accessToken || !acct.instanceUrl) {
|
|
258
|
+
log(`Skipping org ${acct.orgId} — missing access token or instance URL`);
|
|
259
|
+
orgSnapshot.push({
|
|
260
|
+
orgId: acct.orgId,
|
|
261
|
+
instanceUrl: acct.instanceUrl,
|
|
262
|
+
displayName: acct.displayName,
|
|
263
|
+
connectedAt: acct.connectedAt,
|
|
264
|
+
connected: false,
|
|
265
|
+
reason: "missing_access_token_or_instance_url"
|
|
266
|
+
});
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (clients.has(acct.orgId)) {
|
|
270
|
+
log(`Duplicate orgId ${acct.orgId} returned by getSalesforceAccounts() — keeping the first cached client`);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
clients.set(acct.orgId, new SalesforceClient({
|
|
274
|
+
accessToken: acct.accessToken,
|
|
275
|
+
instanceUrl: acct.instanceUrl,
|
|
276
|
+
apiVersion,
|
|
277
|
+
onRefresh: async () => {
|
|
278
|
+
return { accessToken: await refreshOrg(apiClient, acct.orgId) };
|
|
279
|
+
}
|
|
280
|
+
}));
|
|
281
|
+
orgSnapshot.push({
|
|
282
|
+
orgId: acct.orgId,
|
|
283
|
+
instanceUrl: acct.instanceUrl,
|
|
284
|
+
displayName: acct.displayName,
|
|
285
|
+
connectedAt: acct.connectedAt,
|
|
286
|
+
connected: true
|
|
287
|
+
});
|
|
288
|
+
log(`Cached client for org ${acct.orgId} (${acct.displayName ?? "no display name"})`);
|
|
289
|
+
}
|
|
290
|
+
const server = new McpServer({
|
|
291
|
+
name: "salesforce-mcp-server",
|
|
292
|
+
version: "1.0.0"
|
|
293
|
+
});
|
|
294
|
+
registerDataTools(server, resolveClient);
|
|
295
|
+
const registerTool = server.registerTool.bind(server);
|
|
296
|
+
registerTool("salesforce_list_orgs", { description: "List the Salesforce orgs the agent has connected. Returns one entry per OAuth connection — use the returned orgId values as the `orgId` selector arg on every other salesforce_* tool." }, () => {
|
|
297
|
+
return { content: [{
|
|
298
|
+
type: "text",
|
|
299
|
+
text: JSON.stringify({ orgs: orgSnapshot }, null, 2)
|
|
300
|
+
}] };
|
|
301
|
+
});
|
|
302
|
+
registerTool("salesforce_refresh_token", {
|
|
303
|
+
description: "Force-refresh the Salesforce OAuth2 access token for a connected org. Pass an orgId to refresh that org, or omit it to refresh every connected org. Use this if API calls are failing with authentication errors.",
|
|
304
|
+
inputSchema: { orgId: z.string().optional().describe("Optional Salesforce org id — omit to refresh every connected org.") }
|
|
305
|
+
}, async (args) => {
|
|
306
|
+
try {
|
|
307
|
+
if (args.orgId) resolveClient(args.orgId);
|
|
308
|
+
const targets = args.orgId ? [args.orgId] : [...clients.keys()];
|
|
309
|
+
for (const orgId of targets) await refreshOrg(apiClient, orgId);
|
|
310
|
+
return { content: [{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify({ refreshed: targets })
|
|
313
|
+
}] };
|
|
314
|
+
} catch (err) {
|
|
315
|
+
return {
|
|
316
|
+
content: [{
|
|
317
|
+
type: "text",
|
|
318
|
+
text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) })
|
|
319
|
+
}],
|
|
320
|
+
isError: true
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
const transport = new StdioServerTransport();
|
|
325
|
+
await server.connect(transport);
|
|
326
|
+
log(`Salesforce MCP server running with ${String(clients.size)} connected org(s) and Pattern A selector enforcement`);
|
|
327
|
+
}
|
|
328
|
+
for (const signal of ["SIGTERM", "SIGINT"]) process.on(signal, () => {
|
|
329
|
+
process.exit(0);
|
|
330
|
+
});
|
|
331
|
+
main().catch((err) => {
|
|
332
|
+
log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
});
|
|
335
|
+
//#endregion
|
|
336
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alfe.ai/salesforce-mcp",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Salesforce MCP server — direct REST/SOQL integration with Alfe OAuth credentials and automatic per-org token refresh",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"salesforce-mcp-server": "./dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/server.d.ts",
|
|
13
|
+
"import": "./dist/server.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
21
|
+
"zod": "^4.0.5",
|
|
22
|
+
"@alfe.ai/config": "0.1.0",
|
|
23
|
+
"@alfe.ai/agent-api-client": "0.3.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "UNLICENSED",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"dev": "tsdown --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
30
|
+
"lint": "eslint ."
|
|
31
|
+
}
|
|
32
|
+
}
|