goblin-malin 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/LICENSE +132 -0
- package/README.md +31 -0
- package/dist/chunk-DJTET7PY.js +9124 -0
- package/dist/chunk-ECLAXOR2.js +423 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +8 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/metadata-Y5IDFTMA.js +8 -0
- package/package.json +89 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/utils/metadata.ts
|
|
5
|
+
import * as fs3 from "fs";
|
|
6
|
+
import * as path5 from "path";
|
|
7
|
+
import NodeId3 from "node-id3";
|
|
8
|
+
import { readFlacTags, writeFlacTags } from "flac-tagger";
|
|
9
|
+
|
|
10
|
+
// src/base/logger/logger.ts
|
|
11
|
+
import fs2 from "fs";
|
|
12
|
+
import path4 from "path";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import winston from "winston";
|
|
15
|
+
|
|
16
|
+
// src/base/logger/ink-transport.ts
|
|
17
|
+
import Transport from "winston-transport";
|
|
18
|
+
var InkTransport = class extends Transport {
|
|
19
|
+
static {
|
|
20
|
+
__name(this, "InkTransport");
|
|
21
|
+
}
|
|
22
|
+
history = [];
|
|
23
|
+
pending = [];
|
|
24
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
super(opts);
|
|
27
|
+
}
|
|
28
|
+
log(info, callback) {
|
|
29
|
+
setImmediate(() => {
|
|
30
|
+
this.emit("logged", info);
|
|
31
|
+
});
|
|
32
|
+
this.history.push(info);
|
|
33
|
+
this.pending.push(info);
|
|
34
|
+
this.notifySubscribers();
|
|
35
|
+
callback();
|
|
36
|
+
}
|
|
37
|
+
subscribe(callback) {
|
|
38
|
+
this.subscribers.add(callback);
|
|
39
|
+
callback([
|
|
40
|
+
...this.history
|
|
41
|
+
]);
|
|
42
|
+
return () => {
|
|
43
|
+
this.subscribers.delete(callback);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
notifySubscribers() {
|
|
47
|
+
const batch = [
|
|
48
|
+
...this.pending
|
|
49
|
+
];
|
|
50
|
+
this.pending = [];
|
|
51
|
+
this.subscribers.forEach((callback) => callback(batch));
|
|
52
|
+
}
|
|
53
|
+
getLogs() {
|
|
54
|
+
return [
|
|
55
|
+
...this.history
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
filterLogs(predicate) {
|
|
59
|
+
return this.history.filter(predicate);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var inkTransport = new InkTransport({
|
|
63
|
+
maxLogs: 300,
|
|
64
|
+
level: "debug"
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/base/logger/types.ts
|
|
68
|
+
var LogLevel = /* @__PURE__ */ (function(LogLevel2) {
|
|
69
|
+
LogLevel2["INFO"] = "INFO";
|
|
70
|
+
LogLevel2["WARN"] = "WARN";
|
|
71
|
+
LogLevel2["ERROR"] = "ERROR";
|
|
72
|
+
LogLevel2["DEBUG"] = "DEBUG";
|
|
73
|
+
return LogLevel2;
|
|
74
|
+
})({});
|
|
75
|
+
|
|
76
|
+
// src/utils/appPaths.ts
|
|
77
|
+
import path3 from "path";
|
|
78
|
+
|
|
79
|
+
// src/settings/settingsStore.ts
|
|
80
|
+
import * as fs from "fs";
|
|
81
|
+
import * as path2 from "path";
|
|
82
|
+
import { EventEmitter } from "events";
|
|
83
|
+
|
|
84
|
+
// src/constants.ts
|
|
85
|
+
import dotenv from "dotenv";
|
|
86
|
+
import path from "path";
|
|
87
|
+
import { fileURLToPath } from "url";
|
|
88
|
+
dotenv.config();
|
|
89
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
90
|
+
var __dirname = path.dirname(__filename);
|
|
91
|
+
var PROJECT_ROOT = path.join(__dirname, "..");
|
|
92
|
+
|
|
93
|
+
// src/settings/appSettings.ts
|
|
94
|
+
var DEFAULT_APP_SETTINGS = {
|
|
95
|
+
general: {
|
|
96
|
+
reopenLastSession: false,
|
|
97
|
+
appDataDir: PROJECT_ROOT,
|
|
98
|
+
animationsEnabled: false
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/utils/deepMerge.ts
|
|
103
|
+
function deepMerge(target, source) {
|
|
104
|
+
if (typeof source !== "object" || source === null) return source;
|
|
105
|
+
if (typeof target !== "object" || target === null) return source;
|
|
106
|
+
const result = {
|
|
107
|
+
...target
|
|
108
|
+
};
|
|
109
|
+
for (const key of Object.keys(source)) {
|
|
110
|
+
const srcVal = source[key];
|
|
111
|
+
const tgtVal = target[key];
|
|
112
|
+
if (srcVal !== void 0) {
|
|
113
|
+
if (typeof srcVal === "object" && srcVal !== null && !Array.isArray(srcVal) && typeof tgtVal === "object" && tgtVal !== null && !Array.isArray(tgtVal)) {
|
|
114
|
+
result[key] = deepMerge(tgtVal, srcVal);
|
|
115
|
+
} else {
|
|
116
|
+
result[key] = srcVal;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
__name(deepMerge, "deepMerge");
|
|
123
|
+
|
|
124
|
+
// src/settings/settingsStore.ts
|
|
125
|
+
var CONFIG_DIR = path2.join(PROJECT_ROOT, "config");
|
|
126
|
+
var SETTINGS_PATH = path2.join(CONFIG_DIR, "settings.json");
|
|
127
|
+
var SettingsStore = class _SettingsStore {
|
|
128
|
+
static {
|
|
129
|
+
__name(this, "SettingsStore");
|
|
130
|
+
}
|
|
131
|
+
static instance;
|
|
132
|
+
cache = null;
|
|
133
|
+
emitter = new EventEmitter();
|
|
134
|
+
static getInstance() {
|
|
135
|
+
if (!_SettingsStore.instance) _SettingsStore.instance = new _SettingsStore();
|
|
136
|
+
return _SettingsStore.instance;
|
|
137
|
+
}
|
|
138
|
+
readFromDisk() {
|
|
139
|
+
try {
|
|
140
|
+
const raw = fs.readFileSync(SETTINGS_PATH, "utf-8");
|
|
141
|
+
return JSON.parse(raw);
|
|
142
|
+
} catch {
|
|
143
|
+
return {
|
|
144
|
+
general: DEFAULT_APP_SETTINGS.general,
|
|
145
|
+
flows: {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
writeToDisk(settings) {
|
|
150
|
+
fs.mkdirSync(CONFIG_DIR, {
|
|
151
|
+
recursive: true
|
|
152
|
+
});
|
|
153
|
+
const tmp = SETTINGS_PATH + ".tmp";
|
|
154
|
+
fs.writeFileSync(tmp, JSON.stringify(settings, null, 2), "utf-8");
|
|
155
|
+
fs.renameSync(tmp, SETTINGS_PATH);
|
|
156
|
+
this.cache = settings;
|
|
157
|
+
this.emitter.emit("change");
|
|
158
|
+
}
|
|
159
|
+
getCached() {
|
|
160
|
+
if (!this.cache) this.cache = this.readFromDisk();
|
|
161
|
+
return this.cache;
|
|
162
|
+
}
|
|
163
|
+
// ── App (global) settings ──────────────────────────────────────────────────
|
|
164
|
+
getAppSettings() {
|
|
165
|
+
const s = this.getCached();
|
|
166
|
+
return deepMerge(DEFAULT_APP_SETTINGS, {
|
|
167
|
+
general: s.general
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
writeAppSettings(settings) {
|
|
171
|
+
const current = this.getCached();
|
|
172
|
+
this.writeToDisk({
|
|
173
|
+
...current,
|
|
174
|
+
general: settings.general
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// ── Flow settings ──────────────────────────────────────────────────────────
|
|
178
|
+
getFlowSettings(flowId, defaults) {
|
|
179
|
+
const stored = this.getCached().flows?.[flowId] ?? {};
|
|
180
|
+
return deepMerge(defaults, stored);
|
|
181
|
+
}
|
|
182
|
+
writeFlowSettings(flowId, settings) {
|
|
183
|
+
const current = this.getCached();
|
|
184
|
+
this.writeToDisk({
|
|
185
|
+
...current,
|
|
186
|
+
flows: {
|
|
187
|
+
...current.flows ?? {},
|
|
188
|
+
[flowId]: settings
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// ── Change notifications ───────────────────────────────────────────────────
|
|
193
|
+
onSettingsChanged(callback) {
|
|
194
|
+
this.emitter.on("change", callback);
|
|
195
|
+
return () => {
|
|
196
|
+
this.emitter.off("change", callback);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/utils/appPaths.ts
|
|
202
|
+
function dataDir() {
|
|
203
|
+
return SettingsStore.getInstance().getAppSettings().general.appDataDir;
|
|
204
|
+
}
|
|
205
|
+
__name(dataDir, "dataDir");
|
|
206
|
+
function getCacheDir() {
|
|
207
|
+
return path3.join(dataDir(), "cache");
|
|
208
|
+
}
|
|
209
|
+
__name(getCacheDir, "getCacheDir");
|
|
210
|
+
function getBinDir() {
|
|
211
|
+
return path3.join(dataDir(), "bin");
|
|
212
|
+
}
|
|
213
|
+
__name(getBinDir, "getBinDir");
|
|
214
|
+
function getLogsPath() {
|
|
215
|
+
return path3.join(dataDir(), "app.log");
|
|
216
|
+
}
|
|
217
|
+
__name(getLogsPath, "getLogsPath");
|
|
218
|
+
|
|
219
|
+
// src/base/logger/logger.ts
|
|
220
|
+
var logsPath = getLogsPath();
|
|
221
|
+
fs2.mkdirSync(path4.dirname(logsPath), {
|
|
222
|
+
recursive: true
|
|
223
|
+
});
|
|
224
|
+
fs2.writeFileSync(logsPath, "");
|
|
225
|
+
function getString(obj) {
|
|
226
|
+
if (typeof obj === "string") {
|
|
227
|
+
return obj;
|
|
228
|
+
} else if (obj instanceof Error) {
|
|
229
|
+
return obj.message;
|
|
230
|
+
} else {
|
|
231
|
+
return JSON.stringify(obj);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
__name(getString, "getString");
|
|
235
|
+
function setLogColor(level, message) {
|
|
236
|
+
switch (level) {
|
|
237
|
+
case LogLevel.INFO:
|
|
238
|
+
return chalk.blue(message);
|
|
239
|
+
case LogLevel.WARN:
|
|
240
|
+
return chalk.yellow(message);
|
|
241
|
+
case LogLevel.ERROR:
|
|
242
|
+
return chalk.red(message);
|
|
243
|
+
case LogLevel.DEBUG:
|
|
244
|
+
return chalk.gray(message);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
__name(setLogColor, "setLogColor");
|
|
248
|
+
var Logger = class _Logger {
|
|
249
|
+
static {
|
|
250
|
+
__name(this, "Logger");
|
|
251
|
+
}
|
|
252
|
+
static logger;
|
|
253
|
+
metadata = {};
|
|
254
|
+
constructor({ metadata, parentLogger } = {}) {
|
|
255
|
+
if (!_Logger.logger) {
|
|
256
|
+
_Logger.logger = winston.createLogger({
|
|
257
|
+
transports: [
|
|
258
|
+
inkTransport,
|
|
259
|
+
// Combined log file
|
|
260
|
+
new winston.transports.File({
|
|
261
|
+
filename: logsPath,
|
|
262
|
+
format: winston.format.combine(winston.format.timestamp(), winston.format.json())
|
|
263
|
+
})
|
|
264
|
+
]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
this.metadata = {};
|
|
268
|
+
if (parentLogger) {
|
|
269
|
+
this.metadata = {
|
|
270
|
+
...this.metadata,
|
|
271
|
+
...parentLogger.metadata
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (metadata) this.metadata = {
|
|
275
|
+
...this.metadata,
|
|
276
|
+
...metadata
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
log(level, message, details = {}) {
|
|
280
|
+
const service = this.metadata?.service;
|
|
281
|
+
const prefix = service ? `[${service}] ` : "";
|
|
282
|
+
const formattedMessage = prefix + setLogColor(level, getString(message));
|
|
283
|
+
const metadata = {
|
|
284
|
+
...this.metadata,
|
|
285
|
+
id: crypto.randomUUID(),
|
|
286
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
287
|
+
level,
|
|
288
|
+
// message: formattedMessage,
|
|
289
|
+
details
|
|
290
|
+
};
|
|
291
|
+
if (level === LogLevel.ERROR) {
|
|
292
|
+
_Logger.logger.error(formattedMessage, metadata);
|
|
293
|
+
} else if (level === LogLevel.WARN) {
|
|
294
|
+
_Logger.logger.warn(formattedMessage, metadata);
|
|
295
|
+
} else if (level === LogLevel.DEBUG) {
|
|
296
|
+
_Logger.logger.debug(formattedMessage, metadata);
|
|
297
|
+
} else {
|
|
298
|
+
_Logger.logger.info(formattedMessage, metadata);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
info(message, details) {
|
|
302
|
+
this.log(LogLevel.INFO, message, details);
|
|
303
|
+
}
|
|
304
|
+
warn(message, details) {
|
|
305
|
+
this.log(LogLevel.WARN, message, details);
|
|
306
|
+
}
|
|
307
|
+
error(message, details) {
|
|
308
|
+
this.log(LogLevel.ERROR, message, details);
|
|
309
|
+
}
|
|
310
|
+
debug(message, details) {
|
|
311
|
+
this.log(LogLevel.DEBUG, message, details);
|
|
312
|
+
}
|
|
313
|
+
// Create a child logger with additional metadata
|
|
314
|
+
createChild(additionalMetadata) {
|
|
315
|
+
return new _Logger({
|
|
316
|
+
metadata: additionalMetadata,
|
|
317
|
+
parentLogger: this
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var globalLogger = new Logger();
|
|
322
|
+
|
|
323
|
+
// src/utils/metadata.ts
|
|
324
|
+
async function cleanAndTagFlac(filePath, metadata) {
|
|
325
|
+
globalLogger.info(`--- Step 1: Cleaning invalid ID3 tags from ${filePath} ---`);
|
|
326
|
+
try {
|
|
327
|
+
const success = NodeId3.removeTags(filePath);
|
|
328
|
+
if (success) {
|
|
329
|
+
globalLogger.info("Successfully removed invalid ID3 tags. \u2705");
|
|
330
|
+
} else {
|
|
331
|
+
globalLogger.info("File did not contain any ID3 tags to remove.");
|
|
332
|
+
}
|
|
333
|
+
} catch (err) {
|
|
334
|
+
globalLogger.error(`Error cleaning file: ${err.message}. Stopping.`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
globalLogger.info(`
|
|
338
|
+
--- Step 2: Writing correct FLAC metadata ---`);
|
|
339
|
+
try {
|
|
340
|
+
const currentTags = await readFlacTags(filePath);
|
|
341
|
+
const newTags = {
|
|
342
|
+
...currentTags.tagMap,
|
|
343
|
+
TITLE: metadata.trackTitle,
|
|
344
|
+
ARTIST: metadata.artists,
|
|
345
|
+
ALBUMARTIST: metadata.albumArtists ?? metadata.artists,
|
|
346
|
+
...metadata.albumName ? {
|
|
347
|
+
ALBUM: metadata.albumName
|
|
348
|
+
} : {},
|
|
349
|
+
...metadata.year ? {
|
|
350
|
+
YEAR: metadata.year,
|
|
351
|
+
DATE: metadata.year
|
|
352
|
+
} : {},
|
|
353
|
+
...metadata.trackNumber ? {
|
|
354
|
+
TRACKNUMBER: metadata.trackNumber
|
|
355
|
+
} : {},
|
|
356
|
+
...metadata.isrc ? {
|
|
357
|
+
ISRC: metadata.isrc
|
|
358
|
+
} : {},
|
|
359
|
+
...metadata.genres?.length ? {
|
|
360
|
+
GENRE: metadata.genres
|
|
361
|
+
} : {},
|
|
362
|
+
...metadata.bpm != null ? {
|
|
363
|
+
BPM: String(metadata.bpm)
|
|
364
|
+
} : {},
|
|
365
|
+
...metadata.key ? {
|
|
366
|
+
KEY: metadata.key
|
|
367
|
+
} : {},
|
|
368
|
+
...metadata.musicBrainzTrackId ? {
|
|
369
|
+
MUSICBRAINZ_TRACKID: metadata.musicBrainzTrackId
|
|
370
|
+
} : {},
|
|
371
|
+
...metadata.musicBrainzAlbumId ? {
|
|
372
|
+
MUSICBRAINZ_ALBUMID: metadata.musicBrainzAlbumId
|
|
373
|
+
} : {},
|
|
374
|
+
...metadata.musicBrainzArtistId ? {
|
|
375
|
+
MUSICBRAINZ_ARTISTID: metadata.musicBrainzArtistId
|
|
376
|
+
} : {},
|
|
377
|
+
...metadata.musicBrainzReleaseGroupId ? {
|
|
378
|
+
MUSICBRAINZ_RELEASEGROUPID: metadata.musicBrainzReleaseGroupId
|
|
379
|
+
} : {}
|
|
380
|
+
};
|
|
381
|
+
const tagsToWrite = {
|
|
382
|
+
...currentTags,
|
|
383
|
+
tagMap: newTags
|
|
384
|
+
};
|
|
385
|
+
await writeFlacTags(tagsToWrite, filePath);
|
|
386
|
+
globalLogger.info("FLAC metadata updated successfully! \u2705");
|
|
387
|
+
} catch (error) {
|
|
388
|
+
const err = error;
|
|
389
|
+
globalLogger.error(`Error writing FLAC metadata: ${err.message}`);
|
|
390
|
+
if (err.message === "Invalid stream header") {
|
|
391
|
+
globalLogger.error("This is unexpected, the file should be clean.");
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
__name(cleanAndTagFlac, "cleanAndTagFlac");
|
|
396
|
+
async function moveFile(srcPath, destPath) {
|
|
397
|
+
await fs3.promises.mkdir(path5.dirname(destPath), {
|
|
398
|
+
recursive: true
|
|
399
|
+
});
|
|
400
|
+
try {
|
|
401
|
+
await fs3.promises.rename(srcPath, destPath);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (err.code === "EXDEV") {
|
|
404
|
+
await fs3.promises.copyFile(srcPath, destPath);
|
|
405
|
+
await fs3.promises.unlink(srcPath);
|
|
406
|
+
} else {
|
|
407
|
+
throw err;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
__name(moveFile, "moveFile");
|
|
412
|
+
|
|
413
|
+
export {
|
|
414
|
+
__name,
|
|
415
|
+
deepMerge,
|
|
416
|
+
SettingsStore,
|
|
417
|
+
inkTransport,
|
|
418
|
+
getCacheDir,
|
|
419
|
+
getBinDir,
|
|
420
|
+
globalLogger,
|
|
421
|
+
cleanAndTagFlac,
|
|
422
|
+
moveFile
|
|
423
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { render, Instance } from 'ink';
|
|
2
|
+
|
|
3
|
+
type WithFullScreen = (...args: Parameters<typeof render>) => {
|
|
4
|
+
instance: Instance;
|
|
5
|
+
start: () => Promise<void>;
|
|
6
|
+
waitUntilExit: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
declare const withFullScreen: WithFullScreen;
|
|
9
|
+
declare function start(): void;
|
|
10
|
+
declare const GoblinMalin: {
|
|
11
|
+
start: typeof start;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { GoblinMalin as default, start, withFullScreen };
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "goblin-malin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A keyboard-driven terminal UI for downloading and tagging music tracks with metadata from Spotify and YouTube",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"goblin-malin": "dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "tsx src/cli.ts",
|
|
23
|
+
"start": "ts-node src/index.ts",
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"type-check": "tsc --noEmit",
|
|
26
|
+
"prepublishOnly": "tsup"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"music",
|
|
30
|
+
"download",
|
|
31
|
+
"downloader",
|
|
32
|
+
"tui",
|
|
33
|
+
"cli",
|
|
34
|
+
"terminal",
|
|
35
|
+
"spotify",
|
|
36
|
+
"youtube",
|
|
37
|
+
"yt-dlp",
|
|
38
|
+
"metadata",
|
|
39
|
+
"tags",
|
|
40
|
+
"ink",
|
|
41
|
+
"react"
|
|
42
|
+
],
|
|
43
|
+
"author": "Tetraxel",
|
|
44
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/adm-zip": "^0.5.7",
|
|
47
|
+
"@types/dotenv": "^8.2.3",
|
|
48
|
+
"@types/ink": "^2.0.3",
|
|
49
|
+
"@types/ink-select-input": "^3.0.5",
|
|
50
|
+
"@types/ink-spinner": "^3.0.5",
|
|
51
|
+
"@types/ink-text-input": "^2.0.5",
|
|
52
|
+
"@types/node": "^25.6.2",
|
|
53
|
+
"@types/react": "^19.2.14",
|
|
54
|
+
"@swc/core": "^1.10.0",
|
|
55
|
+
"ts-node": "^10.9.2",
|
|
56
|
+
"tsup": "^8.0.0",
|
|
57
|
+
"tsx": "^4.21.0",
|
|
58
|
+
"typescript": "^6.0.3"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"7zip-bin": "^5.2.0",
|
|
62
|
+
"@spotify/web-api-ts-sdk": "^1.2.0",
|
|
63
|
+
"adm-zip": "^0.5.17",
|
|
64
|
+
"chalk": "^5.6.2",
|
|
65
|
+
"clipboardy": "^5.3.1",
|
|
66
|
+
"dotenv": "^17.4.2",
|
|
67
|
+
"flac-tagger": "^2.0.0",
|
|
68
|
+
"flat-cache": "^6.1.22",
|
|
69
|
+
"fullscreen-ink": "^0.1.0",
|
|
70
|
+
"ink": "^7.0.2",
|
|
71
|
+
"ink-big-text": "^2.0.0",
|
|
72
|
+
"ink-form": "^2.0.1",
|
|
73
|
+
"ink-gradient": "^4.0.0",
|
|
74
|
+
"ink-link": "^5.0.0",
|
|
75
|
+
"ink-picture": "^1.3.5",
|
|
76
|
+
"ink-select-input": "^6.2.0",
|
|
77
|
+
"ink-spinner": "^5.0.0",
|
|
78
|
+
"ink-text-input": "^6.0.0",
|
|
79
|
+
"musicbrainz-api": "^1.2.1",
|
|
80
|
+
"node-id3": "^0.2.9",
|
|
81
|
+
"open": "^11.0.0",
|
|
82
|
+
"react": "^19.2.6",
|
|
83
|
+
"slsk-client": "^1.1.0",
|
|
84
|
+
"winston": "^3.19.0",
|
|
85
|
+
"winston-transport": "^4.9.0",
|
|
86
|
+
"ytdlp-nodejs": "^3.4.4",
|
|
87
|
+
"ytmusic-api": "^5.3.1"
|
|
88
|
+
}
|
|
89
|
+
}
|