@iksdev/shard-cli 0.1.46 → 0.1.48

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 +264 -186
  2. package/package.json +1 -1
package/bin/shard.js CHANGED
@@ -106,7 +106,7 @@ Options avancees:
106
106
  whoami [--server <url>]
107
107
  account [--server <url>]
108
108
  sync <dossier> [--server <url>] [--dry-run] [--force] [--once] [--interval-ms <n>]
109
- share <fichier> [--server <url>] [--limits <n>] [--temps <jours>] [--upload]
109
+ share <fichier> [--server <url>] [--limits <n>] [--temps <jours>]
110
110
 
111
111
  Exemples:
112
112
  shard login
@@ -114,7 +114,6 @@ Exemples:
114
114
  shard sync ./MonDossier
115
115
  shard sync ./MonDossier --once
116
116
  shard share ./MonFichier.mp4
117
- shard share ./MonFichier.mp4 --upload
118
117
 
119
118
  Serveur par defaut: https://shard-0ow4.onrender.com
120
119
  `);
@@ -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);
@@ -710,25 +701,74 @@ async function shareFile(positionals, flags) {
710
701
  const n = Number(share.id);
711
702
  if (Number.isFinite(n) && n > 0) createdShareId = n;
712
703
  }
713
- console.log(`Partage relay cree pour: ${fileName}`);
714
- if (share.url) console.log(`URL Shard: ${share.url}`);
715
- if (share.token) console.log(`Token: ${share.token}`);
716
- console.log(`Relay client id: ${relayClientId}`);
717
- console.log(`Relay file id: ${relayFileId}`);
718
- console.log(`Limite downloads: ${limits && limits > 0 ? limits : 'illimitee'}`);
719
- console.log(`Expiration: ${temps && temps > 0 ? `${temps} jour(s)` : 'aucune'}`);
720
- console.log('Laisse cette commande ouverte tant que le partage doit fonctionner.');
721
704
 
705
+ // ── Affichage statut partage actif ─────────────────────────────────────
706
+ const WS = 58;
707
+ const hr = (l, m, r, col) => `${c(col, l + m.repeat(WS) + r)}`;
708
+ const rowS = (content, col) => {
709
+ const vis = content.replace(/\x1b\[[0-9;]*m/g, '');
710
+ const pad = WS - vis.length;
711
+ return `${c(col, '│')}${content}${' '.repeat(Math.max(0, pad))}${c(col, '│')}`;
712
+ };
713
+
714
+ process.stdout.write('\x1Bc');
715
+ console.log('');
716
+ console.log(hr('┌', '─', '┐', '\x1b[32m'));
717
+ const titleShare = '\x1b[1m\x1b[32m' + ' PARTAGE ACTIF' + '\x1b[0m';
718
+ console.log(rowS(titleShare + ' '.repeat(WS - 15), '\x1b[32m'));
719
+ console.log(hr('├', '─', '┤', '\x1b[32m'));
720
+ console.log(rowS('', '\x1b[32m'));
721
+ if (share.url) {
722
+ const urlLabel = ' \x1b[90mURL \x1b[0m \x1b[1m\x1b[97m' + share.url + '\x1b[0m';
723
+ console.log(rowS(urlLabel, '\x1b[32m'));
724
+ }
725
+ if (share.token) {
726
+ const tokLabel = ' \x1b[90mToken \x1b[0m \x1b[36m' + share.token + '\x1b[0m';
727
+ console.log(rowS(tokLabel, '\x1b[32m'));
728
+ }
729
+ const fileLabel = ' \x1b[90mFichier \x1b[0m \x1b[97m' + fileName + '\x1b[0m';
730
+ console.log(rowS(fileLabel, '\x1b[32m'));
731
+ const dlLabel = ' \x1b[90mLimite \x1b[0m \x1b[97m' + (limits && limits > 0 ? String(limits) + ' telechargement(s)' : 'illimite') + '\x1b[0m';
732
+ console.log(rowS(dlLabel, '\x1b[32m'));
733
+ const expLabel = ' \x1b[90mExpire \x1b[0m \x1b[97m' + (temps && temps > 0 ? `dans ${temps} jour(s)` : 'jamais') + '\x1b[0m';
734
+ console.log(rowS(expLabel, '\x1b[32m'));
735
+ console.log(rowS('', '\x1b[32m'));
736
+ console.log(hr('├', '─', '┤', '\x1b[90m'));
737
+ const hint = ' \x1b[90mTape \x1b[0m\x1b[1m\x1b[97m q \x1b[0m\x1b[90m + Entree pour cloture le partage et revenir au menu\x1b[0m';
738
+ console.log(rowS(hint, '\x1b[90m'));
739
+ console.log(hr('└', '─', '┘', '\x1b[90m'));
740
+ console.log('');
741
+
742
+ // Attend soit la fermeture du socket, soit que l'utilisateur tape 'q'
722
743
  await new Promise((resolve) => {
723
- relaySocket.on('close', () => resolve());
744
+ // Fermeture côté serveur
745
+ relaySocket.on('close', () => resolve('closed'));
746
+
747
+ // Écoute clavier : 'q' + Entrée = clôture manuelle
748
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
749
+ const onLine = (line) => {
750
+ if (line.trim().toLowerCase() === 'q') {
751
+ rl.close();
752
+ resolve('quit');
753
+ }
754
+ };
755
+ rl.on('line', onLine);
756
+ // Nettoyer si le socket se ferme en premier
757
+ relaySocket.once('close', () => {
758
+ rl.removeListener('line', onLine);
759
+ rl.close();
760
+ });
724
761
  });
762
+
725
763
  if (heartbeat) {
726
764
  clearInterval(heartbeat);
727
765
  heartbeat = null;
728
766
  }
767
+ await revokeCreatedShare();
729
768
  for (const sig of stopSignals) {
730
769
  process.off(sig, stopRelayShare);
731
770
  }
771
+ closeRelay();
732
772
  return;
733
773
  } catch (error) {
734
774
  if (heartbeat) {
@@ -743,43 +783,6 @@ async function shareFile(positionals, flags) {
743
783
  throw error;
744
784
  }
745
785
  }
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
786
  }
784
787
 
785
788
  function fileListToMap(files) {
@@ -1144,114 +1147,164 @@ async function syncFolder(positionals, flags) {
1144
1147
  }
1145
1148
  }
1146
1149
 
1150
+ function printPlanLimitBox(error) {
1151
+ const data = error?.data || {};
1152
+ const plan = String(data.plan || '').toUpperCase() || 'FREE';
1153
+ const maxFileSize = Number(data.maxFileSize || 0);
1154
+ const fileSize = Number(data.fileSize || 0);
1155
+ const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
1156
+ const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
1157
+ const lines = [
1158
+ '',
1159
+ `${c(C.bYellow, '╔════════════════════════════════════════════════════╗')}`,
1160
+ `${c(C.bYellow, '║')} ${c(C.bRed, '⚠')} ${c(C.bWhite, `Limite du plan ${plan}`)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
1161
+ `${c(C.bYellow, '╠════════════════════════════════════════════════════╣')}`,
1162
+ `${c(C.bYellow, '║')} ${c(C.gray, 'Taille fichier')} : ${c(C.white, fileLabel)}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
1163
+ `${c(C.bYellow, '║')} ${c(C.gray, 'Limite max')} : ${c(C.white, maxLabel + ' / fichier')}`.padEnd(52 + 20) + ` ${c(C.bYellow, '║')}`,
1164
+ `${c(C.bYellow, '║')} ${c(C.bYellow, '║')}`,
1165
+ `${c(C.bYellow, '║')} ${c(C.gray, 'Passe à une offre supérieure pour continuer.')} ${c(C.bYellow, '║')}`,
1166
+ `${c(C.bYellow, '╚════════════════════════════════════════════════════╝')}`,
1167
+ ''
1168
+ ];
1169
+ console.error(lines.join('\n'));
1170
+ }
1171
+
1147
1172
  // ─── Couleurs ANSI ───────────────────────────────────────────────────────────
1148
1173
 
1149
1174
  const C = {
1150
- reset: '\x1b[0m',
1151
- bold: '\x1b[1m',
1152
- dim: '\x1b[2m',
1153
- cyan: '\x1b[36m',
1154
- blue: '\x1b[34m',
1155
- green: '\x1b[32m',
1156
- yellow: '\x1b[33m',
1157
- red: '\x1b[31m',
1158
- white: '\x1b[97m',
1159
- gray: '\x1b[90m',
1160
- bgBlue: '\x1b[44m',
1161
- bCyan: '\x1b[1m\x1b[36m',
1162
- bWhite: '\x1b[1m\x1b[97m',
1163
- bGreen: '\x1b[1m\x1b[32m',
1164
- bRed: '\x1b[1m\x1b[31m',
1165
- bYellow: '\x1b[1m\x1b[33m',
1175
+ reset: '\x1b[0m',
1176
+ bold: '\x1b[1m',
1177
+ dim: '\x1b[2m',
1178
+ cyan: '\x1b[36m',
1179
+ blue: '\x1b[34m',
1180
+ green: '\x1b[32m',
1181
+ yellow: '\x1b[33m',
1182
+ red: '\x1b[31m',
1183
+ white: '\x1b[97m',
1184
+ gray: '\x1b[90m',
1185
+ bgCyan: '\x1b[46m',
1186
+ bgGray: '\x1b[100m',
1187
+ black: '\x1b[30m',
1188
+ bCyan: '\x1b[1m\x1b[36m',
1189
+ bWhite: '\x1b[1m\x1b[97m',
1190
+ bGreen: '\x1b[1m\x1b[32m',
1191
+ bRed: '\x1b[1m\x1b[31m',
1192
+ bYellow: '\x1b[1m\x1b[33m',
1193
+ bBlue: '\x1b[1m\x1b[34m',
1166
1194
  };
1167
1195
 
1168
1196
  function c(color, text) { return `${color}${text}${C.reset}`; }
1197
+ function strip(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
1169
1198
 
1170
1199
  // ─── Panel interactif ────────────────────────────────────────────────────────
1171
1200
 
1172
- const W = 64;
1201
+ const W = 58; // largeur intérieure (sans les bordures)
1173
1202
 
1174
1203
  function clearScreen() {
1175
1204
  process.stdout.write('\x1Bc');
1176
1205
  }
1177
1206
 
1178
- function line(char = '─', color = C.gray) {
1179
- return c(color, char.repeat(W));
1207
+ // Ligne de bordure
1208
+ function hr(left = '├', mid = '─', right = '┤', color = C.gray) {
1209
+ return c(color, `${left}${mid.repeat(W)}${right}`);
1180
1210
  }
1181
1211
 
1182
- function boxLine(content = '', color = C.cyan) {
1183
- const visible = content.replace(/\x1b\[[0-9;]*m/g, '');
1184
- const pad = W - 2 - visible.length;
1185
- return `${c(color, '│')} ${content}${' '.repeat(Math.max(0, pad))}${c(color, '│')}`;
1212
+ // Ligne avec bordures latérales, contenu centré ou libre
1213
+ function row(content = '', color = C.gray) {
1214
+ const vis = strip(content);
1215
+ const pad = W - vis.length;
1216
+ return `${c(color, '│')}${content}${' '.repeat(Math.max(0, pad))}${c(color, '│')}`;
1186
1217
  }
1187
1218
 
1188
- function centered(text, totalWidth = W - 2) {
1189
- const visible = text.replace(/\x1b\[[0-9;]*m/g, '');
1190
- const left = Math.floor((totalWidth - visible.length) / 2);
1191
- const right = totalWidth - visible.length - left;
1192
- return ' '.repeat(left) + text + ' '.repeat(right);
1219
+ function rowCentered(content, color = C.gray) {
1220
+ const vis = strip(content);
1221
+ const l = Math.floor((W - vis.length) / 2);
1222
+ const r = W - vis.length - l;
1223
+ return row(' '.repeat(l) + content + ' '.repeat(r), color);
1193
1224
  }
1194
1225
 
1195
1226
  function printPanel(username, plan, statusMsg) {
1196
1227
  const now = new Date();
1197
1228
  const timeStr = now.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
1229
+ const planLabel = plan ? plan.toUpperCase() : 'FREE';
1230
+ const planColor = planLabel === 'FREE' ? C.gray : C.bYellow;
1198
1231
 
1232
+ // ── Titre ──────────────────────────────────────────────────────────────────
1199
1233
  console.log('');
1200
- console.log(`${c(C.cyan, '' + ''.repeat(W - 2) + '╗')}`);
1201
- console.log(`${c(C.cyan, '║')}${centered(c(C.bCyan, '◈ S H A R D ◈'), W - 2)}${c(C.cyan, '║')}`);
1202
- console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
1234
+ console.log(hr('┌', '', '', C.cyan));
1235
+
1236
+ const title = c(C.bCyan, 'S H A R D');
1237
+ console.log(rowCentered(title, C.cyan));
1238
+
1239
+ console.log(hr('├', '─', '┤', C.cyan));
1203
1240
 
1204
- // Infos utilisateur
1241
+ // ── Infos session ──────────────────────────────────────────────────────────
1205
1242
  if (username) {
1206
- const userInfo = `${c(C.gray, 'connecté ')}${c(C.bWhite, username)}${plan ? c(C.gray, ' · ') + c(C.bYellow, plan.toUpperCase()) : ''}`;
1207
- console.log(boxLine(centered(userInfo, W - 2), C.cyan));
1243
+ const userPart = ` ${c(C.gray, 'Utilisateur')} ${c(C.bWhite, username)}`;
1244
+ const planPart = ` ${c(C.gray, 'Plan')} ${c(planColor, planLabel)} `;
1245
+ const userVis = strip(userPart);
1246
+ const planVis = strip(planPart);
1247
+ const spacer = W - userVis.length - planVis.length;
1248
+ const infoLine = userPart + ' '.repeat(Math.max(1, spacer)) + planPart;
1249
+ console.log(row(infoLine, C.cyan));
1250
+ console.log(hr('├', '─', '┤', C.cyan));
1208
1251
  }
1209
- console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
1210
1252
 
1211
- // Menu
1253
+ // ── Menu ───────────────────────────────────────────────────────────────────
1212
1254
  const entries = [
1213
- { key: '1', icon: '', label: 'share', desc: 'Partager un fichier via relay' },
1214
- { key: '2', icon: '', label: 'sync', desc: 'Synchroniser un dossier local' },
1215
- { key: '3', icon: '', label: 'account', desc: 'Infos du compte' },
1216
- { key: '4', icon: '?', label: 'whoami', desc: 'Utilisateur connecté' },
1217
- { key: '5', icon: '', label: 'config', desc: 'Configuration serveur' },
1218
- { key: '6', icon: '', label: 'logout', desc: 'Se déconnecter et quitter' },
1255
+ { key: '1', tag: 'SHARE', label: 'share', desc: 'Partager un fichier via relay' },
1256
+ { key: '2', tag: 'SYNC', label: 'sync', desc: 'Synchroniser un dossier local' },
1257
+ { key: '3', tag: 'ACCOUNT', label: 'account', desc: 'Informations du compte' },
1258
+ { key: '4', tag: 'WHOAMI', label: 'whoami', desc: 'Utilisateur connecté' },
1259
+ { key: '5', tag: 'CONFIG', label: 'config', desc: 'Configuration serveur' },
1260
+ { key: '6', tag: 'LOGOUT', label: 'logout', desc: 'Se déconnecter et quitter' },
1219
1261
  ];
1220
1262
 
1221
- console.log(boxLine('', C.cyan));
1263
+ console.log(row('', C.cyan));
1222
1264
  for (const e of entries) {
1223
- const keyPart = ` ${c(C.bgBlue + C.bWhite, ` ${e.key} `)}`;
1224
- const iconPart = ` ${c(C.cyan, e.icon)}`;
1225
- const lblPart = ` ${c(C.bWhite, e.label.padEnd(10))}`;
1226
- const dscPart = c(C.gray, e.desc);
1227
- console.log(boxLine(`${keyPart}${iconPart}${lblPart}${dscPart}`, C.cyan));
1265
+ const num = ` ${c(C.bgGray + C.bWhite, ` ${e.key} `)}`;
1266
+ const lbl = ` ${c(C.bWhite, e.label.padEnd(9))}`;
1267
+ const sep = `${c(C.gray, '│')}`;
1268
+ const desc = ` ${c(C.gray, e.desc)}`;
1269
+ const numVis = strip(num);
1270
+ const lblVis = strip(lbl);
1271
+ const descVis = strip(desc);
1272
+ const padRight = W - numVis.length - lblVis.length - 1 - descVis.length;
1273
+ const line = `${num}${lbl}${sep}${desc}${' '.repeat(Math.max(0, padRight - 2))}`;
1274
+ console.log(row(line, C.cyan));
1228
1275
  }
1229
- console.log(boxLine('', C.cyan));
1276
+ console.log(row('', C.cyan));
1230
1277
 
1231
- console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
1278
+ // ── Footer ─────────────────────────────────────────────────────────────────
1279
+ console.log(hr('├', '─', '┤', C.gray));
1232
1280
 
1233
- // Status / message
1234
1281
  if (statusMsg) {
1235
- console.log(boxLine(centered(statusMsg, W - 2), C.cyan));
1282
+ console.log(rowCentered(statusMsg, C.gray));
1236
1283
  } else {
1237
- console.log(boxLine(centered(c(C.gray, `${timeStr} · tape un numéro ou le nom de la commande`), W - 2), C.cyan));
1284
+ const hint = `${c(C.gray, timeStr + ' ── Entre un numéro ou un nom de commande')}`;
1285
+ console.log(rowCentered(hint, C.gray));
1238
1286
  }
1239
1287
 
1240
- console.log(`${c(C.cyan, '' + ''.repeat(W - 2) + '╝')}`);
1288
+ console.log(hr('└', '', '', C.gray));
1241
1289
  console.log('');
1242
1290
  }
1243
1291
 
1244
- function printSubHeader(title, icon = '◈') {
1292
+ // ── Sous-écrans ───────────────────────────────────────────────────────────────
1293
+
1294
+ function printSubHeader(title) {
1245
1295
  console.log('');
1246
- console.log(line('─', C.cyan));
1247
- console.log(` ${c(C.cyan, icon)} ${c(C.bWhite, title)}`);
1248
- console.log(line('─', C.gray));
1296
+ console.log(c(C.cyan, `┌${'─'.repeat(W)}┐`));
1297
+ const vis = strip(title);
1298
+ const l = Math.floor((W - vis.length) / 2);
1299
+ const r = W - vis.length - l;
1300
+ console.log(`${c(C.cyan, '│')}${' '.repeat(l)}${c(C.bWhite, title)}${' '.repeat(r)}${c(C.cyan, '│')}`);
1301
+ console.log(c(C.gray, `└${'─'.repeat(W)}┘`));
1249
1302
  console.log('');
1250
1303
  }
1251
1304
 
1252
- function printSuccess(msg) { console.log(`\n ${c(C.bGreen, '✔')} ${c(C.green, msg)}`); }
1253
- function printError(msg) { console.log(`\n ${c(C.bRed, '')} ${c(C.red, msg)}`); }
1254
- function printInfo(msg) { console.log(` ${c(C.cyan, '·')} ${c(C.white, msg)}`); }
1305
+ function printSuccess(msg) { console.log(`\n ${c(C.bGreen, '+ ')} ${c(C.green, msg)}`); }
1306
+ function printError(msg) { console.log(`\n ${c(C.bRed, 'x ')} ${c(C.red, msg)}`); }
1307
+ function printInfo(msg) { console.log(` ${c(C.bCyan, '> ')} ${c(C.white, msg)}`); }
1255
1308
 
1256
1309
  async function askPanelInput(prompt) {
1257
1310
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -1261,50 +1314,54 @@ async function askPanelInput(prompt) {
1261
1314
  }
1262
1315
 
1263
1316
  async function pressEnter() {
1264
- await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entrée pour revenir au menu…')}`);
1317
+ await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entree pour revenir au menu…')}`);
1265
1318
  }
