obscr 0.1.1 → 0.2.1
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 +92 -0
- package/README.md +214 -26
- package/bin/index.js +417 -85
- package/bin/utils/crypto.js +69 -30
- package/bin/utils/steg.js +193 -86
- package/bin/utils/utils.js +113 -64
- package/package.json +24 -4
- package/.github/workflows/publish.yml +0 -18
package/bin/index.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
const yargs = require("yargs");
|
|
3
3
|
const chalk = require("chalk");
|
|
4
4
|
const fs = require("fs");
|
|
5
|
-
|
|
6
5
|
const { prompt } = require("inquirer");
|
|
6
|
+
const ora = require("ora");
|
|
7
|
+
const boxen = require("boxen");
|
|
7
8
|
|
|
8
9
|
const { encrypt, decrypt } = require("./utils/crypto");
|
|
9
10
|
const {
|
|
@@ -11,147 +12,478 @@ const {
|
|
|
11
12
|
extractMessageFromImage,
|
|
12
13
|
} = require("./utils/steg");
|
|
13
14
|
|
|
14
|
-
//just to increase obfuscation for
|
|
15
|
+
// NOTE: This is just to increase obfuscation for steganography, NOT used for AES encryption
|
|
16
|
+
// Kept for backward compatibility with images encoded using older versions
|
|
15
17
|
const SECRET_KEY = "S3cReTK3Y";
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
// Global flags
|
|
20
|
+
let VERBOSE = false;
|
|
21
|
+
let QUIET = false;
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Logging utilities
|
|
25
|
+
*/
|
|
26
|
+
const log = {
|
|
27
|
+
info: (msg) => !QUIET && console.log(chalk.blue("ℹ"), msg),
|
|
28
|
+
success: (msg) => !QUIET && console.log(chalk.green("✔"), msg),
|
|
29
|
+
error: (msg) => console.error(chalk.red("✖"), msg),
|
|
30
|
+
warn: (msg) => !QUIET && console.log(chalk.yellow("⚠"), msg),
|
|
31
|
+
verbose: (msg) => VERBOSE && console.log(chalk.gray("→"), msg),
|
|
32
|
+
box: (msg, options = {}) => !QUIET && console.log(boxen(msg, {
|
|
33
|
+
padding: 1,
|
|
34
|
+
margin: 1,
|
|
35
|
+
borderStyle: "round",
|
|
36
|
+
borderColor: "cyan",
|
|
37
|
+
...options
|
|
38
|
+
})),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Display welcome banner
|
|
43
|
+
*/
|
|
44
|
+
function showBanner() {
|
|
45
|
+
if (QUIET) return;
|
|
46
|
+
|
|
47
|
+
const banner = chalk.keyword("violet").bold("obscr") +
|
|
48
|
+
chalk.gray(" - Encrypt and hide your secure data\n") +
|
|
49
|
+
chalk.dim("v0.2.1");
|
|
50
|
+
|
|
51
|
+
log.box(banner, { borderColor: "magenta" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Show usage examples
|
|
56
|
+
*/
|
|
57
|
+
function showExamples() {
|
|
58
|
+
const examples = `
|
|
59
|
+
${chalk.bold("Examples:")}
|
|
60
|
+
|
|
61
|
+
${chalk.cyan("Encrypt with compression:")}
|
|
62
|
+
$ obscr encrypt -f photo.png -o secret.png --compress
|
|
63
|
+
|
|
64
|
+
${chalk.cyan("Decrypt to file:")}
|
|
65
|
+
$ obscr decrypt -f secret.png -o message.txt
|
|
66
|
+
|
|
67
|
+
${chalk.cyan("Interactive mode:")}
|
|
68
|
+
$ obscr interactive
|
|
69
|
+
|
|
70
|
+
${chalk.cyan("Verbose output:")}
|
|
71
|
+
$ obscr encrypt -f image.png --verbose
|
|
72
|
+
|
|
73
|
+
${chalk.cyan("Quiet mode (minimal output):")}
|
|
74
|
+
$ obscr decrypt -f image.png --quiet
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
console.log(examples);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Interactive mode - guided workflow
|
|
82
|
+
*/
|
|
83
|
+
async function interactiveMode() {
|
|
84
|
+
showBanner();
|
|
85
|
+
|
|
86
|
+
const { action } = await prompt({
|
|
87
|
+
type: "list",
|
|
88
|
+
name: "action",
|
|
89
|
+
message: "What would you like to do?",
|
|
90
|
+
choices: [
|
|
91
|
+
{ name: "🔒 Encrypt and hide a message in an image", value: "encrypt" },
|
|
92
|
+
{ name: "🔓 Decrypt and extract a message from an image", value: "decrypt" },
|
|
93
|
+
{ name: "ℹ️ Show usage examples", value: "examples" },
|
|
94
|
+
{ name: "❌ Exit", value: "exit" },
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (action === "exit") {
|
|
99
|
+
log.info("Goodbye!");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (action === "examples") {
|
|
104
|
+
showExamples();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (action === "encrypt") {
|
|
109
|
+
const answers = await prompt([
|
|
110
|
+
{
|
|
111
|
+
type: "input",
|
|
112
|
+
name: "filename",
|
|
113
|
+
message: "Path to the PNG image:",
|
|
114
|
+
validate: (input) => {
|
|
115
|
+
if (!input) return "Filename is required";
|
|
116
|
+
if (!fs.existsSync(input)) return "File does not exist";
|
|
117
|
+
if (!input.toLowerCase().endsWith(".png")) return "Must be a PNG file";
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "input",
|
|
123
|
+
name: "output",
|
|
124
|
+
message: "Output filename:",
|
|
125
|
+
default: "encoded.png",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: "confirm",
|
|
129
|
+
name: "compress",
|
|
130
|
+
message: "Enable compression?",
|
|
131
|
+
default: false,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
type: "editor",
|
|
135
|
+
name: "message",
|
|
136
|
+
message: "Enter your secret message (editor will open):",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "password",
|
|
140
|
+
name: "password",
|
|
141
|
+
message: "Enter password:",
|
|
142
|
+
mask: "*",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: "password",
|
|
146
|
+
name: "confirmPassword",
|
|
147
|
+
message: "Confirm password:",
|
|
148
|
+
mask: "*",
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
if (answers.password !== answers.confirmPassword) {
|
|
153
|
+
log.error("Passwords don't match!");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await encryptCommand(
|
|
158
|
+
answers.filename,
|
|
159
|
+
answers.output,
|
|
160
|
+
answers.compress,
|
|
161
|
+
answers.message,
|
|
162
|
+
answers.password
|
|
163
|
+
);
|
|
164
|
+
} else if (action === "decrypt") {
|
|
165
|
+
const answers = await prompt([
|
|
166
|
+
{
|
|
167
|
+
type: "input",
|
|
168
|
+
name: "filename",
|
|
169
|
+
message: "Path to the encoded PNG image:",
|
|
170
|
+
validate: (input) => {
|
|
171
|
+
if (!input) return "Filename is required";
|
|
172
|
+
if (!fs.existsSync(input)) return "File does not exist";
|
|
173
|
+
return true;
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: "input",
|
|
178
|
+
name: "output",
|
|
179
|
+
message: "Save to file (leave empty to display):",
|
|
180
|
+
default: "",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: "password",
|
|
184
|
+
name: "password",
|
|
185
|
+
message: "Enter password:",
|
|
186
|
+
mask: "*",
|
|
187
|
+
},
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
await decryptCommand(answers.filename, answers.output || null, answers.password);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Encrypt command with enhanced UX
|
|
196
|
+
*/
|
|
197
|
+
async function encryptCommand(filename, output, compress, message, password) {
|
|
198
|
+
const spinner = ora();
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// Validate input file
|
|
202
|
+
log.verbose(`Validating input file: ${filename}`);
|
|
203
|
+
if (!fs.existsSync(filename)) {
|
|
204
|
+
log.error(`Image file not found: ${filename}`);
|
|
205
|
+
return process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check for output file overwrite
|
|
209
|
+
if (fs.existsSync(output)) {
|
|
210
|
+
const { overwrite } = await prompt({
|
|
211
|
+
type: "confirm",
|
|
212
|
+
name: "overwrite",
|
|
213
|
+
message: `File ${output} already exists. Overwrite?`,
|
|
214
|
+
default: false,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!overwrite) {
|
|
218
|
+
log.warn("Operation cancelled");
|
|
219
|
+
return process.exit(0);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!message || message.trim().length === 0) {
|
|
224
|
+
log.error("Message cannot be empty");
|
|
225
|
+
return process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Step 1: Encrypt message
|
|
229
|
+
spinner.start("Encrypting message with AES-256-GCM...");
|
|
230
|
+
log.verbose(`Using ${compress ? "compressed" : "uncompressed"} format`);
|
|
231
|
+
|
|
232
|
+
const encrypted = await encrypt(message, password, compress);
|
|
233
|
+
|
|
234
|
+
spinner.succeed("Message encrypted");
|
|
235
|
+
log.verbose(`Encrypted data length: ${encrypted.length} characters`);
|
|
236
|
+
|
|
237
|
+
if (compress) {
|
|
238
|
+
log.info("Compression enabled");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Step 2: Embed in image
|
|
242
|
+
spinner.start("Embedding encrypted message into image...");
|
|
243
|
+
log.verbose(`Encoding to: ${output}`);
|
|
244
|
+
|
|
245
|
+
const result = await encodeMessageToImage(
|
|
246
|
+
filename,
|
|
247
|
+
encrypted,
|
|
248
|
+
password + SECRET_KEY,
|
|
249
|
+
output
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (!result.success) {
|
|
253
|
+
spinner.fail("Encoding failed");
|
|
254
|
+
log.error(result.error);
|
|
255
|
+
return process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
spinner.succeed("Message successfully hidden in image");
|
|
259
|
+
|
|
260
|
+
// Show summary
|
|
261
|
+
if (!QUIET) {
|
|
262
|
+
const summary = `
|
|
263
|
+
${chalk.green.bold("✔ Encryption Successful")}
|
|
21
264
|
|
|
22
|
-
|
|
265
|
+
${chalk.bold("Output:")} ${output}
|
|
266
|
+
${chalk.bold("Capacity Used:")} ${result.capacity.utilization}
|
|
267
|
+
${chalk.bold(" Total:")} ${result.capacity.totalBits} bits
|
|
268
|
+
${chalk.bold(" Used:")} ${result.capacity.usedBits} bits
|
|
269
|
+
${compress ? chalk.bold("Compression:") + " Enabled" : ""}
|
|
270
|
+
`;
|
|
271
|
+
log.box(summary.trim(), { borderColor: "green" });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
log.verbose("Encryption complete");
|
|
275
|
+
} catch (err) {
|
|
276
|
+
spinner.fail("Encryption failed");
|
|
277
|
+
log.error(err.message);
|
|
278
|
+
log.verbose(err.stack);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Decrypt command with enhanced UX
|
|
285
|
+
*/
|
|
286
|
+
async function decryptCommand(filename, output, password) {
|
|
287
|
+
const spinner = ora();
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
// Validate input file
|
|
291
|
+
log.verbose(`Validating input file: ${filename}`);
|
|
292
|
+
if (!fs.existsSync(filename)) {
|
|
293
|
+
log.error(`Image file not found: ${filename}`);
|
|
294
|
+
return process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Step 1: Extract from image
|
|
298
|
+
spinner.start("Extracting encrypted message from image...");
|
|
299
|
+
log.verbose(`Reading from: ${filename}`);
|
|
300
|
+
|
|
301
|
+
const extractResult = await extractMessageFromImage(
|
|
302
|
+
filename,
|
|
303
|
+
password + SECRET_KEY
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (!extractResult.success) {
|
|
307
|
+
spinner.fail("Extraction failed");
|
|
308
|
+
log.error(extractResult.error);
|
|
309
|
+
return process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
spinner.succeed("Encrypted message extracted");
|
|
313
|
+
log.verbose(`Extracted ${extractResult.data.length} characters`);
|
|
314
|
+
|
|
315
|
+
// Step 2: Decrypt message
|
|
316
|
+
spinner.start("Decrypting message with AES-256-GCM...");
|
|
317
|
+
|
|
318
|
+
const decrypted = await decrypt(extractResult.data, password);
|
|
319
|
+
|
|
320
|
+
spinner.succeed("Message decrypted successfully");
|
|
321
|
+
|
|
322
|
+
// Display or save result
|
|
323
|
+
if (output) {
|
|
324
|
+
fs.writeFileSync(output, decrypted);
|
|
325
|
+
log.success(`Message saved to: ${output}`);
|
|
326
|
+
} else {
|
|
327
|
+
if (!QUIET) {
|
|
328
|
+
const messageBox = `
|
|
329
|
+
${chalk.green.bold("✔ Decryption Successful")}
|
|
330
|
+
|
|
331
|
+
${chalk.bold("Message:")}
|
|
332
|
+
${chalk.white(decrypted)}
|
|
333
|
+
`;
|
|
334
|
+
log.box(messageBox.trim(), { borderColor: "green" });
|
|
335
|
+
} else {
|
|
336
|
+
console.log(decrypted);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
log.verbose("Decryption complete");
|
|
341
|
+
} catch (err) {
|
|
342
|
+
spinner.fail("Decryption failed");
|
|
343
|
+
log.error("Could not decrypt. Wrong password or corrupted image.");
|
|
344
|
+
log.verbose(err.message);
|
|
345
|
+
log.verbose(err.stack);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Parse arguments
|
|
351
|
+
const usage = chalk.keyword("violet")("\nUsage: obscr <command> [options]");
|
|
352
|
+
|
|
353
|
+
const argv = yargs
|
|
354
|
+
.usage(usage)
|
|
355
|
+
.option("verbose", {
|
|
356
|
+
alias: "v",
|
|
357
|
+
describe: "Show detailed output",
|
|
358
|
+
type: "boolean",
|
|
359
|
+
global: true,
|
|
360
|
+
})
|
|
361
|
+
.option("quiet", {
|
|
362
|
+
alias: "q",
|
|
363
|
+
describe: "Minimal output (only essential messages)",
|
|
364
|
+
type: "boolean",
|
|
365
|
+
global: true,
|
|
366
|
+
})
|
|
23
367
|
.command(
|
|
24
368
|
"encrypt",
|
|
25
|
-
"
|
|
369
|
+
"Encrypt and hide a message in an image",
|
|
26
370
|
{
|
|
27
371
|
f: {
|
|
28
372
|
alias: "filename",
|
|
29
|
-
describe: "
|
|
30
|
-
demandOption: true,
|
|
373
|
+
describe: "PNG image to hide the message in",
|
|
374
|
+
demandOption: true,
|
|
31
375
|
type: "string",
|
|
32
376
|
},
|
|
377
|
+
o: {
|
|
378
|
+
alias: "output",
|
|
379
|
+
describe: "Output filename for encoded image",
|
|
380
|
+
demandOption: false,
|
|
381
|
+
type: "string",
|
|
382
|
+
default: "encoded.png",
|
|
383
|
+
},
|
|
33
384
|
c: {
|
|
34
385
|
alias: "compress",
|
|
35
|
-
describe: "Compress
|
|
386
|
+
describe: "Compress message before encryption (50-90% size reduction)",
|
|
36
387
|
demandOption: false,
|
|
37
|
-
type: "
|
|
388
|
+
type: "boolean",
|
|
389
|
+
default: false,
|
|
38
390
|
},
|
|
39
391
|
},
|
|
40
|
-
|
|
41
392
|
async (argv) => {
|
|
42
|
-
|
|
393
|
+
VERBOSE = argv.verbose;
|
|
394
|
+
QUIET = argv.quiet;
|
|
395
|
+
|
|
396
|
+
if (!QUIET) showBanner();
|
|
397
|
+
|
|
398
|
+
const { f: filename, o: output, c: compress } = argv;
|
|
43
399
|
|
|
44
400
|
const { password, confirmPassword, message } = await prompt([
|
|
45
401
|
{
|
|
46
402
|
type: "editor",
|
|
47
403
|
name: "message",
|
|
48
|
-
message: "Type the secret message",
|
|
404
|
+
message: "Type the secret message:",
|
|
49
405
|
},
|
|
50
406
|
{
|
|
51
407
|
type: "password",
|
|
52
408
|
name: "password",
|
|
53
|
-
message: "Enter
|
|
409
|
+
message: "Enter password:",
|
|
410
|
+
mask: "*",
|
|
54
411
|
},
|
|
55
412
|
{
|
|
56
413
|
type: "password",
|
|
57
414
|
name: "confirmPassword",
|
|
58
|
-
message: "
|
|
415
|
+
message: "Confirm password:",
|
|
416
|
+
mask: "*",
|
|
59
417
|
},
|
|
60
418
|
]);
|
|
61
419
|
|
|
62
|
-
// config.set({ token });
|
|
63
420
|
if (password !== confirmPassword) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
/*
|
|
68
|
-
#1. encrypt message with key using AES-256-GCM
|
|
69
|
-
*/
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const encrypted = encrypt(message, password);
|
|
73
|
-
console.log(encrypted);
|
|
74
|
-
await encodeMessageToImage(filename, encrypted, password + SECRET_KEY);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
return console.log(chalk.keyword("red")(e.message));
|
|
421
|
+
log.error("Passwords don't match");
|
|
422
|
+
return process.exit(1);
|
|
77
423
|
}
|
|
78
424
|
|
|
79
|
-
|
|
80
|
-
#2. embed encrypted message scattered into image
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
console.log(chalk.keyword("green")("Successful"));
|
|
425
|
+
await encryptCommand(filename, output, compress, message, password);
|
|
84
426
|
}
|
|
85
427
|
)
|
|
86
|
-
//create decrypt command
|
|
87
428
|
.command(
|
|
88
429
|
"decrypt",
|
|
89
|
-
"
|
|
430
|
+
"Decrypt and extract a message from an image",
|
|
90
431
|
{
|
|
91
432
|
f: {
|
|
92
433
|
alias: "filename",
|
|
93
|
-
describe: "
|
|
94
|
-
demandOption: true,
|
|
434
|
+
describe: "PNG image containing hidden message",
|
|
435
|
+
demandOption: true,
|
|
95
436
|
type: "string",
|
|
96
437
|
},
|
|
97
438
|
o: {
|
|
98
439
|
alias: "output",
|
|
99
|
-
describe: "
|
|
100
|
-
demandOption: false,
|
|
440
|
+
describe: "Save decrypted message to file",
|
|
441
|
+
demandOption: false,
|
|
101
442
|
type: "string",
|
|
102
443
|
},
|
|
103
444
|
},
|
|
104
|
-
|
|
105
445
|
async (argv) => {
|
|
446
|
+
VERBOSE = argv.verbose;
|
|
447
|
+
QUIET = argv.quiet;
|
|
448
|
+
|
|
449
|
+
if (!QUIET) showBanner();
|
|
450
|
+
|
|
106
451
|
const { f: filename, o: output } = argv;
|
|
107
452
|
|
|
108
453
|
const { password } = await prompt({
|
|
109
454
|
type: "password",
|
|
110
455
|
name: "password",
|
|
111
|
-
message: "Enter
|
|
456
|
+
message: "Enter password:",
|
|
457
|
+
mask: "*",
|
|
112
458
|
});
|
|
113
459
|
|
|
114
|
-
|
|
115
|
-
#2. extract encrypted message from image
|
|
116
|
-
*/
|
|
117
|
-
|
|
118
|
-
/*
|
|
119
|
-
#1. decrypt message with key using AES-256-GCM
|
|
120
|
-
*/
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const [succeeded, extracted] = await extractMessageFromImage(
|
|
124
|
-
filename,
|
|
125
|
-
password + SECRET_KEY
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
if (!succeeded) throw extracted;
|
|
129
|
-
const decrypted = decrypt(extracted, password);
|
|
130
|
-
|
|
131
|
-
console.log(decrypted);
|
|
132
|
-
|
|
133
|
-
if (output) fs.writeFileSync(output, decrypted);
|
|
134
|
-
} catch (e) {
|
|
135
|
-
console.log(e);
|
|
136
|
-
return console.log(chalk.keyword("red")("Could not decrypt"));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log(chalk.keyword("green")("Successful"));
|
|
460
|
+
await decryptCommand(filename, output, password);
|
|
140
461
|
}
|
|
141
462
|
)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
463
|
+
.command(
|
|
464
|
+
"interactive",
|
|
465
|
+
"Start interactive mode with guided workflow",
|
|
466
|
+
{},
|
|
467
|
+
async (argv) => {
|
|
468
|
+
VERBOSE = argv.verbose;
|
|
469
|
+
QUIET = argv.quiet;
|
|
470
|
+
await interactiveMode();
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
.command(
|
|
474
|
+
"examples",
|
|
475
|
+
"Show usage examples",
|
|
476
|
+
{},
|
|
477
|
+
() => {
|
|
478
|
+
showBanner();
|
|
479
|
+
showExamples();
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
.epilog(`Run ${chalk.cyan("obscr examples")} to see usage examples`)
|
|
483
|
+
.help()
|
|
484
|
+
.alias("help", "h")
|
|
485
|
+
.version("0.2.1")
|
|
486
|
+
.alias("version", "V")
|
|
487
|
+
.demandCommand(1, "You must specify a command")
|
|
488
|
+
.strict()
|
|
489
|
+
.argv;
|