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,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for creating Farseer API client based on available authentication
|
|
3
|
+
*
|
|
4
|
+
* Priority:
|
|
5
|
+
* 1. JWT token (from browser login) - try first
|
|
6
|
+
* 2. API key (from config) - fallback if JWT fails or unavailable
|
|
7
|
+
* 3. Prompt user to login - if nothing works
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Check if an error is an authentication error (401/403)
|
|
11
|
+
*/
|
|
12
|
+
export declare function isAuthError(error: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Show a consistent "please login" message
|
|
15
|
+
*/
|
|
16
|
+
export declare function showLoginPrompt(tenant?: string): void;
|
|
17
|
+
export interface RemoteFile {
|
|
18
|
+
name: string;
|
|
19
|
+
path: string;
|
|
20
|
+
reference: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_SCRIPT_EXTENSIONS: string[];
|
|
23
|
+
import { AppArgument, AppListItem, RemoteApp, ModelExport } from './farseerApi';
|
|
24
|
+
export interface IFarseerClient {
|
|
25
|
+
testConnection(): Promise<boolean>;
|
|
26
|
+
getFilesFolder(): Promise<{
|
|
27
|
+
id: number;
|
|
28
|
+
name: string;
|
|
29
|
+
type: string;
|
|
30
|
+
} | null>;
|
|
31
|
+
listFiles(folderPath?: string[]): Promise<RemoteFile[]>;
|
|
32
|
+
getFileContent(reference: string): Promise<string>;
|
|
33
|
+
getFileContentAsBuffer(reference: string): Promise<Buffer>;
|
|
34
|
+
createFile(content: string | Buffer, fileName: string, folderPath?: string[]): Promise<void>;
|
|
35
|
+
updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void>;
|
|
36
|
+
deleteFile(reference: string): Promise<void>;
|
|
37
|
+
getFileByPath(filePath: string): Promise<RemoteFile | null>;
|
|
38
|
+
isTextFile(filename: string): boolean;
|
|
39
|
+
isBinaryFile(filename: string): boolean;
|
|
40
|
+
listApps(): Promise<AppListItem[]>;
|
|
41
|
+
getApp(referenceId: string): Promise<RemoteApp | null>;
|
|
42
|
+
getAppByName(name: string): Promise<RemoteApp | null>;
|
|
43
|
+
createApp(name: string): Promise<RemoteApp>;
|
|
44
|
+
updateApp(referenceId: string, update: {
|
|
45
|
+
name?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
expectedArguments?: AppArgument[];
|
|
48
|
+
mainScriptFileId?: string | null;
|
|
49
|
+
scriptFileIds?: string[];
|
|
50
|
+
}): Promise<RemoteApp>;
|
|
51
|
+
deleteApp(referenceId: string): Promise<void>;
|
|
52
|
+
getScriptFiles(): Promise<{
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}[]>;
|
|
56
|
+
exportModel(): Promise<ModelExport>;
|
|
57
|
+
}
|
|
58
|
+
export interface FarseerClientResult {
|
|
59
|
+
client: IFarseerClient;
|
|
60
|
+
authType: 'apiKey' | 'jwt';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get a Farseer client for the given tenant.
|
|
64
|
+
* Tries JWT token first (supports apps), then falls back to API key.
|
|
65
|
+
*
|
|
66
|
+
* @param tenant - The tenant hostname (e.g., "omnicom" for omnicom.farseer.io)
|
|
67
|
+
* @param tenantId - Optional tenant ID for x-tenant-id header (e.g., "omnicom-reporting")
|
|
68
|
+
* Only used with JWT auth. Defaults to tenant if not specified.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getFarseerClient(tenant: string, tenantId?: string): Promise<FarseerClientResult | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Check what authentication methods are available
|
|
73
|
+
*/
|
|
74
|
+
export declare function getAvailableAuth(tenant: string): {
|
|
75
|
+
hasApiKey: boolean;
|
|
76
|
+
hasJwt: boolean;
|
|
77
|
+
jwtValid: boolean;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Get a Farseer client with automatic fallback and connection testing.
|
|
81
|
+
* This is the recommended way to get a client for commands.
|
|
82
|
+
*
|
|
83
|
+
* Flow:
|
|
84
|
+
* 1. Try JWT if available and valid
|
|
85
|
+
* 2. If JWT fails or unavailable, try API key
|
|
86
|
+
* 3. If nothing works, return null (caller should show login prompt)
|
|
87
|
+
*/
|
|
88
|
+
export declare function getFarseerClientWithFallback(tenant: string, tenantId?: string): Promise<FarseerClientResult | null>;
|
|
@@ -0,0 +1,179 @@
|
|
|
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 farseerFactory_exports = {};
|
|
19
|
+
__export(farseerFactory_exports, {
|
|
20
|
+
DEFAULT_SCRIPT_EXTENSIONS: () => DEFAULT_SCRIPT_EXTENSIONS,
|
|
21
|
+
ensureFreshToken: () => ensureFreshToken,
|
|
22
|
+
getAvailableAuth: () => getAvailableAuth,
|
|
23
|
+
getFarseerClient: () => getFarseerClient,
|
|
24
|
+
getFarseerClientWithFallback: () => getFarseerClientWithFallback,
|
|
25
|
+
isAuthError: () => isAuthError,
|
|
26
|
+
showLoginPrompt: () => showLoginPrompt
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(farseerFactory_exports);
|
|
29
|
+
var import_farseerService = require("./farseerService");
|
|
30
|
+
var import_farseerApi = require("./farseerApi");
|
|
31
|
+
var import_configService = require("./configService");
|
|
32
|
+
var import_farseerService2 = require("./farseerService");
|
|
33
|
+
var import_logger = require("../utils/logger");
|
|
34
|
+
function isAuthError(error) {
|
|
35
|
+
if (error instanceof Error) {
|
|
36
|
+
const msg = error.message.toLowerCase();
|
|
37
|
+
return msg.includes("401") || msg.includes("403") || msg.includes("unauthorized") || msg.includes("forbidden");
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
function showLoginPrompt(tenant) {
|
|
42
|
+
import_logger.logger.error("Authentication required.");
|
|
43
|
+
if (tenant) {
|
|
44
|
+
import_logger.logger.dim(`No valid credentials found for tenant "${tenant}".`);
|
|
45
|
+
}
|
|
46
|
+
import_logger.logger.info('Run "farseer login" to authenticate via browser.');
|
|
47
|
+
import_logger.logger.dim('Or use "farseer config set <tenant> --api-key <key>" for API key auth.');
|
|
48
|
+
}
|
|
49
|
+
const DEFAULT_SCRIPT_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs"];
|
|
50
|
+
async function getFarseerClient(organisation, tenant) {
|
|
51
|
+
const auth = (0, import_configService.getUserAuth)();
|
|
52
|
+
if (auth) {
|
|
53
|
+
let accessToken = auth.accessToken;
|
|
54
|
+
if (!(0, import_configService.isUserAuthValid)()) {
|
|
55
|
+
import_logger.logger.dim("Token expired, refreshing...");
|
|
56
|
+
const refreshed = await (0, import_farseerService2.refreshAccessToken)(auth.refreshToken, auth.realm || "master");
|
|
57
|
+
if (!refreshed) {
|
|
58
|
+
import_logger.logger.warning("Could not refresh token. Please login again with: farseer login");
|
|
59
|
+
} else {
|
|
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
|
+
accessToken = refreshed.accessToken;
|
|
67
|
+
import_logger.logger.dim("Token refreshed");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if ((0, import_configService.isUserAuthValid)()) {
|
|
71
|
+
return {
|
|
72
|
+
client: new import_farseerApi.FarseerApi(accessToken, organisation, tenant),
|
|
73
|
+
authType: "jwt"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const credential = (0, import_configService.getCredential)(tenant);
|
|
78
|
+
if (credential) {
|
|
79
|
+
return {
|
|
80
|
+
client: new import_farseerService.FarseerService(credential),
|
|
81
|
+
authType: "apiKey"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function getAvailableAuth(tenant) {
|
|
87
|
+
const credential = (0, import_configService.getCredential)(tenant);
|
|
88
|
+
const auth = (0, import_configService.getUserAuth)();
|
|
89
|
+
return {
|
|
90
|
+
hasApiKey: !!credential,
|
|
91
|
+
hasJwt: !!auth,
|
|
92
|
+
jwtValid: (0, import_configService.isUserAuthValid)()
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function getFarseerClientWithFallback(organisation, tenant) {
|
|
96
|
+
const auth = (0, import_configService.getUserAuth)();
|
|
97
|
+
const credential = (0, import_configService.getCredential)(tenant);
|
|
98
|
+
if (auth) {
|
|
99
|
+
let accessToken = auth.accessToken;
|
|
100
|
+
if (!(0, import_configService.isUserAuthValid)()) {
|
|
101
|
+
import_logger.logger.dim("Token expired, refreshing...");
|
|
102
|
+
const refreshed = await (0, import_farseerService2.refreshAccessToken)(auth.refreshToken, auth.realm || "master");
|
|
103
|
+
if (refreshed) {
|
|
104
|
+
(0, import_configService.setUserAuth)({
|
|
105
|
+
accessToken: refreshed.accessToken,
|
|
106
|
+
refreshToken: refreshed.refreshToken,
|
|
107
|
+
expiresAt: new Date(Date.now() + refreshed.expiresIn * 1e3).toISOString(),
|
|
108
|
+
realm: auth.realm
|
|
109
|
+
});
|
|
110
|
+
accessToken = refreshed.accessToken;
|
|
111
|
+
import_logger.logger.dim("Token refreshed");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if ((0, import_configService.isUserAuthValid)()) {
|
|
115
|
+
const jwtClient = new import_farseerApi.FarseerApi(accessToken, organisation, tenant);
|
|
116
|
+
try {
|
|
117
|
+
const connected = await jwtClient.testConnection();
|
|
118
|
+
if (connected) {
|
|
119
|
+
return { client: jwtClient, authType: "jwt" };
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
import_logger.logger.dim("JWT auth failed for this tenant, trying API key...");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (credential) {
|
|
127
|
+
const apiKeyClient = new import_farseerService.FarseerService(credential);
|
|
128
|
+
try {
|
|
129
|
+
const connected = await apiKeyClient.testConnection();
|
|
130
|
+
if (connected) {
|
|
131
|
+
return { client: apiKeyClient, authType: "apiKey" };
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
import_logger.logger.dim("API key auth failed.");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
async function ensureFreshToken(client) {
|
|
140
|
+
const auth = (0, import_configService.getUserAuth)();
|
|
141
|
+
if (!auth) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
const expiresAt = new Date(auth.expiresAt);
|
|
145
|
+
const now = /* @__PURE__ */ new Date();
|
|
146
|
+
const bufferMs = 30 * 1e3;
|
|
147
|
+
if (expiresAt.getTime() - now.getTime() > bufferMs) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
import_logger.logger.dim("Refreshing token before operation...");
|
|
151
|
+
const refreshed = await (0, import_farseerService2.refreshAccessToken)(auth.refreshToken, auth.realm || "master");
|
|
152
|
+
if (!refreshed) {
|
|
153
|
+
import_logger.logger.warning("Could not refresh token.");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
(0, import_configService.setUserAuth)({
|
|
157
|
+
accessToken: refreshed.accessToken,
|
|
158
|
+
refreshToken: refreshed.refreshToken,
|
|
159
|
+
expiresAt: new Date(Date.now() + refreshed.expiresIn * 1e3).toISOString(),
|
|
160
|
+
realm: auth.realm
|
|
161
|
+
});
|
|
162
|
+
if (client.updateAccessToken) {
|
|
163
|
+
client.updateAccessToken(refreshed.accessToken);
|
|
164
|
+
import_logger.logger.dim("Token refreshed");
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
170
|
+
0 && (module.exports = {
|
|
171
|
+
DEFAULT_SCRIPT_EXTENSIONS,
|
|
172
|
+
ensureFreshToken,
|
|
173
|
+
getAvailableAuth,
|
|
174
|
+
getFarseerClient,
|
|
175
|
+
getFarseerClientWithFallback,
|
|
176
|
+
isAuthError,
|
|
177
|
+
showLoginPrompt
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=farseerFactory.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/services/farseerFactory.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Factory for creating Farseer API client based on available authentication\n *\n * Priority:\n * 1. JWT token (from browser login) - try first\n * 2. API key (from config) - fallback if JWT fails or unavailable\n * 3. Prompt user to login - if nothing works\n */\n\nimport { FarseerService } from './farseerService';\nimport { FarseerApi } from './farseerApi';\nimport { getCredential, getUserAuth, isUserAuthValid, setUserAuth } from './configService';\nimport { refreshAccessToken } from './farseerService';\nimport { logger } from '../utils/logger';\n\n/**\n * Check if an error is an authentication error (401/403)\n */\nexport function isAuthError(error: unknown): boolean {\n if (error instanceof Error) {\n const msg = error.message.toLowerCase();\n return msg.includes('401') || msg.includes('403') || msg.includes('unauthorized') || msg.includes('forbidden');\n }\n return false;\n}\n\n/**\n * Show a consistent \"please login\" message\n */\nexport function showLoginPrompt(tenant?: string): void {\n logger.error('Authentication required.');\n if (tenant) {\n logger.dim(`No valid credentials found for tenant \"${tenant}\".`);\n }\n logger.info('Run \"farseer login\" to authenticate via browser.');\n logger.dim('Or use \"farseer config set <tenant> --api-key <key>\" for API key auth.');\n}\n\nexport interface FileMetadata {\n uploadTime?: string; // ISO timestamp\n uploader?: {\n id: number;\n email: string;\n firstName: string;\n lastName: string;\n };\n size?: number;\n}\n\nexport interface RemoteFile {\n name: string;\n path: string;\n reference: string;\n metadata?: FileMetadata;\n}\n\n// Default file extensions to sync (scripts only)\nexport const DEFAULT_SCRIPT_EXTENSIONS = ['.ts', '.js', '.mjs', '.cjs'];\n\nimport { AppArgument, AppListItem, RemoteApp, ModelExport } from './farseerApi';\n\n// Common interface that both FarseerService and FarseerApi implement\nexport interface IFarseerClient {\n testConnection(): Promise<boolean>;\n getFilesFolder(): Promise<{ id: number; name: string; type: string } | null>;\n listFiles(folderPath?: string[]): Promise<RemoteFile[]>;\n getFileContent(reference: string): Promise<string>;\n getFileContentAsBuffer(reference: string): Promise<Buffer>;\n createFile(content: string | Buffer, fileName: string, folderPath?: string[]): Promise<void>;\n updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void>;\n deleteFile(reference: string): Promise<void>;\n getFileByPath(filePath: string): Promise<RemoteFile | null>;\n getFileMetadata(reference: string): Promise<FileMetadata | null>;\n isTextFile(filename: string): boolean;\n isBinaryFile(filename: string): boolean;\n // Apps API methods\n listApps(): Promise<AppListItem[]>;\n getApp(referenceId: string): Promise<RemoteApp | null>;\n getAppByName(name: string): Promise<RemoteApp | null>;\n createApp(name: string): Promise<RemoteApp>;\n updateApp(referenceId: string, update: {\n name?: string;\n description?: string;\n expectedArguments?: AppArgument[];\n mainScriptFileId?: string | null;\n scriptFileIds?: string[];\n }): Promise<RemoteApp>;\n deleteApp(referenceId: string): Promise<void>;\n getScriptFiles(): Promise<{ id: string; name: string }[]>;\n // Model export\n exportModel(): Promise<ModelExport>;\n // Token management (optional - only FarseerApi implements this)\n updateAccessToken?(newToken: string): void;\n}\n\nexport interface FarseerClientResult {\n client: IFarseerClient;\n authType: 'apiKey' | 'jwt';\n}\n\n/**\n * Get a Farseer client for the given organisation and tenant.\n * Tries JWT token first (supports apps), then falls back to API key.\n *\n * @param organisation - The organisation hostname (e.g., \"jgl\" for jgl.farseer.io)\n * @param tenant - Tenant name for X-TENANT-ID header (e.g., \"tt-hotels-hr-dev\")\n */\nexport async function getFarseerClient(organisation: string, tenant: string): Promise<FarseerClientResult | null> {\n // 1. Try JWT token from browser login first (supports apps sync)\n const auth = getUserAuth();\n if (auth) {\n // Check if token needs refresh\n let accessToken = auth.accessToken;\n if (!isUserAuthValid()) {\n logger.dim('Token expired, refreshing...');\n const refreshed = await refreshAccessToken(auth.refreshToken, auth.realm || 'master');\n if (!refreshed) {\n logger.warning('Could not refresh token. Please login again with: farseer login');\n // Fall through to API key\n } else {\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 accessToken = refreshed.accessToken;\n logger.dim('Token refreshed');\n }\n }\n\n if (isUserAuthValid()) {\n return {\n client: new FarseerApi(accessToken, organisation, tenant),\n authType: 'jwt',\n };\n }\n }\n\n // 2. Fall back to API key config\n const credential = getCredential(tenant);\n if (credential) {\n return {\n client: new FarseerService(credential),\n authType: 'apiKey',\n };\n }\n\n return null;\n}\n\n/**\n * Check what authentication methods are available\n */\nexport function getAvailableAuth(tenant: string): {\n hasApiKey: boolean;\n hasJwt: boolean;\n jwtValid: boolean;\n} {\n const credential = getCredential(tenant);\n const auth = getUserAuth();\n\n return {\n hasApiKey: !!credential,\n hasJwt: !!auth,\n jwtValid: isUserAuthValid(),\n };\n}\n\n/**\n * Get a Farseer client with automatic fallback and connection testing.\n * This is the recommended way to get a client for commands.\n *\n * Flow:\n * 1. Try JWT if available and valid\n * 2. If JWT fails or unavailable, try API key\n * 3. If nothing works, return null (caller should show login prompt)\n *\n * @param organisation - The organisation hostname (e.g., \"jgl\" for jgl.farseer.io)\n * @param tenant - Tenant name for X-TENANT-ID header (e.g., \"tt-hotels-hr-dev\")\n */\nexport async function getFarseerClientWithFallback(\n organisation: string,\n tenant: string\n): Promise<FarseerClientResult | null> {\n const auth = getUserAuth();\n const credential = getCredential(tenant);\n\n // 1. Try JWT token first\n if (auth) {\n let accessToken = auth.accessToken;\n\n // Refresh token if needed\n if (!isUserAuthValid()) {\n logger.dim('Token expired, refreshing...');\n const refreshed = await refreshAccessToken(auth.refreshToken, auth.realm || 'master');\n if (refreshed) {\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 accessToken = refreshed.accessToken;\n logger.dim('Token refreshed');\n }\n }\n\n if (isUserAuthValid()) {\n const jwtClient = new FarseerApi(accessToken, organisation, tenant);\n\n // Test JWT connection\n try {\n const connected = await jwtClient.testConnection();\n if (connected) {\n return { client: jwtClient, authType: 'jwt' };\n }\n } catch (error) {\n // JWT failed, will try API key\n logger.dim('JWT auth failed for this tenant, trying API key...');\n }\n }\n }\n\n // 2. Try API key\n if (credential) {\n const apiKeyClient = new FarseerService(credential);\n\n try {\n const connected = await apiKeyClient.testConnection();\n if (connected) {\n return { client: apiKeyClient, authType: 'apiKey' };\n }\n } catch {\n // API key also failed\n logger.dim('API key auth failed.');\n }\n }\n\n // 3. Nothing worked\n return null;\n}\n\n/**\n * Ensure the token is fresh and update the client if needed.\n * Call this before long-running operations to avoid token expiration.\n *\n * @returns true if token was refreshed, false if no refresh needed or API key auth\n */\nexport async function ensureFreshToken(client: IFarseerClient): Promise<boolean> {\n const auth = getUserAuth();\n if (!auth) {\n return false; // No JWT auth, probably using API key\n }\n\n // Check if token needs refresh (give 30 second buffer)\n const expiresAt = new Date(auth.expiresAt);\n const now = new Date();\n const bufferMs = 30 * 1000; // 30 seconds\n\n if (expiresAt.getTime() - now.getTime() > bufferMs) {\n return false; // Token is still fresh enough\n }\n\n // Refresh the token\n logger.dim('Refreshing token before operation...');\n const refreshed = await refreshAccessToken(auth.refreshToken, auth.realm || 'master');\n\n if (!refreshed) {\n logger.warning('Could not refresh token.');\n return false;\n }\n\n // Save the new 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 // Update the client's token if it supports it\n if (client.updateAccessToken) {\n client.updateAccessToken(refreshed.accessToken);\n logger.dim('Token refreshed');\n return true;\n }\n\n return false;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,4BAA+B;AAC/B,wBAA2B;AAC3B,2BAAyE;AACzE,IAAAA,yBAAmC;AACnC,oBAAuB;AAKhB,SAAS,YAAY,OAAyB;AACjD,MAAI,iBAAiB,OAAO;AACxB,UAAM,MAAM,MAAM,QAAQ,YAAY;AACtC,WAAO,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,cAAc,KAAK,IAAI,SAAS,WAAW;AAAA,EACjH;AACA,SAAO;AACX;AAKO,SAAS,gBAAgB,QAAuB;AACnD,uBAAO,MAAM,0BAA0B;AACvC,MAAI,QAAQ;AACR,yBAAO,IAAI,0CAA0C,MAAM,IAAI;AAAA,EACnE;AACA,uBAAO,KAAK,kDAAkD;AAC9D,uBAAO,IAAI,wEAAwE;AACvF;AAqBO,MAAM,4BAA4B,CAAC,OAAO,OAAO,QAAQ,MAAM;AAkDtE,eAAsB,iBAAiB,cAAsB,QAAqD;AAE9G,QAAM,WAAO,kCAAY;AACzB,MAAI,MAAM;AAEN,QAAI,cAAc,KAAK;AACvB,QAAI,KAAC,sCAAgB,GAAG;AACpB,2BAAO,IAAI,8BAA8B;AACzC,YAAM,YAAY,UAAM,2CAAmB,KAAK,cAAc,KAAK,SAAS,QAAQ;AACpF,UAAI,CAAC,WAAW;AACZ,6BAAO,QAAQ,iEAAiE;AAAA,MAEpF,OAAO;AAEH,8CAAY;AAAA,UACR,aAAa,UAAU;AAAA,UACvB,cAAc,UAAU;AAAA,UACxB,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,YAAY,GAAI,EAAE,YAAY;AAAA,UACzE,OAAO,KAAK;AAAA,QAChB,CAAC;AACD,sBAAc,UAAU;AACxB,6BAAO,IAAI,iBAAiB;AAAA,MAChC;AAAA,IACJ;AAEA,YAAI,sCAAgB,GAAG;AACnB,aAAO;AAAA,QACH,QAAQ,IAAI,6BAAW,aAAa,cAAc,MAAM;AAAA,QACxD,UAAU;AAAA,MACd;AAAA,IACJ;AAAA,EACJ;AAGA,QAAM,iBAAa,oCAAc,MAAM;AACvC,MAAI,YAAY;AACZ,WAAO;AAAA,MACH,QAAQ,IAAI,qCAAe,UAAU;AAAA,MACrC,UAAU;AAAA,IACd;AAAA,EACJ;AAEA,SAAO;AACX;AAKO,SAAS,iBAAiB,QAI/B;AACE,QAAM,iBAAa,oCAAc,MAAM;AACvC,QAAM,WAAO,kCAAY;AAEzB,SAAO;AAAA,IACH,WAAW,CAAC,CAAC;AAAA,IACb,QAAQ,CAAC,CAAC;AAAA,IACV,cAAU,sCAAgB;AAAA,EAC9B;AACJ;AAcA,eAAsB,6BAClB,cACA,QACmC;AACnC,QAAM,WAAO,kCAAY;AACzB,QAAM,iBAAa,oCAAc,MAAM;AAGvC,MAAI,MAAM;AACN,QAAI,cAAc,KAAK;AAGvB,QAAI,KAAC,sCAAgB,GAAG;AACpB,2BAAO,IAAI,8BAA8B;AACzC,YAAM,YAAY,UAAM,2CAAmB,KAAK,cAAc,KAAK,SAAS,QAAQ;AACpF,UAAI,WAAW;AACX,8CAAY;AAAA,UACR,aAAa,UAAU;AAAA,UACvB,cAAc,UAAU;AAAA,UACxB,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,YAAY,GAAI,EAAE,YAAY;AAAA,UACzE,OAAO,KAAK;AAAA,QAChB,CAAC;AACD,sBAAc,UAAU;AACxB,6BAAO,IAAI,iBAAiB;AAAA,MAChC;AAAA,IACJ;AAEA,YAAI,sCAAgB,GAAG;AACnB,YAAM,YAAY,IAAI,6BAAW,aAAa,cAAc,MAAM;AAGlE,UAAI;AACA,cAAM,YAAY,MAAM,UAAU,eAAe;AACjD,YAAI,WAAW;AACX,iBAAO,EAAE,QAAQ,WAAW,UAAU,MAAM;AAAA,QAChD;AAAA,MACJ,SAAS,OAAO;AAEZ,6BAAO,IAAI,oDAAoD;AAAA,MACnE;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,YAAY;AACZ,UAAM,eAAe,IAAI,qCAAe,UAAU;AAElD,QAAI;AACA,YAAM,YAAY,MAAM,aAAa,eAAe;AACpD,UAAI,WAAW;AACX,eAAO,EAAE,QAAQ,cAAc,UAAU,SAAS;AAAA,MACtD;AAAA,IACJ,QAAQ;AAEJ,2BAAO,IAAI,sBAAsB;AAAA,IACrC;AAAA,EACJ;AAGA,SAAO;AACX;AAQA,eAAsB,iBAAiB,QAA0C;AAC7E,QAAM,WAAO,kCAAY;AACzB,MAAI,CAAC,MAAM;AACP,WAAO;AAAA,EACX;AAGA,QAAM,YAAY,IAAI,KAAK,KAAK,SAAS;AACzC,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,WAAW,KAAK;AAEtB,MAAI,UAAU,QAAQ,IAAI,IAAI,QAAQ,IAAI,UAAU;AAChD,WAAO;AAAA,EACX;AAGA,uBAAO,IAAI,sCAAsC;AACjD,QAAM,YAAY,UAAM,2CAAmB,KAAK,cAAc,KAAK,SAAS,QAAQ;AAEpF,MAAI,CAAC,WAAW;AACZ,yBAAO,QAAQ,0BAA0B;AACzC,WAAO;AAAA,EACX;AAGA,wCAAY;AAAA,IACR,aAAa,UAAU;AAAA,IACvB,cAAc,UAAU;AAAA,IACxB,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,YAAY,GAAI,EAAE,YAAY;AAAA,IACzE,OAAO,KAAK;AAAA,EAChB,CAAC;AAGD,MAAI,OAAO,mBAAmB;AAC1B,WAAO,kBAAkB,UAAU,WAAW;AAC9C,yBAAO,IAAI,iBAAiB;AAC5B,WAAO;AAAA,EACX;AAEA,SAAO;AACX;",
|
|
6
|
+
"names": ["import_farseerService"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ApiKeyCredential } from './configService';
|
|
2
|
+
import { AppArgument, AppListItem, RemoteApp } from './farseerApi';
|
|
3
|
+
export interface RemoteFile {
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
reference: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface FolderItem {
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
type: string;
|
|
13
|
+
reference?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class FarseerService {
|
|
16
|
+
private client;
|
|
17
|
+
private credential;
|
|
18
|
+
constructor(credential: ApiKeyCredential);
|
|
19
|
+
static fromUserAuth(tenant: string, basePath: string): FarseerService | null;
|
|
20
|
+
testConnection(): Promise<boolean>;
|
|
21
|
+
getFilesFolder(): Promise<FolderItem | null>;
|
|
22
|
+
listFiles(folderPath?: string[]): Promise<RemoteFile[]>;
|
|
23
|
+
getFileContent(reference: string): Promise<string>;
|
|
24
|
+
createFile(content: string | Buffer, fileName: string, folderPath?: string[]): Promise<void>;
|
|
25
|
+
updateFile(reference: string, content: string | Buffer, fileName: string): Promise<void>;
|
|
26
|
+
deleteFile(reference: string): Promise<void>;
|
|
27
|
+
ensureFolderPath(folderPath: string[]): Promise<void>;
|
|
28
|
+
getFileByPath(filePath: string): Promise<RemoteFile | null>;
|
|
29
|
+
getFileContentAsBuffer(reference: string): Promise<Buffer>;
|
|
30
|
+
isTextFile(filename: string): boolean;
|
|
31
|
+
isBinaryFile(filename: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* List all apps (Remote Jobs) on the tenant
|
|
34
|
+
*/
|
|
35
|
+
listApps(): Promise<AppListItem[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Get app details by reference ID
|
|
38
|
+
*/
|
|
39
|
+
getApp(referenceId: string): Promise<RemoteApp | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Get app by name
|
|
42
|
+
*/
|
|
43
|
+
getAppByName(name: string): Promise<RemoteApp | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new app
|
|
46
|
+
*/
|
|
47
|
+
createApp(name: string): Promise<RemoteApp>;
|
|
48
|
+
/**
|
|
49
|
+
* Update an app
|
|
50
|
+
*/
|
|
51
|
+
updateApp(referenceId: string, update: {
|
|
52
|
+
name?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
expectedArguments?: AppArgument[];
|
|
55
|
+
mainScriptFileId?: string | null;
|
|
56
|
+
scriptFileIds?: string[];
|
|
57
|
+
}): Promise<RemoteApp>;
|
|
58
|
+
/**
|
|
59
|
+
* Delete an app
|
|
60
|
+
*/
|
|
61
|
+
deleteApp(referenceId: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Get script files available for apps
|
|
64
|
+
*/
|
|
65
|
+
getScriptFiles(): Promise<{
|
|
66
|
+
id: string;
|
|
67
|
+
name: string;
|
|
68
|
+
}[]>;
|
|
69
|
+
/**
|
|
70
|
+
* Export the entire model structure
|
|
71
|
+
*/
|
|
72
|
+
exportModel(): Promise<import('./farseerApi').ModelExport>;
|
|
73
|
+
/**
|
|
74
|
+
* Get the Apps folder ID
|
|
75
|
+
*/
|
|
76
|
+
private getAppsFolderId;
|
|
77
|
+
/**
|
|
78
|
+
* Make a direct API request (for endpoints not in farseer-client)
|
|
79
|
+
*/
|
|
80
|
+
private apiRequest;
|
|
81
|
+
}
|
|
82
|
+
export declare function loginWithPassword(email: string, password: string): Promise<{
|
|
83
|
+
accessToken: string;
|
|
84
|
+
refreshToken: string;
|
|
85
|
+
expiresIn: number;
|
|
86
|
+
} | null>;
|
|
87
|
+
export declare function loginWithBrowser(): Promise<{
|
|
88
|
+
accessToken: string;
|
|
89
|
+
refreshToken: string;
|
|
90
|
+
expiresIn: number;
|
|
91
|
+
} | null>;
|
|
92
|
+
export declare function refreshAccessToken(refreshToken: string): Promise<{
|
|
93
|
+
accessToken: string;
|
|
94
|
+
refreshToken: string;
|
|
95
|
+
expiresIn: number;
|
|
96
|
+
} | null>;
|