@acquis-consulting/asm 1.0.15 → 1.0.16
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/index.js +24 -3
- package/dist/index.js.map +1 -1
- package/dist/tools/apiRequest.d.ts +70 -0
- package/dist/tools/apiRequest.js +67 -0
- package/dist/tools/apiRequest.js.map +1 -0
- package/dist/tools/auditTrailDownload.d.ts +57 -0
- package/dist/tools/auditTrailDownload.js +128 -0
- package/dist/tools/auditTrailDownload.js.map +1 -0
- package/dist/tools/auditTrailSearch.d.ts +102 -0
- package/dist/tools/auditTrailSearch.js +215 -0
- package/dist/tools/auditTrailSearch.js.map +1 -0
- package/dist/tools/bulkExportResults.js +49 -10
- package/dist/tools/bulkExportResults.js.map +1 -1
- package/dist/tools/bulkImportAbort.d.ts +37 -0
- package/dist/tools/bulkImportAbort.js +46 -0
- package/dist/tools/bulkImportAbort.js.map +1 -0
- package/dist/tools/bulkImportResults.d.ts +51 -0
- package/dist/tools/bulkImportResults.js +168 -0
- package/dist/tools/bulkImportResults.js.map +1 -0
- package/dist/tools/bulkImportStart.d.ts +120 -0
- package/dist/tools/bulkImportStart.js +183 -0
- package/dist/tools/bulkImportStart.js.map +1 -0
- package/dist/tools/bulkImportStatus.d.ts +37 -0
- package/dist/tools/bulkImportStatus.js +56 -0
- package/dist/tools/bulkImportStatus.js.map +1 -0
- package/dist/tools/salesforceCliLogin.d.ts +36 -0
- package/dist/tools/salesforceCliLogin.js +73 -0
- package/dist/tools/salesforceCliLogin.js.map +1 -0
- package/dist/tools/salesforceCliLoginStatus.d.ts +25 -0
- package/dist/tools/salesforceCliLoginStatus.js +66 -0
- package/dist/tools/salesforceCliLoginStatus.js.map +1 -0
- package/dist/tools/salesforceLogin.js +2 -1
- package/dist/tools/salesforceLogin.js.map +1 -1
- package/dist/utils/auditTrailCsv.d.ts +42 -0
- package/dist/utils/auditTrailCsv.js +165 -0
- package/dist/utils/auditTrailCsv.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -0
- package/dist/utils/errors.js +14 -0
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/sfCliWebLogin.d.ts +56 -0
- package/dist/utils/sfCliWebLogin.js +208 -0
- package/dist/utils/sfCliWebLogin.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Column order of the audit trail CSV written by salesforce_audit_trail_download. */
|
|
2
|
+
export declare const AUDIT_TRAIL_COLUMNS: readonly ["Id", "CreatedDate", "Username", "UserFullName", "Action", "Section", "Display", "DelegateUser"];
|
|
3
|
+
export type AuditTrailRow = Record<(typeof AUDIT_TRAIL_COLUMNS)[number], string>;
|
|
4
|
+
/** Quotes a value per RFC 4180 when it contains a comma, quote, or line break. */
|
|
5
|
+
export declare function escapeCsvValue(value: unknown): string;
|
|
6
|
+
export declare function toCsvLine(values: unknown[]): string;
|
|
7
|
+
/**
|
|
8
|
+
* Parses one complete CSV record (which may span multiple physical lines).
|
|
9
|
+
* Returns null if the record has an unterminated quoted field, so the caller
|
|
10
|
+
* can append the next physical line and retry.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseCsvRecord(text: string): string[] | null;
|
|
13
|
+
/**
|
|
14
|
+
* Streams audit trail rows from a CSV file one record at a time, so the whole
|
|
15
|
+
* file is never held in memory. The header row is used to map columns.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readAuditTrailCsv(filePath: string): AsyncGenerator<AuditTrailRow>;
|
|
18
|
+
/**
|
|
19
|
+
* Normalizes a user-supplied date or datetime into a SOQL datetime literal
|
|
20
|
+
* (e.g. 2024-05-01T00:00:00Z). A bare date expands to the start or end of that
|
|
21
|
+
* UTC day depending on `boundary`. Throws on unparseable input, which also
|
|
22
|
+
* keeps arbitrary text out of the SOQL WHERE clause.
|
|
23
|
+
*/
|
|
24
|
+
export declare function normalizeAuditDate(input: string, boundary: 'start' | 'end'): string;
|
|
25
|
+
/** Increments a counter in a Map and returns the map. */
|
|
26
|
+
export declare function countInto(map: Map<string, number>, key: string): Map<string, number>;
|
|
27
|
+
export interface UserActivity {
|
|
28
|
+
fullName: string;
|
|
29
|
+
count: number;
|
|
30
|
+
first: string;
|
|
31
|
+
last: string;
|
|
32
|
+
sections: Map<string, number>;
|
|
33
|
+
}
|
|
34
|
+
/** Adds one audit trail change to the per-user activity map (keyed by username). */
|
|
35
|
+
export declare function trackUserActivity(map: Map<string, UserActivity>, row: Pick<AuditTrailRow, 'Username' | 'UserFullName' | 'CreatedDate' | 'Section'>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Formats per-user activity as one line per user, most active first:
|
|
38
|
+
* " user@example.com (Full Name): 12 changes, 2024-05-01 → 2024-05-20 — Flows 5, Apex Class 4, …"
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatUserActivity(map: Map<string, UserActivity>, top: number): string[];
|
|
41
|
+
/** Formats the top N entries of a counter as " name: count" lines, highest first. */
|
|
42
|
+
export declare function formatTopCounts(map: Map<string, number>, top: number): string[];
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
3
|
+
/** Column order of the audit trail CSV written by salesforce_audit_trail_download. */
|
|
4
|
+
export const AUDIT_TRAIL_COLUMNS = [
|
|
5
|
+
'Id',
|
|
6
|
+
'CreatedDate',
|
|
7
|
+
'Username',
|
|
8
|
+
'UserFullName',
|
|
9
|
+
'Action',
|
|
10
|
+
'Section',
|
|
11
|
+
'Display',
|
|
12
|
+
'DelegateUser',
|
|
13
|
+
];
|
|
14
|
+
/** Quotes a value per RFC 4180 when it contains a comma, quote, or line break. */
|
|
15
|
+
export function escapeCsvValue(value) {
|
|
16
|
+
const str = value === null || value === undefined ? '' : String(value);
|
|
17
|
+
return /[",\r\n]/.test(str) ? `"${str.replace(/"/g, '""')}"` : str;
|
|
18
|
+
}
|
|
19
|
+
export function toCsvLine(values) {
|
|
20
|
+
return values.map(escapeCsvValue).join(',') + '\n';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses one complete CSV record (which may span multiple physical lines).
|
|
24
|
+
* Returns null if the record has an unterminated quoted field, so the caller
|
|
25
|
+
* can append the next physical line and retry.
|
|
26
|
+
*/
|
|
27
|
+
export function parseCsvRecord(text) {
|
|
28
|
+
const fields = [];
|
|
29
|
+
let field = '';
|
|
30
|
+
let inQuotes = false;
|
|
31
|
+
for (let i = 0; i < text.length; i++) {
|
|
32
|
+
const ch = text[i];
|
|
33
|
+
if (inQuotes) {
|
|
34
|
+
if (ch === '"') {
|
|
35
|
+
if (text[i + 1] === '"') {
|
|
36
|
+
field += '"';
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
inQuotes = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
field += ch;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (ch === '"') {
|
|
48
|
+
inQuotes = true;
|
|
49
|
+
}
|
|
50
|
+
else if (ch === ',') {
|
|
51
|
+
fields.push(field);
|
|
52
|
+
field = '';
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
field += ch;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (inQuotes)
|
|
59
|
+
return null;
|
|
60
|
+
fields.push(field);
|
|
61
|
+
return fields;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Streams audit trail rows from a CSV file one record at a time, so the whole
|
|
65
|
+
* file is never held in memory. The header row is used to map columns.
|
|
66
|
+
*/
|
|
67
|
+
export async function* readAuditTrailCsv(filePath) {
|
|
68
|
+
const rl = readline.createInterface({
|
|
69
|
+
input: fs.createReadStream(filePath, { encoding: 'utf-8' }),
|
|
70
|
+
crlfDelay: Infinity,
|
|
71
|
+
});
|
|
72
|
+
let header;
|
|
73
|
+
let pending = '';
|
|
74
|
+
try {
|
|
75
|
+
for await (const line of rl) {
|
|
76
|
+
pending = pending ? `${pending}\n${line}` : line;
|
|
77
|
+
const fields = parseCsvRecord(pending);
|
|
78
|
+
if (!fields)
|
|
79
|
+
continue;
|
|
80
|
+
pending = '';
|
|
81
|
+
if (!header) {
|
|
82
|
+
header = fields;
|
|
83
|
+
const missing = AUDIT_TRAIL_COLUMNS.filter((c) => !header.includes(c));
|
|
84
|
+
if (missing.length > 0) {
|
|
85
|
+
throw new Error(`File is not an audit trail CSV (missing columns: ${missing.join(', ')}). ` +
|
|
86
|
+
`Use salesforce_audit_trail_download to create it.`);
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (fields.length === 1 && fields[0] === '')
|
|
91
|
+
continue;
|
|
92
|
+
const row = {};
|
|
93
|
+
for (const col of AUDIT_TRAIL_COLUMNS) {
|
|
94
|
+
row[col] = fields[header.indexOf(col)] ?? '';
|
|
95
|
+
}
|
|
96
|
+
yield row;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rl.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Normalizes a user-supplied date or datetime into a SOQL datetime literal
|
|
105
|
+
* (e.g. 2024-05-01T00:00:00Z). A bare date expands to the start or end of that
|
|
106
|
+
* UTC day depending on `boundary`. Throws on unparseable input, which also
|
|
107
|
+
* keeps arbitrary text out of the SOQL WHERE clause.
|
|
108
|
+
*/
|
|
109
|
+
export function normalizeAuditDate(input, boundary) {
|
|
110
|
+
const trimmed = input.trim();
|
|
111
|
+
const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
|
|
112
|
+
const date = new Date(isDateOnly ? `${trimmed}T${boundary === 'start' ? '00:00:00' : '23:59:59'}Z` : trimmed);
|
|
113
|
+
if (Number.isNaN(date.getTime())) {
|
|
114
|
+
throw new Error(`Invalid date "${input}". Use YYYY-MM-DD or an ISO 8601 datetime.`);
|
|
115
|
+
}
|
|
116
|
+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
117
|
+
}
|
|
118
|
+
/** Increments a counter in a Map and returns the map. */
|
|
119
|
+
export function countInto(map, key) {
|
|
120
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
121
|
+
return map;
|
|
122
|
+
}
|
|
123
|
+
/** Adds one audit trail change to the per-user activity map (keyed by username). */
|
|
124
|
+
export function trackUserActivity(map, row) {
|
|
125
|
+
let activity = map.get(row.Username);
|
|
126
|
+
if (!activity) {
|
|
127
|
+
activity = { fullName: row.UserFullName, count: 0, first: row.CreatedDate, last: row.CreatedDate, sections: new Map() };
|
|
128
|
+
map.set(row.Username, activity);
|
|
129
|
+
}
|
|
130
|
+
activity.count++;
|
|
131
|
+
if (row.CreatedDate < activity.first)
|
|
132
|
+
activity.first = row.CreatedDate;
|
|
133
|
+
if (row.CreatedDate > activity.last)
|
|
134
|
+
activity.last = row.CreatedDate;
|
|
135
|
+
countInto(activity.sections, row.Section);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Formats per-user activity as one line per user, most active first:
|
|
139
|
+
* " user@example.com (Full Name): 12 changes, 2024-05-01 → 2024-05-20 — Flows 5, Apex Class 4, …"
|
|
140
|
+
*/
|
|
141
|
+
export function formatUserActivity(map, top) {
|
|
142
|
+
const sorted = [...map.entries()].sort((a, b) => b[1].count - a[1].count);
|
|
143
|
+
const lines = sorted.slice(0, top).map(([username, a]) => {
|
|
144
|
+
const who = username ? `${username}${a.fullName ? ` (${a.fullName})` : ''}` : '(no user — automated/system changes)';
|
|
145
|
+
const range = `${a.first.slice(0, 10)} → ${a.last.slice(0, 10)}`;
|
|
146
|
+
const sections = [...a.sections.entries()]
|
|
147
|
+
.sort((x, y) => y[1] - x[1])
|
|
148
|
+
.slice(0, 3)
|
|
149
|
+
.map(([section, count]) => `${section || '(no section)'} ${count}`)
|
|
150
|
+
.join(', ');
|
|
151
|
+
return ` ${who}: ${a.count} change${a.count === 1 ? '' : 's'}, ${range} — ${sections}${a.sections.size > 3 ? ', …' : ''}`;
|
|
152
|
+
});
|
|
153
|
+
if (sorted.length > top)
|
|
154
|
+
lines.push(` … and ${sorted.length - top} more users`);
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
/** Formats the top N entries of a counter as " name: count" lines, highest first. */
|
|
158
|
+
export function formatTopCounts(map, top) {
|
|
159
|
+
const sorted = [...map.entries()].sort((a, b) => b[1] - a[1]);
|
|
160
|
+
const lines = sorted.slice(0, top).map(([key, count]) => ` ${key || '(blank)'}: ${count}`);
|
|
161
|
+
if (sorted.length > top)
|
|
162
|
+
lines.push(` … and ${sorted.length - top} more`);
|
|
163
|
+
return lines;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=auditTrailCsv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auditTrailCsv.js","sourceRoot":"","sources":["../../src/utils/auditTrailCsv.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/errors.d.ts
CHANGED
package/dist/utils/errors.js
CHANGED
|
@@ -37,4 +37,18 @@ export function toolResponse(text, isError = false) {
|
|
|
37
37
|
isError,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
// jsforce surfaces Salesforce API errors with `.name`/`.errorCode` set to the
|
|
41
|
+
// Salesforce error code (e.g. "INVALID_SESSION_ID"). That's distinct from this
|
|
42
|
+
// server's own session_id concept, but looks identical to a caller once wrapped
|
|
43
|
+
// in a generic "Tool execution failed" message — so rewrite it to point at the
|
|
44
|
+
// actual fix (re-run salesforce_login) instead of leaving the raw SF error.
|
|
45
|
+
export function formatToolError(error, sessionId) {
|
|
46
|
+
const code = error?.errorCode || error?.name;
|
|
47
|
+
if (code === 'INVALID_SESSION_ID') {
|
|
48
|
+
return sessionId
|
|
49
|
+
? `Your Salesforce session has expired. Call salesforce_login again to get a fresh session_id, then retry this tool with it.`
|
|
50
|
+
: `Salesforce session expired or invalid. Re-authenticate and retry.`;
|
|
51
|
+
}
|
|
52
|
+
return `Tool execution failed: ${error.message}`;
|
|
53
|
+
}
|
|
40
54
|
//# sourceMappingURL=errors.js.map
|
package/dist/utils/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,MAAW;IAC9C,IAAI,CAAC,MAAM;QAAE,OAAO,oCAAoC,CAAC;IAEzD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAE9B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChH,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1D,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IACxD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;QAC1C,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,MAAW;IAC9C,IAAI,CAAC,MAAM;QAAE,OAAO,oCAAoC,CAAC;IAEzD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAE9B,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACjC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,CAAC;iBAAM,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChH,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1D,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,GAAG;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IACxD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;QAC1C,OAAO;KACR,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,+EAA+E;AAC/E,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,KAAU,EAAE,SAAkB;IAC5D,MAAM,IAAI,GAAG,KAAK,EAAE,SAAS,IAAI,KAAK,EAAE,IAAI,CAAC;IAC7C,IAAI,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAClC,OAAO,SAAS;YACd,CAAC,CAAC,2HAA2H;YAC7H,CAAC,CAAC,mEAAmE,CAAC;IAC1E,CAAC;IACD,OAAO,0BAA0B,KAAK,CAAC,OAAO,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface SfCliOrgInfo {
|
|
2
|
+
instanceUrl: string;
|
|
3
|
+
username: string;
|
|
4
|
+
orgId: string;
|
|
5
|
+
/** null if the org authenticated but the alias could not be set. */
|
|
6
|
+
alias: string | null;
|
|
7
|
+
}
|
|
8
|
+
export type SfCliLoginState = {
|
|
9
|
+
status: 'pending';
|
|
10
|
+
loginUrl: string;
|
|
11
|
+
startedAt: number;
|
|
12
|
+
} | {
|
|
13
|
+
status: 'success';
|
|
14
|
+
loginUrl: string;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
org: SfCliOrgInfo;
|
|
17
|
+
} | {
|
|
18
|
+
status: 'error';
|
|
19
|
+
loginUrl: string;
|
|
20
|
+
startedAt: number;
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function getSfCliLoginState(sessionId: string): SfCliLoginState | null;
|
|
24
|
+
/**
|
|
25
|
+
* Builds the alias for an authenticated username, e.g. `sfcli-ada@example.com.dev`.
|
|
26
|
+
*
|
|
27
|
+
* The CLI stores aliases as plain JSON keys and validates nothing — `@`, `.` and even
|
|
28
|
+
* spaces are accepted. Only `=` is unusable, because `sf alias set` splits name=value on
|
|
29
|
+
* it; whitespace is stripped too so the alias stays usable as a bare CLI argument.
|
|
30
|
+
*/
|
|
31
|
+
export declare function aliasForUsername(username: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Spawns `sf org login web` and returns as soon as the browser has been launched.
|
|
34
|
+
*
|
|
35
|
+
* The login CANNOT be awaited inside the MCP request: a human browser login routinely
|
|
36
|
+
* outlasts the client's request timeout (~60s in Claude Desktop), and once the client
|
|
37
|
+
* abandons the request the login is orphaned — the CLI's loopback listener on port 1717
|
|
38
|
+
* dies and the Salesforce redirect lands on ERR_CONNECTION_REFUSED.
|
|
39
|
+
*
|
|
40
|
+
* So the child is spawned `detached` with no timeout, into its own process group, and
|
|
41
|
+
* outlives the request that started it. Completion is recorded in `loginState` and polled
|
|
42
|
+
* via the salesforce_cli_login_status tool.
|
|
43
|
+
*
|
|
44
|
+
* No `--alias` is passed: the username isn't known until the user has actually logged in,
|
|
45
|
+
* so the alias is set afterwards from the authenticated username. Aliasing by username
|
|
46
|
+
* keeps it stable and self-overwriting across sessions, rather than leaving one dead
|
|
47
|
+
* alias behind per login.
|
|
48
|
+
*
|
|
49
|
+
* The CLI writes auth to its global store in the user's home directory, never to the
|
|
50
|
+
* current directory, so this works from any folder — including an empty one with no
|
|
51
|
+
* sfdx-project.json. We still run it in a throwaway temp cwd so a stray project file
|
|
52
|
+
* can't influence the login and no local .sf/ directory is left in the user's folder.
|
|
53
|
+
*
|
|
54
|
+
* `--set-default` is deliberately omitted: the user's global target-org stays untouched.
|
|
55
|
+
*/
|
|
56
|
+
export declare function startSfCliWebLogin(loginUrl: string, sessionId: string): Promise<void>;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { ensureSfCli, runSfCli } from './sfCli.js';
|
|
6
|
+
import { setOAuthWebFlowToken } from './oauthWebFlow.js';
|
|
7
|
+
// In-memory, keyed by sessionId — same lifetime rules as the OAuth web-flow token store.
|
|
8
|
+
const loginState = new Map();
|
|
9
|
+
const ALIAS_PREFIX = 'sfcli-';
|
|
10
|
+
const CLI_STEP_TIMEOUT_MS = 60 * 1000;
|
|
11
|
+
export function getSfCliLoginState(sessionId) {
|
|
12
|
+
return loginState.get(sessionId) ?? null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds the alias for an authenticated username, e.g. `sfcli-ada@example.com.dev`.
|
|
16
|
+
*
|
|
17
|
+
* The CLI stores aliases as plain JSON keys and validates nothing — `@`, `.` and even
|
|
18
|
+
* spaces are accepted. Only `=` is unusable, because `sf alias set` splits name=value on
|
|
19
|
+
* it; whitespace is stripped too so the alias stays usable as a bare CLI argument.
|
|
20
|
+
*/
|
|
21
|
+
export function aliasForUsername(username) {
|
|
22
|
+
const sanitized = username.replace(/[=\s]/g, '');
|
|
23
|
+
return `${ALIAS_PREFIX}${sanitized}`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Spawns `sf org login web` and returns as soon as the browser has been launched.
|
|
27
|
+
*
|
|
28
|
+
* The login CANNOT be awaited inside the MCP request: a human browser login routinely
|
|
29
|
+
* outlasts the client's request timeout (~60s in Claude Desktop), and once the client
|
|
30
|
+
* abandons the request the login is orphaned — the CLI's loopback listener on port 1717
|
|
31
|
+
* dies and the Salesforce redirect lands on ERR_CONNECTION_REFUSED.
|
|
32
|
+
*
|
|
33
|
+
* So the child is spawned `detached` with no timeout, into its own process group, and
|
|
34
|
+
* outlives the request that started it. Completion is recorded in `loginState` and polled
|
|
35
|
+
* via the salesforce_cli_login_status tool.
|
|
36
|
+
*
|
|
37
|
+
* No `--alias` is passed: the username isn't known until the user has actually logged in,
|
|
38
|
+
* so the alias is set afterwards from the authenticated username. Aliasing by username
|
|
39
|
+
* keeps it stable and self-overwriting across sessions, rather than leaving one dead
|
|
40
|
+
* alias behind per login.
|
|
41
|
+
*
|
|
42
|
+
* The CLI writes auth to its global store in the user's home directory, never to the
|
|
43
|
+
* current directory, so this works from any folder — including an empty one with no
|
|
44
|
+
* sfdx-project.json. We still run it in a throwaway temp cwd so a stray project file
|
|
45
|
+
* can't influence the login and no local .sf/ directory is left in the user's folder.
|
|
46
|
+
*
|
|
47
|
+
* `--set-default` is deliberately omitted: the user's global target-org stays untouched.
|
|
48
|
+
*/
|
|
49
|
+
export async function startSfCliWebLogin(loginUrl, sessionId) {
|
|
50
|
+
await ensureSfCli();
|
|
51
|
+
const existing = loginState.get(sessionId);
|
|
52
|
+
if (existing?.status === 'pending') {
|
|
53
|
+
throw new Error('A browser login is already in progress for this session. ' +
|
|
54
|
+
'Complete it in the browser, then call salesforce_cli_login_status.');
|
|
55
|
+
}
|
|
56
|
+
const cwd = mkdtempSync(join(tmpdir(), 'sf-mcp-login-'));
|
|
57
|
+
const startedAt = Date.now();
|
|
58
|
+
const args = ['org', 'login', 'web', '--instance-url', loginUrl, '--json'];
|
|
59
|
+
console.error(`[sf-cli-login] spawning: sf ${args.join(' ')}`);
|
|
60
|
+
const child = spawn('sf', args, {
|
|
61
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
62
|
+
cwd,
|
|
63
|
+
detached: true, // own process group — survives the abandoned MCP request
|
|
64
|
+
});
|
|
65
|
+
loginState.set(sessionId, { status: 'pending', loginUrl, startedAt });
|
|
66
|
+
let stdout = '';
|
|
67
|
+
let stderr = '';
|
|
68
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
69
|
+
child.stderr.on('data', (d) => {
|
|
70
|
+
const chunk = d.toString();
|
|
71
|
+
stderr += chunk;
|
|
72
|
+
process.stderr.write(chunk);
|
|
73
|
+
});
|
|
74
|
+
const fail = (error) => {
|
|
75
|
+
console.error(`[sf-cli-login] failed: ${error}`);
|
|
76
|
+
loginState.set(sessionId, { status: 'error', loginUrl, startedAt, error });
|
|
77
|
+
};
|
|
78
|
+
const cleanup = () => {
|
|
79
|
+
try {
|
|
80
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error(`[sf-cli-login] failed to remove temp dir ${cwd}: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
child.on('error', (err) => {
|
|
87
|
+
cleanup();
|
|
88
|
+
fail(err.code === 'ENOENT'
|
|
89
|
+
? 'Salesforce CLI (sf) is not installed or not on PATH. Install it from https://developer.salesforce.com/tools/salesforcecli'
|
|
90
|
+
: err.message);
|
|
91
|
+
});
|
|
92
|
+
child.on('close', async (code) => {
|
|
93
|
+
try {
|
|
94
|
+
if (code !== 0) {
|
|
95
|
+
let msg = stderr.trim() || 'Unknown error';
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(stdout);
|
|
98
|
+
if (parsed?.message)
|
|
99
|
+
msg = parsed.message;
|
|
100
|
+
}
|
|
101
|
+
catch { /* stdout wasn't JSON */ }
|
|
102
|
+
fail(`sf org login web exited with code ${code}: ${msg}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const org = parseLoginPayload(stdout) ?? await readOrgFromCli(cwd);
|
|
106
|
+
setOAuthWebFlowToken(org.accessToken, org.instanceUrl, sessionId);
|
|
107
|
+
const alias = await setAlias(org.username, cwd);
|
|
108
|
+
console.error(`[sf-cli-login] authenticated ${org.username} (${org.instanceUrl}) alias=${alias ?? '(none)'}`);
|
|
109
|
+
loginState.set(sessionId, {
|
|
110
|
+
status: 'success',
|
|
111
|
+
loginUrl,
|
|
112
|
+
startedAt,
|
|
113
|
+
org: {
|
|
114
|
+
instanceUrl: org.instanceUrl,
|
|
115
|
+
username: org.username,
|
|
116
|
+
orgId: org.orgId,
|
|
117
|
+
alias,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
fail(err.message);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
cleanup();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// Don't hold the event loop open on the child's account.
|
|
129
|
+
child.unref();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Aliases the freshly authenticated org by username. Never throws: the session is already
|
|
133
|
+
* usable at this point, so a failed alias is a cosmetic problem, not a failed login.
|
|
134
|
+
*/
|
|
135
|
+
async function setAlias(username, cwd) {
|
|
136
|
+
const alias = aliasForUsername(username);
|
|
137
|
+
try {
|
|
138
|
+
const result = await runSfCli(['alias', 'set', `${alias}=${username}`, '--json'], { cwd, timeout: CLI_STEP_TIMEOUT_MS });
|
|
139
|
+
if (result.exitCode !== 0) {
|
|
140
|
+
const msg = result.jsonOutput?.message || result.stderr.trim() || 'Unknown error';
|
|
141
|
+
console.error(`[sf-cli-login] could not set alias ${alias}: ${msg}`);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return alias;
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error(`[sf-cli-login] could not set alias ${alias}: ${err.message}`);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* `sf org login web --json` already returns the token on stdout, so in the normal case no
|
|
153
|
+
* second CLI call is needed. Returns null if the payload isn't the shape we expect, and the
|
|
154
|
+
* caller falls back to reading the org explicitly.
|
|
155
|
+
*/
|
|
156
|
+
function parseLoginPayload(stdout) {
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(stdout);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const r = parsed?.result;
|
|
165
|
+
if (!r?.accessToken || !r?.instanceUrl || !r?.username)
|
|
166
|
+
return null;
|
|
167
|
+
return {
|
|
168
|
+
accessToken: r.accessToken,
|
|
169
|
+
instanceUrl: r.instanceUrl,
|
|
170
|
+
username: r.username,
|
|
171
|
+
orgId: r.orgId ?? '(unknown)',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Fallback when the login payload didn't carry the token. Without an alias the org has to
|
|
176
|
+
* be addressed by the username the CLI just recorded as most recently authenticated.
|
|
177
|
+
*/
|
|
178
|
+
async function readOrgFromCli(cwd) {
|
|
179
|
+
const list = await runSfCli(['org', 'list', '--json'], { cwd, timeout: CLI_STEP_TIMEOUT_MS });
|
|
180
|
+
if (list.exitCode !== 0) {
|
|
181
|
+
const msg = list.jsonOutput?.message || list.stderr.trim() || 'Unknown error';
|
|
182
|
+
throw new Error(`Could not list authenticated orgs (exit ${list.exitCode}): ${msg}`);
|
|
183
|
+
}
|
|
184
|
+
const orgs = [
|
|
185
|
+
...(list.jsonOutput?.result?.nonScratchOrgs ?? []),
|
|
186
|
+
...(list.jsonOutput?.result?.scratchOrgs ?? []),
|
|
187
|
+
];
|
|
188
|
+
const username = orgs[orgs.length - 1]?.username;
|
|
189
|
+
if (!username) {
|
|
190
|
+
throw new Error('The Salesforce CLI reported no authenticated orgs after login');
|
|
191
|
+
}
|
|
192
|
+
const result = await runSfCli(['org', 'display', '--target-org', username, '--json'], { cwd, timeout: CLI_STEP_TIMEOUT_MS });
|
|
193
|
+
if (result.exitCode !== 0) {
|
|
194
|
+
const msg = result.jsonOutput?.message || result.stderr.trim() || 'Unknown error';
|
|
195
|
+
throw new Error(`Could not read the newly authenticated org (exit ${result.exitCode}): ${msg}`);
|
|
196
|
+
}
|
|
197
|
+
const r = result.jsonOutput?.result ?? {};
|
|
198
|
+
if (!r.accessToken || !r.instanceUrl) {
|
|
199
|
+
throw new Error('The Salesforce CLI returned no accessToken or instanceUrl for the new org');
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
accessToken: r.accessToken,
|
|
203
|
+
instanceUrl: r.instanceUrl,
|
|
204
|
+
username: r.username ?? username,
|
|
205
|
+
orgId: r.id ?? '(unknown)',
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=sfCliWebLogin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfCliWebLogin.js","sourceRoot":"","sources":["../../src/utils/sfCliWebLogin.ts"],"names":[],"mappings":"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"}
|