kagami-cli 0.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/README.md +21 -0
- package/dist/args.d.ts +27 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +70 -0
- package/dist/download.d.ts +3 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +156 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/plugins.d.ts +4 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +169 -0
- package/dist/search.d.ts +4 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +101 -0
- package/dist/ui.d.ts +5 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +76 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @kagami/cli
|
|
2
|
+
|
|
3
|
+
CLI tool for Kagami manga parser with interactive interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @kagami/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
kagami "<search-query>"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Interactive workflow: select source → search → view results → select manga → download chapters
|
|
18
|
+
|
|
19
|
+
## License
|
|
20
|
+
|
|
21
|
+
MIT
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import commandLineArgs from 'command-line-args';
|
|
2
|
+
export declare const optionDefinitions: ({
|
|
3
|
+
name: string;
|
|
4
|
+
alias: string;
|
|
5
|
+
type: BooleanConstructor;
|
|
6
|
+
description: string;
|
|
7
|
+
multiple?: undefined;
|
|
8
|
+
defaultOption?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
name: string;
|
|
11
|
+
type: StringConstructor;
|
|
12
|
+
description: string;
|
|
13
|
+
alias?: undefined;
|
|
14
|
+
multiple?: undefined;
|
|
15
|
+
defaultOption?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
name: string;
|
|
18
|
+
type: StringConstructor;
|
|
19
|
+
multiple: boolean;
|
|
20
|
+
defaultOption: boolean;
|
|
21
|
+
description: string;
|
|
22
|
+
alias?: undefined;
|
|
23
|
+
})[];
|
|
24
|
+
export declare function showUsage(): void;
|
|
25
|
+
export declare function showInstallHelp(): void;
|
|
26
|
+
export declare function parseArgs(): commandLineArgs.CommandLineOptions;
|
|
27
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAGhD,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;IAK7B,CAAC;AAgDF,wBAAgB,SAAS,SAExB;AAED,wBAAgB,eAAe,SAE9B;AAED,wBAAgB,SAAS,uCAExB"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.optionDefinitions = void 0;
|
|
7
|
+
exports.showUsage = showUsage;
|
|
8
|
+
exports.showInstallHelp = showInstallHelp;
|
|
9
|
+
exports.parseArgs = parseArgs;
|
|
10
|
+
const command_line_args_1 = __importDefault(require("command-line-args"));
|
|
11
|
+
const command_line_usage_1 = __importDefault(require("command-line-usage"));
|
|
12
|
+
exports.optionDefinitions = [
|
|
13
|
+
{ name: 'help', alias: 'h', type: Boolean, description: 'Show this help message' },
|
|
14
|
+
{ name: 'install', type: String, description: 'Install a plugin source (e.g. mangadex) or local path' },
|
|
15
|
+
{ name: 'link', type: String, description: 'Download directly from URL (e.g. https://mangadex.org/title/...)' },
|
|
16
|
+
{ name: 'query', type: String, multiple: true, defaultOption: true, description: 'Search query for manga' },
|
|
17
|
+
];
|
|
18
|
+
const sections = [
|
|
19
|
+
{
|
|
20
|
+
header: 'Kagami',
|
|
21
|
+
content: 'CLI tool for downloading manga from various sources.'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
header: 'Usage',
|
|
25
|
+
content: [
|
|
26
|
+
'$ kagami <search-query>',
|
|
27
|
+
'$ kagami --link=<url>',
|
|
28
|
+
'$ kagami --install <sourcename>',
|
|
29
|
+
'$ kagami --install <path-to-local-plugin>'
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
header: 'Options',
|
|
34
|
+
optionList: exports.optionDefinitions.filter(opt => opt.name !== 'query')
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
header: 'Examples',
|
|
38
|
+
content: [
|
|
39
|
+
'$ kagami "Toaru no Index"',
|
|
40
|
+
'$ kagami --link=https://mangadex.org/title/...',
|
|
41
|
+
'$ kagami --install mangadex',
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
const installSections = [
|
|
46
|
+
{
|
|
47
|
+
header: 'No sources found',
|
|
48
|
+
content: 'Add source via installation command:'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
header: 'Install Commands',
|
|
52
|
+
optionList: exports.optionDefinitions.filter(opt => opt.name === 'install')
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
header: 'Examples',
|
|
56
|
+
content: [
|
|
57
|
+
'$ kagami --install mangadex',
|
|
58
|
+
'$ kagami --install ./packages/plugin-mangadex'
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
function showUsage() {
|
|
63
|
+
console.log((0, command_line_usage_1.default)(sections));
|
|
64
|
+
}
|
|
65
|
+
function showInstallHelp() {
|
|
66
|
+
console.log((0, command_line_usage_1.default)(installSections));
|
|
67
|
+
}
|
|
68
|
+
function parseArgs() {
|
|
69
|
+
return (0, command_line_args_1.default)(exports.optionDefinitions, { argv: process.argv.slice(2) });
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAY,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAS3D,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,iBAqHjE"}
|
package/dist/download.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.downloadManga = downloadManga;
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const cli_progress_1 = require("cli-progress");
|
|
49
|
+
const OUTPUT_DIR = './downloads';
|
|
50
|
+
function getMangaName(manga) {
|
|
51
|
+
return Array.isArray(manga.name) ? manga.name[0] : manga.name;
|
|
52
|
+
}
|
|
53
|
+
function downloadManga(manga, plugin) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const mangaName = getMangaName(manga);
|
|
57
|
+
console.log(`\nDownloading: ${mangaName} from ${manga.source}`);
|
|
58
|
+
const safeName = mangaName.replace(/[\/\\:*?"<>|]/g, '_');
|
|
59
|
+
const mangaDir = path.join(OUTPUT_DIR, manga.source, safeName);
|
|
60
|
+
fs.mkdirSync(mangaDir, { recursive: true });
|
|
61
|
+
// Download covers
|
|
62
|
+
if (manga.covers && manga.covers.length > 0) {
|
|
63
|
+
const coversDir = path.join(mangaDir, 'covers');
|
|
64
|
+
fs.mkdirSync(coversDir, { recursive: true });
|
|
65
|
+
console.log(`Downloading ${manga.covers.length} cover(s)...`);
|
|
66
|
+
const coverBar = new cli_progress_1.SingleBar({}, cli_progress_1.Presets.shades_classic);
|
|
67
|
+
coverBar.start(manga.covers.length, 0);
|
|
68
|
+
for (let i = 0; i < manga.covers.length; i++) {
|
|
69
|
+
const cover = manga.covers[i];
|
|
70
|
+
const ext = ((_a = cover.url.split('.').pop()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) || 'jpg';
|
|
71
|
+
const fileName = `cover-${i + 1}.${ext}`;
|
|
72
|
+
const filePath = path.join(coversDir, fileName);
|
|
73
|
+
if (fs.existsSync(filePath)) {
|
|
74
|
+
coverBar.update(i + 1);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const buffer = yield plugin.downloadImage(cover.url);
|
|
79
|
+
fs.writeFileSync(filePath, Buffer.from(buffer));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const error = err;
|
|
83
|
+
console.error(`\n Error downloading cover ${fileName}: ${error.message || err}`);
|
|
84
|
+
}
|
|
85
|
+
coverBar.update(i + 1);
|
|
86
|
+
yield plugin.delay();
|
|
87
|
+
}
|
|
88
|
+
coverBar.stop();
|
|
89
|
+
console.log(` ✓ Covers downloaded`);
|
|
90
|
+
}
|
|
91
|
+
console.log('Fetching chapters...');
|
|
92
|
+
let chapters = [];
|
|
93
|
+
try {
|
|
94
|
+
chapters = yield plugin.getChapters(manga);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const error = err;
|
|
98
|
+
console.error(` Error fetching chapters from ${manga.source}: ${error.message || err}`);
|
|
99
|
+
if (err instanceof SyntaxError) {
|
|
100
|
+
console.error(` (Invalid response from ${manga.source} - possible 403/404/network error)`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
yield plugin.delay();
|
|
104
|
+
if (!chapters || chapters.length === 0) {
|
|
105
|
+
console.log('No chapters found.');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(`Found ${chapters.length} chapters. Downloading...\n`);
|
|
109
|
+
for (const chapter of chapters) {
|
|
110
|
+
const chapterDir = path.join(mangaDir, `chapter-${chapter.number}`);
|
|
111
|
+
fs.mkdirSync(chapterDir, { recursive: true });
|
|
112
|
+
console.log(`Chapter ${chapter.number}: Fetching pages...`);
|
|
113
|
+
let pages = [];
|
|
114
|
+
try {
|
|
115
|
+
pages = yield plugin.getChapterPages(chapter);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const error = err;
|
|
119
|
+
console.error(` Error fetching pages for chapter ${chapter.number} from ${manga.source}: ${error.message || err}`);
|
|
120
|
+
if (err instanceof SyntaxError) {
|
|
121
|
+
console.error(` (Invalid response from ${manga.source} - possible 403/404/network error)`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
yield plugin.delay();
|
|
125
|
+
if (pages.length === 0) {
|
|
126
|
+
console.log(' No pages found, skipping.');
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const bar = new cli_progress_1.SingleBar({}, cli_progress_1.Presets.shades_classic);
|
|
130
|
+
bar.start(pages.length, 0);
|
|
131
|
+
for (let i = 0; i < pages.length; i++) {
|
|
132
|
+
const page = pages[i];
|
|
133
|
+
const ext = ((_b = page.url.split('.').pop()) === null || _b === void 0 ? void 0 : _b.split('?')[0]) || 'jpg';
|
|
134
|
+
const fileName = `${String(i + 1).padStart(3, '0')}.${ext}`;
|
|
135
|
+
const filePath = path.join(chapterDir, fileName);
|
|
136
|
+
if (fs.existsSync(filePath)) {
|
|
137
|
+
bar.update(i + 1);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const buffer = yield plugin.downloadImage(page.url);
|
|
142
|
+
fs.writeFileSync(filePath, Buffer.from(buffer));
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
const error = err;
|
|
146
|
+
console.error(`\nError downloading ${page.url}: ${error.message || err}`);
|
|
147
|
+
}
|
|
148
|
+
bar.update(i + 1);
|
|
149
|
+
yield plugin.delay();
|
|
150
|
+
}
|
|
151
|
+
bar.stop();
|
|
152
|
+
console.log(` ✓ Chapter ${chapter.number} complete`);
|
|
153
|
+
}
|
|
154
|
+
console.log('\nDownload complete!');
|
|
155
|
+
});
|
|
156
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const ui_1 = require("./ui");
|
|
14
|
+
const download_1 = require("./download");
|
|
15
|
+
const search_1 = require("./search");
|
|
16
|
+
const plugins_1 = require("./plugins");
|
|
17
|
+
const args_1 = require("./args");
|
|
18
|
+
function main() {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
var _a;
|
|
21
|
+
// Clear terminal and use full height
|
|
22
|
+
process.stdout.write('\x1b[2J\x1b[0;0H');
|
|
23
|
+
const options = (0, args_1.parseArgs)();
|
|
24
|
+
if (options.help) {
|
|
25
|
+
(0, args_1.showUsage)();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (options.install !== undefined) {
|
|
29
|
+
const sourceName = options.install;
|
|
30
|
+
if (!sourceName) {
|
|
31
|
+
(0, args_1.showUsage)();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
yield (0, plugins_1.installPlugin)(sourceName);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const query = options.query ? options.query.join(' ') : null;
|
|
38
|
+
const link = options.link || null;
|
|
39
|
+
if (!query && !link) {
|
|
40
|
+
(0, args_1.showUsage)();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const plugins = yield (0, plugins_1.discoverPlugins)();
|
|
44
|
+
if (Object.keys(plugins).length === 0) {
|
|
45
|
+
(0, args_1.showInstallHelp)();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (link) {
|
|
49
|
+
const plugin = Object.values(plugins).find(p => p.matchUrl(link));
|
|
50
|
+
if (!plugin) {
|
|
51
|
+
console.log('Unsupported URL or source not found.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const mangaId = plugin.extractMangaId(link);
|
|
55
|
+
if (!mangaId) {
|
|
56
|
+
console.log('Could not extract manga ID from URL.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const mangaInfo = yield plugin.getMangaInfo(mangaId);
|
|
60
|
+
const manga = {
|
|
61
|
+
source: plugin.key,
|
|
62
|
+
id: mangaId,
|
|
63
|
+
name: (_a = mangaInfo === null || mangaInfo === void 0 ? void 0 : mangaInfo.name) !== null && _a !== void 0 ? _a : mangaId
|
|
64
|
+
};
|
|
65
|
+
yield (0, search_1.prepareManga)(manga, plugin);
|
|
66
|
+
let chapters = [];
|
|
67
|
+
try {
|
|
68
|
+
chapters = yield plugin.getChapters(manga);
|
|
69
|
+
manga.chapters = chapters.length;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error('Error fetching manga info:', err);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
console.log(`\nFound: ${manga.name} (${chapters.length} chapters)`);
|
|
76
|
+
const confirmed = yield (0, ui_1.confirmDownload)(manga);
|
|
77
|
+
if (!confirmed)
|
|
78
|
+
return;
|
|
79
|
+
yield (0, download_1.downloadManga)(manga, plugin);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const sources = yield (0, ui_1.selectSources)(Object.keys(plugins));
|
|
83
|
+
if (sources.length === 0) {
|
|
84
|
+
console.log('No sources selected.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const results = yield (0, search_1.searchManga)(query, sources, plugins);
|
|
88
|
+
if (results.length === 0) {
|
|
89
|
+
console.log('No results found. Try a different query.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const selected = yield (0, ui_1.selectManga)(results, plugins);
|
|
93
|
+
for (const manga of selected) {
|
|
94
|
+
const plugin = plugins[manga.source];
|
|
95
|
+
if (plugin) {
|
|
96
|
+
yield (0, download_1.downloadManga)(manga, plugin);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
main().catch(err => {
|
|
102
|
+
if ((err === null || err === void 0 ? void 0 : err.name) === 'ExitPromptError') {
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
console.error(err);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAqCzC,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA6CxE;AAED,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,iBAsCrD"}
|
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.discoverPlugins = discoverPlugins;
|
|
46
|
+
exports.installPlugin = installPlugin;
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
const util_1 = require("util");
|
|
51
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
52
|
+
function instantiatePlugin(PluginModule) {
|
|
53
|
+
// Try different export patterns
|
|
54
|
+
const PluginClass = PluginModule.default || // ES module default export
|
|
55
|
+
PluginModule.MangaDexPlugin || // Named export (mangadex)
|
|
56
|
+
PluginModule.MangaLibPlugin || // Named export (mangalib)
|
|
57
|
+
PluginModule; // Direct export
|
|
58
|
+
try {
|
|
59
|
+
const plugin = new PluginClass();
|
|
60
|
+
if (plugin.key && plugin.search) {
|
|
61
|
+
return plugin;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (_a) {
|
|
65
|
+
// If instantiation fails, try to find a class that can be instantiated
|
|
66
|
+
for (const key of Object.keys(PluginModule)) {
|
|
67
|
+
try {
|
|
68
|
+
const cls = PluginModule[key];
|
|
69
|
+
if (typeof cls === 'function') {
|
|
70
|
+
const plugin = new cls();
|
|
71
|
+
if (plugin.key && plugin.search) {
|
|
72
|
+
return plugin;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (_b) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function discoverPlugins() {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
const cwd = process.cwd();
|
|
86
|
+
const nodeModules = path.join(cwd, 'node_modules');
|
|
87
|
+
const plugins = {};
|
|
88
|
+
if (!fs.existsSync(nodeModules))
|
|
89
|
+
return plugins;
|
|
90
|
+
// Check @kagami scope
|
|
91
|
+
const kagamiScope = path.join(nodeModules, '@kagami');
|
|
92
|
+
if (fs.existsSync(kagamiScope)) {
|
|
93
|
+
const dirs = fs.readdirSync(kagamiScope);
|
|
94
|
+
for (const dir of dirs) {
|
|
95
|
+
if (dir.startsWith('plugin-')) {
|
|
96
|
+
try {
|
|
97
|
+
const pkgPath = path.join(kagamiScope, dir);
|
|
98
|
+
const PluginModule = require(pkgPath);
|
|
99
|
+
const plugin = instantiatePlugin(PluginModule);
|
|
100
|
+
if (plugin) {
|
|
101
|
+
plugins[plugin.key] = plugin;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
console.warn(`Failed to load plugin @kagami/${dir}:`, e);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Check unscoped kagami-plugin-*
|
|
111
|
+
const allDirs = fs.readdirSync(nodeModules);
|
|
112
|
+
for (const dir of allDirs) {
|
|
113
|
+
if (dir.startsWith('kagami-plugin-')) {
|
|
114
|
+
try {
|
|
115
|
+
const pkgPath = path.join(nodeModules, dir);
|
|
116
|
+
const PluginModule = require(pkgPath);
|
|
117
|
+
const plugin = instantiatePlugin(PluginModule);
|
|
118
|
+
if (plugin) {
|
|
119
|
+
plugins[plugin.key] = plugin;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
console.warn(`Failed to load plugin ${dir}:`, e);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return plugins;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
function installPlugin(sourceName) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
if (sourceName.startsWith('.') || path.isAbsolute(sourceName)) {
|
|
133
|
+
if (!fs.existsSync(sourceName)) {
|
|
134
|
+
console.error(`Path not found: ${sourceName}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
yield execAsync(`npm install ${sourceName}`);
|
|
139
|
+
console.log(`✓ Installed from ${sourceName}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
console.error(`Failed to install from path:`, e.message);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const candidates = [
|
|
148
|
+
`@kagami/plugin-${sourceName}`,
|
|
149
|
+
`kagami-plugin-${sourceName}`
|
|
150
|
+
];
|
|
151
|
+
for (const pkg of candidates) {
|
|
152
|
+
try {
|
|
153
|
+
yield execAsync(`npm view ${pkg}`);
|
|
154
|
+
console.log(`Installing ${pkg}...`);
|
|
155
|
+
yield execAsync(`npm install ${pkg}`);
|
|
156
|
+
console.log(`✓ Installed ${pkg}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
catch (_a) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
console.error(`Plugin "${sourceName}" not found. Tried:`);
|
|
164
|
+
for (const pkg of candidates) {
|
|
165
|
+
console.error(` - ${pkg}`);
|
|
166
|
+
}
|
|
167
|
+
console.error(`\nFor local plugins, use: kagami install <path>`);
|
|
168
|
+
});
|
|
169
|
+
}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { IManga, IPlugin } from "@kagami/plugin";
|
|
2
|
+
export declare function prepareManga(manga: IManga, plugin: IPlugin, writeStatus?: (msg: string) => void): Promise<void>;
|
|
3
|
+
export declare function searchManga(query: string, sources: string[], plugins: Record<string, IPlugin>): Promise<Array<IManga>>;
|
|
4
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CrH;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAwC1H"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.prepareManga = prepareManga;
|
|
13
|
+
exports.searchManga = searchManga;
|
|
14
|
+
function prepareManga(manga, plugin, writeStatus) {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
const status = writeStatus || ((msg) => { });
|
|
17
|
+
const mangaName = Array.isArray(manga.name) ? manga.name[0] : manga.name;
|
|
18
|
+
// Fetch detailed manga info
|
|
19
|
+
status(`| \x1b[33m${mangaName}\x1b[0m: fetching info...`);
|
|
20
|
+
try {
|
|
21
|
+
const info = yield plugin.getMangaInfo(String(manga.id));
|
|
22
|
+
if (info) {
|
|
23
|
+
if (info.author)
|
|
24
|
+
manga.author = info.author;
|
|
25
|
+
if (info.status)
|
|
26
|
+
manga.status = info.status;
|
|
27
|
+
if (info.year)
|
|
28
|
+
manga.year = info.year;
|
|
29
|
+
if (info.covers)
|
|
30
|
+
manga.covers = info.covers;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
// Ignore errors for optional info
|
|
35
|
+
}
|
|
36
|
+
// Fetch covers (via separate API)
|
|
37
|
+
try {
|
|
38
|
+
const covers = yield plugin.getMangaCovers(manga);
|
|
39
|
+
if (covers && covers.length > 0) {
|
|
40
|
+
manga.covers = covers;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// Ignore errors for optional covers
|
|
45
|
+
}
|
|
46
|
+
// Fetch chapters
|
|
47
|
+
status(`| \x1b[33m${mangaName}\x1b[0m: fetching chapters...`);
|
|
48
|
+
try {
|
|
49
|
+
const chapters = yield plugin.getChapters(manga);
|
|
50
|
+
manga.chapters = chapters.length;
|
|
51
|
+
status(`| \x1b[33m${mangaName}\x1b[0m: \x1b[32m${chapters.length} ✓\x1b[0m`);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const error = err;
|
|
55
|
+
manga.chapters = undefined;
|
|
56
|
+
status(`| \x1b[33m${mangaName}\x1b[0m: \x1b[31m? (error)\x1b[0m`);
|
|
57
|
+
if (err instanceof SyntaxError) {
|
|
58
|
+
console.error(`\n Error: Invalid response from ${manga.source} - possible 403/404/network error`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
yield plugin.delay();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function searchManga(query, sources, plugins) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
const allResults = [];
|
|
67
|
+
let totalResults = 0;
|
|
68
|
+
const writeStatus = (msg) => {
|
|
69
|
+
process.stdout.write(`\r\x1b[2K${msg}`);
|
|
70
|
+
};
|
|
71
|
+
writeStatus(`Searching for "${query}"...`);
|
|
72
|
+
for (const source of sources) {
|
|
73
|
+
const plugin = plugins[source];
|
|
74
|
+
try {
|
|
75
|
+
writeStatus(`[\x1b[32m${source}\x1b[0m: searching...`);
|
|
76
|
+
const results = yield plugin.search(query);
|
|
77
|
+
writeStatus(`[\x1b[32m${source}\x1b[0m: ${results.length} found`);
|
|
78
|
+
for (let i = 0; i < results.length; i++) {
|
|
79
|
+
const manga = results[i];
|
|
80
|
+
yield prepareManga(manga, plugin, (msg) => {
|
|
81
|
+
const mangaName = Array.isArray(manga.name) ? manga.name[0] : manga.name;
|
|
82
|
+
writeStatus(`[\x1b[32m${source}\x1b[0m: ${results.length} found ${msg}`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
allResults.push(...results);
|
|
86
|
+
totalResults += results.length;
|
|
87
|
+
writeStatus(`[\x1b[32m${source}\x1b[0m: ${results.length} found]`);
|
|
88
|
+
yield plugin.delay();
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
const error = err;
|
|
92
|
+
writeStatus(`[\x1b[31m${source}\x1b[0m: error - ${error.message || err}]`);
|
|
93
|
+
if (err instanceof SyntaxError) {
|
|
94
|
+
console.error(`\n (Invalid JSON response from ${source} - possible 403/404/network error)`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write(`\nFound ${totalResults} results total\n\n`);
|
|
99
|
+
return allResults;
|
|
100
|
+
});
|
|
101
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IManga, IPlugin } from '@kagami/plugin';
|
|
2
|
+
export declare function selectSources(availableSources: string[]): Promise<string[]>;
|
|
3
|
+
export declare function selectManga(results: Array<IManga>, plugins: Record<string, IPlugin>): Promise<IManga[]>;
|
|
4
|
+
export declare function confirmDownload(manga: IManga): Promise<boolean>;
|
|
5
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEjD,wBAAsB,aAAa,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAajF;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8B7G;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASrE"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.selectSources = selectSources;
|
|
16
|
+
exports.selectManga = selectManga;
|
|
17
|
+
exports.confirmDownload = confirmDownload;
|
|
18
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
19
|
+
function selectSources(availableSources) {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const answer = yield inquirer_1.default.prompt({
|
|
22
|
+
type: 'checkbox',
|
|
23
|
+
name: 'sources',
|
|
24
|
+
message: 'Select sources to search:',
|
|
25
|
+
choices: availableSources.map(key => ({
|
|
26
|
+
name: key,
|
|
27
|
+
value: key,
|
|
28
|
+
checked: true
|
|
29
|
+
}))
|
|
30
|
+
});
|
|
31
|
+
return answer.sources;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function selectManga(results, plugins) {
|
|
35
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
36
|
+
if (results.length === 0) {
|
|
37
|
+
console.log('No results found.');
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const answer = yield inquirer_1.default.prompt({
|
|
41
|
+
type: 'checkbox',
|
|
42
|
+
name: 'selected',
|
|
43
|
+
message: 'Select manga to download:',
|
|
44
|
+
pageSize: process.stdout.rows - 4 || 20,
|
|
45
|
+
choices: results.map((manga, idx) => {
|
|
46
|
+
var _a;
|
|
47
|
+
const name = Array.isArray(manga.name) ? manga.name[0] : manga.name;
|
|
48
|
+
const chapterCount = (_a = manga.chapters) !== null && _a !== void 0 ? _a : '?';
|
|
49
|
+
return {
|
|
50
|
+
name: `${idx + 1}. [${manga.source}] ${name} (${chapterCount} chapters)`,
|
|
51
|
+
value: manga,
|
|
52
|
+
short: name
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
validate: (answers) => {
|
|
56
|
+
if (answers.length === 0) {
|
|
57
|
+
return 'Please select at least one manga';
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return answer.selected;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function confirmDownload(manga) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const name = Array.isArray(manga.name) ? manga.name[0] : manga.name;
|
|
68
|
+
const answer = yield inquirer_1.default.prompt({
|
|
69
|
+
type: 'confirm',
|
|
70
|
+
name: 'confirmed',
|
|
71
|
+
message: `Download "${name}" from ${manga.source}?`,
|
|
72
|
+
default: true
|
|
73
|
+
});
|
|
74
|
+
return answer.confirmed;
|
|
75
|
+
});
|
|
76
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kagami-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for kagami",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/index.js",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"kagami": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc --build",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["manga", "parser", "cli", "kagami"],
|
|
28
|
+
"author": "Ilya Tovstenok <iliya.tovstyonok@gmail.com>",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/iliyat/kagami.git",
|
|
33
|
+
"directory": "packages/cli"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/iliyat/kagami#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@kagami-cli/plugin": "^0.1.0",
|
|
38
|
+
"cli-progress": "^3.12.0",
|
|
39
|
+
"command-line-args": "^6.0.1",
|
|
40
|
+
"command-line-usage": "^7.0.4",
|
|
41
|
+
"inquirer": "^13.4.2"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/cli-progress": "^3.11.6",
|
|
45
|
+
"@types/command-line-args": "^5.2.3",
|
|
46
|
+
"@types/command-line-usage": "^5.0.4",
|
|
47
|
+
"@types/inquirer": "^9.0.9",
|
|
48
|
+
"@types/node": "^25.4.0",
|
|
49
|
+
"typescript": "^5.5.3"
|
|
50
|
+
}
|
|
51
|
+
}
|