epistery 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/Architecture.md +84 -0
- package/CLI.md +291 -0
- package/LICENSE +21 -0
- package/MONGODB_GOTCHA.md +69 -0
- package/README.md +50 -0
- package/SESSION.md +98 -0
- package/cli/epistery.mjs +576 -0
- package/client/client.js +18 -0
- package/client/ethers.js +24441 -0
- package/client/ethers.min.js +1 -0
- package/client/export.js +67 -0
- package/client/status.html +707 -0
- package/client/wallet.js +213 -0
- package/client/witness.js +663 -0
- package/contracts/agent.sol +108 -0
- package/default.ini +14 -0
- package/docs/EpisteryModuleConfig.md +317 -0
- package/docs/blog-unified-config.md +125 -0
- package/hardhat.config.js +33 -0
- package/index.mjs +385 -0
- package/package.json +46 -0
- package/scripts/deploy-agent.js +33 -0
- package/scripts/verify-agent.js +39 -0
- package/src/epistery.ts +275 -0
- package/src/utils/Aqua.ts +194 -0
- package/src/utils/CliWallet.ts +334 -0
- package/src/utils/Config.ts +196 -0
- package/src/utils/Utils.ts +571 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/types.ts +114 -0
- package/test/README.md +50 -0
- package/test/index.html +13 -0
- package/test/package.json +15 -0
- package/test/server.mjs +87 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { ethers, Wallet } from 'ethers';
|
|
2
|
+
import { Config } from './Config';
|
|
3
|
+
import { DomainConfig } from './types';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CliWallet - Manages wallet operations for CLI/bot contexts
|
|
9
|
+
*
|
|
10
|
+
* Uses Epistery's domain configuration system:
|
|
11
|
+
* - Domain configs stored in ~/.epistery/{domain}/config.ini
|
|
12
|
+
* - Each domain has its own wallet (like server-side)
|
|
13
|
+
* - Default domain configurable in ~/.epistery/config.ini [cli] section
|
|
14
|
+
* - Automatic wallet creation on initialize
|
|
15
|
+
*
|
|
16
|
+
* This matches the server-side model where each domain has a wallet,
|
|
17
|
+
* making CLI usage consistent with server architecture.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface KeyExchangeRequest {
|
|
21
|
+
clientAddress: string;
|
|
22
|
+
clientPublicKey: string;
|
|
23
|
+
challenge: string;
|
|
24
|
+
message: string;
|
|
25
|
+
signature: string;
|
|
26
|
+
walletSource: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface KeyExchangeResponse {
|
|
30
|
+
serverAddress: string;
|
|
31
|
+
serverPublicKey: string;
|
|
32
|
+
services: string[];
|
|
33
|
+
challenge: string;
|
|
34
|
+
signature: string;
|
|
35
|
+
identified: boolean;
|
|
36
|
+
authenticated?: boolean;
|
|
37
|
+
profile?: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SessionInfo {
|
|
41
|
+
domain: string;
|
|
42
|
+
cookie: string;
|
|
43
|
+
authenticated: boolean;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class CliWallet {
|
|
48
|
+
private config: Config;
|
|
49
|
+
private domainName: string;
|
|
50
|
+
private domainConfig: DomainConfig;
|
|
51
|
+
private wallet: Wallet;
|
|
52
|
+
public address: string;
|
|
53
|
+
public publicKey: string;
|
|
54
|
+
|
|
55
|
+
private constructor(config: Config, domainName: string, domainConfig: DomainConfig, wallet: Wallet) {
|
|
56
|
+
this.config = config;
|
|
57
|
+
this.domainName = domainName.toLowerCase();
|
|
58
|
+
this.domainConfig = domainConfig;
|
|
59
|
+
this.wallet = wallet;
|
|
60
|
+
this.address = wallet.address;
|
|
61
|
+
this.publicKey = wallet.publicKey;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the default domain from config.ini [cli] section
|
|
66
|
+
*/
|
|
67
|
+
static getDefaultDomain(): string {
|
|
68
|
+
const config = new Config();
|
|
69
|
+
return (config.data as any).cli?.default_domain || 'localhost';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set the default domain in config.ini [cli] section
|
|
74
|
+
*/
|
|
75
|
+
static setDefaultDomain(domain: string): void {
|
|
76
|
+
const config = new Config();
|
|
77
|
+
if (!(config.data as any).cli) {
|
|
78
|
+
(config.data as any).cli = {};
|
|
79
|
+
}
|
|
80
|
+
(config.data as any).cli.default_domain = domain;
|
|
81
|
+
config.save();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Initialize a new domain with wallet
|
|
86
|
+
* Creates ~/.epistery/{domain}/config.ini with new wallet
|
|
87
|
+
*/
|
|
88
|
+
static initialize(domain: string, provider?: { name: string, chainId: number, rpc: string }): CliWallet {
|
|
89
|
+
const config = new Config();
|
|
90
|
+
|
|
91
|
+
// Check if domain already exists
|
|
92
|
+
config.setPath(`/${domain}`);
|
|
93
|
+
config.load();
|
|
94
|
+
if (config.data.wallet) {
|
|
95
|
+
throw new Error(`Domain '${domain}' already initialized. Use load() to access it.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Create new wallet
|
|
99
|
+
const ethersWallet = ethers.Wallet.createRandom();
|
|
100
|
+
|
|
101
|
+
// Get provider from root default, argument, or fallback default
|
|
102
|
+
config.setPath('/');
|
|
103
|
+
config.load();
|
|
104
|
+
const providerConfig = provider || config.data.default?.provider || {
|
|
105
|
+
chainId: 420420422,
|
|
106
|
+
name: 'polkadot-hub-testnet',
|
|
107
|
+
rpc: 'https://testnet-passet-hub-eth-rpc.polkadot.io'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Create domain config
|
|
111
|
+
config.setPath(`/${domain}`);
|
|
112
|
+
config.data = {
|
|
113
|
+
domain: domain,
|
|
114
|
+
wallet: {
|
|
115
|
+
address: ethersWallet.address,
|
|
116
|
+
mnemonic: ethersWallet.mnemonic?.phrase || '',
|
|
117
|
+
publicKey: ethersWallet.publicKey,
|
|
118
|
+
privateKey: ethersWallet.privateKey
|
|
119
|
+
},
|
|
120
|
+
provider: providerConfig
|
|
121
|
+
};
|
|
122
|
+
config.save();
|
|
123
|
+
|
|
124
|
+
console.log(`Initialized domain: ${domain}`);
|
|
125
|
+
console.log(`Address: ${ethersWallet.address}`);
|
|
126
|
+
console.log(`Provider: ${providerConfig.name}`);
|
|
127
|
+
|
|
128
|
+
return new CliWallet(config, domain, config.data, ethersWallet);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load domain wallet from config
|
|
133
|
+
* Throws if domain doesn't exist - use initialize() first
|
|
134
|
+
*/
|
|
135
|
+
static load(domain?: string): CliWallet {
|
|
136
|
+
const config = new Config();
|
|
137
|
+
const domainName = domain || CliWallet.getDefaultDomain();
|
|
138
|
+
|
|
139
|
+
config.setPath(`/${domainName}`);
|
|
140
|
+
config.load();
|
|
141
|
+
|
|
142
|
+
if (!config.data.wallet) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Domain '${domainName}' not found or has no wallet. ` +
|
|
145
|
+
`Initialize with: epistery initialize ${domainName}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Reconstruct wallet from config
|
|
150
|
+
let ethersWallet: Wallet;
|
|
151
|
+
if (config.data.wallet.mnemonic) {
|
|
152
|
+
ethersWallet = ethers.Wallet.fromMnemonic(config.data.wallet.mnemonic);
|
|
153
|
+
} else if (config.data.wallet.privateKey) {
|
|
154
|
+
ethersWallet = new ethers.Wallet(config.data.wallet.privateKey);
|
|
155
|
+
} else {
|
|
156
|
+
throw new Error(`Domain '${domainName}' wallet has no mnemonic or privateKey`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new CliWallet(config, domainName, config.data, ethersWallet);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get domain name
|
|
164
|
+
*/
|
|
165
|
+
getDomain(): string {
|
|
166
|
+
return this.domainName;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get provider info
|
|
171
|
+
*/
|
|
172
|
+
getProvider() {
|
|
173
|
+
return this.domainConfig.provider;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Sign a message
|
|
178
|
+
*/
|
|
179
|
+
async sign(message: string): Promise<string> {
|
|
180
|
+
return await this.wallet.signMessage(message);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Perform key exchange with an Epistery server
|
|
185
|
+
* Automatically saves session cookie to domain config
|
|
186
|
+
*/
|
|
187
|
+
async performKeyExchange(serverUrl: string): Promise<KeyExchangeResponse> {
|
|
188
|
+
// Ensure server URL is properly formatted
|
|
189
|
+
const baseUrl = serverUrl.replace(/\/$/, '');
|
|
190
|
+
const connectUrl = `${baseUrl}/.well-known/epistery/connect`;
|
|
191
|
+
|
|
192
|
+
// Generate challenge for key exchange
|
|
193
|
+
const challenge = ethers.utils.hexlify(ethers.utils.randomBytes(32));
|
|
194
|
+
const message = `Epistery Key Exchange - ${this.address} - ${challenge}`;
|
|
195
|
+
|
|
196
|
+
// Sign the message
|
|
197
|
+
const signature = await this.sign(message);
|
|
198
|
+
|
|
199
|
+
// Prepare key exchange request
|
|
200
|
+
const requestData: KeyExchangeRequest = {
|
|
201
|
+
clientAddress: this.address,
|
|
202
|
+
clientPublicKey: this.publicKey,
|
|
203
|
+
challenge: challenge,
|
|
204
|
+
message: message,
|
|
205
|
+
signature: signature,
|
|
206
|
+
walletSource: 'server'
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Perform key exchange
|
|
210
|
+
const response = await fetch(connectUrl, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: {
|
|
213
|
+
'Content-Type': 'application/json'
|
|
214
|
+
},
|
|
215
|
+
body: JSON.stringify(requestData)
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
const errorText = await response.text();
|
|
220
|
+
throw new Error(`Key exchange failed: ${response.status} - ${errorText}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const serverResponse = await response.json() as KeyExchangeResponse;
|
|
224
|
+
|
|
225
|
+
// Verify server's identity
|
|
226
|
+
const expectedMessage = `Epistery Server Response - ${serverResponse.serverAddress} - ${serverResponse.challenge}`;
|
|
227
|
+
const recoveredAddress = ethers.utils.verifyMessage(expectedMessage, serverResponse.signature);
|
|
228
|
+
|
|
229
|
+
if (recoveredAddress.toLowerCase() !== serverResponse.serverAddress.toLowerCase()) {
|
|
230
|
+
throw new Error('Server identity verification failed');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Extract and save session cookie if present
|
|
234
|
+
const cookies = response.headers.get('set-cookie');
|
|
235
|
+
if (cookies) {
|
|
236
|
+
const sessionMatch = cookies.match(/_rhonda_session=([^;]+)/);
|
|
237
|
+
if (sessionMatch) {
|
|
238
|
+
const sessionToken = sessionMatch[1];
|
|
239
|
+
|
|
240
|
+
// Save session to domain config
|
|
241
|
+
this.saveSession({
|
|
242
|
+
domain: serverUrl,
|
|
243
|
+
cookie: sessionToken,
|
|
244
|
+
authenticated: serverResponse.authenticated || false,
|
|
245
|
+
timestamp: new Date().toISOString()
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return serverResponse;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get saved session for a specific server URL
|
|
255
|
+
*/
|
|
256
|
+
getSession(serverUrl: string): SessionInfo | null {
|
|
257
|
+
const sessionFile = this.getSessionFilePath(serverUrl);
|
|
258
|
+
if (!fs.existsSync(sessionFile)) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const data = fs.readFileSync(sessionFile, 'utf8');
|
|
264
|
+
return JSON.parse(data) as SessionInfo;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Save session info to domain directory, keyed by server URL
|
|
272
|
+
*/
|
|
273
|
+
private saveSession(session: SessionInfo): void {
|
|
274
|
+
const sessionFile = this.getSessionFilePath(session.domain);
|
|
275
|
+
fs.writeFileSync(sessionFile, JSON.stringify(session, null, 2), { mode: 0o600 });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Clear saved session for a server URL
|
|
280
|
+
*/
|
|
281
|
+
clearSession(serverUrl: string): void {
|
|
282
|
+
const sessionFile = this.getSessionFilePath(serverUrl);
|
|
283
|
+
if (fs.existsSync(sessionFile)) {
|
|
284
|
+
fs.unlinkSync(sessionFile);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get session file path for a server URL
|
|
290
|
+
* Hashes the server URL to create a safe filename
|
|
291
|
+
*/
|
|
292
|
+
private getSessionFilePath(serverUrl: string): string {
|
|
293
|
+
// Create a safe filename from the server URL
|
|
294
|
+
const crypto = require('crypto');
|
|
295
|
+
const hash = crypto.createHash('md5').update(serverUrl).digest('hex');
|
|
296
|
+
const sessionsDir = join(this.config.configDir, this.domainName, 'sessions');
|
|
297
|
+
|
|
298
|
+
// Ensure sessions directory exists
|
|
299
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
300
|
+
fs.mkdirSync(sessionsDir, { mode: 0o700, recursive: true });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return join(sessionsDir, `${hash}.json`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Create bot authentication header
|
|
308
|
+
* Format: Authorization: Bot <base64-json>
|
|
309
|
+
*/
|
|
310
|
+
async createBotAuthHeader(): Promise<string> {
|
|
311
|
+
const message = `Rhonda Bot Authentication - ${new Date().toISOString()}`;
|
|
312
|
+
const signature = await this.sign(message);
|
|
313
|
+
|
|
314
|
+
const payload = {
|
|
315
|
+
address: this.address,
|
|
316
|
+
signature,
|
|
317
|
+
message
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return `Bot ${Buffer.from(JSON.stringify(payload)).toString('base64')}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Export wallet data (for migration or backup)
|
|
325
|
+
*/
|
|
326
|
+
toJSON() {
|
|
327
|
+
return {
|
|
328
|
+
domain: this.domainName,
|
|
329
|
+
address: this.wallet.address,
|
|
330
|
+
publicKey: this.wallet.publicKey,
|
|
331
|
+
provider: this.domainConfig.provider
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import ini from 'ini';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Epistery Config - Path-based configuration system
|
|
7
|
+
*
|
|
8
|
+
* Provides unified, filesystem-like config management:
|
|
9
|
+
* - setPath('/') → ~/.epistery/config.ini
|
|
10
|
+
* - setPath('/domain') → ~/.epistery/domain/config.ini
|
|
11
|
+
* - setPath('/.ssl/domain') → ~/.epistery/.ssl/domain/config.ini
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const config = new Config('epistery');
|
|
15
|
+
* config.setPath('/wiki.rootz.global');
|
|
16
|
+
* config.load();
|
|
17
|
+
* config.data.verified = true;
|
|
18
|
+
* config.save();
|
|
19
|
+
*/
|
|
20
|
+
export class Config {
|
|
21
|
+
public readonly rootName: string;
|
|
22
|
+
public readonly homeDir: string;
|
|
23
|
+
public readonly configDir: string;
|
|
24
|
+
|
|
25
|
+
private currentPath: string = '/';
|
|
26
|
+
private currentDir: string;
|
|
27
|
+
private currentFile: string;
|
|
28
|
+
|
|
29
|
+
public data: any = {};
|
|
30
|
+
|
|
31
|
+
constructor(rootName: string = 'epistery') {
|
|
32
|
+
this.rootName = rootName;
|
|
33
|
+
this.homeDir = (process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME) || '';
|
|
34
|
+
this.configDir = join(this.homeDir, '.' + this.rootName);
|
|
35
|
+
|
|
36
|
+
this.currentDir = this.configDir;
|
|
37
|
+
this.currentFile = join(this.configDir, 'config.ini');
|
|
38
|
+
|
|
39
|
+
// Initialize root config if it doesn't exist
|
|
40
|
+
if (!fs.existsSync(this.currentFile)) {
|
|
41
|
+
this.initialize();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set current working path (like cd)
|
|
47
|
+
* Examples: '/', '/domain', '/.ssl/domain'
|
|
48
|
+
*/
|
|
49
|
+
public setPath(path: string): void {
|
|
50
|
+
// Normalize path: ensure leading slash, remove trailing slash, lowercase
|
|
51
|
+
path = path.trim();
|
|
52
|
+
if (!path.startsWith('/')) path = '/' + path;
|
|
53
|
+
if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1);
|
|
54
|
+
path = path.toLowerCase();
|
|
55
|
+
|
|
56
|
+
this.currentPath = path;
|
|
57
|
+
|
|
58
|
+
// Calculate directory and file paths
|
|
59
|
+
if (path === '/') {
|
|
60
|
+
this.currentDir = this.configDir;
|
|
61
|
+
this.currentFile = join(this.configDir, 'config.ini');
|
|
62
|
+
} else {
|
|
63
|
+
this.currentDir = join(this.configDir, path.slice(1)); // Remove leading /
|
|
64
|
+
this.currentFile = join(this.currentDir, 'config.ini');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get current path
|
|
70
|
+
*/
|
|
71
|
+
public getPath(): string {
|
|
72
|
+
return this.currentPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Initialize config at current path
|
|
77
|
+
*/
|
|
78
|
+
private initialize(): void {
|
|
79
|
+
if (!fs.existsSync(this.currentDir)) {
|
|
80
|
+
fs.mkdirSync(this.currentDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Write default config for root, empty for paths
|
|
84
|
+
const defaultContent = this.currentPath === '/' ? defaultIni : '';
|
|
85
|
+
fs.writeFileSync(this.currentFile, defaultContent);
|
|
86
|
+
this.data = ini.decode(defaultContent);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Load config from current path
|
|
91
|
+
*/
|
|
92
|
+
public load(): void {
|
|
93
|
+
if (!fs.existsSync(this.currentFile)) {
|
|
94
|
+
this.data = {};
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const fileData = fs.readFileSync(this.currentFile, 'utf8');
|
|
99
|
+
this.data = ini.decode(fileData);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Read config from arbitrary path without changing current path
|
|
104
|
+
* @param path Path to read from (e.g., '/', '/domain')
|
|
105
|
+
* @returns Parsed config data from that path
|
|
106
|
+
*/
|
|
107
|
+
public read(path: string): any {
|
|
108
|
+
// Normalize path
|
|
109
|
+
path = path.trim();
|
|
110
|
+
if (!path.startsWith('/')) path = '/' + path;
|
|
111
|
+
if (path.length > 1 && path.endsWith('/')) path = path.slice(0, -1);
|
|
112
|
+
path = path.toLowerCase();
|
|
113
|
+
|
|
114
|
+
// Calculate file location
|
|
115
|
+
let configFile: string;
|
|
116
|
+
if (path === '/') {
|
|
117
|
+
configFile = join(this.configDir, 'config.ini');
|
|
118
|
+
} else {
|
|
119
|
+
configFile = join(this.configDir, path.slice(1), 'config.ini');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read and parse
|
|
123
|
+
if (!fs.existsSync(configFile)) {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const fileData = fs.readFileSync(configFile, 'utf8');
|
|
128
|
+
return ini.decode(fileData);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Save config to current path
|
|
133
|
+
*/
|
|
134
|
+
public save(): void {
|
|
135
|
+
if (!fs.existsSync(this.currentDir)) {
|
|
136
|
+
fs.mkdirSync(this.currentDir, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const text = ini.stringify(this.data);
|
|
140
|
+
fs.writeFileSync(this.currentFile, text);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Read file from current path directory
|
|
145
|
+
*/
|
|
146
|
+
public readFile(filename: string): Buffer {
|
|
147
|
+
return fs.readFileSync(join(this.currentDir, filename));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Write file to current path directory
|
|
152
|
+
*/
|
|
153
|
+
public writeFile(filename: string, data: string | Buffer): void {
|
|
154
|
+
if (!fs.existsSync(this.currentDir)) {
|
|
155
|
+
fs.mkdirSync(this.currentDir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
fs.writeFileSync(join(this.currentDir, filename), data);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if config exists at current path
|
|
162
|
+
*/
|
|
163
|
+
public exists(): boolean {
|
|
164
|
+
return fs.existsSync(this.currentFile);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* List all subdirectories at current path
|
|
169
|
+
*/
|
|
170
|
+
public listPaths(): string[] {
|
|
171
|
+
if (!fs.existsSync(this.currentDir)) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return fs.readdirSync(this.currentDir, { withFileTypes: true })
|
|
176
|
+
.filter(dirent => dirent.isDirectory())
|
|
177
|
+
.map(dirent => dirent.name);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const defaultIni =
|
|
182
|
+
`[profile]
|
|
183
|
+
name=
|
|
184
|
+
email=
|
|
185
|
+
|
|
186
|
+
[ipfs]
|
|
187
|
+
url=https://rootz.digital/api/v0
|
|
188
|
+
|
|
189
|
+
[default.provider]
|
|
190
|
+
chainId=1,
|
|
191
|
+
name=Ethereum Mainnet
|
|
192
|
+
rpc=https://eth.llamarpc.com
|
|
193
|
+
nativeCurrencyName=Ether
|
|
194
|
+
nativeCurrencySymbol=ETH
|
|
195
|
+
nativeCurrencyDecimals=18
|
|
196
|
+
`
|