@iksdev/shard-cli 0.1.45 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/shard.js +467 -134
- package/package.json +1 -1
package/bin/shard.js
CHANGED
|
@@ -88,33 +88,32 @@ function printHelp() {
|
|
|
88
88
|
║ Shard ║
|
|
89
89
|
╚══════════════════════════════════════════════════╝
|
|
90
90
|
|
|
91
|
-
Commandes disponibles:
|
|
92
|
-
shard login Se connecter au serveur
|
|
93
|
-
shard whoami Afficher l'utilisateur connecte
|
|
94
|
-
shard account Afficher username, email et plan
|
|
95
|
-
shard sync <dossier> Synchroniser un dossier local
|
|
96
|
-
shard share <fichier> Partager un fichier via relay
|
|
97
|
-
shard logout Se deconnecter
|
|
91
|
+
Commandes disponibles:
|
|
92
|
+
shard login Se connecter au serveur
|
|
93
|
+
shard whoami Afficher l'utilisateur connecte
|
|
94
|
+
shard account Afficher username, email et plan
|
|
95
|
+
shard sync <dossier> Synchroniser un dossier local
|
|
96
|
+
shard share <fichier> Partager un fichier via relay
|
|
97
|
+
shard logout Se deconnecter
|
|
98
98
|
shard config show Afficher la configuration
|
|
99
99
|
shard config set-server <url> Changer de serveur
|
|
100
100
|
|
|
101
101
|
Mode interactif:
|
|
102
102
|
Lance une commande sans arguments et la CLI te guidera etape par etape.
|
|
103
103
|
|
|
104
|
-
Options avancees:
|
|
105
|
-
login --username <n> --password <pass> [--server <url>]
|
|
106
|
-
whoami [--server <url>]
|
|
107
|
-
account [--server <url>]
|
|
108
|
-
sync <dossier> [--server <url>] [--dry-run] [--force] [--once] [--interval-ms <n>]
|
|
109
|
-
share <fichier> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
104
|
+
Options avancees:
|
|
105
|
+
login --username <n> --password <pass> [--server <url>]
|
|
106
|
+
whoami [--server <url>]
|
|
107
|
+
account [--server <url>]
|
|
108
|
+
sync <dossier> [--server <url>] [--dry-run] [--force] [--once] [--interval-ms <n>]
|
|
109
|
+
share <fichier> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
110
110
|
|
|
111
111
|
Exemples:
|
|
112
|
-
shard login
|
|
113
|
-
shard account
|
|
114
|
-
shard sync ./MonDossier
|
|
115
|
-
shard sync ./MonDossier --once
|
|
116
|
-
shard share ./MonFichier.mp4
|
|
117
|
-
shard share ./MonFichier.mp4 --upload
|
|
112
|
+
shard login
|
|
113
|
+
shard account
|
|
114
|
+
shard sync ./MonDossier
|
|
115
|
+
shard sync ./MonDossier --once
|
|
116
|
+
shard share ./MonFichier.mp4
|
|
118
117
|
|
|
119
118
|
Serveur par defaut: https://shard-0ow4.onrender.com
|
|
120
119
|
`);
|
|
@@ -300,7 +299,7 @@ async function login(flags) {
|
|
|
300
299
|
}
|
|
301
300
|
}
|
|
302
301
|
|
|
303
|
-
async function whoami(flags) {
|
|
302
|
+
async function whoami(flags) {
|
|
304
303
|
const config = await readConfig();
|
|
305
304
|
const server = getServer(flags, config);
|
|
306
305
|
const token = getToken(config);
|
|
@@ -316,28 +315,28 @@ async function whoami(flags) {
|
|
|
316
315
|
const user = data.user || {};
|
|
317
316
|
console.log(`Server: ${server}`);
|
|
318
317
|
console.log(`User: ${user.username || user.userId || 'inconnu'}`);
|
|
319
|
-
if (user.email) console.log(`Email: ${user.email}`);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function account(flags) {
|
|
323
|
-
const config = await readConfig();
|
|
324
|
-
const server = getServer(flags, config);
|
|
325
|
-
const token = getToken(config);
|
|
326
|
-
if (!token) {
|
|
327
|
-
throw new Error('Non connecte. Lance: shard login --username ... --password ...');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
331
|
-
method: 'GET',
|
|
332
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const user = data.user || {};
|
|
336
|
-
const plan = String(user.billing_plan || 'free').toLowerCase();
|
|
337
|
-
console.log(`Username: ${user.username || 'inconnu'}`);
|
|
338
|
-
console.log(`Email: ${user.email || 'inconnu'}`);
|
|
339
|
-
console.log(`Plan: ${plan}`);
|
|
340
|
-
}
|
|
318
|
+
if (user.email) console.log(`Email: ${user.email}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function account(flags) {
|
|
322
|
+
const config = await readConfig();
|
|
323
|
+
const server = getServer(flags, config);
|
|
324
|
+
const token = getToken(config);
|
|
325
|
+
if (!token) {
|
|
326
|
+
throw new Error('Non connecte. Lance: shard login --username ... --password ...');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
330
|
+
method: 'GET',
|
|
331
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const user = data.user || {};
|
|
335
|
+
const plan = String(user.billing_plan || 'free').toLowerCase();
|
|
336
|
+
console.log(`Username: ${user.username || 'inconnu'}`);
|
|
337
|
+
console.log(`Email: ${user.email || 'inconnu'}`);
|
|
338
|
+
console.log(`Plan: ${plan}`);
|
|
339
|
+
}
|
|
341
340
|
|
|
342
341
|
async function logout() {
|
|
343
342
|
const config = await readConfig();
|
|
@@ -473,14 +472,6 @@ async function uploadOneFile(server, token, file) {
|
|
|
473
472
|
});
|
|
474
473
|
}
|
|
475
474
|
|
|
476
|
-
async function findRemoteFileByNameAndSize(server, token, fileName, fileSize) {
|
|
477
|
-
const data = await httpJson(`${server}/api/files?limit=100&offset=0&sort=created_at&order=desc&search=${encodeURIComponent(fileName)}`, {
|
|
478
|
-
method: 'GET',
|
|
479
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
480
|
-
});
|
|
481
|
-
const rows = Array.isArray(data.files) ? data.files : [];
|
|
482
|
-
return rows.find((row) => row.original_name === fileName && Number(row.file_size || 0) === Number(fileSize || 0)) || null;
|
|
483
|
-
}
|
|
484
475
|
|
|
485
476
|
function parseOptionalPositiveInt(raw, flagName) {
|
|
486
477
|
if (raw === undefined || raw === null) return undefined;
|
|
@@ -520,7 +511,7 @@ async function shareFile(positionals, flags) {
|
|
|
520
511
|
target = await askText('Fichier a partager');
|
|
521
512
|
}
|
|
522
513
|
if (!target) {
|
|
523
|
-
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>]
|
|
514
|
+
throw new Error('Usage: shard share <file> [--server <url>] [--limits <n>] [--temps <jours>]');
|
|
524
515
|
}
|
|
525
516
|
|
|
526
517
|
const absPath = path.resolve(process.cwd(), target);
|
|
@@ -534,7 +525,7 @@ async function shareFile(positionals, flags) {
|
|
|
534
525
|
|
|
535
526
|
const limits = parseOptionalPositiveInt(flags.limits, '--limits');
|
|
536
527
|
const temps = parseOptionalPositiveInt(flags.temps, '--temps');
|
|
537
|
-
const localMode =
|
|
528
|
+
const localMode = true; // transfert direct uniquement — upload serveur désactivé
|
|
538
529
|
|
|
539
530
|
const config = await readConfig();
|
|
540
531
|
const server = getServer(flags, config);
|
|
@@ -743,43 +734,6 @@ async function shareFile(positionals, flags) {
|
|
|
743
734
|
throw error;
|
|
744
735
|
}
|
|
745
736
|
}
|
|
746
|
-
|
|
747
|
-
let remote = await findRemoteFileByNameAndSize(server, token, fileName, st.size);
|
|
748
|
-
let fileId = remote?.id || null;
|
|
749
|
-
|
|
750
|
-
if (!fileId) {
|
|
751
|
-
console.log(`Upload necessaire: ${fileName} (${formatBytes(st.size)})`);
|
|
752
|
-
const uploaded = await uploadOneFile(server, token, {
|
|
753
|
-
absPath,
|
|
754
|
-
relPath: fileName,
|
|
755
|
-
size: st.size,
|
|
756
|
-
mtimeMs: Math.round(st.mtimeMs)
|
|
757
|
-
});
|
|
758
|
-
fileId = uploaded?.file?.id;
|
|
759
|
-
if (!fileId) {
|
|
760
|
-
throw new Error('Upload reussi mais ID fichier manquant');
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const payload = { fileId };
|
|
765
|
-
if (limits !== undefined && limits > 0) payload.maxDownloads = limits;
|
|
766
|
-
if (temps !== undefined && temps > 0) payload.expiresInDays = temps;
|
|
767
|
-
|
|
768
|
-
const created = await httpJson(`${server}/api/share/create`, {
|
|
769
|
-
method: 'POST',
|
|
770
|
-
headers: {
|
|
771
|
-
Authorization: `Bearer ${token}`,
|
|
772
|
-
'Content-Type': 'application/json'
|
|
773
|
-
},
|
|
774
|
-
body: JSON.stringify(payload)
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
const share = created?.share || {};
|
|
778
|
-
console.log(`Partage cree pour: ${fileName}`);
|
|
779
|
-
if (share.url) console.log(`URL: ${share.url}`);
|
|
780
|
-
if (share.token) console.log(`Token: ${share.token}`);
|
|
781
|
-
console.log(`Limite downloads: ${limits && limits > 0 ? limits : 'illimitee'}`);
|
|
782
|
-
console.log(`Expiration: ${temps && temps > 0 ? `${temps} jour(s)` : 'aucune'}`);
|
|
783
737
|
}
|
|
784
738
|
|
|
785
739
|
function fileListToMap(files) {
|
|
@@ -1144,7 +1098,377 @@ async function syncFolder(positionals, flags) {
|
|
|
1144
1098
|
}
|
|
1145
1099
|
}
|
|
1146
1100
|
|
|
1147
|
-
|
|
1101
|
+
function printPlanLimitBox(error) {
|
|
1102
|
+
const data = error?.data || {};
|
|
1103
|
+
const plan = String(data.plan || '').toUpperCase() || 'FREE';
|
|
1104
|
+
const maxFileSize = Number(data.maxFileSize || 0);
|
|
1105
|
+
const fileSize = Number(data.fileSize || 0);
|
|
1106
|
+
const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
|
|
1107
|
+
const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
|
|
1108
|
+
const lines = [
|
|
1109
|
+
'',
|
|
1110
|
+
`${c(C.bYellow, '╔════════════════════════════════════════════════════╗')}`,
|
|
1111
|
+
`${c(C.bYellow, '║')} ${c(C.bRed, '⚠')} ${c(C.bWhite, `Limite du plan ${plan}`)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1112
|
+
`${c(C.bYellow, '╠════════════════════════════════════════════════════╣')}`,
|
|
1113
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Taille fichier')} : ${c(C.white, fileLabel)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1114
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Limite max')} : ${c(C.white, maxLabel + ' / fichier')}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
|
|
1115
|
+
`${c(C.bYellow, '║')} ${c(C.bYellow, '║')}`,
|
|
1116
|
+
`${c(C.bYellow, '║')} ${c(C.gray, 'Passe à une offre supérieure pour continuer.')} ${c(C.bYellow, '║')}`,
|
|
1117
|
+
`${c(C.bYellow, '╚════════════════════════════════════════════════════╝')}`,
|
|
1118
|
+
''
|
|
1119
|
+
];
|
|
1120
|
+
console.error(lines.join('\n'));
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// ─── Couleurs ANSI ───────────────────────────────────────────────────────────
|
|
1124
|
+
|
|
1125
|
+
const C = {
|
|
1126
|
+
reset: '\x1b[0m',
|
|
1127
|
+
bold: '\x1b[1m',
|
|
1128
|
+
dim: '\x1b[2m',
|
|
1129
|
+
cyan: '\x1b[36m',
|
|
1130
|
+
blue: '\x1b[34m',
|
|
1131
|
+
green: '\x1b[32m',
|
|
1132
|
+
yellow: '\x1b[33m',
|
|
1133
|
+
red: '\x1b[31m',
|
|
1134
|
+
white: '\x1b[97m',
|
|
1135
|
+
gray: '\x1b[90m',
|
|
1136
|
+
bgCyan: '\x1b[46m',
|
|
1137
|
+
bgGray: '\x1b[100m',
|
|
1138
|
+
black: '\x1b[30m',
|
|
1139
|
+
bCyan: '\x1b[1m\x1b[36m',
|
|
1140
|
+
bWhite: '\x1b[1m\x1b[97m',
|
|
1141
|
+
bGreen: '\x1b[1m\x1b[32m',
|
|
1142
|
+
bRed: '\x1b[1m\x1b[31m',
|
|
1143
|
+
bYellow: '\x1b[1m\x1b[33m',
|
|
1144
|
+
bBlue: '\x1b[1m\x1b[34m',
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
function c(color, text) { return `${color}${text}${C.reset}`; }
|
|
1148
|
+
function strip(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
1149
|
+
|
|
1150
|
+
// ─── Panel interactif ────────────────────────────────────────────────────────
|
|
1151
|
+
|
|
1152
|
+
const W = 58; // largeur intérieure (sans les bordures)
|
|
1153
|
+
|
|
1154
|
+
function clearScreen() {
|
|
1155
|
+
process.stdout.write('\x1Bc');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Ligne de bordure
|
|
1159
|
+
function hr(left = '├', mid = '─', right = '┤', color = C.gray) {
|
|
1160
|
+
return c(color, `${left}${mid.repeat(W)}${right}`);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Ligne avec bordures latérales, contenu centré ou libre
|
|
1164
|
+
function row(content = '', color = C.gray) {
|
|
1165
|
+
const vis = strip(content);
|
|
1166
|
+
const pad = W - vis.length;
|
|
1167
|
+
return `${c(color, '│')}${content}${' '.repeat(Math.max(0, pad))}${c(color, '│')}`;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function rowCentered(content, color = C.gray) {
|
|
1171
|
+
const vis = strip(content);
|
|
1172
|
+
const l = Math.floor((W - vis.length) / 2);
|
|
1173
|
+
const r = W - vis.length - l;
|
|
1174
|
+
return row(' '.repeat(l) + content + ' '.repeat(r), color);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function printPanel(username, plan, statusMsg) {
|
|
1178
|
+
const now = new Date();
|
|
1179
|
+
const timeStr = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
1180
|
+
const planLabel = plan ? plan.toUpperCase() : 'FREE';
|
|
1181
|
+
const planColor = planLabel === 'FREE' ? C.gray : C.bYellow;
|
|
1182
|
+
|
|
1183
|
+
// ── Titre ──────────────────────────────────────────────────────────────────
|
|
1184
|
+
console.log('');
|
|
1185
|
+
console.log(hr('┌', '─', '┐', C.cyan));
|
|
1186
|
+
|
|
1187
|
+
const title = c(C.bCyan, 'S H A R D');
|
|
1188
|
+
console.log(rowCentered(title, C.cyan));
|
|
1189
|
+
|
|
1190
|
+
console.log(hr('├', '─', '┤', C.cyan));
|
|
1191
|
+
|
|
1192
|
+
// ── Infos session ──────────────────────────────────────────────────────────
|
|
1193
|
+
if (username) {
|
|
1194
|
+
const userPart = ` ${c(C.gray, 'Utilisateur')} ${c(C.bWhite, username)}`;
|
|
1195
|
+
const planPart = ` ${c(C.gray, 'Plan')} ${c(planColor, planLabel)} `;
|
|
1196
|
+
const userVis = strip(userPart);
|
|
1197
|
+
const planVis = strip(planPart);
|
|
1198
|
+
const spacer = W - userVis.length - planVis.length;
|
|
1199
|
+
const infoLine = userPart + ' '.repeat(Math.max(1, spacer)) + planPart;
|
|
1200
|
+
console.log(row(infoLine, C.cyan));
|
|
1201
|
+
console.log(hr('├', '─', '┤', C.cyan));
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// ── Menu ───────────────────────────────────────────────────────────────────
|
|
1205
|
+
const entries = [
|
|
1206
|
+
{ key: '1', tag: 'SHARE', label: 'share', desc: 'Partager un fichier via relay' },
|
|
1207
|
+
{ key: '2', tag: 'SYNC', label: 'sync', desc: 'Synchroniser un dossier local' },
|
|
1208
|
+
{ key: '3', tag: 'ACCOUNT', label: 'account', desc: 'Informations du compte' },
|
|
1209
|
+
{ key: '4', tag: 'WHOAMI', label: 'whoami', desc: 'Utilisateur connecté' },
|
|
1210
|
+
{ key: '5', tag: 'CONFIG', label: 'config', desc: 'Configuration serveur' },
|
|
1211
|
+
{ key: '6', tag: 'LOGOUT', label: 'logout', desc: 'Se déconnecter et quitter' },
|
|
1212
|
+
];
|
|
1213
|
+
|
|
1214
|
+
console.log(row('', C.cyan));
|
|
1215
|
+
for (const e of entries) {
|
|
1216
|
+
const num = ` ${c(C.bgGray + C.bWhite, ` ${e.key} `)}`;
|
|
1217
|
+
const lbl = ` ${c(C.bWhite, e.label.padEnd(9))}`;
|
|
1218
|
+
const sep = `${c(C.gray, '│')}`;
|
|
1219
|
+
const desc = ` ${c(C.gray, e.desc)}`;
|
|
1220
|
+
const numVis = strip(num);
|
|
1221
|
+
const lblVis = strip(lbl);
|
|
1222
|
+
const descVis = strip(desc);
|
|
1223
|
+
const padRight = W - numVis.length - lblVis.length - 1 - descVis.length;
|
|
1224
|
+
const line = `${num}${lbl}${sep}${desc}${' '.repeat(Math.max(0, padRight - 2))}`;
|
|
1225
|
+
console.log(row(line, C.cyan));
|
|
1226
|
+
}
|
|
1227
|
+
console.log(row('', C.cyan));
|
|
1228
|
+
|
|
1229
|
+
// ── Footer ─────────────────────────────────────────────────────────────────
|
|
1230
|
+
console.log(hr('├', '─', '┤', C.gray));
|
|
1231
|
+
|
|
1232
|
+
if (statusMsg) {
|
|
1233
|
+
console.log(rowCentered(statusMsg, C.gray));
|
|
1234
|
+
} else {
|
|
1235
|
+
const hint = `${c(C.gray, timeStr + ' ── Entre un numéro ou un nom de commande')}`;
|
|
1236
|
+
console.log(rowCentered(hint, C.gray));
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
console.log(hr('└', '─', '┘', C.gray));
|
|
1240
|
+
console.log('');
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// ── Sous-écrans ───────────────────────────────────────────────────────────────
|
|
1244
|
+
|
|
1245
|
+
function printSubHeader(title) {
|
|
1246
|
+
console.log('');
|
|
1247
|
+
console.log(c(C.cyan, `┌${'─'.repeat(W)}┐`));
|
|
1248
|
+
const vis = strip(title);
|
|
1249
|
+
const l = Math.floor((W - vis.length) / 2);
|
|
1250
|
+
const r = W - vis.length - l;
|
|
1251
|
+
console.log(`${c(C.cyan, '│')}${' '.repeat(l)}${c(C.bWhite, title)}${' '.repeat(r)}${c(C.cyan, '│')}`);
|
|
1252
|
+
console.log(c(C.gray, `└${'─'.repeat(W)}┘`));
|
|
1253
|
+
console.log('');
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
function printSuccess(msg) { console.log(`\n ${c(C.bGreen, '+ ')} ${c(C.green, msg)}`); }
|
|
1257
|
+
function printError(msg) { console.log(`\n ${c(C.bRed, 'x ')} ${c(C.red, msg)}`); }
|
|
1258
|
+
function printInfo(msg) { console.log(` ${c(C.bCyan, '> ')} ${c(C.white, msg)}`); }
|
|
1259
|
+
|
|
1260
|
+
async function askPanelInput(prompt) {
|
|
1261
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1262
|
+
const answer = await new Promise((resolve) => rl.question(prompt, resolve));
|
|
1263
|
+
rl.close();
|
|
1264
|
+
return answer.trim();
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async function pressEnter() {
|
|
1268
|
+
await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entree pour revenir au menu…')}`);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// ── Actions ───────────────────────────────────────────────────────────────────
|
|
1272
|
+
|
|
1273
|
+
async function panelShare() {
|
|
1274
|
+
clearScreen();
|
|
1275
|
+
printSubHeader('Partager un fichier');
|
|
1276
|
+
|
|
1277
|
+
const target = await askText(` ${c(C.cyan, '>')} Chemin du fichier`);
|
|
1278
|
+
if (!target) return;
|
|
1279
|
+
const limits = await askText(` ${c(C.cyan, '>')} Limite de telechargements ${c(C.gray, '(0 = illimite)')}`, '0');
|
|
1280
|
+
const temps = await askText(` ${c(C.cyan, '>')} Expiration en jours ${c(C.gray, '(0 = aucune)')}`, '0');
|
|
1281
|
+
|
|
1282
|
+
const flags = {};
|
|
1283
|
+
if (parseInt(limits, 10) > 0) flags.limits = limits;
|
|
1284
|
+
if (parseInt(temps, 10) > 0) flags.temps = temps;
|
|
1285
|
+
|
|
1286
|
+
console.log('');
|
|
1287
|
+
console.log(c(C.gray, '─'.repeat(W + 2)));
|
|
1288
|
+
try {
|
|
1289
|
+
await shareFile([target], flags);
|
|
1290
|
+
printSuccess('Partage cree avec succes.');
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
if (err?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
|
|
1293
|
+
printPlanLimitBox(err);
|
|
1294
|
+
} else {
|
|
1295
|
+
printError(err.message);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
await pressEnter();
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
async function panelSync() {
|
|
1302
|
+
clearScreen();
|
|
1303
|
+
printSubHeader('Synchroniser un dossier');
|
|
1304
|
+
|
|
1305
|
+
const target = await askText(` ${c(C.cyan, '>')} Chemin du dossier`);
|
|
1306
|
+
if (!target) return;
|
|
1307
|
+
const onceRaw = await askText(` ${c(C.cyan, '>')} Sync unique (sans surveillance) ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1308
|
+
const dryRaw = await askText(` ${c(C.cyan, '>')} Simulation dry-run ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1309
|
+
const forceRaw = await askText(` ${c(C.cyan, '>')} Forcer le re-upload ? ${c(C.gray, '[o/n]')}`, 'n');
|
|
1310
|
+
|
|
1311
|
+
const flags = {};
|
|
1312
|
+
if (onceRaw.toLowerCase() === 'o') flags.once = true;
|
|
1313
|
+
if (dryRaw.toLowerCase() === 'o') flags['dry-run'] = true;
|
|
1314
|
+
if (forceRaw.toLowerCase() === 'o') flags.force = true;
|
|
1315
|
+
|
|
1316
|
+
console.log('');
|
|
1317
|
+
console.log(c(C.gray, '─'.repeat(W + 2)));
|
|
1318
|
+
try {
|
|
1319
|
+
await syncFolder([target], flags);
|
|
1320
|
+
if (flags.once || flags['dry-run']) {
|
|
1321
|
+
printSuccess('Synchronisation terminee.');
|
|
1322
|
+
await pressEnter();
|
|
1323
|
+
}
|
|
1324
|
+
} catch (err) {
|
|
1325
|
+
printError(err.message);
|
|
1326
|
+
await pressEnter();
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
async function panelAccount() {
|
|
1331
|
+
clearScreen();
|
|
1332
|
+
printSubHeader('Informations du compte');
|
|
1333
|
+
try {
|
|
1334
|
+
const config = await readConfig();
|
|
1335
|
+
const server = getServer({}, config);
|
|
1336
|
+
const token = getToken(config);
|
|
1337
|
+
if (!token) throw new Error('Non connecte.');
|
|
1338
|
+
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
1339
|
+
method: 'GET',
|
|
1340
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1341
|
+
});
|
|
1342
|
+
const u = data.user || {};
|
|
1343
|
+
const planRaw = String(u.billing_plan || 'free').toUpperCase();
|
|
1344
|
+
const planCol = planRaw === 'FREE' ? C.gray : C.bYellow;
|
|
1345
|
+
console.log('');
|
|
1346
|
+
printInfo(`Utilisateur ${c(C.bWhite, u.username || '—')}`);
|
|
1347
|
+
printInfo(`Email ${c(C.white, u.email || '—')}`);
|
|
1348
|
+
printInfo(`Plan ${c(planCol, planRaw)}`);
|
|
1349
|
+
printInfo(`Serveur ${c(C.gray, server)}`);
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
printError(err.message);
|
|
1352
|
+
}
|
|
1353
|
+
await pressEnter();
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
async function panelWhoami() {
|
|
1357
|
+
clearScreen();
|
|
1358
|
+
printSubHeader('Utilisateur connecte');
|
|
1359
|
+
try {
|
|
1360
|
+
const config = await readConfig();
|
|
1361
|
+
const server = getServer({}, config);
|
|
1362
|
+
const token = getToken(config);
|
|
1363
|
+
if (!token) throw new Error('Non connecte.');
|
|
1364
|
+
const data = await httpJson(`${server}/api/auth/verify`, {
|
|
1365
|
+
method: 'POST',
|
|
1366
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1367
|
+
});
|
|
1368
|
+
const u = data.user || {};
|
|
1369
|
+
console.log('');
|
|
1370
|
+
printInfo(`Utilisateur ${c(C.bWhite, u.username || u.userId || '—')}`);
|
|
1371
|
+
if (u.email) printInfo(`Email ${c(C.white, u.email)}`);
|
|
1372
|
+
printInfo(`Serveur ${c(C.gray, server)}`);
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
printError(err.message);
|
|
1375
|
+
}
|
|
1376
|
+
await pressEnter();
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
async function panelConfig() {
|
|
1380
|
+
clearScreen();
|
|
1381
|
+
printSubHeader('Configuration');
|
|
1382
|
+
|
|
1383
|
+
printInfo(`${c(C.bgGray + C.bWhite, ' a ')} Afficher la configuration actuelle`);
|
|
1384
|
+
printInfo(`${c(C.bgGray + C.bWhite, ' b ')} Changer de serveur`);
|
|
1385
|
+
console.log('');
|
|
1386
|
+
|
|
1387
|
+
const choice = await askPanelInput(` ${c(C.cyan, '>')} Choix ${c(C.gray, '[a / b]')} : `);
|
|
1388
|
+
|
|
1389
|
+
if (choice.toLowerCase() === 'a') {
|
|
1390
|
+
console.log('');
|
|
1391
|
+
console.log(c(C.gray, '─'.repeat(W + 2)));
|
|
1392
|
+
try {
|
|
1393
|
+
const config = await readConfig();
|
|
1394
|
+
printInfo(`Serveur ${c(C.white, config.server)}`);
|
|
1395
|
+
printInfo(`Token ${config.token ? c(C.green, 'present') : c(C.red, 'absent')}`);
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
printError(err.message);
|
|
1398
|
+
}
|
|
1399
|
+
} else if (choice.toLowerCase() === 'b') {
|
|
1400
|
+
const url = await askText(` ${c(C.cyan, '>')} Nouvelle URL du serveur`);
|
|
1401
|
+
if (url) {
|
|
1402
|
+
try {
|
|
1403
|
+
await setServer([url]);
|
|
1404
|
+
printSuccess(`Serveur mis a jour : ${url}`);
|
|
1405
|
+
} catch (err) {
|
|
1406
|
+
printError(err.message);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
} else {
|
|
1410
|
+
printError('Choix invalide. Entre a ou b.');
|
|
1411
|
+
}
|
|
1412
|
+
await pressEnter();
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// ── Boucle principale ─────────────────────────────────────────────────────────
|
|
1416
|
+
|
|
1417
|
+
async function runPanel(username, plan) {
|
|
1418
|
+
// Logout automatique à la fermeture du process
|
|
1419
|
+
process.on('exit', () => {
|
|
1420
|
+
try {
|
|
1421
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
1422
|
+
const parsed = JSON.parse(raw);
|
|
1423
|
+
parsed.token = '';
|
|
1424
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(parsed, null, 2), 'utf8');
|
|
1425
|
+
} catch (_) {}
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
const handleStop = async (sig) => {
|
|
1429
|
+
process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})...`)}\n`);
|
|
1430
|
+
try {
|
|
1431
|
+
const cfg = await readConfig();
|
|
1432
|
+
await writeConfig({ ...cfg, token: '' });
|
|
1433
|
+
process.stdout.write(` ${c(C.green, '+')} Session fermee. A bientot !\n\n`);
|
|
1434
|
+
} catch (_) {}
|
|
1435
|
+
process.exit(0);
|
|
1436
|
+
};
|
|
1437
|
+
process.on('SIGINT', handleStop);
|
|
1438
|
+
process.on('SIGTERM', handleStop);
|
|
1439
|
+
|
|
1440
|
+
let statusMsg = null;
|
|
1441
|
+
|
|
1442
|
+
while (true) {
|
|
1443
|
+
clearScreen();
|
|
1444
|
+
printPanel(username, plan, statusMsg);
|
|
1445
|
+
statusMsg = null;
|
|
1446
|
+
|
|
1447
|
+
const answer = (await askPanelInput(` ${c(C.bCyan, '>')} `)).toLowerCase();
|
|
1448
|
+
|
|
1449
|
+
if (answer === '1' || answer === 'share') { await panelShare(); }
|
|
1450
|
+
else if (answer === '2' || answer === 'sync') { await panelSync(); }
|
|
1451
|
+
else if (answer === '3' || answer === 'account') { await panelAccount(); }
|
|
1452
|
+
else if (answer === '4' || answer === 'whoami') { await panelWhoami(); }
|
|
1453
|
+
else if (answer === '5' || answer === 'config') { await panelConfig(); }
|
|
1454
|
+
else if (answer === '6' || answer === 'logout') {
|
|
1455
|
+
clearScreen();
|
|
1456
|
+
process.removeListener('SIGINT', handleStop);
|
|
1457
|
+
process.removeListener('SIGTERM', handleStop);
|
|
1458
|
+
await logout();
|
|
1459
|
+
console.log(`\n ${c(C.bGreen, '+')} Deconnecte avec succes. A bientot !\n`);
|
|
1460
|
+
process.exit(0);
|
|
1461
|
+
} else if (answer === '') {
|
|
1462
|
+
// reboucle
|
|
1463
|
+
} else {
|
|
1464
|
+
statusMsg = c(C.bRed, `x Inconnu : "${answer}" ── choisis 1 a 6`);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// ─── main ─────────────────────────────────────────────────────────────────────
|
|
1470
|
+
|
|
1471
|
+
async function main() {
|
|
1148
1472
|
const { command, positionals, flags } = parseArgs(process.argv.slice(2));
|
|
1149
1473
|
|
|
1150
1474
|
// Vérifie la mise à jour à chaque commande (sauf --help)
|
|
@@ -1171,19 +1495,48 @@ async function main() {
|
|
|
1171
1495
|
|
|
1172
1496
|
if (command === 'login') {
|
|
1173
1497
|
await login(flags);
|
|
1498
|
+
// Après un login réussi en mode interactif, on lance le panel
|
|
1499
|
+
if (isInteractive()) {
|
|
1500
|
+
const config = await readConfig();
|
|
1501
|
+
const server = getServer(flags, config);
|
|
1502
|
+
const token = getToken(config);
|
|
1503
|
+
let username = String(flags.username || '').trim();
|
|
1504
|
+
let plan = '';
|
|
1505
|
+
// Récupère le username et le plan depuis le serveur si possible
|
|
1506
|
+
if (token) {
|
|
1507
|
+
try {
|
|
1508
|
+
const data = await httpJson(`${server}/api/auth/profile`, {
|
|
1509
|
+
method: 'GET',
|
|
1510
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1511
|
+
});
|
|
1512
|
+
const user = data.user || {};
|
|
1513
|
+
if (!username) username = user.username || user.userId || '';
|
|
1514
|
+
plan = String(user.billing_plan || 'free').toLowerCase();
|
|
1515
|
+
} catch (_) {
|
|
1516
|
+
try {
|
|
1517
|
+
const data = await httpJson(`${server}/api/auth/verify`, {
|
|
1518
|
+
method: 'POST',
|
|
1519
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1520
|
+
});
|
|
1521
|
+
if (!username) username = data.user?.username || data.user?.userId || '';
|
|
1522
|
+
} catch (_2) {}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
await runPanel(username, plan);
|
|
1526
|
+
}
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
if (command === 'whoami') {
|
|
1531
|
+
await whoami(flags);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
if (command === 'account') {
|
|
1536
|
+
await account(flags);
|
|
1174
1537
|
return;
|
|
1175
1538
|
}
|
|
1176
1539
|
|
|
1177
|
-
if (command === 'whoami') {
|
|
1178
|
-
await whoami(flags);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (command === 'account') {
|
|
1183
|
-
await account(flags);
|
|
1184
|
-
return;
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
1540
|
if (command === 'logout') {
|
|
1188
1541
|
await logout();
|
|
1189
1542
|
return;
|
|
@@ -1212,37 +1565,17 @@ async function main() {
|
|
|
1212
1565
|
throw new Error('Usage: shard config show | shard config set-server <url>');
|
|
1213
1566
|
}
|
|
1214
1567
|
|
|
1215
|
-
throw new Error(`Commande inconnue: ${command}`);
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
'╠════════════════════════════════════════════════════╣',
|
|
1230
|
-
`║ Taille fichier : ${fileLabel}`.padEnd(52) + ' ║',
|
|
1231
|
-
`║ Limite max : ${maxLabel} / fichier`.padEnd(52) + ' ║',
|
|
1232
|
-
'║ ║',
|
|
1233
|
-
'║ Passe a une offre superieure pour continuer. ║',
|
|
1234
|
-
'╚════════════════════════════════════════════════════╝',
|
|
1235
|
-
''
|
|
1236
|
-
];
|
|
1237
|
-
console.error(lines.join('\n'));
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
main().catch((error) => {
|
|
1241
|
-
if (error?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
|
|
1242
|
-
printPlanLimitBox(error);
|
|
1243
|
-
process.exitCode = 1;
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
console.error(`Erreur: ${error.message}`);
|
|
1247
|
-
process.exitCode = 1;
|
|
1248
|
-
});
|
|
1568
|
+
throw new Error(`Commande inconnue: ${command}`);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
|
|
1572
|
+
|
|
1573
|
+
main().catch((error) => {
|
|
1574
|
+
if (error?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
|
|
1575
|
+
printPlanLimitBox(error);
|
|
1576
|
+
process.exitCode = 1;
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
console.error(`Erreur: ${error.message}`);
|
|
1580
|
+
process.exitCode = 1;
|
|
1581
|
+
});
|