1266
1319
 
1320
+ // ── Actions ───────────────────────────────────────────────────────────────────
1321
+
1267
1322
  async function panelShare() {
1268
1323
  clearScreen();
1269
- printSubHeader('Partager un fichier', '⬆');
1324
+ printSubHeader('Partager un fichier');
1270
1325
 
1271
- const target = await askText(` ${c(C.cyan, '')} Chemin du fichier`);
1326
+ const target = await askText(` ${c(C.cyan, '>')} Chemin du fichier`);
1272
1327
  if (!target) return;
1273
-
1274
- const limits = await askText(` ${c(C.cyan, '')} Limite de téléchargements ${c(C.gray, '(0 = illimité)')}`, '0');
1275
- const temps = await askText(` ${c(C.cyan, '›')} Expiration en jours ${c(C.gray, '(0 = aucune)')}`, '0');
1276
- const modeRaw = await askText(` ${c(C.cyan, '›')} Mode upload serveur ? ${c(C.gray, '[o/n]')}`, 'n');
1328
+ const limits = await askText(` ${c(C.cyan, '>')} Limite de telechargements ${c(C.gray, '(0 = illimite)')}`, '0');
1329
+ const temps = await askText(` ${c(C.cyan, '>')} Expiration en jours ${c(C.gray, '(0 = aucune)')}`, '0');
1277
1330
 
1278
1331
  const flags = {};
1279
1332
  if (parseInt(limits, 10) > 0) flags.limits = limits;
1280
1333
  if (parseInt(temps, 10) > 0) flags.temps = temps;
1281
- if (modeRaw.toLowerCase() === 'o') flags.upload = true;
1282
1334
 
1283
- console.log('');
1284
- console.log(line('', C.gray));
1335
+ clearScreen();
1336
+ printSubHeader('Partage en cours...');
1337
+ console.log(` ${c(C.gray, 'Connexion au relay...')}\n`);
1338
+
1285
1339
  try {
1286
1340
  await shareFile([target], flags);
1287
- printSuccess('Partage créé avec succès.');
1341
+ // shareFile retourne quand l'utilisateur tape 'q' ou que le socket se ferme
1342
+ printSuccess('Partage cloture.');
1288
1343
  } catch (err) {
1289
1344
  if (err?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
1290
1345
  printPlanLimitBox(err);
1291
1346
  } else {
1292
1347
  printError(err.message);
1293
1348
  }
1349
+ await pressEnter();
1350
+ return;
1294
1351
  }
1352
+
1295
1353
  await pressEnter();
1296
1354
  }
