httptmux 1.1.2 → 1.2.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/CHANGELOG.md +2 -0
- package/README.md +1 -1
- package/index.js +97 -90
- package/index_desktop.js +97 -90
- package/package.json +17 -16
package/CHANGELOG.md
CHANGED
|
@@ -31,3 +31,5 @@ v1.1.0 (15/1/2026): Added JWT expiry awareness, better error handling and better
|
|
|
31
31
|
v1.1.1 (16/1/2026): Added more badges to README.md
|
|
32
32
|
|
|
33
33
|
v1.1.2 (16/1/2026): Changed README.md badge layout
|
|
34
|
+
|
|
35
|
+
v1.2.0 (19/1/2026): Added non-interactive mode and PATCH, HEAD and OPTIONS support
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://packagephobia.com/result?p=httptmux)
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
An interactive CLI tool originally meant for termux but now extended to desktops with support for JWT requests (now with
|
|
9
|
+
An interactive CLI tool originally meant for termux but now extended to desktops with support for JWT requests (now with a non-interactive mode and support fot PATCH, HEAD and OPTIONS requests)
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
package/index.js
CHANGED
|
@@ -5,14 +5,16 @@ import chalk from 'chalk';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import os from 'os';
|
|
8
|
+
import minimist from 'minimist';
|
|
8
9
|
|
|
9
10
|
// Load package.json for version info
|
|
10
11
|
import pkg from "./package.json" with { type: "json" };
|
|
11
12
|
|
|
12
13
|
// CLI flags
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const args = minimist(process.argv.slice(2));
|
|
15
|
+
const verbose = args.verbose || args.v;
|
|
16
|
+
const showHelp = args.help || args.h === true;
|
|
17
|
+
const showVersion = args.version || args.V === true;
|
|
16
18
|
|
|
17
19
|
// Config paths
|
|
18
20
|
const historyFile = path.join(os.homedir(), ".api-cli-history.json");
|
|
@@ -26,25 +28,25 @@ function logVerbose(message) {
|
|
|
26
28
|
function printHelp() {
|
|
27
29
|
console.log(chalk.cyan("\nhttptmux CLI Help"));
|
|
28
30
|
console.log(`
|
|
29
|
-
Usage:
|
|
31
|
+
Usage:
|
|
32
|
+
httptmux METHOD -u <url> [-h <headers>] [-b <body>]
|
|
33
|
+
httptmux -c
|
|
34
|
+
httptmux -e <file>
|
|
35
|
+
httptmux -f "status=200 since=YYYY-MM-DD"
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
--version Show version number
|
|
34
|
-
--verbose Enable verbose logging
|
|
37
|
+
Methods:
|
|
38
|
+
GET | POST | PUT | DELETE | PATCH | HEAD | OPTIONS
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
• Version
|
|
47
|
-
• Exit
|
|
40
|
+
Flags:
|
|
41
|
+
-u, --url API URL
|
|
42
|
+
-h, --headers Headers as JSON string
|
|
43
|
+
-b, --body Body as JSON string
|
|
44
|
+
-c, --clear-history Clear request history
|
|
45
|
+
-e, --export-history Export history to file
|
|
46
|
+
-f, --filter-history Filter history (status=XXX since=YYYY-MM-DD)
|
|
47
|
+
-v, --verbose Enable verbose logging
|
|
48
|
+
-V, --version Show version
|
|
49
|
+
--help Show help
|
|
48
50
|
`);
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -75,14 +77,8 @@ function exportHistory(filePath = path.join(os.homedir(), "httptmux-history-expo
|
|
|
75
77
|
fs.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
76
78
|
console.log(chalk.green(`History exported to ${filePath}`));
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (history.length === 0) return console.log(chalk.yellow("No history found."));
|
|
81
|
-
const { status, since } = await inquirer.prompt([
|
|
82
|
-
{ type: "input", name: "status", message: chalk.blue("Filter by status code (or leave empty):") },
|
|
83
|
-
{ type: "input", name: "since", message: chalk.blue("Filter by date (YYYY-MM-DD or leave empty):") }
|
|
84
|
-
]);
|
|
85
|
-
let results = history;
|
|
80
|
+
function filterHistory(status, since) {
|
|
81
|
+
let results = loadHistory();
|
|
86
82
|
if (status) results = results.filter(e => String(e.status) === status);
|
|
87
83
|
if (since) results = results.filter(e => new Date(e.timestamp) >= new Date(since));
|
|
88
84
|
if (results.length === 0) return console.log(chalk.yellow("No matching entries."));
|
|
@@ -150,8 +146,16 @@ async function executeRequest({ method, url, headers, body }) {
|
|
|
150
146
|
const response = await axios({ method, url, headers, data: body });
|
|
151
147
|
const duration = Date.now() - start;
|
|
152
148
|
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
if (method === "HEAD") {
|
|
150
|
+
console.log(chalk.green("\nResponse headers:"));
|
|
151
|
+
console.log(chalk.gray(JSON.stringify(response.headers, null, 2)));
|
|
152
|
+
} else if (method === "OPTIONS") {
|
|
153
|
+
console.log(chalk.green("\nAllowed methods:"));
|
|
154
|
+
console.log(chalk.gray(response.headers.allow || JSON.stringify(response.headers, null, 2)));
|
|
155
|
+
} else {
|
|
156
|
+
console.log(chalk.green("\nResponse received:"));
|
|
157
|
+
console.log(chalk.gray(JSON.stringify(response.data, null, 2)));
|
|
158
|
+
}
|
|
155
159
|
|
|
156
160
|
logVerbose(`Request completed in ${duration} ms`);
|
|
157
161
|
logVerbose(`Status code: ${response.status}`);
|
|
@@ -165,13 +169,15 @@ async function executeRequest({ method, url, headers, body }) {
|
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
//
|
|
172
|
+
// Interactive request
|
|
169
173
|
async function runRequest() {
|
|
170
|
-
const { method } = await inquirer.prompt([
|
|
174
|
+
const { method } = await inquirer.prompt([
|
|
175
|
+
{ type: "list", name: "method", message: chalk.blue("Select HTTP method:"), choices: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] }
|
|
176
|
+
]);
|
|
171
177
|
const { url } = await inquirer.prompt([{ type: "input", name: "url", message: chalk.blue("Enter API URL:") }]);
|
|
172
178
|
const { headersInput } = await inquirer.prompt([{ type: "input", name: "headersInput", message: chalk.yellow("Enter headers as JSON (or leave empty):") }]);
|
|
173
179
|
let bodyInput = "";
|
|
174
|
-
if (
|
|
180
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
175
181
|
const bodyAnswer = await inquirer.prompt([{ type: "input", name: "bodyInput", message: chalk.yellow("Enter request body as JSON (or leave empty):") }]);
|
|
176
182
|
bodyInput = bodyAnswer.bodyInput;
|
|
177
183
|
}
|
|
@@ -184,61 +190,31 @@ async function runRequest() {
|
|
|
184
190
|
await executeRequest({ method, url, headers, body });
|
|
185
191
|
}
|
|
186
192
|
|
|
187
|
-
//
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const entry = history[index];
|
|
194
|
-
console.log(chalk.cyan(`\nRe-running: ${entry.method} ${entry.url}`));
|
|
195
|
-
await executeRequest(entry);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Search/filter history
|
|
199
|
-
async function searchHistory() {
|
|
200
|
-
const history = loadHistory();
|
|
201
|
-
if (history.length === 0) return console.log(chalk.yellow("No history found."));
|
|
202
|
-
const { keyword } = await inquirer.prompt([{ type: "input", name: "keyword", message: chalk.blue("Enter keyword to search (method, URL, status):") }]);
|
|
203
|
-
const results = history.filter(entry =>
|
|
204
|
-
entry.method.includes(keyword.toUpperCase()) ||
|
|
205
|
-
entry.url.includes(keyword) ||
|
|
206
|
-
String(entry.status).includes(keyword)
|
|
207
|
-
);
|
|
208
|
-
if (results.length === 0) return console.log(chalk.yellow("No matching entries."));
|
|
209
|
-
console.log(chalk.cyan("\nSearch Results:"));
|
|
210
|
-
results.forEach((entry, i) => console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`)));
|
|
211
|
-
}
|
|
193
|
+
// Non-interactive mode
|
|
194
|
+
async function runNonInteractive() {
|
|
195
|
+
const cliMethod = args._[0];
|
|
196
|
+
const cliUrl = args.u || args.url;
|
|
197
|
+
const cliHeaders = args.h || args.headers ? JSON.parse(args.h || args.headers) : {};
|
|
198
|
+
const cliBody = args.b || args.body ? JSON.parse(args.b || args.body) : {};
|
|
212
199
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (current) {
|
|
217
|
-
console.log(chalk.cyan("Current JWT:"));
|
|
218
|
-
const payload = decodeJWT(current);
|
|
219
|
-
console.log(chalk.gray(JSON.stringify({ tokenPreview: `${current.slice(0, 12)}...`, payload }, null, 2)));
|
|
220
|
-
checkJWTExpiry(current);
|
|
200
|
+
if (args.c || args["clear-history"]) {
|
|
201
|
+
clearHistory();
|
|
202
|
+
return;
|
|
221
203
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
saveJWT(token);
|
|
237
|
-
} else if (action === "Remove token") {
|
|
238
|
-
if (fs.existsSync(jwtFile)) fs.unlinkSync(jwtFile);
|
|
239
|
-
console.log(chalk.green("JWT removed."));
|
|
240
|
-
} else {
|
|
241
|
-
// Back
|
|
204
|
+
if (args.e || args["export-history"]) {
|
|
205
|
+
exportHistory(args.e || args["export-history"]);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (args.f || args["filter-history"]) {
|
|
209
|
+
const filters = (args.f || args["filter-history"]).split(" ");
|
|
210
|
+
const status = filters.find(f => f.startsWith("status="))?.split("=")[1];
|
|
211
|
+
const since = filters.find(f => f.startsWith("since="))?.split("=")[1];
|
|
212
|
+
filterHistory(status, since);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (cliMethod && cliUrl) {
|
|
216
|
+
await executeRequest({ method: cliMethod, url: cliUrl, headers: cliHeaders, body: cliBody });
|
|
217
|
+
return;
|
|
242
218
|
}
|
|
243
219
|
}
|
|
244
220
|
|
|
@@ -247,6 +223,13 @@ async function main() {
|
|
|
247
223
|
if (showHelp) return printHelp();
|
|
248
224
|
if (showVersion) return printVersion();
|
|
249
225
|
|
|
226
|
+
// Non-interactive mode
|
|
227
|
+
if (args._.length > 0 || args.c || args.e || args.f) {
|
|
228
|
+
await runNonInteractive();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Interactive fallback
|
|
250
233
|
console.log(chalk.cyan("httptmux"));
|
|
251
234
|
|
|
252
235
|
let keepGoing = true;
|
|
@@ -276,17 +259,40 @@ async function main() {
|
|
|
276
259
|
else if (action === "View history") {
|
|
277
260
|
const history = loadHistory();
|
|
278
261
|
if (history.length === 0) console.log(chalk.yellow("No history found."));
|
|
279
|
-
else history.forEach((entry, i) =>
|
|
262
|
+
else history.forEach((entry, i) =>
|
|
263
|
+
console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`))
|
|
264
|
+
);
|
|
280
265
|
}
|
|
281
266
|
else if (action === "Re-run from history") await rerunHistory();
|
|
282
|
-
else if (action === "Search history")
|
|
267
|
+
else if (action === "Search history") {
|
|
268
|
+
const { keyword } = await inquirer.prompt([{ type: "input", name: "keyword", message: chalk.blue("Enter keyword to search:") }]);
|
|
269
|
+
const results = loadHistory().filter(entry =>
|
|
270
|
+
entry.method.includes(keyword.toUpperCase()) ||
|
|
271
|
+
entry.url.includes(keyword) ||
|
|
272
|
+
String(entry.status).includes(keyword)
|
|
273
|
+
);
|
|
274
|
+
if (results.length === 0) console.log(chalk.yellow("No matching entries."));
|
|
275
|
+
else results.forEach((entry, i) =>
|
|
276
|
+
console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`))
|
|
277
|
+
);
|
|
278
|
+
}
|
|
283
279
|
else if (action === "Clear history") clearHistory();
|
|
284
280
|
else if (action === "Export history") {
|
|
285
281
|
const { filePath } = await inquirer.prompt([{ type: "input", name: "filePath", message: chalk.blue("Export file path (default in HOME):") }]);
|
|
286
282
|
exportHistory(filePath && filePath.trim() ? filePath.trim() : undefined);
|
|
287
283
|
}
|
|
288
|
-
else if (action === "Filter history")
|
|
289
|
-
|
|
284
|
+
else if (action === "Filter history") {
|
|
285
|
+
const { status, since } = await inquirer.prompt([
|
|
286
|
+
{ type: "input", name: "status", message: chalk.blue("Filter by status code (or leave empty):") },
|
|
287
|
+
{ type: "input", name: "since", message: chalk.blue("Filter by date (YYYY-MM-DD or leave empty):") }
|
|
288
|
+
]);
|
|
289
|
+
filterHistory(status, since);
|
|
290
|
+
}
|
|
291
|
+
else if (action === "Set JWT token") {
|
|
292
|
+
const { token } = await inquirer.prompt([{ type: "input", name: "token", message: chalk.blue("Enter JWT token:") }]);
|
|
293
|
+
if (!token || !token.includes(".")) console.log(chalk.red("Invalid JWT format."));
|
|
294
|
+
else saveJWT(token);
|
|
295
|
+
}
|
|
290
296
|
else if (action === "Help") printHelp();
|
|
291
297
|
else if (action === "Version") printVersion();
|
|
292
298
|
else keepGoing = false;
|
|
@@ -296,6 +302,7 @@ async function main() {
|
|
|
296
302
|
console.log(chalk.cyan("\nExiting httptmux. Goodbye!"));
|
|
297
303
|
}
|
|
298
304
|
|
|
305
|
+
// Entry point
|
|
299
306
|
main().catch(err => {
|
|
300
307
|
console.error(chalk.red("Fatal error:"), err);
|
|
301
308
|
process.exit(1);
|
package/index_desktop.js
CHANGED
|
@@ -5,14 +5,16 @@ import chalk from 'chalk';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import os from 'os';
|
|
8
|
+
import minimist from 'minimist';
|
|
8
9
|
|
|
9
10
|
// Load package.json for version info
|
|
10
11
|
import pkg from "./package.json" with { type: "json" };
|
|
11
12
|
|
|
12
13
|
// CLI flags
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const args = minimist(process.argv.slice(2));
|
|
15
|
+
const verbose = args.verbose || args.v;
|
|
16
|
+
const showHelp = args.help || args.h === true;
|
|
17
|
+
const showVersion = args.version || args.V === true;
|
|
16
18
|
|
|
17
19
|
// Config paths
|
|
18
20
|
const historyFile = path.join(os.homedir(), ".api-cli-history.json");
|
|
@@ -26,25 +28,25 @@ function logVerbose(message) {
|
|
|
26
28
|
function printHelp() {
|
|
27
29
|
console.log(chalk.cyan("\nhttptmux CLI Help"));
|
|
28
30
|
console.log(`
|
|
29
|
-
Usage:
|
|
31
|
+
Usage:
|
|
32
|
+
httptmux METHOD -u <url> [-h <headers>] [-b <body>]
|
|
33
|
+
httptmux -c
|
|
34
|
+
httptmux -e <file>
|
|
35
|
+
httptmux -f "status=200 since=YYYY-MM-DD"
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
--version Show version number
|
|
34
|
-
--verbose Enable verbose logging
|
|
37
|
+
Methods:
|
|
38
|
+
GET | POST | PUT | DELETE | PATCH | HEAD | OPTIONS
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
• Version
|
|
47
|
-
• Exit
|
|
40
|
+
Flags:
|
|
41
|
+
-u, --url API URL
|
|
42
|
+
-h, --headers Headers as JSON string
|
|
43
|
+
-b, --body Body as JSON string
|
|
44
|
+
-c, --clear-history Clear request history
|
|
45
|
+
-e, --export-history Export history to file
|
|
46
|
+
-f, --filter-history Filter history (status=XXX since=YYYY-MM-DD)
|
|
47
|
+
-v, --verbose Enable verbose logging
|
|
48
|
+
-V, --version Show version
|
|
49
|
+
--help Show help
|
|
48
50
|
`);
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -75,14 +77,8 @@ function exportHistory(filePath = path.join(os.homedir(), "httptmux-history-expo
|
|
|
75
77
|
fs.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
76
78
|
console.log(chalk.green(`History exported to ${filePath}`));
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (history.length === 0) return console.log(chalk.yellow("No history found."));
|
|
81
|
-
const { status, since } = await inquirer.prompt([
|
|
82
|
-
{ type: "input", name: "status", message: chalk.blue("Filter by status code (or leave empty):") },
|
|
83
|
-
{ type: "input", name: "since", message: chalk.blue("Filter by date (YYYY-MM-DD or leave empty):") }
|
|
84
|
-
]);
|
|
85
|
-
let results = history;
|
|
80
|
+
function filterHistory(status, since) {
|
|
81
|
+
let results = loadHistory();
|
|
86
82
|
if (status) results = results.filter(e => String(e.status) === status);
|
|
87
83
|
if (since) results = results.filter(e => new Date(e.timestamp) >= new Date(since));
|
|
88
84
|
if (results.length === 0) return console.log(chalk.yellow("No matching entries."));
|
|
@@ -150,8 +146,16 @@ async function executeRequest({ method, url, headers, body }) {
|
|
|
150
146
|
const response = await axios({ method, url, headers, data: body });
|
|
151
147
|
const duration = Date.now() - start;
|
|
152
148
|
|
|
153
|
-
|
|
154
|
-
|
|
149
|
+
if (method === "HEAD") {
|
|
150
|
+
console.log(chalk.green("\nResponse headers:"));
|
|
151
|
+
console.log(chalk.gray(JSON.stringify(response.headers, null, 2)));
|
|
152
|
+
} else if (method === "OPTIONS") {
|
|
153
|
+
console.log(chalk.green("\nAllowed methods:"));
|
|
154
|
+
console.log(chalk.gray(response.headers.allow || JSON.stringify(response.headers, null, 2)));
|
|
155
|
+
} else {
|
|
156
|
+
console.log(chalk.green("\nResponse received:"));
|
|
157
|
+
console.log(chalk.gray(JSON.stringify(response.data, null, 2)));
|
|
158
|
+
}
|
|
155
159
|
|
|
156
160
|
logVerbose(`Request completed in ${duration} ms`);
|
|
157
161
|
logVerbose(`Status code: ${response.status}`);
|
|
@@ -165,13 +169,15 @@ async function executeRequest({ method, url, headers, body }) {
|
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
//
|
|
172
|
+
// Interactive request
|
|
169
173
|
async function runRequest() {
|
|
170
|
-
const { method } = await inquirer.prompt([
|
|
174
|
+
const { method } = await inquirer.prompt([
|
|
175
|
+
{ type: "list", name: "method", message: chalk.blue("Select HTTP method:"), choices: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] }
|
|
176
|
+
]);
|
|
171
177
|
const { url } = await inquirer.prompt([{ type: "input", name: "url", message: chalk.blue("Enter API URL:") }]);
|
|
172
178
|
const { headersInput } = await inquirer.prompt([{ type: "input", name: "headersInput", message: chalk.yellow("Enter headers as JSON (or leave empty):") }]);
|
|
173
179
|
let bodyInput = "";
|
|
174
|
-
if (
|
|
180
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
175
181
|
const bodyAnswer = await inquirer.prompt([{ type: "input", name: "bodyInput", message: chalk.yellow("Enter request body as JSON (or leave empty):") }]);
|
|
176
182
|
bodyInput = bodyAnswer.bodyInput;
|
|
177
183
|
}
|
|
@@ -184,61 +190,31 @@ async function runRequest() {
|
|
|
184
190
|
await executeRequest({ method, url, headers, body });
|
|
185
191
|
}
|
|
186
192
|
|
|
187
|
-
//
|
|
188
|
-
async function
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const entry = history[index];
|
|
194
|
-
console.log(chalk.cyan(`\nRe-running: ${entry.method} ${entry.url}`));
|
|
195
|
-
await executeRequest(entry);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Search/filter history
|
|
199
|
-
async function searchHistory() {
|
|
200
|
-
const history = loadHistory();
|
|
201
|
-
if (history.length === 0) return console.log(chalk.yellow("No history found."));
|
|
202
|
-
const { keyword } = await inquirer.prompt([{ type: "input", name: "keyword", message: chalk.blue("Enter keyword to search (method, URL, status):") }]);
|
|
203
|
-
const results = history.filter(entry =>
|
|
204
|
-
entry.method.includes(keyword.toUpperCase()) ||
|
|
205
|
-
entry.url.includes(keyword) ||
|
|
206
|
-
String(entry.status).includes(keyword)
|
|
207
|
-
);
|
|
208
|
-
if (results.length === 0) return console.log(chalk.yellow("No matching entries."));
|
|
209
|
-
console.log(chalk.cyan("\nSearch Results:"));
|
|
210
|
-
results.forEach((entry, i) => console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`)));
|
|
211
|
-
}
|
|
193
|
+
// Non-interactive mode
|
|
194
|
+
async function runNonInteractive() {
|
|
195
|
+
const cliMethod = args._[0];
|
|
196
|
+
const cliUrl = args.u || args.url;
|
|
197
|
+
const cliHeaders = args.h || args.headers ? JSON.parse(args.h || args.headers) : {};
|
|
198
|
+
const cliBody = args.b || args.body ? JSON.parse(args.b || args.body) : {};
|
|
212
199
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (current) {
|
|
217
|
-
console.log(chalk.cyan("Current JWT:"));
|
|
218
|
-
const payload = decodeJWT(current);
|
|
219
|
-
console.log(chalk.gray(JSON.stringify({ tokenPreview: `${current.slice(0, 12)}...`, payload }, null, 2)));
|
|
220
|
-
checkJWTExpiry(current);
|
|
200
|
+
if (args.c || args["clear-history"]) {
|
|
201
|
+
clearHistory();
|
|
202
|
+
return;
|
|
221
203
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
saveJWT(token);
|
|
237
|
-
} else if (action === "Remove token") {
|
|
238
|
-
if (fs.existsSync(jwtFile)) fs.unlinkSync(jwtFile);
|
|
239
|
-
console.log(chalk.green("JWT removed."));
|
|
240
|
-
} else {
|
|
241
|
-
// Back
|
|
204
|
+
if (args.e || args["export-history"]) {
|
|
205
|
+
exportHistory(args.e || args["export-history"]);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (args.f || args["filter-history"]) {
|
|
209
|
+
const filters = (args.f || args["filter-history"]).split(" ");
|
|
210
|
+
const status = filters.find(f => f.startsWith("status="))?.split("=")[1];
|
|
211
|
+
const since = filters.find(f => f.startsWith("since="))?.split("=")[1];
|
|
212
|
+
filterHistory(status, since);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (cliMethod && cliUrl) {
|
|
216
|
+
await executeRequest({ method: cliMethod, url: cliUrl, headers: cliHeaders, body: cliBody });
|
|
217
|
+
return;
|
|
242
218
|
}
|
|
243
219
|
}
|
|
244
220
|
|
|
@@ -247,6 +223,13 @@ async function main() {
|
|
|
247
223
|
if (showHelp) return printHelp();
|
|
248
224
|
if (showVersion) return printVersion();
|
|
249
225
|
|
|
226
|
+
// Non-interactive mode
|
|
227
|
+
if (args._.length > 0 || args.c || args.e || args.f) {
|
|
228
|
+
await runNonInteractive();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Interactive fallback
|
|
250
233
|
console.log(chalk.cyan("httptmux"));
|
|
251
234
|
|
|
252
235
|
let keepGoing = true;
|
|
@@ -276,17 +259,40 @@ async function main() {
|
|
|
276
259
|
else if (action === "View history") {
|
|
277
260
|
const history = loadHistory();
|
|
278
261
|
if (history.length === 0) console.log(chalk.yellow("No history found."));
|
|
279
|
-
else history.forEach((entry, i) =>
|
|
262
|
+
else history.forEach((entry, i) =>
|
|
263
|
+
console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`))
|
|
264
|
+
);
|
|
280
265
|
}
|
|
281
266
|
else if (action === "Re-run from history") await rerunHistory();
|
|
282
|
-
else if (action === "Search history")
|
|
267
|
+
else if (action === "Search history") {
|
|
268
|
+
const { keyword } = await inquirer.prompt([{ type: "input", name: "keyword", message: chalk.blue("Enter keyword to search:") }]);
|
|
269
|
+
const results = loadHistory().filter(entry =>
|
|
270
|
+
entry.method.includes(keyword.toUpperCase()) ||
|
|
271
|
+
entry.url.includes(keyword) ||
|
|
272
|
+
String(entry.status).includes(keyword)
|
|
273
|
+
);
|
|
274
|
+
if (results.length === 0) console.log(chalk.yellow("No matching entries."));
|
|
275
|
+
else results.forEach((entry, i) =>
|
|
276
|
+
console.log(chalk.gray(`${i + 1}. [${entry.timestamp}] ${entry.method} ${entry.url} (status: ${entry.status})`))
|
|
277
|
+
);
|
|
278
|
+
}
|
|
283
279
|
else if (action === "Clear history") clearHistory();
|
|
284
280
|
else if (action === "Export history") {
|
|
285
281
|
const { filePath } = await inquirer.prompt([{ type: "input", name: "filePath", message: chalk.blue("Export file path (default in HOME):") }]);
|
|
286
282
|
exportHistory(filePath && filePath.trim() ? filePath.trim() : undefined);
|
|
287
283
|
}
|
|
288
|
-
else if (action === "Filter history")
|
|
289
|
-
|
|
284
|
+
else if (action === "Filter history") {
|
|
285
|
+
const { status, since } = await inquirer.prompt([
|
|
286
|
+
{ type: "input", name: "status", message: chalk.blue("Filter by status code (or leave empty):") },
|
|
287
|
+
{ type: "input", name: "since", message: chalk.blue("Filter by date (YYYY-MM-DD or leave empty):") }
|
|
288
|
+
]);
|
|
289
|
+
filterHistory(status, since);
|
|
290
|
+
}
|
|
291
|
+
else if (action === "Set JWT token") {
|
|
292
|
+
const { token } = await inquirer.prompt([{ type: "input", name: "token", message: chalk.blue("Enter JWT token:") }]);
|
|
293
|
+
if (!token || !token.includes(".")) console.log(chalk.red("Invalid JWT format."));
|
|
294
|
+
else saveJWT(token);
|
|
295
|
+
}
|
|
290
296
|
else if (action === "Help") printHelp();
|
|
291
297
|
else if (action === "Version") printVersion();
|
|
292
298
|
else keepGoing = false;
|
|
@@ -296,6 +302,7 @@ async function main() {
|
|
|
296
302
|
console.log(chalk.cyan("\nExiting httptmux. Goodbye!"));
|
|
297
303
|
}
|
|
298
304
|
|
|
305
|
+
// Entry point
|
|
299
306
|
main().catch(err => {
|
|
300
307
|
console.error(chalk.red("Fatal error:"), err);
|
|
301
308
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "httptmux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "httptmux is an interactive CLI tool for API requests. Originally made for Termux (hence the name), but decided to make a second index.js file for desktops",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,23 +18,24 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"axios": "^1.6.0",
|
|
21
|
+
"chalk": "^5.3.0",
|
|
21
22
|
"inquirer": "^9.0.0",
|
|
22
|
-
"
|
|
23
|
+
"minimist": "^1.2.8"
|
|
23
24
|
},
|
|
24
25
|
"license": "MIT",
|
|
25
26
|
"keywords": [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
"http",
|
|
28
|
+
"cli",
|
|
29
|
+
"termux",
|
|
30
|
+
"desktop",
|
|
31
|
+
"api",
|
|
32
|
+
"requests",
|
|
33
|
+
"axios",
|
|
34
|
+
"interactive",
|
|
35
|
+
"jwt",
|
|
36
|
+
"developer-tools",
|
|
37
|
+
"nodejs",
|
|
38
|
+
"command-line",
|
|
39
|
+
"automation"
|
|
40
|
+
]
|
|
40
41
|
}
|