@gowelle/stint-agent 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Gowelle John
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # Stint Agent
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@gowelle/stint-agent.svg)](https://www.npmjs.com/package/@gowelle/stint-agent)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@gowelle/stint-agent.svg)](https://www.npmjs.com/package/@gowelle/stint-agent)
5
+ [![CI](https://github.com/gowelle/stint-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/gowelle/stint-agent/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org/)
8
+
9
+ The official CLI agent for [Stint](https://stint.codes) — a lightweight daemon that bridges the Stint web app and your local git repositories, enabling automatic commit execution and real-time repo syncing.
10
+
11
+ ## Features
12
+
13
+ - 🔐 Secure authentication with OAuth
14
+ - 🔄 Real-time WebSocket connection to Stint
15
+ - 📦 Automatic commit execution
16
+ - 🔍 Repository status syncing
17
+ - 🖥️ Background daemon process
18
+ - 📝 Comprehensive logging
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install -g @gowelle/stint-agent
24
+ # or
25
+ pnpm add -g @gowelle/stint-agent
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # Authenticate with Stint
32
+ stint login
33
+
34
+ # Check your authentication status
35
+ stint whoami
36
+
37
+ # Link a project
38
+ cd /path/to/your/project
39
+ stint link
40
+
41
+ # Start the daemon
42
+ stint daemon start
43
+
44
+ # Check daemon status
45
+ stint daemon status
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ ### Authentication
51
+
52
+ | Command | Description |
53
+ |---------|-------------|
54
+ | `stint login` | Authenticate with Stint (opens browser for OAuth) |
55
+ | `stint logout` | Remove stored credentials |
56
+ | `stint whoami` | Show current user and machine information |
57
+
58
+ ### Daemon
59
+
60
+ | Command | Description |
61
+ |---------|-------------|
62
+ | `stint daemon start` | Start background daemon |
63
+ | `stint daemon stop` | Stop daemon gracefully |
64
+ | `stint daemon status` | Check if daemon is running |
65
+ | `stint daemon logs [--lines N]` | View daemon logs (default: 50 lines) |
66
+ | `stint daemon restart` | Restart the daemon |
67
+
68
+ ### Project Management
69
+
70
+ | Command | Description |
71
+ |---------|-------------|
72
+ | `stint link` | Link current directory to a Stint project |
73
+ | `stint unlink [--force]` | Remove project link |
74
+ | `stint status` | Show project, git, auth, and daemon status |
75
+ | `stint sync` | Manually sync repository information to server |
76
+
77
+ ### Commit Operations
78
+
79
+ | Command | Description |
80
+ |---------|-------------|
81
+ | `stint commits` | List pending commits for this repository |
82
+ | `stint commit <id>` | Execute a specific pending commit |
83
+
84
+ ## Complete Workflow
85
+
86
+ ```bash
87
+ # 1. Install and authenticate
88
+ npm install -g @gowelle/stint-agent
89
+ stint login
90
+
91
+ # 2. Link your project
92
+ cd /path/to/your/project
93
+ stint link
94
+
95
+ # 3. Start the daemon
96
+ stint daemon start
97
+
98
+ # 4. Check status
99
+ stint status
100
+
101
+ # Now commits approved in the web app will execute automatically!
102
+ ```
103
+
104
+ ## Troubleshooting
105
+
106
+ ### "Not authenticated" error
107
+
108
+ Run `stint login` to authenticate with your Stint account.
109
+
110
+ ### "Repository has uncommitted changes"
111
+
112
+ The agent requires a clean repository to execute commits:
113
+
114
+ ```bash
115
+ git stash # Temporarily stash changes
116
+ # or
117
+ git add . && git commit -m "message"
118
+ ```
119
+
120
+ ### Daemon won't start
121
+
122
+ ```bash
123
+ stint daemon status # Check if already running
124
+ stint daemon logs # Check logs for errors
125
+ stint daemon stop # Stop first
126
+ stint daemon start # Then start again
127
+ ```
128
+
129
+ ### WebSocket connection issues
130
+
131
+ Check your network connection and firewall settings.
132
+
133
+ ## Logging
134
+
135
+ Logs are stored in your system's config directory:
136
+
137
+ | Platform | Log Location |
138
+ |----------|--------------|
139
+ | **macOS** | `~/.config/stint/logs/` |
140
+ | **Linux** | `~/.config/stint/logs/` |
141
+ | **Windows** | `%USERPROFILE%\.config\stint\logs\` |
142
+
143
+ Log files:
144
+ - `agent.log` - General CLI operations
145
+ - `daemon.log` - Daemon process logs
146
+ - `error.log` - Error details
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ git clone https://github.com/gowelle/stint-agent.git
152
+ cd stint-agent
153
+ pnpm install
154
+ pnpm build
155
+ pnpm dev # Watch mode
156
+ ```
157
+
158
+ ## Security
159
+
160
+ - Tokens are encrypted at rest using machine-specific keys
161
+ - All API communication uses HTTPS
162
+ - WebSocket connections are authenticated
163
+ - Git operations are restricted to linked directories
164
+
165
+ ## License
166
+
167
+ MIT © [Gowelle John](https://github.com/gowelle)
168
+
169
+ ## Support
170
+
171
+ For issues and questions, please [open an issue](https://github.com/gowelle/stint-agent/issues).
@@ -0,0 +1,6 @@
1
+ import {
2
+ apiService
3
+ } from "./chunk-5DWSNHS6.js";
4
+ export {
5
+ apiService
6
+ };
@@ -0,0 +1,448 @@
1
+ // src/services/api.ts
2
+ import { createRequire } from "module";
3
+
4
+ // src/utils/config.ts
5
+ import Conf from "conf";
6
+ import { randomUUID } from "crypto";
7
+ import os from "os";
8
+ var DEFAULT_CONFIG = {
9
+ apiUrl: "https://stint.codes",
10
+ wsUrl: "wss://stint.codes/reverb",
11
+ reverbAppKey: "wtn6tu6lirfv6yflujk7",
12
+ projects: {}
13
+ };
14
+ var ConfigManager = class {
15
+ conf;
16
+ constructor() {
17
+ this.conf = new Conf({
18
+ projectName: "stint",
19
+ defaults: {
20
+ ...DEFAULT_CONFIG,
21
+ machineId: randomUUID(),
22
+ machineName: os.hostname()
23
+ }
24
+ });
25
+ }
26
+ get(key) {
27
+ return this.conf.get(key);
28
+ }
29
+ set(key, value) {
30
+ this.conf.set(key, value);
31
+ }
32
+ getAll() {
33
+ return this.conf.store;
34
+ }
35
+ clear() {
36
+ this.conf.clear();
37
+ }
38
+ // Token management
39
+ getToken() {
40
+ return this.conf.get("token");
41
+ }
42
+ setToken(token) {
43
+ this.conf.set("token", token);
44
+ }
45
+ clearToken() {
46
+ this.conf.delete("token");
47
+ }
48
+ // Machine info
49
+ getMachineId() {
50
+ return this.conf.get("machineId");
51
+ }
52
+ getMachineName() {
53
+ return this.conf.get("machineName");
54
+ }
55
+ // Project management
56
+ getProjects() {
57
+ return this.conf.get("projects") || {};
58
+ }
59
+ getProject(path2) {
60
+ const projects = this.getProjects();
61
+ return projects[path2];
62
+ }
63
+ setProject(path2, project) {
64
+ const projects = this.getProjects();
65
+ projects[path2] = project;
66
+ this.conf.set("projects", projects);
67
+ }
68
+ removeProject(path2) {
69
+ const projects = this.getProjects();
70
+ delete projects[path2];
71
+ this.conf.set("projects", projects);
72
+ }
73
+ // API URLs
74
+ getApiUrl() {
75
+ return this.conf.get("apiUrl");
76
+ }
77
+ getWsUrl() {
78
+ const environment = this.getEnvironment();
79
+ const reverbAppKey = this.getReverbAppKey();
80
+ let baseUrl;
81
+ if (environment === "development") {
82
+ baseUrl = "ws://localhost:8080";
83
+ } else {
84
+ baseUrl = this.conf.get("wsUrl") || "wss://stint.codes/reverb";
85
+ }
86
+ if (reverbAppKey && reverbAppKey.trim() !== "") {
87
+ const cleanBaseUrl2 = baseUrl.replace(/\/$/, "");
88
+ const baseWithoutReverb = cleanBaseUrl2.replace(/\/reverb$/, "");
89
+ return `${baseWithoutReverb}/app/${reverbAppKey}`;
90
+ }
91
+ const cleanBaseUrl = baseUrl.replace(/\/$/, "");
92
+ if (!cleanBaseUrl.includes("/reverb")) {
93
+ return `${cleanBaseUrl}/reverb`;
94
+ }
95
+ return cleanBaseUrl;
96
+ }
97
+ // Environment management
98
+ getEnvironment() {
99
+ const configEnv = this.conf.get("environment");
100
+ if (configEnv === "development" || configEnv === "production") {
101
+ return configEnv;
102
+ }
103
+ const nodeEnv = process.env.NODE_ENV;
104
+ if (nodeEnv === "development" || nodeEnv === "dev") {
105
+ return "development";
106
+ }
107
+ return "production";
108
+ }
109
+ setEnvironment(environment) {
110
+ this.conf.set("environment", environment);
111
+ }
112
+ // Reverb App Key management
113
+ getReverbAppKey() {
114
+ return process.env.REVERB_APP_KEY || process.env.STINT_REVERB_APP_KEY || this.conf.get("reverbAppKey");
115
+ }
116
+ setReverbAppKey(reverbAppKey) {
117
+ this.conf.set("reverbAppKey", reverbAppKey);
118
+ }
119
+ };
120
+ var config = new ConfigManager();
121
+
122
+ // src/utils/crypto.ts
123
+ import crypto from "crypto";
124
+ import os2 from "os";
125
+ function getMachineKey() {
126
+ const machineInfo = `${os2.hostname()}-${os2.platform()}-${os2.arch()}`;
127
+ return crypto.createHash("sha256").update(machineInfo).digest();
128
+ }
129
+ var ALGORITHM = "aes-256-gcm";
130
+ var IV_LENGTH = 16;
131
+ var AUTH_TAG_LENGTH = 16;
132
+ function encrypt(text) {
133
+ const key = getMachineKey();
134
+ const iv = crypto.randomBytes(IV_LENGTH);
135
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
136
+ let encrypted = cipher.update(text, "utf8", "hex");
137
+ encrypted += cipher.final("hex");
138
+ const authTag = cipher.getAuthTag();
139
+ return iv.toString("hex") + authTag.toString("hex") + encrypted;
140
+ }
141
+ function decrypt(encryptedText) {
142
+ const key = getMachineKey();
143
+ const iv = Buffer.from(encryptedText.slice(0, IV_LENGTH * 2), "hex");
144
+ const authTag = Buffer.from(
145
+ encryptedText.slice(IV_LENGTH * 2, (IV_LENGTH + AUTH_TAG_LENGTH) * 2),
146
+ "hex"
147
+ );
148
+ const encrypted = encryptedText.slice((IV_LENGTH + AUTH_TAG_LENGTH) * 2);
149
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
150
+ decipher.setAuthTag(authTag);
151
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
152
+ decrypted += decipher.final("utf8");
153
+ return decrypted;
154
+ }
155
+
156
+ // src/utils/logger.ts
157
+ import fs from "fs";
158
+ import path from "path";
159
+ import os3 from "os";
160
+ var LOG_DIR = path.join(os3.homedir(), ".config", "stint", "logs");
161
+ var AGENT_LOG = path.join(LOG_DIR, "agent.log");
162
+ var ERROR_LOG = path.join(LOG_DIR, "error.log");
163
+ var MAX_LOG_SIZE = 10 * 1024 * 1024;
164
+ var MAX_LOG_FILES = 7;
165
+ var Logger = class {
166
+ ensureLogDir() {
167
+ if (!fs.existsSync(LOG_DIR)) {
168
+ fs.mkdirSync(LOG_DIR, { recursive: true });
169
+ }
170
+ }
171
+ rotateLog(logFile) {
172
+ if (!fs.existsSync(logFile)) return;
173
+ const stats = fs.statSync(logFile);
174
+ if (stats.size < MAX_LOG_SIZE) return;
175
+ for (let i = MAX_LOG_FILES - 1; i > 0; i--) {
176
+ const oldFile = `${logFile}.${i}`;
177
+ const newFile = `${logFile}.${i + 1}`;
178
+ if (fs.existsSync(oldFile)) {
179
+ if (i === MAX_LOG_FILES - 1) {
180
+ fs.unlinkSync(oldFile);
181
+ } else {
182
+ fs.renameSync(oldFile, newFile);
183
+ }
184
+ }
185
+ }
186
+ fs.renameSync(logFile, `${logFile}.1`);
187
+ }
188
+ writeLog(level, category, message, logFile) {
189
+ this.ensureLogDir();
190
+ this.rotateLog(logFile);
191
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
192
+ const logLine = `[${timestamp}] ${level.padEnd(5)} [${category}] ${message}
193
+ `;
194
+ fs.appendFileSync(logFile, logLine);
195
+ }
196
+ info(category, message) {
197
+ this.writeLog("INFO", category, message, AGENT_LOG);
198
+ console.log(`\u2139 [${category}] ${message}`);
199
+ }
200
+ warn(category, message) {
201
+ this.writeLog("WARN", category, message, AGENT_LOG);
202
+ console.warn(`\u26A0 [${category}] ${message}`);
203
+ }
204
+ error(category, message, error) {
205
+ const fullMessage = error ? `${message}: ${error.message}` : message;
206
+ this.writeLog("ERROR", category, fullMessage, ERROR_LOG);
207
+ this.writeLog("ERROR", category, fullMessage, AGENT_LOG);
208
+ console.error(`\u2716 [${category}] ${fullMessage}`);
209
+ if (error?.stack) {
210
+ this.writeLog("ERROR", category, error.stack, ERROR_LOG);
211
+ }
212
+ }
213
+ debug(category, message) {
214
+ if (process.env.DEBUG) {
215
+ this.writeLog("DEBUG", category, message, AGENT_LOG);
216
+ console.debug(`\u{1F41B} [${category}] ${message}`);
217
+ }
218
+ }
219
+ success(category, message) {
220
+ this.writeLog("INFO", category, message, AGENT_LOG);
221
+ console.log(`\u2713 [${category}] ${message}`);
222
+ }
223
+ };
224
+ var logger = new Logger();
225
+
226
+ // src/services/auth.ts
227
+ var AuthServiceImpl = class {
228
+ async saveToken(token) {
229
+ try {
230
+ const encryptedToken = encrypt(token);
231
+ config.setToken(encryptedToken);
232
+ logger.info("auth", "Token saved successfully");
233
+ } catch (error) {
234
+ logger.error("auth", "Failed to save token", error);
235
+ throw error;
236
+ }
237
+ }
238
+ async getToken() {
239
+ try {
240
+ const encryptedToken = config.getToken();
241
+ if (!encryptedToken) {
242
+ return null;
243
+ }
244
+ return decrypt(encryptedToken);
245
+ } catch (error) {
246
+ logger.error("auth", "Failed to decrypt token", error);
247
+ return null;
248
+ }
249
+ }
250
+ async clearToken() {
251
+ config.clearToken();
252
+ logger.info("auth", "Token cleared");
253
+ }
254
+ async validateToken() {
255
+ const token = await this.getToken();
256
+ if (!token) {
257
+ return null;
258
+ }
259
+ try {
260
+ const { apiService: apiService2 } = await import("./api-K3EUONWR.js");
261
+ const user = await apiService2.getCurrentUser();
262
+ logger.info("auth", `Token validated for user: ${user.email}`);
263
+ return user;
264
+ } catch (error) {
265
+ logger.warn("auth", "Token validation failed");
266
+ logger.error("auth", "Failed to validate token", error);
267
+ return null;
268
+ }
269
+ }
270
+ getMachineId() {
271
+ return config.getMachineId();
272
+ }
273
+ getMachineName() {
274
+ return config.getMachineName();
275
+ }
276
+ };
277
+ var authService = new AuthServiceImpl();
278
+
279
+ // src/services/api.ts
280
+ var require2 = createRequire(import.meta.url);
281
+ var packageJson = require2("../../package.json");
282
+ var AGENT_VERSION = packageJson.version;
283
+ var ApiServiceImpl = class {
284
+ sessionId = null;
285
+ async getHeaders() {
286
+ const token = await authService.getToken();
287
+ if (!token) {
288
+ throw new Error('No authentication token found. Please run "stint login" first.');
289
+ }
290
+ return {
291
+ Authorization: `Bearer ${token}`,
292
+ "Content-Type": "application/json"
293
+ };
294
+ }
295
+ async request(endpoint, options = {}) {
296
+ const url = `${config.getApiUrl()}${endpoint}`;
297
+ const headers = await this.getHeaders();
298
+ try {
299
+ const response = await fetch(url, {
300
+ ...options,
301
+ headers: {
302
+ ...headers,
303
+ ...options.headers
304
+ }
305
+ });
306
+ if (!response.ok) {
307
+ const errorText = await response.text();
308
+ throw new Error(`API request failed: ${response.status} ${errorText}`);
309
+ }
310
+ return await response.json();
311
+ } catch (error) {
312
+ logger.error("api", `Request to ${endpoint} failed`, error);
313
+ throw error;
314
+ }
315
+ }
316
+ /**
317
+ * Retry wrapper with exponential backoff
318
+ */
319
+ async withRetry(operation, operationName, maxRetries = 3) {
320
+ let lastError;
321
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
322
+ try {
323
+ return await operation();
324
+ } catch (error) {
325
+ lastError = error;
326
+ if (lastError.message.includes("401") || lastError.message.includes("403")) {
327
+ throw lastError;
328
+ }
329
+ if (attempt < maxRetries) {
330
+ const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 5e3);
331
+ logger.warn("api", `${operationName} failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
332
+ await new Promise((resolve) => setTimeout(resolve, delay));
333
+ } else {
334
+ logger.error("api", `${operationName} failed after ${maxRetries} attempts`);
335
+ }
336
+ }
337
+ }
338
+ throw lastError;
339
+ }
340
+ async connect() {
341
+ logger.info("api", "Connecting agent session...");
342
+ const os4 = `${process.platform}-${process.arch}`;
343
+ return this.withRetry(async () => {
344
+ const response = await this.request("/api/agent/connect", {
345
+ method: "POST",
346
+ body: JSON.stringify({
347
+ machine_id: authService.getMachineId(),
348
+ machine_name: authService.getMachineName(),
349
+ os: os4,
350
+ agent_version: AGENT_VERSION
351
+ })
352
+ });
353
+ const session = response.data;
354
+ this.sessionId = session.id;
355
+ logger.success("api", `Agent session connected: ${session.id}`);
356
+ return session;
357
+ }, "Connect");
358
+ }
359
+ async disconnect() {
360
+ if (!this.sessionId) {
361
+ return;
362
+ }
363
+ logger.info("api", "Disconnecting agent session...");
364
+ await this.request("/api/agent/disconnect", {
365
+ method: "POST",
366
+ body: JSON.stringify({
367
+ session_id: this.sessionId
368
+ })
369
+ });
370
+ this.sessionId = null;
371
+ logger.success("api", "Agent session disconnected");
372
+ }
373
+ async heartbeat() {
374
+ if (!this.sessionId) {
375
+ throw new Error("No active session");
376
+ }
377
+ await this.withRetry(async () => {
378
+ await this.request("/api/agent/heartbeat", {
379
+ method: "POST",
380
+ body: JSON.stringify({
381
+ session_id: this.sessionId
382
+ })
383
+ });
384
+ logger.debug("api", "Heartbeat sent");
385
+ }, "Heartbeat");
386
+ }
387
+ async getPendingCommits(projectId) {
388
+ logger.info("api", `Fetching pending commits for project ${projectId}`);
389
+ const commits = await this.request(
390
+ `/api/agent/projects/${projectId}/pending-commits`
391
+ );
392
+ logger.info("api", `Found ${commits.length} pending commits`);
393
+ return commits;
394
+ }
395
+ async markCommitExecuted(commitId, sha) {
396
+ logger.info("api", `Marking commit ${commitId} as executed (SHA: ${sha})`);
397
+ return this.withRetry(async () => {
398
+ const commit = await this.request(
399
+ `/api/agent/commits/${commitId}/executed`,
400
+ {
401
+ method: "POST",
402
+ body: JSON.stringify({ sha })
403
+ }
404
+ );
405
+ logger.success("api", `Commit ${commitId} marked as executed`);
406
+ return commit;
407
+ }, "Mark commit executed");
408
+ }
409
+ async markCommitFailed(commitId, error) {
410
+ logger.error("api", `Marking commit ${commitId} as failed: ${error}`);
411
+ await this.withRetry(async () => {
412
+ await this.request(`/api/agent/commits/${commitId}/failed`, {
413
+ method: "POST",
414
+ body: JSON.stringify({ error })
415
+ });
416
+ }, "Mark commit failed");
417
+ }
418
+ async syncProject(projectId, data) {
419
+ logger.info("api", `Syncing project ${projectId}`);
420
+ await this.withRetry(async () => {
421
+ await this.request(`/api/agent/projects/${projectId}/sync`, {
422
+ method: "POST",
423
+ body: JSON.stringify(data)
424
+ });
425
+ logger.success("api", `Project ${projectId} synced`);
426
+ }, "Sync project");
427
+ }
428
+ async getLinkedProjects() {
429
+ logger.info("api", "Fetching linked projects");
430
+ const projects = await this.request("/api/agent/projects");
431
+ logger.info("api", `Found ${projects.length} linked projects`);
432
+ return projects;
433
+ }
434
+ async getCurrentUser() {
435
+ logger.info("api", "Fetching current user");
436
+ const user = await this.request("/api/user");
437
+ logger.info("api", `Fetched user: ${user.email}`);
438
+ return user;
439
+ }
440
+ };
441
+ var apiService = new ApiServiceImpl();
442
+
443
+ export {
444
+ config,
445
+ logger,
446
+ apiService,
447
+ authService
448
+ };