@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.
Files changed (2) hide show
  1. package/bin/shard.js +467 -134
  2. 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>] [--upload]
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>] [--upload]');
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 = !Boolean(flags.upload);
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
- async function main() {
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
- function printPlanLimitBox(error) {
1219
- const data = error?.data || {};
1220
- const plan = String(data.plan || '').toUpperCase() || 'FREE';
1221
- const maxFileSize = Number(data.maxFileSize || 0);
1222
- const fileSize = Number(data.fileSize || 0);
1223
- const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
1224
- const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
1225
- const lines = [
1226
- '',
1227
- '╔════════════════════════════════════════════════════╗',
1228
- `║ ⚠ Limite du plan ${plan}`.padEnd(52) + ' ║',
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iksdev/shard-cli",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "CLI pour synchroniser un dossier local avec Shard",
5
5
  "bin": {
6
6
  "shard": "bin/shard.js"