farseer-cli 1.0.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/LICENSE +15 -0
- package/README.md +741 -0
- package/dist/commands/app.d.ts +2 -0
- package/dist/commands/app.js +349 -0
- package/dist/commands/app.js.map +7 -0
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +111 -0
- package/dist/commands/apps.js.map +7 -0
- package/dist/commands/checkout.d.ts +2 -0
- package/dist/commands/checkout.js +166 -0
- package/dist/commands/checkout.js.map +7 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +139 -0
- package/dist/commands/config.js.map +7 -0
- package/dist/commands/diff.d.ts +2 -0
- package/dist/commands/diff.js +183 -0
- package/dist/commands/diff.js.map +7 -0
- package/dist/commands/files.js +99 -0
- package/dist/commands/files.js.map +7 -0
- package/dist/commands/install.d.ts +2 -0
- package/dist/commands/install.js +79 -0
- package/dist/commands/install.js.map +7 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +92 -0
- package/dist/commands/list.js.map +7 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +134 -0
- package/dist/commands/login.js.map +7 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +59 -0
- package/dist/commands/logout.js.map +7 -0
- package/dist/commands/mcp-server.d.ts +8 -0
- package/dist/commands/mcp-server.js +41 -0
- package/dist/commands/mcp-server.js.map +7 -0
- package/dist/commands/model.d.ts +2 -0
- package/dist/commands/model.js +189 -0
- package/dist/commands/model.js.map +7 -0
- package/dist/commands/pull.d.ts +2 -0
- package/dist/commands/pull.js +287 -0
- package/dist/commands/pull.js.map +7 -0
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +251 -0
- package/dist/commands/push.js.map +7 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +246 -0
- package/dist/commands/run.js.map +7 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +137 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +145 -0
- package/dist/commands/status.js.map +7 -0
- package/dist/commands/unsetup.d.ts +2 -0
- package/dist/commands/unsetup.js +122 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +63 -0
- package/dist/commands/whoami.js.map +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +135 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +35 -0
- package/dist/mcp/index.js.map +7 -0
- package/dist/mcp/prompts/workflows.d.ts +7 -0
- package/dist/mcp/prompts/workflows.js +374 -0
- package/dist/mcp/prompts/workflows.js.map +7 -0
- package/dist/mcp/resources/documentation.d.ts +8 -0
- package/dist/mcp/resources/documentation.js +167 -0
- package/dist/mcp/resources/documentation.js.map +7 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +49 -0
- package/dist/mcp/server.js.map +7 -0
- package/dist/mcp/tools/appTools.d.ts +7 -0
- package/dist/mcp/tools/appTools.js +377 -0
- package/dist/mcp/tools/appTools.js.map +7 -0
- package/dist/mcp/tools/authTools.d.ts +7 -0
- package/dist/mcp/tools/authTools.js +158 -0
- package/dist/mcp/tools/authTools.js.map +7 -0
- package/dist/mcp/tools/modelTools.d.ts +7 -0
- package/dist/mcp/tools/modelTools.js +331 -0
- package/dist/mcp/tools/modelTools.js.map +7 -0
- package/dist/mcp/tools/runTools.d.ts +7 -0
- package/dist/mcp/tools/runTools.js +231 -0
- package/dist/mcp/tools/runTools.js.map +7 -0
- package/dist/mcp/tools/syncTools.d.ts +7 -0
- package/dist/mcp/tools/syncTools.js +382 -0
- package/dist/mcp/tools/syncTools.js.map +7 -0
- package/dist/mcp/utils/helpers.d.ts +69 -0
- package/dist/mcp/utils/helpers.js +113 -0
- package/dist/mcp/utils/helpers.js.map +7 -0
- package/dist/services/appSyncService.d.ts +75 -0
- package/dist/services/appSyncService.js +370 -0
- package/dist/services/appSyncService.js.map +7 -0
- package/dist/services/configService.d.ts +39 -0
- package/dist/services/configService.js +196 -0
- package/dist/services/configService.js.map +7 -0
- package/dist/services/farseerApi.d.ts +166 -0
- package/dist/services/farseerApi.js +378 -0
- package/dist/services/farseerApi.js.map +7 -0
- package/dist/services/farseerFactory.d.ts +88 -0
- package/dist/services/farseerFactory.js +179 -0
- package/dist/services/farseerFactory.js.map +7 -0
- package/dist/services/farseerService.d.ts +96 -0
- package/dist/services/farseerService.js +614 -0
- package/dist/services/farseerService.js.map +7 -0
- package/dist/services/gitService.d.ts +31 -0
- package/dist/services/gitService.js +134 -0
- package/dist/services/gitService.js.map +7 -0
- package/dist/services/syncService.d.ts +44 -0
- package/dist/services/syncService.js +320 -0
- package/dist/services/syncService.js.map +7 -0
- package/dist/utils/constants.d.ts +7 -0
- package/dist/utils/constants.js +46 -0
- package/dist/utils/constants.js.map +7 -0
- package/dist/utils/helpers.d.ts +69 -0
- package/dist/utils/helpers.js +413 -0
- package/dist/utils/helpers.js.map +7 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +76 -0
- package/dist/utils/logger.js.map +7 -0
- package/package.json +62 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var runTools_exports = {};
|
|
29
|
+
__export(runTools_exports, {
|
|
30
|
+
registerRunTools: () => registerRunTools
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(runTools_exports);
|
|
33
|
+
var import_zod = require("zod");
|
|
34
|
+
var import_child_process = require("child_process");
|
|
35
|
+
var fs = __toESM(require("fs"));
|
|
36
|
+
var path = __toESM(require("path"));
|
|
37
|
+
var import_farseerFactory = require("../../services/farseerFactory");
|
|
38
|
+
var import_configService = require("../../services/configService");
|
|
39
|
+
var import_farseerService = require("../../services/farseerService");
|
|
40
|
+
var import_helpers = require("../../utils/helpers");
|
|
41
|
+
var import_helpers2 = require("../utils/helpers");
|
|
42
|
+
async function getRunCredentials(tenant, tenantId) {
|
|
43
|
+
const credential = (0, import_configService.getCredential)(tenant);
|
|
44
|
+
if (credential) {
|
|
45
|
+
return {
|
|
46
|
+
tenantId: credential.tenantId,
|
|
47
|
+
apiKey: credential.apiKey,
|
|
48
|
+
basePath: credential.basePath
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const auth = (0, import_configService.getUserAuth)();
|
|
52
|
+
if (!auth) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!(0, import_configService.isUserAuthValid)()) {
|
|
56
|
+
const refreshed = await (0, import_farseerService.refreshAccessToken)(auth.refreshToken, auth.realm || "master");
|
|
57
|
+
if (!refreshed) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
(0, import_configService.setUserAuth)({
|
|
61
|
+
accessToken: refreshed.accessToken,
|
|
62
|
+
refreshToken: refreshed.refreshToken,
|
|
63
|
+
expiresAt: new Date(Date.now() + refreshed.expiresIn * 1e3).toISOString(),
|
|
64
|
+
realm: auth.realm
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const updatedAuth = (0, import_configService.getUserAuth)();
|
|
68
|
+
if (!updatedAuth) return null;
|
|
69
|
+
return {
|
|
70
|
+
tenantId: tenantId || tenant,
|
|
71
|
+
accessToken: updatedAuth.accessToken,
|
|
72
|
+
basePath: (0, import_configService.generateBasePath)(tenant)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const runAppSchema = {
|
|
76
|
+
tenant: import_zod.z.string().optional().describe("Tenant name"),
|
|
77
|
+
appName: import_zod.z.string().describe("Name of the app to run"),
|
|
78
|
+
argumentOverrides: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe("Override argument values (Name: Value pairs)")
|
|
79
|
+
};
|
|
80
|
+
function registerRunTools(server) {
|
|
81
|
+
server.tool(
|
|
82
|
+
"farseer_run_app",
|
|
83
|
+
`Run a Farseer app locally using tsx (TypeScript executor).
|
|
84
|
+
|
|
85
|
+
How it works:
|
|
86
|
+
1. Looks up app configuration to find the entrypoint script
|
|
87
|
+
2. Injects credentials via environment variables:
|
|
88
|
+
- TENANT_ID: The tenant identifier
|
|
89
|
+
- FARSEER_URL: Base API URL
|
|
90
|
+
- FARSEER_API_KEY or FARSEER_ACCESS_TOKEN: Authentication
|
|
91
|
+
3. Passes app arguments via command line (in order defined by app config)
|
|
92
|
+
4. Executes the script using npx tsx
|
|
93
|
+
|
|
94
|
+
Requirements:
|
|
95
|
+
- Scripts must be pulled locally first (farseer_pull)
|
|
96
|
+
- Dependencies must be installed (run "farseer install" in terminal)
|
|
97
|
+
|
|
98
|
+
Arguments can be overridden using the argumentOverrides parameter.
|
|
99
|
+
Arguments not overridden use their default values from app config.
|
|
100
|
+
|
|
101
|
+
If no tenant is specified, uses the currently checked-out tenant.`,
|
|
102
|
+
runAppSchema,
|
|
103
|
+
async (params) => {
|
|
104
|
+
const { tenant: tenantArg, appName, argumentOverrides } = params;
|
|
105
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
106
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
107
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
108
|
+
if (!tenant || !organisation) {
|
|
109
|
+
return (0, import_helpers2.tenantRequiredResponse)();
|
|
110
|
+
}
|
|
111
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenant);
|
|
112
|
+
if (!clientResult) {
|
|
113
|
+
return (0, import_helpers2.authRequiredResponse)(tenant);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const app = await clientResult.client.getAppByName(appName);
|
|
117
|
+
if (!app) {
|
|
118
|
+
return (0, import_helpers2.errorResponse)(`App "${appName}" not found`, "NOT_FOUND");
|
|
119
|
+
}
|
|
120
|
+
if (!app.mainScriptFileId || app.scriptFiles.length === 0) {
|
|
121
|
+
return (0, import_helpers2.errorResponse)(
|
|
122
|
+
`App "${appName}" has no scripts configured. Configure it with farseer_configure_app.`,
|
|
123
|
+
"VALIDATION_ERROR"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const mainScript = app.scriptFiles.find((s) => s.id === app.mainScriptFileId);
|
|
127
|
+
if (!mainScript) {
|
|
128
|
+
return (0, import_helpers2.errorResponse)(`Entrypoint script not found for app "${appName}"`, "NOT_FOUND");
|
|
129
|
+
}
|
|
130
|
+
const credentials = await getRunCredentials(organisation, tenant);
|
|
131
|
+
if (!credentials) {
|
|
132
|
+
return (0, import_helpers2.authRequiredResponse)(tenant);
|
|
133
|
+
}
|
|
134
|
+
const tenantDir = (0, import_helpers.getTenantDir)(organisation);
|
|
135
|
+
const srcDir = (0, import_helpers.getTenantSrcDir)(organisation);
|
|
136
|
+
const scriptPath = path.resolve(srcDir, mainScript.name);
|
|
137
|
+
if (!fs.existsSync(scriptPath)) {
|
|
138
|
+
return (0, import_helpers2.errorResponse)(
|
|
139
|
+
`Script not found locally: ${mainScript.name}. Run farseer_pull first.`,
|
|
140
|
+
"NOT_FOUND"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const nodeModulesPath = path.join(tenantDir, "node_modules");
|
|
144
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
145
|
+
return (0, import_helpers2.errorResponse)(
|
|
146
|
+
`Dependencies not installed. Run "farseer install ${organisation}" in terminal first.`,
|
|
147
|
+
"VALIDATION_ERROR"
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const appArgs = {};
|
|
151
|
+
for (const arg of app.expectedArguments) {
|
|
152
|
+
appArgs[arg.name] = arg.defaultValue;
|
|
153
|
+
}
|
|
154
|
+
if (argumentOverrides) {
|
|
155
|
+
for (const [name, value] of Object.entries(argumentOverrides)) {
|
|
156
|
+
appArgs[name] = value;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const envVars = {
|
|
160
|
+
...process.env,
|
|
161
|
+
TENANT_ID: credentials.tenantId,
|
|
162
|
+
FARSEER_URL: credentials.basePath
|
|
163
|
+
};
|
|
164
|
+
if (credentials.apiKey) {
|
|
165
|
+
envVars.FARSEER_API_KEY = credentials.apiKey;
|
|
166
|
+
}
|
|
167
|
+
if (credentials.accessToken) {
|
|
168
|
+
envVars.FARSEER_ACCESS_TOKEN = credentials.accessToken;
|
|
169
|
+
}
|
|
170
|
+
const cmdArgs = [];
|
|
171
|
+
for (const arg of app.expectedArguments) {
|
|
172
|
+
cmdArgs.push(appArgs[arg.name] ?? arg.defaultValue);
|
|
173
|
+
}
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
let stdout = "";
|
|
176
|
+
let stderr = "";
|
|
177
|
+
const child = (0, import_child_process.spawn)("npx", ["tsx", `files/Scripts/${mainScript.name}`, ...cmdArgs], {
|
|
178
|
+
cwd: tenantDir,
|
|
179
|
+
shell: true,
|
|
180
|
+
env: envVars
|
|
181
|
+
});
|
|
182
|
+
child.stdout?.on("data", (data) => {
|
|
183
|
+
stdout += data.toString();
|
|
184
|
+
});
|
|
185
|
+
child.stderr?.on("data", (data) => {
|
|
186
|
+
stderr += data.toString();
|
|
187
|
+
});
|
|
188
|
+
child.on("close", (code) => {
|
|
189
|
+
resolve(
|
|
190
|
+
(0, import_helpers2.successResponse)({
|
|
191
|
+
tenant,
|
|
192
|
+
app: appName,
|
|
193
|
+
script: mainScript.name,
|
|
194
|
+
arguments: appArgs,
|
|
195
|
+
exitCode: code ?? 0,
|
|
196
|
+
stdout: stdout.slice(0, 1e4),
|
|
197
|
+
// Limit output size
|
|
198
|
+
stderr: stderr.slice(0, 5e3),
|
|
199
|
+
truncated: stdout.length > 1e4 || stderr.length > 5e3
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
child.on("error", (err) => {
|
|
204
|
+
resolve(
|
|
205
|
+
(0, import_helpers2.errorResponse)(
|
|
206
|
+
`Failed to run script: ${err.message}. Make sure tsx is installed: npm install -g tsx`,
|
|
207
|
+
"UNKNOWN"
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
child.kill();
|
|
213
|
+
resolve(
|
|
214
|
+
(0, import_helpers2.errorResponse)("Script execution timed out after 5 minutes", "UNKNOWN")
|
|
215
|
+
);
|
|
216
|
+
}, 5 * 60 * 1e3);
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return (0, import_helpers2.errorResponse)(
|
|
220
|
+
`Run app failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
221
|
+
"NETWORK_ERROR"
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
228
|
+
0 && (module.exports = {
|
|
229
|
+
registerRunTools
|
|
230
|
+
});
|
|
231
|
+
//# sourceMappingURL=runTools.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/mcp/tools/runTools.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * App Execution Tools\n *\n * Tools for running Farseer apps locally.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { spawn } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { getFarseerClientWithFallback } from '../../services/farseerFactory';\nimport {\n getCredential,\n getUserAuth,\n isUserAuthValid,\n setUserAuth,\n generateBasePath,\n getCurrentCheckout,\n} from '../../services/configService';\nimport { refreshAccessToken } from '../../services/farseerService';\nimport { getTenantDir, getTenantSrcDir } from '../../utils/helpers';\nimport {\n successResponse,\n errorResponse,\n authRequiredResponse,\n tenantRequiredResponse,\n} from '../utils/helpers';\n\ninterface RunCredentials {\n tenantId: string;\n apiKey?: string;\n accessToken?: string;\n basePath: string;\n}\n\nasync function getRunCredentials(tenant: string, tenantId?: string): Promise<RunCredentials | null> {\n // First try API key config\n const credential = getCredential(tenant);\n if (credential) {\n return {\n tenantId: credential.tenantId,\n apiKey: credential.apiKey,\n basePath: credential.basePath,\n };\n }\n\n // Try user auth (browser login)\n const auth = getUserAuth();\n if (!auth) {\n return null;\n }\n\n // Check if token needs refresh\n if (!isUserAuthValid()) {\n const refreshed = await refreshAccessToken(auth.refreshToken, auth.realm || 'master');\n if (!refreshed) {\n return null;\n }\n // Save refreshed token\n setUserAuth({\n accessToken: refreshed.accessToken,\n refreshToken: refreshed.refreshToken,\n expiresAt: new Date(Date.now() + refreshed.expiresIn * 1000).toISOString(),\n realm: auth.realm,\n });\n }\n\n const updatedAuth = getUserAuth();\n if (!updatedAuth) return null;\n\n return {\n tenantId: tenantId || tenant,\n accessToken: updatedAuth.accessToken,\n basePath: generateBasePath(tenant),\n };\n}\n\n// Define schemas outside of tool registration to avoid deep type instantiation\nconst runAppSchema = {\n tenant: z.string().optional().describe('Tenant name'),\n appName: z.string().describe('Name of the app to run'),\n argumentOverrides: z\n .record(z.string(), z.string())\n .optional()\n .describe('Override argument values (Name: Value pairs)'),\n};\n\nexport function registerRunTools(server: McpServer): void {\n // farseer_run_app - Run an app locally\n server.tool(\n 'farseer_run_app',\n `Run a Farseer app locally using tsx (TypeScript executor).\n\nHow it works:\n1. Looks up app configuration to find the entrypoint script\n2. Injects credentials via environment variables:\n - TENANT_ID: The tenant identifier\n - FARSEER_URL: Base API URL\n - FARSEER_API_KEY or FARSEER_ACCESS_TOKEN: Authentication\n3. Passes app arguments via command line (in order defined by app config)\n4. Executes the script using npx tsx\n\nRequirements:\n- Scripts must be pulled locally first (farseer_pull)\n- Dependencies must be installed (run \"farseer install\" in terminal)\n\nArguments can be overridden using the argumentOverrides parameter.\nArguments not overridden use their default values from app config.\n\nIf no tenant is specified, uses the currently checked-out tenant.`,\n runAppSchema,\n async (params: { tenant?: string; appName: string; argumentOverrides?: Record<string, string> }) => {\n const { tenant: tenantArg, appName, argumentOverrides } = params;\n const checkout = getCurrentCheckout();\n const tenant = tenantArg || checkout?.tenant;\n const organisation = tenantArg || checkout?.organisation;\n\n if (!tenant || !organisation) {\n return tenantRequiredResponse();\n }\n\n const clientResult = await getFarseerClientWithFallback(organisation, tenant);\n if (!clientResult) {\n return authRequiredResponse(tenant);\n }\n\n try {\n // Get app details\n const app = await clientResult.client.getAppByName(appName);\n if (!app) {\n return errorResponse(`App \"${appName}\" not found`, 'NOT_FOUND');\n }\n\n if (!app.mainScriptFileId || app.scriptFiles.length === 0) {\n return errorResponse(\n `App \"${appName}\" has no scripts configured. Configure it with farseer_configure_app.`,\n 'VALIDATION_ERROR'\n );\n }\n\n // Find entrypoint script\n const mainScript = app.scriptFiles.find((s) => s.id === app.mainScriptFileId);\n if (!mainScript) {\n return errorResponse(`Entrypoint script not found for app \"${appName}\"`, 'NOT_FOUND');\n }\n\n // Get run credentials\n const credentials = await getRunCredentials(organisation, tenant);\n if (!credentials) {\n return authRequiredResponse(tenant);\n }\n\n const tenantDir = getTenantDir(organisation);\n const srcDir = getTenantSrcDir(organisation);\n const scriptPath = path.resolve(srcDir, mainScript.name);\n\n // Check if script exists locally\n if (!fs.existsSync(scriptPath)) {\n return errorResponse(\n `Script not found locally: ${mainScript.name}. Run farseer_pull first.`,\n 'NOT_FOUND'\n );\n }\n\n // Check if node_modules exists\n const nodeModulesPath = path.join(tenantDir, 'node_modules');\n if (!fs.existsSync(nodeModulesPath)) {\n return errorResponse(\n `Dependencies not installed. Run \"farseer install ${organisation}\" in terminal first.`,\n 'VALIDATION_ERROR'\n );\n }\n\n // Build arguments\n const appArgs: Record<string, string> = {};\n\n // Start with defaults\n for (const arg of app.expectedArguments) {\n appArgs[arg.name] = arg.defaultValue;\n }\n\n // Apply overrides\n if (argumentOverrides) {\n for (const [name, value] of Object.entries(argumentOverrides)) {\n appArgs[name] = value;\n }\n }\n\n // Build environment\n const envVars: Record<string, string> = {\n ...(process.env as Record<string, string>),\n TENANT_ID: credentials.tenantId,\n FARSEER_URL: credentials.basePath,\n };\n\n if (credentials.apiKey) {\n envVars.FARSEER_API_KEY = credentials.apiKey;\n }\n if (credentials.accessToken) {\n envVars.FARSEER_ACCESS_TOKEN = credentials.accessToken;\n }\n\n // Build command line arguments (in order)\n const cmdArgs: string[] = [];\n for (const arg of app.expectedArguments) {\n cmdArgs.push(appArgs[arg.name] ?? arg.defaultValue);\n }\n\n // Run the script\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n\n const child = spawn('npx', ['tsx', `files/Scripts/${mainScript.name}`, ...cmdArgs], {\n cwd: tenantDir,\n shell: true,\n env: envVars,\n });\n\n child.stdout?.on('data', (data: Buffer) => {\n stdout += data.toString();\n });\n\n child.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n child.on('close', (code) => {\n resolve(\n successResponse({\n tenant,\n app: appName,\n script: mainScript.name,\n arguments: appArgs,\n exitCode: code ?? 0,\n stdout: stdout.slice(0, 10000), // Limit output size\n stderr: stderr.slice(0, 5000),\n truncated: stdout.length > 10000 || stderr.length > 5000,\n })\n );\n });\n\n child.on('error', (err) => {\n resolve(\n errorResponse(\n `Failed to run script: ${err.message}. Make sure tsx is installed: npm install -g tsx`,\n 'UNKNOWN'\n )\n );\n });\n\n // Timeout after 5 minutes\n setTimeout(() => {\n child.kill();\n resolve(\n errorResponse('Script execution timed out after 5 minutes', 'UNKNOWN')\n );\n }, 5 * 60 * 1000);\n });\n } catch (error) {\n return errorResponse(\n `Run app failed: ${error instanceof Error ? error.message : 'Unknown error'}`,\n 'NETWORK_ERROR'\n );\n }\n }\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_helpers"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var syncTools_exports = {};
|
|
19
|
+
__export(syncTools_exports, {
|
|
20
|
+
registerSyncTools: () => registerSyncTools
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(syncTools_exports);
|
|
23
|
+
var import_zod = require("zod");
|
|
24
|
+
var import_diff = require("diff");
|
|
25
|
+
var import_syncService = require("../../services/syncService");
|
|
26
|
+
var import_appSyncService = require("../../services/appSyncService");
|
|
27
|
+
var import_farseerFactory = require("../../services/farseerFactory");
|
|
28
|
+
var import_configService = require("../../services/configService");
|
|
29
|
+
var import_helpers = require("../utils/helpers");
|
|
30
|
+
const pullSchema = {
|
|
31
|
+
tenant: import_zod.z.string().optional().describe("Tenant name. Uses checked-out tenant if omitted."),
|
|
32
|
+
tenantId: import_zod.z.string().optional().describe("Override X-TENANT-ID header if different from tenant name"),
|
|
33
|
+
all: import_zod.z.boolean().optional().describe("Download all files, not just scripts (.ts, .js)")
|
|
34
|
+
};
|
|
35
|
+
const pushSchema = {
|
|
36
|
+
tenant: import_zod.z.string().optional().describe("Tenant name. Uses checked-out tenant if omitted."),
|
|
37
|
+
tenantId: import_zod.z.string().optional().describe("Override X-TENANT-ID header if different from tenant name"),
|
|
38
|
+
force: import_zod.z.boolean().optional().describe("Skip remote changes check (may overwrite remote changes)")
|
|
39
|
+
};
|
|
40
|
+
const statusSchema = {
|
|
41
|
+
tenant: import_zod.z.string().optional().describe("Tenant name. Uses checked-out tenant if omitted."),
|
|
42
|
+
tenantId: import_zod.z.string().optional().describe("Override X-TENANT-ID header"),
|
|
43
|
+
all: import_zod.z.boolean().optional().describe("Show all files, not just scripts")
|
|
44
|
+
};
|
|
45
|
+
const diffSchema = {
|
|
46
|
+
tenant: import_zod.z.string().optional().describe("Tenant name"),
|
|
47
|
+
file: import_zod.z.string().optional().describe("Specific file path to diff. If omitted, shows all differences."),
|
|
48
|
+
showRemote: import_zod.z.boolean().optional().describe("Show only the remote content instead of diff")
|
|
49
|
+
};
|
|
50
|
+
const listFilesSchema = {
|
|
51
|
+
tenant: import_zod.z.string().optional().describe("Tenant name"),
|
|
52
|
+
all: import_zod.z.boolean().optional().describe("Show all files, not just scripts")
|
|
53
|
+
};
|
|
54
|
+
function registerSyncTools(server) {
|
|
55
|
+
server.tool(
|
|
56
|
+
"farseer_pull",
|
|
57
|
+
`Pull (download) files and app configurations from a Farseer instance to the local repository.
|
|
58
|
+
|
|
59
|
+
What gets pulled:
|
|
60
|
+
- TypeScript/JavaScript scripts from Files/ folder (searched recursively)
|
|
61
|
+
- App configurations (saved as JSON in apps/ folder)
|
|
62
|
+
|
|
63
|
+
Files are saved to: apps/<tenant>/files/
|
|
64
|
+
Apps are saved to: apps/<tenant>/apps/
|
|
65
|
+
|
|
66
|
+
By default only script files (.ts, .js, .mjs, .cjs) are pulled.
|
|
67
|
+
Use all=true to pull all files including images, data files, etc.
|
|
68
|
+
|
|
69
|
+
If no tenant is specified, uses the currently checked-out tenant.`,
|
|
70
|
+
pullSchema,
|
|
71
|
+
async (params) => {
|
|
72
|
+
const { tenant: tenantArg, tenantId, all } = params;
|
|
73
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
74
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
75
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
76
|
+
if (!tenant || !organisation) {
|
|
77
|
+
return (0, import_helpers.tenantRequiredResponse)();
|
|
78
|
+
}
|
|
79
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenantId || tenant);
|
|
80
|
+
if (!clientResult) {
|
|
81
|
+
return (0, import_helpers.authRequiredResponse)(tenant);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const syncService = new import_syncService.SyncService(organisation, clientResult.client);
|
|
85
|
+
const fileResult = await syncService.pull({ all });
|
|
86
|
+
let appResult = null;
|
|
87
|
+
if (clientResult.authType === "jwt") {
|
|
88
|
+
const appSyncService = new import_appSyncService.AppSyncService(organisation, clientResult.client);
|
|
89
|
+
appResult = await appSyncService.pull();
|
|
90
|
+
}
|
|
91
|
+
return (0, import_helpers.successResponse)({
|
|
92
|
+
tenant,
|
|
93
|
+
files: {
|
|
94
|
+
downloaded: fileResult.downloaded,
|
|
95
|
+
deleted: fileResult.deleted,
|
|
96
|
+
unchanged: fileResult.unchanged.length
|
|
97
|
+
},
|
|
98
|
+
apps: appResult ? {
|
|
99
|
+
created: appResult.created,
|
|
100
|
+
updated: appResult.updated,
|
|
101
|
+
deleted: appResult.deleted,
|
|
102
|
+
unchanged: appResult.unchanged.length
|
|
103
|
+
} : { note: "App sync requires browser login (JWT)" },
|
|
104
|
+
message: `Pulled ${fileResult.downloaded.length} files${appResult ? ` and ${appResult.created.length + appResult.updated.length} apps` : ""}`
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return (0, import_helpers.errorResponse)(
|
|
108
|
+
`Pull failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
109
|
+
"NETWORK_ERROR"
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
server.tool(
|
|
115
|
+
"farseer_push",
|
|
116
|
+
`Push (upload) local file and app changes to a Farseer instance.
|
|
117
|
+
|
|
118
|
+
What gets pushed:
|
|
119
|
+
- New/modified scripts from apps/<tenant>/files/
|
|
120
|
+
- New/modified app configurations from apps/<tenant>/apps/
|
|
121
|
+
- Deleted files (if they were previously synced)
|
|
122
|
+
|
|
123
|
+
Important:
|
|
124
|
+
- Checks for unpulled remote changes first to prevent conflicts
|
|
125
|
+
- Use force=true to skip this check (may overwrite remote changes)
|
|
126
|
+
|
|
127
|
+
If no tenant is specified, uses the currently checked-out tenant.`,
|
|
128
|
+
pushSchema,
|
|
129
|
+
async (params) => {
|
|
130
|
+
const { tenant: tenantArg, tenantId, force } = params;
|
|
131
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
132
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
133
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
134
|
+
if (!tenant || !organisation) {
|
|
135
|
+
return (0, import_helpers.tenantRequiredResponse)();
|
|
136
|
+
}
|
|
137
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenantId || tenant);
|
|
138
|
+
if (!clientResult) {
|
|
139
|
+
return (0, import_helpers.authRequiredResponse)(tenant);
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const syncService = new import_syncService.SyncService(organisation, clientResult.client);
|
|
143
|
+
if (!force) {
|
|
144
|
+
const hasRemoteChanges = await syncService.checkRemoteChanges();
|
|
145
|
+
if (hasRemoteChanges) {
|
|
146
|
+
return (0, import_helpers.errorResponse)(
|
|
147
|
+
"Remote has unpulled changes. Pull first or use force=true to overwrite.",
|
|
148
|
+
"VALIDATION_ERROR"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const fileResult = await syncService.push();
|
|
153
|
+
let appResult = null;
|
|
154
|
+
if (clientResult.authType === "jwt") {
|
|
155
|
+
const appSyncService = new import_appSyncService.AppSyncService(organisation, clientResult.client);
|
|
156
|
+
appResult = await appSyncService.push();
|
|
157
|
+
}
|
|
158
|
+
return (0, import_helpers.successResponse)({
|
|
159
|
+
tenant,
|
|
160
|
+
files: {
|
|
161
|
+
uploaded: fileResult.uploaded,
|
|
162
|
+
updated: fileResult.updated,
|
|
163
|
+
deleted: fileResult.deleted,
|
|
164
|
+
unchanged: fileResult.unchanged.length
|
|
165
|
+
},
|
|
166
|
+
apps: appResult ? {
|
|
167
|
+
created: appResult.created,
|
|
168
|
+
updated: appResult.updated,
|
|
169
|
+
unchanged: appResult.unchanged.length
|
|
170
|
+
} : { note: "App sync requires browser login (JWT)" },
|
|
171
|
+
summary: `Pushed ${fileResult.uploaded.length + fileResult.updated.length} files${appResult ? ` and ${appResult.created.length + appResult.updated.length} apps` : ""}`
|
|
172
|
+
});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return (0, import_helpers.errorResponse)(
|
|
175
|
+
`Push failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
176
|
+
"NETWORK_ERROR"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
server.tool(
|
|
182
|
+
"farseer_status",
|
|
183
|
+
`Check the synchronization status between local files and the remote Farseer instance.
|
|
184
|
+
|
|
185
|
+
Shows which files are:
|
|
186
|
+
- Modified locally (changed since last sync)
|
|
187
|
+
- Modified remotely (changed on server since last sync)
|
|
188
|
+
- Only local (new files not yet pushed)
|
|
189
|
+
- Only remote (files not yet pulled)
|
|
190
|
+
- In sync (no changes)
|
|
191
|
+
|
|
192
|
+
Also shows app sync status if using JWT authentication.
|
|
193
|
+
|
|
194
|
+
If no tenant is specified, uses the currently checked-out tenant.`,
|
|
195
|
+
statusSchema,
|
|
196
|
+
async (params) => {
|
|
197
|
+
const { tenant: tenantArg, tenantId, all } = params;
|
|
198
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
199
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
200
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
201
|
+
if (!tenant || !organisation) {
|
|
202
|
+
return (0, import_helpers.tenantRequiredResponse)();
|
|
203
|
+
}
|
|
204
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenantId || tenant);
|
|
205
|
+
if (!clientResult) {
|
|
206
|
+
return (0, import_helpers.authRequiredResponse)(tenant);
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const syncService = new import_syncService.SyncService(organisation, clientResult.client);
|
|
210
|
+
const fileStatus = await syncService.getStatus({ all });
|
|
211
|
+
let appStatus = null;
|
|
212
|
+
if (clientResult.authType === "jwt") {
|
|
213
|
+
const appSyncService = new import_appSyncService.AppSyncService(organisation, clientResult.client);
|
|
214
|
+
appStatus = await appSyncService.getStatus();
|
|
215
|
+
}
|
|
216
|
+
const hasChanges = fileStatus.modifiedLocally.length > 0 || fileStatus.modifiedRemotely.length > 0 || fileStatus.onlyLocal.length > 0 || fileStatus.onlyRemote.length > 0;
|
|
217
|
+
return (0, import_helpers.successResponse)({
|
|
218
|
+
tenant,
|
|
219
|
+
files: {
|
|
220
|
+
modifiedLocally: fileStatus.modifiedLocally,
|
|
221
|
+
modifiedRemotely: fileStatus.modifiedRemotely,
|
|
222
|
+
onlyLocal: fileStatus.onlyLocal,
|
|
223
|
+
onlyRemote: fileStatus.onlyRemote,
|
|
224
|
+
inSync: fileStatus.inSync.length
|
|
225
|
+
},
|
|
226
|
+
apps: appStatus ? {
|
|
227
|
+
newLocal: appStatus.newLocal,
|
|
228
|
+
newRemote: appStatus.newRemote,
|
|
229
|
+
modified: appStatus.modified,
|
|
230
|
+
synced: appStatus.synced.length
|
|
231
|
+
} : { note: "App sync requires browser login (JWT)" },
|
|
232
|
+
summary: hasChanges ? "Changes detected" : "Everything is in sync"
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return (0, import_helpers.errorResponse)(
|
|
236
|
+
`Status check failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
237
|
+
"NETWORK_ERROR"
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
server.tool(
|
|
243
|
+
"farseer_diff",
|
|
244
|
+
`Show the differences between local and remote versions of files.
|
|
245
|
+
|
|
246
|
+
Can show:
|
|
247
|
+
- Unified diff format (default)
|
|
248
|
+
- Just the remote content (showRemote=true)
|
|
249
|
+
|
|
250
|
+
Useful for reviewing changes before pull or push.
|
|
251
|
+
|
|
252
|
+
If no file is specified, shows diffs for all changed files.`,
|
|
253
|
+
diffSchema,
|
|
254
|
+
async (params) => {
|
|
255
|
+
const { tenant: tenantArg, file, showRemote } = params;
|
|
256
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
257
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
258
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
259
|
+
if (!tenant || !organisation) {
|
|
260
|
+
return (0, import_helpers.tenantRequiredResponse)();
|
|
261
|
+
}
|
|
262
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenant);
|
|
263
|
+
if (!clientResult) {
|
|
264
|
+
return (0, import_helpers.authRequiredResponse)(tenant);
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
const syncService = new import_syncService.SyncService(organisation, clientResult.client);
|
|
268
|
+
if (file) {
|
|
269
|
+
const { localContent, remoteContent } = await syncService.getFileDiff(file);
|
|
270
|
+
if (showRemote) {
|
|
271
|
+
return (0, import_helpers.successResponse)({
|
|
272
|
+
file,
|
|
273
|
+
remoteContent: remoteContent || "(file does not exist on remote)"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
const diff = (0, import_diff.createPatch)(
|
|
277
|
+
file,
|
|
278
|
+
remoteContent || "",
|
|
279
|
+
localContent || "",
|
|
280
|
+
"remote",
|
|
281
|
+
"local"
|
|
282
|
+
);
|
|
283
|
+
return (0, import_helpers.successResponse)({
|
|
284
|
+
file,
|
|
285
|
+
localExists: localContent !== null,
|
|
286
|
+
remoteExists: remoteContent !== null,
|
|
287
|
+
diff
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
const status = await syncService.getStatus();
|
|
291
|
+
const changedFiles = [
|
|
292
|
+
...status.modifiedLocally,
|
|
293
|
+
...status.modifiedRemotely,
|
|
294
|
+
...status.onlyLocal,
|
|
295
|
+
...status.onlyRemote
|
|
296
|
+
];
|
|
297
|
+
const diffs = [];
|
|
298
|
+
for (const f of changedFiles.slice(0, 10)) {
|
|
299
|
+
const { localContent, remoteContent } = await syncService.getFileDiff(f);
|
|
300
|
+
const diff = (0, import_diff.createPatch)(f, remoteContent || "", localContent || "", "remote", "local");
|
|
301
|
+
diffs.push({
|
|
302
|
+
file: f,
|
|
303
|
+
localExists: localContent !== null,
|
|
304
|
+
remoteExists: remoteContent !== null,
|
|
305
|
+
diff
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return (0, import_helpers.successResponse)({
|
|
309
|
+
totalChangedFiles: changedFiles.length,
|
|
310
|
+
diffs,
|
|
311
|
+
note: changedFiles.length > 10 ? `Showing first 10 of ${changedFiles.length} changed files` : void 0
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return (0, import_helpers.errorResponse)(
|
|
316
|
+
`Diff failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
317
|
+
"NETWORK_ERROR"
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
server.tool(
|
|
323
|
+
"farseer_list_files",
|
|
324
|
+
`List all files stored on the remote Farseer instance in the Files/ folder.
|
|
325
|
+
|
|
326
|
+
By default shows only script files (.ts, .js, .mjs, .cjs).
|
|
327
|
+
Use all=true to show all files including images, data files, etc.
|
|
328
|
+
|
|
329
|
+
Returns file paths relative to the Files/ folder.`,
|
|
330
|
+
listFilesSchema,
|
|
331
|
+
async (params) => {
|
|
332
|
+
const { tenant: tenantArg, all } = params;
|
|
333
|
+
const checkout = (0, import_configService.getCurrentCheckout)();
|
|
334
|
+
const tenant = tenantArg || checkout?.tenant;
|
|
335
|
+
const organisation = tenantArg || checkout?.organisation;
|
|
336
|
+
if (!tenant || !organisation) {
|
|
337
|
+
return (0, import_helpers.tenantRequiredResponse)();
|
|
338
|
+
}
|
|
339
|
+
const clientResult = await (0, import_farseerFactory.getFarseerClientWithFallback)(organisation, tenant);
|
|
340
|
+
if (!clientResult) {
|
|
341
|
+
return (0, import_helpers.authRequiredResponse)(tenant);
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
let files = await clientResult.client.listFiles();
|
|
345
|
+
if (!all) {
|
|
346
|
+
const scriptExtensions = [".ts", ".js", ".mjs", ".cjs"];
|
|
347
|
+
files = files.filter(
|
|
348
|
+
(f) => scriptExtensions.some((ext) => f.name.toLowerCase().endsWith(ext))
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const byDirectory = {};
|
|
352
|
+
for (const file of files) {
|
|
353
|
+
const dir = file.path.includes("/") ? file.path.split("/").slice(0, -1).join("/") : ".";
|
|
354
|
+
if (!byDirectory[dir]) {
|
|
355
|
+
byDirectory[dir] = [];
|
|
356
|
+
}
|
|
357
|
+
byDirectory[dir].push(file.name);
|
|
358
|
+
}
|
|
359
|
+
return (0, import_helpers.successResponse)({
|
|
360
|
+
tenant,
|
|
361
|
+
totalFiles: files.length,
|
|
362
|
+
files: files.map((f) => ({
|
|
363
|
+
name: f.name,
|
|
364
|
+
path: f.path,
|
|
365
|
+
reference: f.reference
|
|
366
|
+
})),
|
|
367
|
+
byDirectory
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
return (0, import_helpers.errorResponse)(
|
|
371
|
+
`List files failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
372
|
+
"NETWORK_ERROR"
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
379
|
+
0 && (module.exports = {
|
|
380
|
+
registerSyncTools
|
|
381
|
+
});
|
|
382
|
+
//# sourceMappingURL=syncTools.js.map
|