1297
1355
 
1298
1356
  async function panelSync() {
1299
1357
  clearScreen();
1300
- printSubHeader('Synchroniser un dossier', '↻');
1358
+ printSubHeader('Synchroniser un dossier');
1301
1359
 
1302
- const target = await askText(` ${c(C.cyan, '')} Chemin du dossier`);
1360
+ const target = await askText(` ${c(C.cyan, '>')} Chemin du dossier`);
1303
1361
  if (!target) return;
1304
-
1305
- const onceRaw = await askText(` ${c(C.cyan, '')} Sync unique (pas de surveillance) ? ${c(C.gray, '[o/n]')}`, 'n');
1306
- const dryRaw = await askText(` ${c(C.cyan, '')} Simulation (dry-run) ? ${c(C.gray, '[o/n]')}`, 'n');
1307
- const forceRaw = await askText(` ${c(C.cyan, '›')} Forcer le re-upload de tout ? ${c(C.gray, '[o/n]')}`, 'n');
1362
+ const onceRaw = await askText(` ${c(C.cyan, '>')} Sync unique (sans surveillance) ? ${c(C.gray, '[o/n]')}`, 'n');
1363
+ const dryRaw = await askText(` ${c(C.cyan, '>')} Simulation dry-run ? ${c(C.gray, '[o/n]')}`, 'n');
1364
+ const forceRaw = await askText(` ${c(C.cyan, '>')} Forcer le re-upload ? ${c(C.gray, '[o/n]')}`, 'n');
1308
1365
 
1309
1366
  const flags = {};
1310
1367
  if (onceRaw.toLowerCase() === 'o') flags.once = true;
@@ -1312,14 +1369,13 @@ async function panelSync() {
1312
1369
  if (forceRaw.toLowerCase() === 'o') flags.force = true;
1313
1370
 
1314
1371
  console.log('');
1315
- console.log(line('─', C.gray));
1372
+ console.log(c(C.gray, '─'.repeat(W + 2)));
1316
1373
  try {
1317
1374
  await syncFolder([target], flags);
1318
1375
  if (flags.once || flags['dry-run']) {
1319
- printSuccess('Synchronisation terminée.');
1376
+ printSuccess('Synchronisation terminee.');
1320
1377
  await pressEnter();
1321
1378
  }
1322
- // mode watch : syncFolder bloque, on revient au menu quand il se termine
1323
1379
  } catch (err) {
1324
1380
  printError(err.message);
1325
1381
  await pressEnter();
@@ -1328,9 +1384,24 @@ async function panelSync() {
1328
1384
 
1329
1385
  async function panelAccount() {
1330
1386
  clearScreen();
1331
- printSubHeader('Informations du compte', '◉');
1387
+ printSubHeader('Informations du compte');
1332
1388
  try {
1333
- await account({});
1389
+ const config = await readConfig();
1390
+ const server = getServer({}, config);
1391
+ const token = getToken(config);
1392
+ if (!token) throw new Error('Non connecte.');
1393
+ const data = await httpJson(`${server}/api/auth/profile`, {
1394
+ method: 'GET',
1395
+ headers: { Authorization: `Bearer ${token}` }
1396
+ });
1397
+ const u = data.user || {};
1398
+ const planRaw = String(u.billing_plan || 'free').toUpperCase();
1399
+ const planCol = planRaw === 'FREE' ? C.gray : C.bYellow;
1400
+ console.log('');
1401
+ printInfo(`Utilisateur ${c(C.bWhite, u.username || '—')}`);
1402
+ printInfo(`Email ${c(C.white, u.email || '—')}`);
1403
+ printInfo(`Plan ${c(planCol, planRaw)}`);
1404
+ printInfo(`Serveur ${c(C.gray, server)}`);
1334
1405
  } catch (err) {
1335
1406
  printError(err.message);
1336
1407
  }
@@ -1339,9 +1410,21 @@ async function panelAccount() {
1339
1410
 
1340
1411
  async function panelWhoami() {
1341
1412
  clearScreen();
1342
- printSubHeader('Utilisateur connecté', '?');
1413
+ printSubHeader('Utilisateur connecte');
1343
1414
  try {
1344
- await whoami({});
1415
+ const config = await readConfig();
1416
+ const server = getServer({}, config);
1417
+ const token = getToken(config);
1418
+ if (!token) throw new Error('Non connecte.');
1419
+ const data = await httpJson(`${server}/api/auth/verify`, {
1420
+ method: 'POST',
1421
+ headers: { Authorization: `Bearer ${token}` }
1422
+ });
1423
+ const u = data.user || {};
1424
+ console.log('');
1425
+ printInfo(`Utilisateur ${c(C.bWhite, u.username || u.userId || '—')}`);
1426
+ if (u.email) printInfo(`Email ${c(C.white, u.email)}`);
1427
+ printInfo(`Serveur ${c(C.gray, server)}`);
1345
1428
  } catch (err) {
1346
1429
  printError(err.message);
1347
1430
  }
@@ -1350,38 +1433,42 @@ async function panelWhoami() {
1350
1433
 
1351
1434
  async function panelConfig() {
1352
1435
  clearScreen();
1353
- printSubHeader('Configuration', '⚙');
1436
+ printSubHeader('Configuration');
1354
1437
 
1355
- printInfo(`${c(C.gray, '[a]')} Afficher la configuration actuelle`);
1356
- printInfo(`${c(C.gray, '[b]')} Changer de serveur`);
1438
+ printInfo(`${c(C.bgGray + C.bWhite, ' a ')} Afficher la configuration actuelle`);
1439
+ printInfo(`${c(C.bgGray + C.bWhite, ' b ')} Changer de serveur`);
1357
1440
  console.log('');
1358
1441
 
1359
- const choice = await askPanelInput(` ${c(C.cyan, '')} Choix ${c(C.gray, '[a/b]')} : `);
1442
+ const choice = await askPanelInput(` ${c(C.cyan, '>')} Choix ${c(C.gray, '[a / b]')} : `);
1360
1443
 
1361
1444
  if (choice.toLowerCase() === 'a') {
1362
1445
  console.log('');
1363
- console.log(line('─', C.gray));
1446
+ console.log(c(C.gray, '─'.repeat(W + 2)));
1364
1447
  try {
1365
- await showConfig();
1448
+ const config = await readConfig();
1449
+ printInfo(`Serveur ${c(C.white, config.server)}`);
1450
+ printInfo(`Token ${config.token ? c(C.green, 'present') : c(C.red, 'absent')}`);
1366
1451
  } catch (err) {
1367
1452
  printError(err.message);
1368
1453
  }
1369
1454
  } else if (choice.toLowerCase() === 'b') {
1370
- const url = await askText(` ${c(C.cyan, '')} Nouvelle URL du serveur`);
1455
+ const url = await askText(` ${c(C.cyan, '>')} Nouvelle URL du serveur`);
1371
1456
  if (url) {
1372
1457
  try {
1373
1458
  await setServer([url]);
1374
- printSuccess(`Serveur mis à jour : ${url}`);
1459
+ printSuccess(`Serveur mis a jour : ${url}`);
1375
1460
  } catch (err) {
1376
1461
  printError(err.message);
1377
1462
  }
1378
1463
  }
1379
1464
  } else {
1380
- printError('Choix invalide.');
1465
+ printError('Choix invalide. Entre a ou b.');
1381
1466
  }
1382
1467
  await pressEnter();
1383
1468
  }
1384
1469
 
1470
+ // ── Boucle principale ─────────────────────────────────────────────────────────
1471
+
1385
1472
  async function runPanel(username, plan) {
1386
1473
  // Logout automatique à la fermeture du process
1387
1474
  process.on('exit', () => {
@@ -1394,11 +1481,11 @@ async function runPanel(username, plan) {
1394
1481
  });
1395
1482
 
1396
1483
  const handleStop = async (sig) => {
1397
- process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})…`)}\n`);
1484
+ process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})...`)}\n`);
1398
1485
  try {
1399
1486
  const cfg = await readConfig();
1400
1487
  await writeConfig({ ...cfg, token: '' });
1401
- process.stdout.write(` ${c(C.green, '')} Session fermée. À bientôt !\n\n`);
1488
+ process.stdout.write(` ${c(C.green, '+')} Session fermee. A bientot !\n\n`);
1402
1489
  } catch (_) {}
1403
1490
  process.exit(0);
1404
1491
  };
@@ -1412,7 +1499,7 @@ async function runPanel(username, plan) {
1412
1499
  printPanel(username, plan, statusMsg);
1413
1500
  statusMsg = null;
1414
1501
 
1415
- const answer = (await askPanelInput(` ${c(C.bCyan, '')} `)).toLowerCase();
1502
+ const answer = (await askPanelInput(` ${c(C.bCyan, '>')} `)).toLowerCase();
1416
1503
 
1417
1504
  if (answer === '1' || answer === 'share') { await panelShare(); }
1418
1505
  else if (answer === '2' || answer === 'sync') { await panelSync(); }
@@ -1424,12 +1511,12 @@ async function runPanel(username, plan) {
1424
1511
  process.removeListener('SIGINT', handleStop);
1425
1512
  process.removeListener('SIGTERM', handleStop);
1426
1513
  await logout();
1427
- console.log(`\n ${c(C.bGreen, '')} Déconnecté avec succès. À bientôt !\n`);
1514
+ console.log(`\n ${c(C.bGreen, '+')} Deconnecte avec succes. A bientot !\n`);
1428
1515
  process.exit(0);
1429
1516
  } else if (answer === '') {
1430
- // reboucle silencieusement
1517
+ // reboucle
1431
1518
  } else {
1432
- statusMsg = c(C.bRed, `✘ Option inconnue : "${answer}" · choisis 16`);
1519
+ statusMsg = c(C.bRed, `x Inconnu : "${answer}" ── choisis 1 a 6`);
1433
1520
  }
1434
1521
  }
1435
1522
  }
@@ -1469,17 +1556,28 @@ async function main() {
1469
1556
  const server = getServer(flags, config);
1470
1557
  const token = getToken(config);
1471
1558
  let username = String(flags.username || '').trim();
1472
- // Récupère le username depuis le serveur si possible
1473
- if (!username && token) {
1559
+ let plan = '';
1560
+ // Récupère le username et le plan depuis le serveur si possible
1561
+ if (token) {
1474
1562
  try {
1475
- const data = await httpJson(`${server}/api/auth/verify`, {
1476
- method: 'POST',
1563
+ const data = await httpJson(`${server}/api/auth/profile`, {
1564
+ method: 'GET',
1477
1565
  headers: { Authorization: `Bearer ${token}` }
1478
1566
  });
1479
- username = data.user?.username || data.user?.userId || '';
1480
- } catch (_) {}
1567
+ const user = data.user || {};
1568
+ if (!username) username = user.username || user.userId || '';
1569
+ plan = String(user.billing_plan || 'free').toLowerCase();
1570
+ } catch (_) {
1571
+ try {
1572
+ const data = await httpJson(`${server}/api/auth/verify`, {
1573
+ method: 'POST',
1574
+ headers: { Authorization: `Bearer ${token}` }
1575
+ });
1576
+ if (!username) username = data.user?.username || data.user?.userId || '';
1577
+ } catch (_2) {}
1578
+ }
1481
1579
  }
1482
- await runPanel(username);
1580
+ await runPanel(username, plan);
1483
1581
  }
1484
1582
  return;
1485
1583
  }
@@ -1525,27 +1623,7 @@ async function main() {
1525
1623
  throw new Error(`Commande inconnue: ${command}`);
1526
1624
  }
1527
1625
 
1528
- function printPlanLimitBox(error) {
1529
- const data = error?.data || {};
1530
- const plan = String(data.plan || '').toUpperCase() || 'FREE';
1531
- const maxFileSize = Number(data.maxFileSize || 0);
1532
- const fileSize = Number(data.fileSize || 0);
1533
- const maxLabel = maxFileSize > 0 ? formatBytes(maxFileSize) : '?';
1534
- const fileLabel = fileSize > 0 ? formatBytes(fileSize) : '?';
1535
- const lines = [
1536
- '',
1537
- '╔════════════════════════════════════════════════════╗',
1538
- `║ ⚠ Limite du plan ${plan}`.padEnd(52) + ' ║',
1539
- '╠════════════════════════════════════════════════════╣',
1540
- `║ Taille fichier : ${fileLabel}`.padEnd(52) + ' ║',
1541
- `║ Limite max : ${maxLabel} / fichier`.padEnd(52) + ' ║',
1542
- '║ ║',
1543
- '║ Passe a une offre superieure pour continuer. ║',
1544
- '╚════════════════════════════════════════════════════╝',
1545
- ''
1546
- ];
1547
- console.error(lines.join('\n'));
1548
- }
1626
+
1549
1627
 
1550
1628
  main().catch((error) => {
1551
1629
  if (error?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iksdev/shard-cli",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "CLI pour synchroniser un dossier local avec Shard",
5
5
  "bin": {
6
6
  "shard": "bin/shard.js"