creepercli 1.0.0 → 1.1.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 +190 -0
- package/README.md +85 -117
- package/bin/creepercli.js +12 -24
- package/package.json +48 -48
- package/src/client.js +120 -110
- package/src/commands/auth.js +94 -108
- package/src/commands/exec.js +23 -37
- package/src/commands/fs.js +138 -152
- package/src/commands/index.js +126 -136
- package/src/commands/monitor.js +56 -65
- package/src/commands/search.js +56 -70
- package/src/config.js +47 -61
- package/src/editor.js +84 -98
- package/src/formatters.js +120 -134
- package/src/index.js +160 -171
- package/src/prompts.js +120 -134
- package/src/protocol.js +68 -82
- package/src/repl.js +86 -96
- package/src/session.js +39 -57
- package/src/top.js +39 -53
- package/src/transfer.js +243 -168
- package/src/update.js +71 -85
package/src/client.js
CHANGED
|
@@ -1,127 +1,137 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
'use strict';
|
|
18
|
-
|
|
19
|
-
const net = require('net');
|
|
20
|
-
const crypto = require('crypto');
|
|
21
|
-
const { EventEmitter } = require('events');
|
|
22
|
-
const { StringDecoder } = require('string_decoder');
|
|
23
|
-
const { ProtocolError, ERR } = require('./protocol');
|
|
24
|
-
|
|
25
|
-
class CreeperClient extends EventEmitter {
|
|
26
|
-
constructor({ host, port, timeoutMs = 300000 }) {
|
|
27
|
-
super();
|
|
28
|
-
this.host = host;
|
|
29
|
-
this.port = port;
|
|
30
|
-
this.timeoutMs = timeoutMs;
|
|
31
|
-
this.socket = null;
|
|
32
|
-
this.buffer = '';
|
|
33
|
-
this._decoder = new StringDecoder('utf8');
|
|
34
|
-
this.pending = new Map();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
connect() {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const socket = net.connect({ host: this.host, port: this.port });
|
|
40
|
-
this.socket = socket;
|
|
41
|
-
socket.setNoDelay(true);
|
|
42
|
-
socket.on('connect', () => resolve());
|
|
43
|
-
socket.on('error', (err) => reject(err));
|
|
44
|
-
socket.on('close', () => this._onClose());
|
|
45
|
-
socket.on('data', (chunk) => this._onData(chunk));
|
|
46
|
-
});
|
|
47
|
-
}
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const net = require('net');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
const { EventEmitter } = require('events');
|
|
8
|
+
const { StringDecoder } = require('string_decoder');
|
|
9
|
+
const { ProtocolError, ERR, PROTOCOL_VERSION } = require('./protocol');
|
|
48
10
|
|
|
11
|
+
const MAX_INCOMING_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
12
|
+
|
|
13
|
+
class CreeperClient extends EventEmitter {
|
|
14
|
+
constructor({ host, port, timeoutMs = 300000 }) {
|
|
15
|
+
super();
|
|
16
|
+
this.host = host;
|
|
17
|
+
this.port = port;
|
|
18
|
+
this.timeoutMs = timeoutMs;
|
|
19
|
+
this.socket = null;
|
|
20
|
+
this.buffer = '';
|
|
21
|
+
this._decoder = new StringDecoder('utf8');
|
|
22
|
+
this.pending = new Map();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
connect() {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const socket = net.connect({ host: this.host, port: this.port });
|
|
28
|
+
this.socket = socket;
|
|
29
|
+
socket.setNoDelay(true);
|
|
30
|
+
socket.on('connect', () => resolve());
|
|
31
|
+
socket.on('error', (err) => reject(err));
|
|
32
|
+
socket.on('close', () => {
|
|
33
|
+
if (this.socket === socket) this.socket = null;
|
|
34
|
+
this._onClose();
|
|
35
|
+
});
|
|
36
|
+
socket.on('data', (chunk) => this._onData(chunk));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
49
40
|
_onData(chunk) {
|
|
50
41
|
this.buffer += this._decoder.write(chunk);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
42
|
+
if (Buffer.byteLength(this.buffer, 'utf8') > MAX_INCOMING_BUFFER_BYTES) {
|
|
43
|
+
this._failAll(new ProtocolError(ERR.PAYLOAD_TOO_LARGE, 'Server frame exceeded client buffer limit'));
|
|
44
|
+
this.close();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
let idx;
|
|
48
|
+
while ((idx = this.buffer.indexOf('\n')) !== -1) {
|
|
49
|
+
const line = this.buffer.slice(0, idx).trim();
|
|
50
|
+
this.buffer = this.buffer.slice(idx + 1);
|
|
51
|
+
if (!line) continue;
|
|
52
|
+
this._handleLine(line);
|
|
57
53
|
}
|
|
58
54
|
}
|
|
59
|
-
|
|
60
|
-
_handleLine(line) {
|
|
61
|
-
let frame;
|
|
62
|
-
try {
|
|
63
|
-
frame = JSON.parse(line);
|
|
55
|
+
|
|
56
|
+
_handleLine(line) {
|
|
57
|
+
let frame;
|
|
58
|
+
try {
|
|
59
|
+
frame = JSON.parse(line);
|
|
64
60
|
} catch {
|
|
65
61
|
return;
|
|
66
62
|
}
|
|
67
|
-
if (frame.
|
|
63
|
+
if (frame.v !== PROTOCOL_VERSION) {
|
|
68
64
|
const entry = this.pending.get(frame.id);
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} else {
|
|
75
|
-
entry.reject(new ProtocolError(frame.error && frame.error.code, frame.error && frame.error.message));
|
|
65
|
+
if (entry) {
|
|
66
|
+
this.pending.delete(frame.id);
|
|
67
|
+
clearTimeout(entry.timer);
|
|
68
|
+
entry.reject(new ProtocolError(ERR.BAD_REQUEST,
|
|
69
|
+
`Unsupported protocol version ${frame.v} (expected ${PROTOCOL_VERSION})`));
|
|
76
70
|
}
|
|
77
|
-
|
|
78
|
-
this.emit('event', frame.event, frame.data || {});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async flush() {
|
|
83
|
-
while (this.pending.size > 0) {
|
|
84
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
request(action, params = {}, { timeoutMs } = {}) {
|
|
89
|
-
if (!this.socket) {
|
|
90
|
-
return Promise.reject(new ProtocolError(ERR.INTERNAL, 'Connection closed'));
|
|
71
|
+
return;
|
|
91
72
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
73
|
+
if (frame.type === 'response') {
|
|
74
|
+
const entry = this.pending.get(frame.id);
|
|
75
|
+
if (!entry) return;
|
|
76
|
+
this.pending.delete(frame.id);
|
|
77
|
+
clearTimeout(entry.timer);
|
|
78
|
+
if (frame.ok) {
|
|
79
|
+
entry.resolve(frame.data || {});
|
|
80
|
+
} else {
|
|
81
|
+
entry.reject(new ProtocolError(frame.error && frame.error.code, frame.error && frame.error.message));
|
|
82
|
+
}
|
|
83
|
+
} else if (frame.type === 'event') {
|
|
84
|
+
this.emit('event', frame.event, frame.data || {});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async flush() {
|
|
89
|
+
while (this.pending.size > 0) {
|
|
90
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
request(action, params = {}, { timeoutMs } = {}) {
|
|
95
|
+
if (!this.socket || this.socket.destroyed === true || this.socket.writable === false) {
|
|
96
|
+
return Promise.reject(new ProtocolError(ERR.INTERNAL, 'Connection closed'));
|
|
97
|
+
}
|
|
98
|
+
const id = crypto.randomUUID();
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const timer = setTimeout(() => {
|
|
101
|
+
this.pending.delete(id);
|
|
102
|
+
reject(new ProtocolError(ERR.TIMEOUT, `Request timed out (${action})`));
|
|
103
|
+
}, timeoutMs || this.timeoutMs);
|
|
104
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
105
|
+
const frame = JSON.stringify({ v: PROTOCOL_VERSION, type: 'request', id, action, params });
|
|
106
|
+
try {
|
|
107
|
+
this.socket.write(frame + '\n');
|
|
108
|
+
} catch (err) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
this.pending.delete(id);
|
|
111
|
+
reject(new ProtocolError(ERR.INTERNAL, `Connection closed (${err.code || err.message})`));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_onClose() {
|
|
117
|
+
this._failAll(new ProtocolError(ERR.INTERNAL, 'Connection closed by server'));
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
|
|
120
|
+
_failAll(error) {
|
|
111
121
|
for (const { reject, timer } of this.pending.values()) {
|
|
112
122
|
clearTimeout(timer);
|
|
113
|
-
reject(
|
|
123
|
+
reject(error);
|
|
114
124
|
}
|
|
115
125
|
this.pending.clear();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
close() {
|
|
119
|
-
if (this.socket) {
|
|
120
|
-
this.socket.end();
|
|
121
|
-
this.socket.destroy();
|
|
122
|
-
this.socket = null;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = { CreeperClient };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
close() {
|
|
129
|
+
if (this.socket) {
|
|
130
|
+
this.socket.end();
|
|
131
|
+
this.socket.destroy();
|
|
132
|
+
this.socket = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { CreeperClient };
|
package/src/commands/auth.js
CHANGED
|
@@ -1,108 +1,94 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
ctx.username =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const code = await question('Enter the 6-digit code from your authenticator app: ');
|
|
96
|
-
await ctx.client.request('auth.totp.verify', { code });
|
|
97
|
-
console.log('TOTP enabled.');
|
|
98
|
-
} else if (sub === 'disable') {
|
|
99
|
-
const pass = await readSecret('Password: ');
|
|
100
|
-
const code = await question('TOTP code: ');
|
|
101
|
-
await ctx.client.request('auth.totp.disable', { password: pass, code });
|
|
102
|
-
console.log('TOTP disabled.');
|
|
103
|
-
} else {
|
|
104
|
-
throw new Error('Usage: totp <setup|disable>');
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { login, logout, whoami, ping, passwd, totp, doLogin };
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { question, readSecret } = require('../prompts');
|
|
6
|
+
const session = require('../session');
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
|
|
9
|
+
function saveCreds(cfg, res) {
|
|
10
|
+
session.save({
|
|
11
|
+
host: cfg.host,
|
|
12
|
+
port: cfg.port,
|
|
13
|
+
token: res.token,
|
|
14
|
+
username: res.username,
|
|
15
|
+
expiresAt: Date.now() + res.expiresInMinutes * 60000,
|
|
16
|
+
});
|
|
17
|
+
config.save({ host: cfg.host, port: cfg.port });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function doLogin(ctx) {
|
|
21
|
+
const user = await question('Username: ');
|
|
22
|
+
const pass = await readSecret('Password: ');
|
|
23
|
+
let res;
|
|
24
|
+
try {
|
|
25
|
+
res = await ctx.client.request('auth.login', { username: user, password: pass }, { timeoutMs: 30000 });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err.code === 'E_TOTP_REQUIRED') {
|
|
28
|
+
const code = await question('TOTP code: ');
|
|
29
|
+
res = await ctx.client.request('auth.login', { username: user, password: pass, totp: code }, { timeoutMs: 30000 });
|
|
30
|
+
} else {
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
saveCreds(ctx.cfg, res);
|
|
35
|
+
ctx.username = res.username;
|
|
36
|
+
ctx.cwd = '/';
|
|
37
|
+
console.log(`Authenticated as ${res.username}. Session valid for ${res.expiresInMinutes} minutes.`);
|
|
38
|
+
return res;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function login(ctx) {
|
|
42
|
+
await doLogin(ctx);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function logout(ctx) {
|
|
46
|
+
const creds = session.load(ctx.cfg.host, ctx.cfg.port);
|
|
47
|
+
await ctx.client.request('auth.logout', { token: creds ? creds.token : null }).catch(() => {});
|
|
48
|
+
session.clear();
|
|
49
|
+
ctx.username = null;
|
|
50
|
+
console.log('Logged out. Local credentials cleared.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function whoami(ctx) {
|
|
54
|
+
const res = await ctx.client.request('auth.whoami');
|
|
55
|
+
console.log(`User ${res.username} (IP ${res.ip}), cwd ${res.cwd}, session expires in ${Math.floor(res.expiresInSeconds / 60)}m${res.totpEnabled ? ', 2FA enabled' : ', 2FA disabled'}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function ping(ctx) {
|
|
59
|
+
const res = await ctx.client.request('ping', {}, { timeoutMs: 15000 });
|
|
60
|
+
console.log(`pong (protocol v${res.protocol}, plugin v${res.pluginVersion}, server time ${new Date(res.serverTime).toISOString()})`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function passwd(ctx) {
|
|
64
|
+
const oldP = await readSecret('Current password: ');
|
|
65
|
+
const newP = await readSecret('New password: ');
|
|
66
|
+
if (newP.length < 8) throw new Error('New password must be at least 8 characters');
|
|
67
|
+
await ctx.client.request('auth.passwd', { oldPassword: oldP, newPassword: newP });
|
|
68
|
+
console.log('Password changed. Other sessions were invalidated.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function totp(ctx, args) {
|
|
72
|
+
const sub = args[0];
|
|
73
|
+
if (sub === 'setup') {
|
|
74
|
+
const pass = await readSecret('Password: ');
|
|
75
|
+
const res = await ctx.client.request('auth.totp.setup', { password: pass });
|
|
76
|
+
console.log('Scan the QR below with your authenticator app, or enter the secret manually:');
|
|
77
|
+
console.log(`Secret: ${res.secret}`);
|
|
78
|
+
console.log(`(setup expires in ${Math.floor((res.expiresInSeconds || 600) / 60)} minutes)`);
|
|
79
|
+
const qr = require('qrcode-terminal');
|
|
80
|
+
qr.generate(res.otpauthUrl, { small: true });
|
|
81
|
+
const code = await question('Enter the 6-digit code from your authenticator app: ');
|
|
82
|
+
await ctx.client.request('auth.totp.verify', { code });
|
|
83
|
+
console.log('TOTP enabled.');
|
|
84
|
+
} else if (sub === 'disable') {
|
|
85
|
+
const pass = await readSecret('Password: ');
|
|
86
|
+
const code = await question('TOTP code: ');
|
|
87
|
+
await ctx.client.request('auth.totp.disable', { password: pass, code });
|
|
88
|
+
console.log('TOTP disabled.');
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error('Usage: totp <setup|disable>');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { login, logout, whoami, ping, passwd, totp, doLogin };
|
package/src/commands/exec.js
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!res.success) console.error('Command reported failure');
|
|
25
|
-
if (res.elapsedMs != null) console.error(`(completed in ${res.elapsedMs}ms)`);
|
|
26
|
-
return res.success ? 0 : 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function say(ctx, args) {
|
|
30
|
-
return exec(ctx, ['say', ...args]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function restart(ctx, args) {
|
|
34
|
-
return exec(ctx, ['restart', ...args]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = { exec, say, restart };
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
async function exec(ctx, args) {
|
|
6
|
+
if (!args.length) throw new Error('Usage: exec <minecraft command>');
|
|
7
|
+
const command = args.join(' ');
|
|
8
|
+
const res = await ctx.client.request('exec.run', { command }, { timeoutMs: 150000 });
|
|
9
|
+
if (res.lines && res.lines.length) console.log(res.lines.join('\n'));
|
|
10
|
+
if (!res.success) console.error('Command reported failure');
|
|
11
|
+
if (res.elapsedMs != null) console.error(`(completed in ${res.elapsedMs}ms)`);
|
|
12
|
+
return res.success ? 0 : 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function say(ctx, args) {
|
|
16
|
+
return exec(ctx, ['say', ...args]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function restart(ctx, args) {
|
|
20
|
+
return exec(ctx, ['restart', ...args]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { exec, say, restart };
|