icbauggw 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/bin/icbauggw.js +3 -0
- package/package.json +24 -0
- package/src/commands/login.js +62 -0
- package/src/commands/status.js +46 -0
- package/src/commands/switch.js +45 -0
- package/src/index.js +29 -0
- package/src/utils/api.js +73 -0
- package/src/utils/auth.js +12 -0
- package/src/utils/paths.js +13 -0
- package/src/utils/token.js +30 -0
package/bin/icbauggw.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "icbauggw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for rotating Augment session accounts",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"icbauggw": "./bin/icbauggw.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"augment",
|
|
12
|
+
"session",
|
|
13
|
+
"rotate",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18.0.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^13.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const { apiRequest } = require('../utils/api');
|
|
3
|
+
const { saveToken } = require('../utils/token');
|
|
4
|
+
|
|
5
|
+
function prompt(question, hidden = false) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (hidden) {
|
|
13
|
+
// Mask password input
|
|
14
|
+
const stdin = process.stdin;
|
|
15
|
+
const onData = (char) => {
|
|
16
|
+
char = char.toString();
|
|
17
|
+
if (char === '\n' || char === '\r' || char === '\u0004') return;
|
|
18
|
+
process.stdout.clearLine(0);
|
|
19
|
+
process.stdout.cursorTo(0);
|
|
20
|
+
process.stdout.write(question + '*'.repeat(rl.line.length));
|
|
21
|
+
};
|
|
22
|
+
stdin.on('data', onData);
|
|
23
|
+
rl.question(question, (answer) => {
|
|
24
|
+
stdin.removeListener('data', onData);
|
|
25
|
+
rl.close();
|
|
26
|
+
console.log();
|
|
27
|
+
resolve(answer);
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
rl.question(question, (answer) => {
|
|
31
|
+
rl.close();
|
|
32
|
+
resolve(answer);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function loginCommand() {
|
|
39
|
+
try {
|
|
40
|
+
const username = await prompt('Username: ');
|
|
41
|
+
const password = await prompt('Password: ', true);
|
|
42
|
+
|
|
43
|
+
if (!username || !password) {
|
|
44
|
+
console.error('❌ Username and password are required');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const data = await apiRequest('POST', '/api/auth/login', { username, password });
|
|
49
|
+
saveToken(data.token);
|
|
50
|
+
console.log('✅ Logged in successfully! Token saved.');
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err.status === 401) {
|
|
53
|
+
console.error('❌ Login failed: Invalid credentials');
|
|
54
|
+
} else {
|
|
55
|
+
console.error(`❌ Error: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = loginCommand;
|
|
62
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { apiRequest } = require('../utils/api');
|
|
2
|
+
const { requireAuth } = require('../utils/auth');
|
|
3
|
+
|
|
4
|
+
async function statusCommand() {
|
|
5
|
+
requireAuth();
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const data = await apiRequest('GET', '/api/session/status');
|
|
9
|
+
|
|
10
|
+
if (!data.current) {
|
|
11
|
+
console.log('ℹ️ No active session. Run: icbauggw switch');
|
|
12
|
+
console.log();
|
|
13
|
+
} else {
|
|
14
|
+
console.log('📋 Current Account');
|
|
15
|
+
console.log(` Name: ${data.current.name}`);
|
|
16
|
+
console.log(` Tenant: ${data.current.tenantURL}`);
|
|
17
|
+
console.log(` Token: ...${data.current.accessToken}`);
|
|
18
|
+
console.log(` Status: ${data.current.isLimited ? '🔴 Limited' : '🟢 Active'}`);
|
|
19
|
+
if (data.current.creditTotal) {
|
|
20
|
+
const used = data.current.creditRemaining || 0;
|
|
21
|
+
const total = data.current.creditTotal;
|
|
22
|
+
const pct = Math.round((used / total) * 100);
|
|
23
|
+
console.log(` Credit: ${used.toLocaleString()} / ${total.toLocaleString()} (${pct}%)`);
|
|
24
|
+
}
|
|
25
|
+
if (data.current.clients && data.current.clients.length > 0) {
|
|
26
|
+
console.log(` Clients: ${data.current.clients.length} user(s)`);
|
|
27
|
+
}
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('📊 Pool Status');
|
|
32
|
+
console.log(` Active: ${data.pool.active} accounts`);
|
|
33
|
+
console.log(` Limited: ${data.pool.limited} accounts`);
|
|
34
|
+
console.log(` Total: ${data.pool.total} accounts`);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.status === 401) {
|
|
37
|
+
console.error('❌ Session expired. Please login again: icbauggw login');
|
|
38
|
+
} else {
|
|
39
|
+
console.error(`❌ Error: ${err.message}`);
|
|
40
|
+
}
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = statusCommand;
|
|
46
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { apiRequest } = require('../utils/api');
|
|
4
|
+
const { requireAuth } = require('../utils/auth');
|
|
5
|
+
const { getSessionPath } = require('../utils/paths');
|
|
6
|
+
|
|
7
|
+
async function switchCommand() {
|
|
8
|
+
requireAuth();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const data = await apiRequest('GET', '/api/session/next');
|
|
12
|
+
|
|
13
|
+
const sessionPath = getSessionPath();
|
|
14
|
+
const sessionDir = path.dirname(sessionPath);
|
|
15
|
+
|
|
16
|
+
// Create ~/.augment/ directory if it doesn't exist
|
|
17
|
+
if (!fs.existsSync(sessionDir)) {
|
|
18
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Write session file
|
|
22
|
+
const session = {
|
|
23
|
+
accessToken: data.accessToken,
|
|
24
|
+
tenantURL: data.tenantURL,
|
|
25
|
+
scopes: data.scopes,
|
|
26
|
+
};
|
|
27
|
+
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2), 'utf8');
|
|
28
|
+
|
|
29
|
+
console.log('✅ Session switched!');
|
|
30
|
+
console.log(` Account: ${data.accountName}`);
|
|
31
|
+
console.log(` Tenant: ${data.tenantURL}`);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err.status === 401) {
|
|
34
|
+
console.error('❌ Session expired. Please login again: icbauggw login');
|
|
35
|
+
} else if (err.status === 503) {
|
|
36
|
+
console.error(`❌ ${err.message}`);
|
|
37
|
+
} else {
|
|
38
|
+
console.error(`❌ Error: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = switchCommand;
|
|
45
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const loginCommand = require('./commands/login');
|
|
3
|
+
const switchCommand = require('./commands/switch');
|
|
4
|
+
const statusCommand = require('./commands/status');
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('icbauggw')
|
|
10
|
+
.description('CLI tool for rotating Augment session accounts')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('login')
|
|
15
|
+
.description('Authenticate with the session rotator backend')
|
|
16
|
+
.action(loginCommand);
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('switch')
|
|
20
|
+
.description('Switch to the next available Augment session')
|
|
21
|
+
.action(switchCommand);
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('status')
|
|
25
|
+
.description('View current account and pool status')
|
|
26
|
+
.action(statusCommand);
|
|
27
|
+
|
|
28
|
+
program.parse();
|
|
29
|
+
|
package/src/utils/api.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const { loadToken } = require('./token');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_API_URL = 'http://localhost:3000';
|
|
5
|
+
|
|
6
|
+
function getBaseURL() {
|
|
7
|
+
return process.env.ICBAUGGW_API_URL || DEFAULT_API_URL;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getLocalIP() {
|
|
11
|
+
const interfaces = os.networkInterfaces();
|
|
12
|
+
// Skip virtual/VPN adapters — prefer real Ethernet/Wi-Fi
|
|
13
|
+
const skipPatterns = /tailscale|vpn|virtual|vethernet|bluetooth|loopback|docker|vbox|vmware|wsl/i;
|
|
14
|
+
|
|
15
|
+
// First pass: find IP from real adapters (Ethernet, Wi-Fi)
|
|
16
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
17
|
+
if (skipPatterns.test(name)) continue;
|
|
18
|
+
for (const iface of addrs) {
|
|
19
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
20
|
+
return iface.address;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Fallback: any non-internal IPv4
|
|
25
|
+
for (const addrs of Object.values(interfaces)) {
|
|
26
|
+
for (const iface of addrs) {
|
|
27
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
28
|
+
return iface.address;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return '127.0.0.1';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function apiRequest(method, path, body = null) {
|
|
36
|
+
const url = `${getBaseURL()}${path}`;
|
|
37
|
+
const token = loadToken();
|
|
38
|
+
|
|
39
|
+
const headers = {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'X-Client-IP': getLocalIP(),
|
|
42
|
+
'X-Client-Hostname': os.hostname(),
|
|
43
|
+
};
|
|
44
|
+
if (token) {
|
|
45
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const options = { method, headers };
|
|
49
|
+
if (body) {
|
|
50
|
+
options.body = JSON.stringify(body);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const res = await fetch(url, options);
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
const err = new Error(data.error || `HTTP ${res.status}`);
|
|
59
|
+
err.status = res.status;
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return data;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err.cause && err.cause.code === 'ECONNREFUSED') {
|
|
66
|
+
throw new Error(`Could not connect to server at ${getBaseURL()}`);
|
|
67
|
+
}
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { apiRequest, getBaseURL };
|
|
73
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function getTokenPath() {
|
|
5
|
+
return path.join(os.homedir(), '.icb-aug', 'token');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getSessionPath() {
|
|
9
|
+
return path.join(os.homedir(), '.augment', 'session.json');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { getTokenPath, getSessionPath };
|
|
13
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getTokenPath } = require('./paths');
|
|
4
|
+
|
|
5
|
+
function saveToken(token) {
|
|
6
|
+
const tokenPath = getTokenPath();
|
|
7
|
+
const dir = path.dirname(tokenPath);
|
|
8
|
+
if (!fs.existsSync(dir)) {
|
|
9
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
fs.writeFileSync(tokenPath, token, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadToken() {
|
|
15
|
+
const tokenPath = getTokenPath();
|
|
16
|
+
if (!fs.existsSync(tokenPath)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return fs.readFileSync(tokenPath, 'utf8').trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function clearToken() {
|
|
23
|
+
const tokenPath = getTokenPath();
|
|
24
|
+
if (fs.existsSync(tokenPath)) {
|
|
25
|
+
fs.unlinkSync(tokenPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { saveToken, loadToken, clearToken };
|
|
30
|
+
|