termify-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/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # @termify/agent
2
+
3
+ Connect your local machine to Termify and access it from anywhere.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Using npm
9
+ npm install -g @termify/agent
10
+
11
+ # Using yarn
12
+ yarn global add @termify/agent
13
+
14
+ # Using pnpm
15
+ pnpm add -g @termify/agent
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Get a Pairing Code
21
+
22
+ 1. Go to [Termify Dashboard](https://app.termify.dev)
23
+ 2. Navigate to **Agents** in the sidebar
24
+ 3. Click **Pair New Agent**
25
+ 4. Copy the 6-digit pairing code
26
+
27
+ ### 2. Pair Your Machine
28
+
29
+ ```bash
30
+ termify-agent login --code XXXXXX
31
+ ```
32
+
33
+ ### 3. Start the Agent
34
+
35
+ ```bash
36
+ termify-agent start
37
+ ```
38
+
39
+ That's it! Your machine is now connected to Termify.
40
+
41
+ ## Commands
42
+
43
+ ### `termify-agent login --code <CODE>`
44
+
45
+ Pair this machine with your Termify account using a pairing code.
46
+
47
+ Options:
48
+ - `-c, --code <code>` - The 6-digit pairing code (required)
49
+ - `-s, --server <url>` - Custom server URL (optional)
50
+
51
+ ### `termify-agent start`
52
+
53
+ Start the agent and connect to Termify. The agent will:
54
+ - Connect to Termify servers via WebSocket
55
+ - Listen for terminal creation requests
56
+ - Spawn local PTY processes for each terminal
57
+ - Stream terminal I/O to/from Termify
58
+
59
+ Options:
60
+ - `-f, --foreground` - Run in foreground mode (default)
61
+
62
+ ### `termify-agent status`
63
+
64
+ Show the current status of the agent, including:
65
+ - Login status
66
+ - Machine information
67
+ - Connection details
68
+
69
+ ### `termify-agent logout`
70
+
71
+ Unpair this machine from Termify. This will:
72
+ - Disconnect from Termify servers
73
+ - Clear local credentials
74
+ - Remove the agent from your dashboard
75
+
76
+ ## Security
77
+
78
+ ### End-to-End Encryption
79
+
80
+ All communication between your machine and Termify is encrypted using TLS 1.3. Your terminal data never touches our servers unencrypted.
81
+
82
+ ### No Inbound Ports Required
83
+
84
+ The agent connects outbound to Termify. You don't need to:
85
+ - Open any ports on your firewall
86
+ - Configure port forwarding
87
+ - Expose your machine to the internet
88
+
89
+ ### Zero-Trust Architecture
90
+
91
+ - Each session uses short-lived authentication tokens
92
+ - Pairing codes expire after 5 minutes
93
+ - Tokens are stored securely in your system's credential store
94
+ - Each terminal session is individually authenticated
95
+
96
+ ### Data Privacy
97
+
98
+ - Terminal data is streamed directly, never stored on our servers
99
+ - Session metadata is retained for debugging purposes only
100
+ - You can disconnect and delete your agent at any time
101
+
102
+ ## Running as a Service
103
+
104
+ ### macOS (launchd)
105
+
106
+ ```bash
107
+ # Create a launchd plist
108
+ cat > ~/Library/LaunchAgents/com.termify.agent.plist << EOF
109
+ <?xml version="1.0" encoding="UTF-8"?>
110
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
111
+ <plist version="1.0">
112
+ <dict>
113
+ <key>Label</key>
114
+ <string>com.termify.agent</string>
115
+ <key>ProgramArguments</key>
116
+ <array>
117
+ <string>$(which termify-agent)</string>
118
+ <string>start</string>
119
+ </array>
120
+ <key>RunAtLoad</key>
121
+ <true/>
122
+ <key>KeepAlive</key>
123
+ <true/>
124
+ <key>StandardOutPath</key>
125
+ <string>/tmp/termify-agent.log</string>
126
+ <key>StandardErrorPath</key>
127
+ <string>/tmp/termify-agent.error.log</string>
128
+ </dict>
129
+ </plist>
130
+ EOF
131
+
132
+ # Load the service
133
+ launchctl load ~/Library/LaunchAgents/com.termify.agent.plist
134
+ ```
135
+
136
+ ### Linux (systemd)
137
+
138
+ ```bash
139
+ # Create a systemd service file
140
+ sudo cat > /etc/systemd/system/termify-agent.service << EOF
141
+ [Unit]
142
+ Description=Termify Agent
143
+ After=network.target
144
+
145
+ [Service]
146
+ Type=simple
147
+ User=$USER
148
+ ExecStart=$(which termify-agent) start
149
+ Restart=always
150
+ RestartSec=10
151
+
152
+ [Install]
153
+ WantedBy=multi-user.target
154
+ EOF
155
+
156
+ # Enable and start the service
157
+ sudo systemctl enable termify-agent
158
+ sudo systemctl start termify-agent
159
+ ```
160
+
161
+ ### Windows (Task Scheduler)
162
+
163
+ 1. Open Task Scheduler
164
+ 2. Create a new task
165
+ 3. Set trigger to "At log on"
166
+ 4. Set action to run `termify-agent start`
167
+ 5. Check "Run whether user is logged on or not"
168
+
169
+ ## Troubleshooting
170
+
171
+ ### "Not logged in" error
172
+
173
+ Run `termify-agent login --code <CODE>` to pair your machine.
174
+
175
+ ### "Connection refused" error
176
+
177
+ - Check your internet connection
178
+ - Verify the server URL is correct
179
+ - Check if your firewall is blocking outbound connections
180
+
181
+ ### Agent disconnects frequently
182
+
183
+ - Check your network stability
184
+ - The agent will automatically reconnect up to 10 times
185
+ - Check the logs for more details
186
+
187
+ ### Terminal not working
188
+
189
+ - Ensure your shell is properly configured
190
+ - Check if the shell path is correct: `echo $SHELL`
191
+ - Try setting a custom shell: `SHELL=/bin/bash termify-agent start`
192
+
193
+ ## Environment Variables
194
+
195
+ - `TERMIFY_SERVER_URL` - Override the default server URL
196
+ - `TERMIFY_WS_URL` - Override the WebSocket URL
197
+ - `SHELL` - Override the default shell
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import '../dist/index.js';
@@ -0,0 +1,67 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface AgentOptions {
3
+ daemon?: boolean;
4
+ }
5
+ /**
6
+ * Main agent class that coordinates WebSocket connection and PTY management
7
+ */
8
+ export declare class Agent extends EventEmitter {
9
+ private wsClient;
10
+ private ptyManager;
11
+ private isRunning;
12
+ private agentId;
13
+ private agentName;
14
+ constructor();
15
+ /**
16
+ * Setup event handlers between WebSocket client and PTY manager
17
+ */
18
+ private setupHandlers;
19
+ /**
20
+ * Start the agent
21
+ */
22
+ start(options?: AgentOptions): boolean;
23
+ /**
24
+ * Stop the agent
25
+ */
26
+ stop(): void;
27
+ /**
28
+ * Handle shutdown signals
29
+ */
30
+ private handleShutdown;
31
+ /**
32
+ * Check if agent is running
33
+ */
34
+ get running(): boolean;
35
+ /**
36
+ * Check if connected to server
37
+ */
38
+ get connected(): boolean;
39
+ /**
40
+ * Get current agent ID
41
+ */
42
+ get id(): string | null;
43
+ /**
44
+ * Get current agent name
45
+ */
46
+ get name(): string | null;
47
+ /**
48
+ * Get number of active terminals
49
+ */
50
+ get terminalCount(): number;
51
+ /**
52
+ * Get status information
53
+ */
54
+ getStatus(): {
55
+ running: boolean;
56
+ connected: boolean;
57
+ agentId: string | null;
58
+ agentName: string | null;
59
+ terminalCount: number;
60
+ config: {
61
+ serverUrl: string;
62
+ machineId: string;
63
+ lastConnected: string | null;
64
+ };
65
+ };
66
+ }
67
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,qBAAa,KAAM,SAAQ,YAAY;IACrC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,SAAS,CAAuB;;IASxC;;OAEG;IACH,OAAO,CAAC,aAAa;IA2DrB;;OAEG;IACH,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO;IAwB1C;;OAEG;IACH,IAAI,IAAI,IAAI;IAiBZ;;OAEG;IACH,OAAO,CAAC,cAAc;IAMtB;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;OAEG;IACH,IAAI,EAAE,IAAI,MAAM,GAAG,IAAI,CAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAExB;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;OAEG;IACH,SAAS,IAAI;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,OAAO,CAAC;QACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,MAAM,EAAE;YACN,SAAS,EAAE,MAAM,CAAC;YAClB,SAAS,EAAE,MAAM,CAAC;YAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;SAC9B,CAAC;KACH;CAeF"}
package/dist/agent.js ADDED
@@ -0,0 +1,167 @@
1
+ import { EventEmitter } from 'events';
2
+ import { WSClient } from './ws-client.js';
3
+ import { PTYManager } from './pty-manager.js';
4
+ import { getConfig, isConfigured } from './config.js';
5
+ import { logger } from './utils/logger.js';
6
+ /**
7
+ * Main agent class that coordinates WebSocket connection and PTY management
8
+ */
9
+ export class Agent extends EventEmitter {
10
+ wsClient;
11
+ ptyManager;
12
+ isRunning = false;
13
+ agentId = null;
14
+ agentName = null;
15
+ constructor() {
16
+ super();
17
+ this.wsClient = new WSClient();
18
+ this.ptyManager = new PTYManager();
19
+ this.setupHandlers();
20
+ }
21
+ /**
22
+ * Setup event handlers between WebSocket client and PTY manager
23
+ */
24
+ setupHandlers() {
25
+ // WebSocket -> PTY: Handle server requests
26
+ this.wsClient.on('terminalCreate', (terminalId, cols, rows, options) => {
27
+ logger.info(`Creating terminal ${terminalId}`);
28
+ const created = this.ptyManager.create(terminalId, {
29
+ cols,
30
+ rows,
31
+ cwd: options.cwd,
32
+ shell: options.shell,
33
+ env: options.env,
34
+ });
35
+ if (created) {
36
+ this.wsClient.sendTerminalStatus(terminalId, 'RUNNING');
37
+ }
38
+ else {
39
+ this.wsClient.sendTerminalStatus(terminalId, 'CRASHED', 'Failed to create terminal');
40
+ }
41
+ });
42
+ this.wsClient.on('terminalInput', (terminalId, data) => {
43
+ this.ptyManager.write(terminalId, data);
44
+ });
45
+ this.wsClient.on('terminalResize', (terminalId, cols, rows) => {
46
+ this.ptyManager.resize(terminalId, cols, rows);
47
+ });
48
+ this.wsClient.on('terminalStop', (terminalId) => {
49
+ this.ptyManager.kill(terminalId);
50
+ });
51
+ // PTY -> WebSocket: Forward output to server
52
+ this.ptyManager.setDataHandler((terminalId, data) => {
53
+ this.wsClient.sendTerminalOutput(terminalId, data);
54
+ });
55
+ this.ptyManager.setExitHandler((terminalId, exitCode) => {
56
+ this.wsClient.sendTerminalExit(terminalId, exitCode);
57
+ this.wsClient.sendTerminalStatus(terminalId, 'STOPPED');
58
+ });
59
+ // Connection events
60
+ this.wsClient.on('connected', (agentId, agentName) => {
61
+ this.agentId = agentId;
62
+ this.agentName = agentName;
63
+ this.emit('connected', agentId, agentName);
64
+ });
65
+ this.wsClient.on('disconnected', (reason) => {
66
+ this.emit('disconnected', reason);
67
+ });
68
+ this.wsClient.on('authFailed', (error) => {
69
+ logger.error(`Authentication failed: ${error}`);
70
+ this.emit('authFailed', error);
71
+ this.stop();
72
+ });
73
+ }
74
+ /**
75
+ * Start the agent
76
+ */
77
+ start(options = {}) {
78
+ if (!isConfigured()) {
79
+ logger.error('Agent not configured. Run `termify-agent login` first.');
80
+ return false;
81
+ }
82
+ if (this.isRunning) {
83
+ logger.warn('Agent is already running');
84
+ return false;
85
+ }
86
+ this.isRunning = true;
87
+ logger.info('Starting Termify Agent...');
88
+ // Setup signal handlers for graceful shutdown
89
+ process.on('SIGINT', () => this.handleShutdown('SIGINT'));
90
+ process.on('SIGTERM', () => this.handleShutdown('SIGTERM'));
91
+ // Connect to server
92
+ this.wsClient.connect();
93
+ return true;
94
+ }
95
+ /**
96
+ * Stop the agent
97
+ */
98
+ stop() {
99
+ if (!this.isRunning) {
100
+ return;
101
+ }
102
+ logger.info('Stopping agent...');
103
+ this.isRunning = false;
104
+ // Kill all terminals
105
+ this.ptyManager.killAll();
106
+ // Disconnect from server
107
+ this.wsClient.disconnect();
108
+ this.emit('stopped');
109
+ }
110
+ /**
111
+ * Handle shutdown signals
112
+ */
113
+ handleShutdown(signal) {
114
+ logger.info(`Received ${signal}, shutting down...`);
115
+ this.stop();
116
+ process.exit(0);
117
+ }
118
+ /**
119
+ * Check if agent is running
120
+ */
121
+ get running() {
122
+ return this.isRunning;
123
+ }
124
+ /**
125
+ * Check if connected to server
126
+ */
127
+ get connected() {
128
+ return this.wsClient.connected;
129
+ }
130
+ /**
131
+ * Get current agent ID
132
+ */
133
+ get id() {
134
+ return this.agentId;
135
+ }
136
+ /**
137
+ * Get current agent name
138
+ */
139
+ get name() {
140
+ return this.agentName;
141
+ }
142
+ /**
143
+ * Get number of active terminals
144
+ */
145
+ get terminalCount() {
146
+ return this.ptyManager.count;
147
+ }
148
+ /**
149
+ * Get status information
150
+ */
151
+ getStatus() {
152
+ const config = getConfig();
153
+ return {
154
+ running: this.isRunning,
155
+ connected: this.wsClient.connected,
156
+ agentId: this.agentId,
157
+ agentName: this.agentName,
158
+ terminalCount: this.ptyManager.count,
159
+ config: {
160
+ serverUrl: config.serverUrl,
161
+ machineId: config.machineId,
162
+ lastConnected: config.lastConnected,
163
+ },
164
+ };
165
+ }
166
+ }
167
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
package/dist/auth.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Interactive login flow
3
+ * Opens browser to get pairing code, then polls for verification
4
+ */
5
+ export declare function login(options: {
6
+ serverUrl?: string;
7
+ pairingCode?: string;
8
+ }): Promise<boolean>;
9
+ /**
10
+ * Logout - clear stored credentials
11
+ */
12
+ export declare function logout(): Promise<void>;
13
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAgEA;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,CAAC,CAuFnB;AAED;;GAEG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAK5C"}
package/dist/auth.js ADDED
@@ -0,0 +1,140 @@
1
+ import open from 'open';
2
+ import ora from 'ora';
3
+ import chalk from 'chalk';
4
+ import { getConfig, setAccessToken, setServerUrl } from './config.js';
5
+ import { getMachineId, getSystemInfo } from './utils/machine-id.js';
6
+ import { logger } from './utils/logger.js';
7
+ const VERSION = '1.0.0';
8
+ /**
9
+ * Attempt to verify a pairing code with the server
10
+ */
11
+ async function verifyPairingCode(serverUrl, pairingCode) {
12
+ const systemInfo = getSystemInfo();
13
+ const machineId = getMachineId();
14
+ try {
15
+ const response = await fetch(`${serverUrl}/api/agents/verify`, {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ body: JSON.stringify({
21
+ pairingCode,
22
+ machineId,
23
+ os: systemInfo.os,
24
+ hostname: systemInfo.hostname,
25
+ arch: systemInfo.arch,
26
+ version: VERSION,
27
+ }),
28
+ });
29
+ if (response.status === 404) {
30
+ return null; // Invalid pairing code
31
+ }
32
+ if (response.status === 410) {
33
+ throw new Error('Pairing code has expired. Please generate a new one.');
34
+ }
35
+ if (!response.ok) {
36
+ const errorData = await response.json().catch(() => ({}));
37
+ throw new Error(errorData.error || `Server error: ${response.status}`);
38
+ }
39
+ return await response.json();
40
+ }
41
+ catch (error) {
42
+ if (error instanceof Error && error.message.includes('expired')) {
43
+ throw error;
44
+ }
45
+ logger.debug(`Verify attempt failed: ${error}`);
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Interactive login flow
51
+ * Opens browser to get pairing code, then polls for verification
52
+ */
53
+ export async function login(options) {
54
+ const serverUrl = options.serverUrl || getConfig().serverUrl;
55
+ if (options.serverUrl) {
56
+ setServerUrl(options.serverUrl);
57
+ }
58
+ console.log(chalk.bold('\nšŸ” Termify Agent Login\n'));
59
+ // If pairing code provided directly, verify it
60
+ if (options.pairingCode) {
61
+ const spinner = ora('Verifying pairing code...').start();
62
+ try {
63
+ const result = await verifyPairingCode(serverUrl, options.pairingCode);
64
+ if (result) {
65
+ setAccessToken(result.accessToken, result.agentId);
66
+ spinner.succeed(chalk.green('Login successful!'));
67
+ console.log(chalk.gray(` Agent ID: ${result.agentId}`));
68
+ return true;
69
+ }
70
+ else {
71
+ spinner.fail(chalk.red('Invalid pairing code'));
72
+ return false;
73
+ }
74
+ }
75
+ catch (error) {
76
+ spinner.fail(chalk.red(error instanceof Error ? error.message : 'Login failed'));
77
+ return false;
78
+ }
79
+ }
80
+ // Otherwise, open browser for pairing
81
+ const pairingUrl = serverUrl.replace('/api', '').replace('api.', 'app.') + '/agents/pair';
82
+ console.log(chalk.gray('Opening browser to get a pairing code...\n'));
83
+ console.log(chalk.gray(` If browser doesn't open, visit:`));
84
+ console.log(chalk.cyan(` ${pairingUrl}\n`));
85
+ try {
86
+ await open(pairingUrl);
87
+ }
88
+ catch {
89
+ logger.warn('Could not open browser automatically');
90
+ }
91
+ // Prompt user for the pairing code
92
+ console.log(chalk.yellow('Enter the 6-digit pairing code from the web app:'));
93
+ // Read from stdin
94
+ const readline = await import('readline');
95
+ const rl = readline.createInterface({
96
+ input: process.stdin,
97
+ output: process.stdout,
98
+ });
99
+ return new Promise((resolve) => {
100
+ rl.question(chalk.bold('> '), async (code) => {
101
+ rl.close();
102
+ const cleanCode = code.trim();
103
+ if (!/^\d{6}$/.test(cleanCode)) {
104
+ console.log(chalk.red('\nInvalid code format. Must be 6 digits.'));
105
+ resolve(false);
106
+ return;
107
+ }
108
+ const spinner = ora('Verifying pairing code...').start();
109
+ try {
110
+ const result = await verifyPairingCode(serverUrl, cleanCode);
111
+ if (result) {
112
+ setAccessToken(result.accessToken, result.agentId);
113
+ spinner.succeed(chalk.green('Login successful!'));
114
+ console.log(chalk.gray(` Agent ID: ${result.agentId}`));
115
+ console.log(chalk.gray(` Config saved to: ${(await import('./config.js')).getConfigPath()}`));
116
+ console.log(chalk.cyan('\nRun `termify-agent start` to connect.\n'));
117
+ resolve(true);
118
+ }
119
+ else {
120
+ spinner.fail(chalk.red('Invalid pairing code. Please try again.'));
121
+ resolve(false);
122
+ }
123
+ }
124
+ catch (error) {
125
+ spinner.fail(chalk.red(error instanceof Error ? error.message : 'Login failed'));
126
+ resolve(false);
127
+ }
128
+ });
129
+ });
130
+ }
131
+ /**
132
+ * Logout - clear stored credentials
133
+ */
134
+ export async function logout() {
135
+ const { clearConfig, getConfigPath } = await import('./config.js');
136
+ clearConfig();
137
+ console.log(chalk.green('āœ“ Logged out successfully'));
138
+ console.log(chalk.gray(` Config cleared from: ${getConfigPath()}`));
139
+ }
140
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface AgentConfig {
2
+ accessToken: string | null;
3
+ agentId: string | null;
4
+ machineId: string;
5
+ serverUrl: string;
6
+ wsUrl: string;
7
+ lastConnected: string | null;
8
+ }
9
+ export declare function getConfig(): AgentConfig;
10
+ export declare function setAccessToken(token: string, agentId: string): void;
11
+ export declare function setServerUrl(url: string): void;
12
+ export declare function setLastConnected(): void;
13
+ export declare function clearConfig(): void;
14
+ export declare function isConfigured(): boolean;
15
+ export declare function getConfigPath(): string;
16
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAiBD,wBAAgB,SAAS,IAAI,WAAW,CASvC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGnE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAK9C;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAIlC;AAED,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}