@iksdev/shard-cli 0.1.46 → 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 +199 -176
  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);
@@ -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,114 +1098,164 @@ async function syncFolder(positionals, flags) {
1144
1098
  }
1145
1099
  }
1146
1100
 
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
+
1147
1123
  // ─── Couleurs ANSI ───────────────────────────────────────────────────────────
1148
1124
 
1149
1125
  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',
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',
1166
1145
  };
1167
1146
 
1168
1147
  function c(color, text) { return `${color}${text}${C.reset}`; }
1148
+ function strip(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
1169
1149
 
1170
1150
  // ─── Panel interactif ────────────────────────────────────────────────────────
1171
1151
 
1172
- const W = 64;
1152
+ const W = 58; // largeur intérieure (sans les bordures)
1173
1153
 
1174
1154
  function clearScreen() {
1175
1155
  process.stdout.write('\x1Bc');
1176
1156
  }
1177
1157
 
1178
- function line(char = '─', color = C.gray) {
1179
- return c(color, char.repeat(W));
1158
+ // Ligne de bordure
1159
+ function hr(left = '├', mid = '─', right = '┤', color = C.gray) {
1160
+ return c(color, `${left}${mid.repeat(W)}${right}`);
1180
1161
  }
1181
1162
 
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, '│')}`;
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, '│')}`;
1186
1168
  }
1187
1169
 
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);
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);
1193
1175
  }
1194
1176
 
1195
1177
  function printPanel(username, plan, statusMsg) {
1196
1178
  const now = new Date();
1197
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;
1198
1182
 
1183
+ // ── Titre ──────────────────────────────────────────────────────────────────
1199
1184
  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) + '╣')}`);
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));
1203
1189
 
1204
- // Infos utilisateur
1190
+ console.log(hr('├', '─', '┤', C.cyan));
1191
+
1192
+ // ── Infos session ──────────────────────────────────────────────────────────
1205
1193
  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));
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));
1208
1202
  }
1209
- console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
1210
1203
 
1211
- // Menu
1204
+ // ── Menu ───────────────────────────────────────────────────────────────────
1212
1205
  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' },
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' },
1219
1212
  ];
1220
1213
 
1221
- console.log(boxLine('', C.cyan));
1214
+ console.log(row('', C.cyan));
1222
1215
  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));
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));
1228
1226
  }
1229
- console.log(boxLine('', C.cyan));
1227
+ console.log(row('', C.cyan));
1230
1228
 
1231
- console.log(`${c(C.cyan, '╠' + '═'.repeat(W - 2) + '╣')}`);
1229
+ // ── Footer ─────────────────────────────────────────────────────────────────
1230
+ console.log(hr('├', '─', '┤', C.gray));
1232
1231
 
1233
- // Status / message
1234
1232
  if (statusMsg) {
1235
- console.log(boxLine(centered(statusMsg, W - 2), C.cyan));
1233
+ console.log(rowCentered(statusMsg, C.gray));
1236
1234
  } else {
1237
- console.log(boxLine(centered(c(C.gray, `${timeStr} · tape un numéro ou le nom de la commande`), W - 2), C.cyan));
1235
+ const hint = `${c(C.gray, timeStr + ' ── Entre un numéro ou un nom de commande')}`;
1236
+ console.log(rowCentered(hint, C.gray));
1238
1237
  }
1239
1238
 
1240
- console.log(`${c(C.cyan, '' + ''.repeat(W - 2) + '╝')}`);
1239
+ console.log(hr('└', '', '', C.gray));
1241
1240
  console.log('');
1242
1241
  }
1243
1242
 
1244
- function printSubHeader(title, icon = '◈') {
1243
+ // ── Sous-écrans ───────────────────────────────────────────────────────────────
1244
+
1245
+ function printSubHeader(title) {
1245
1246
  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));
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)}┘`));
1249
1253
  console.log('');
1250
1254
  }
1251
1255
 
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)}`); }
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)}`); }
1255
1259
 
1256
1260
  async function askPanelInput(prompt) {
1257
1261
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -1261,30 +1265,29 @@ async function askPanelInput(prompt) {
1261
1265
  }
1262
1266
 
1263
1267
  async function pressEnter() {
1264
- await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entrée pour revenir au menu…')}`);
1268
+ await askPanelInput(`\n ${c(C.gray, 'Appuie sur Entree pour revenir au menu…')}`);
1265
1269
  }
1266
1270
 
1271
+ // ── Actions ───────────────────────────────────────────────────────────────────
1272
+
1267
1273
  async function panelShare() {
1268
1274
  clearScreen();
1269
- printSubHeader('Partager un fichier', '⬆');
1275
+ printSubHeader('Partager un fichier');
1270
1276
 
1271
- const target = await askText(` ${c(C.cyan, '')} Chemin du fichier`);
1277
+ const target = await askText(` ${c(C.cyan, '>')} Chemin du fichier`);
1272
1278
  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');
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');
1277
1281
 
1278
1282
  const flags = {};
1279
1283
  if (parseInt(limits, 10) > 0) flags.limits = limits;
1280
1284
  if (parseInt(temps, 10) > 0) flags.temps = temps;
1281
- if (modeRaw.toLowerCase() === 'o') flags.upload = true;
1282
1285
 
1283
1286
  console.log('');
1284
- console.log(line('─', C.gray));
1287
+ console.log(c(C.gray, '─'.repeat(W + 2)));
1285
1288
  try {
1286
1289
  await shareFile([target], flags);
1287
- printSuccess('Partage créé avec succès.');
1290
+ printSuccess('Partage cree avec succes.');
1288
1291
  } catch (err) {
1289
1292
  if (err?.data?.code === 'PLAN_FILE_LIMIT_EXCEEDED') {
1290
1293
  printPlanLimitBox(err);
@@ -1297,14 +1300,13 @@ async function panelShare() {
1297
1300
 
1298
1301
  async function panelSync() {
1299
1302
  clearScreen();
1300
- printSubHeader('Synchroniser un dossier', '↻');
1303
+ printSubHeader('Synchroniser un dossier');
1301
1304
 
1302
- const target = await askText(` ${c(C.cyan, '')} Chemin du dossier`);
1305
+ const target = await askText(` ${c(C.cyan, '>')} Chemin du dossier`);
1303
1306
  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');
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');
1308
1310
 
1309
1311
  const flags = {};
1310
1312
  if (onceRaw.toLowerCase() === 'o') flags.once = true;
@@ -1312,14 +1314,13 @@ async function panelSync() {
1312
1314
  if (forceRaw.toLowerCase() === 'o') flags.force = true;
1313
1315
 
1314
1316
  console.log('');
1315
- console.log(line('─', C.gray));
1317
+ console.log(c(C.gray, '─'.repeat(W + 2)));
1316
1318
  try {
1317
1319
  await syncFolder([target], flags);
1318
1320
  if (flags.once || flags['dry-run']) {
1319
- printSuccess('Synchronisation terminée.');
1321
+ printSuccess('Synchronisation terminee.');
1320
1322
  await pressEnter();
1321
1323
  }
1322
- // mode watch : syncFolder bloque, on revient au menu quand il se termine
1323
1324
  } catch (err) {
1324
1325
  printError(err.message);
1325
1326
  await pressEnter();
@@ -1328,9 +1329,24 @@ async function panelSync() {
1328
1329
 
1329
1330
  async function panelAccount() {
1330
1331
  clearScreen();
1331
- printSubHeader('Informations du compte', '◉');
1332
+ printSubHeader('Informations du compte');
1332
1333
  try {
1333
- await account({});
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)}`);
1334
1350
  } catch (err) {
1335
1351
  printError(err.message);
1336
1352
  }
@@ -1339,9 +1355,21 @@ async function panelAccount() {
1339
1355
 
1340
1356
  async function panelWhoami() {
1341
1357
  clearScreen();
1342
- printSubHeader('Utilisateur connecté', '?');
1358
+ printSubHeader('Utilisateur connecte');
1343
1359
  try {
1344
- await whoami({});
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)}`);
1345
1373
  } catch (err) {
1346
1374
  printError(err.message);
1347
1375
  }
@@ -1350,38 +1378,42 @@ async function panelWhoami() {
1350
1378
 
1351
1379
  async function panelConfig() {
1352
1380
  clearScreen();
1353
- printSubHeader('Configuration', '⚙');
1381
+ printSubHeader('Configuration');
1354
1382
 
1355
- printInfo(`${c(C.gray, '[a]')} Afficher la configuration actuelle`);
1356
- printInfo(`${c(C.gray, '[b]')} Changer de serveur`);
1383
+ printInfo(`${c(C.bgGray + C.bWhite, ' a ')} Afficher la configuration actuelle`);
1384
+ printInfo(`${c(C.bgGray + C.bWhite, ' b ')} Changer de serveur`);
1357
1385
  console.log('');
1358
1386
 
1359
- const choice = await askPanelInput(` ${c(C.cyan, '')} Choix ${c(C.gray, '[a/b]')} : `);
1387
+ const choice = await askPanelInput(` ${c(C.cyan, '>')} Choix ${c(C.gray, '[a / b]')} : `);
1360
1388
 
1361
1389
  if (choice.toLowerCase() === 'a') {
1362
1390
  console.log('');
1363
- console.log(line('─', C.gray));
1391
+ console.log(c(C.gray, '─'.repeat(W + 2)));
1364
1392
  try {
1365
- await showConfig();
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')}`);
1366
1396
  } catch (err) {
1367
1397
  printError(err.message);
1368
1398
  }
1369
1399
  } else if (choice.toLowerCase() === 'b') {
1370
- const url = await askText(` ${c(C.cyan, '')} Nouvelle URL du serveur`);
1400
+ const url = await askText(` ${c(C.cyan, '>')} Nouvelle URL du serveur`);
1371
1401
  if (url) {
1372
1402
  try {
1373
1403
  await setServer([url]);
1374
- printSuccess(`Serveur mis à jour : ${url}`);
1404
+ printSuccess(`Serveur mis a jour : ${url}`);
1375
1405
  } catch (err) {
1376
1406
  printError(err.message);
1377
1407
  }
1378
1408
  }
1379
1409
  } else {
1380
- printError('Choix invalide.');
1410
+ printError('Choix invalide. Entre a ou b.');
1381
1411
  }
1382
1412
  await pressEnter();
1383
1413
  }
1384
1414
 
1415
+ // ── Boucle principale ─────────────────────────────────────────────────────────
1416
+
1385
1417
  async function runPanel(username, plan) {
1386
1418
  // Logout automatique à la fermeture du process
1387
1419
  process.on('exit', () => {
@@ -1394,11 +1426,11 @@ async function runPanel(username, plan) {
1394
1426
  });
1395
1427
 
1396
1428
  const handleStop = async (sig) => {
1397
- process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})…`)}\n`);
1429
+ process.stdout.write(`\n\n ${c(C.gray, `Fermeture (${sig})...`)}\n`);
1398
1430
  try {
1399
1431
  const cfg = await readConfig();
1400
1432
  await writeConfig({ ...cfg, token: '' });
1401
- process.stdout.write(` ${c(C.green, '')} Session fermée. À bientôt !\n\n`);
1433
+ process.stdout.write(` ${c(C.green, '+')} Session fermee. A bientot !\n\n`);
1402
1434
  } catch (_) {}
1403
1435
  process.exit(0);
1404
1436
  };
@@ -1412,7 +1444,7 @@ async function runPanel(username, plan) {
1412
1444
  printPanel(username, plan, statusMsg);
1413
1445
  statusMsg = null;
1414
1446
 
1415
- const answer = (await askPanelInput(` ${c(C.bCyan, '')} `)).toLowerCase();
1447
+ const answer = (await askPanelInput(` ${c(C.bCyan, '>')} `)).toLowerCase();
1416
1448
 
1417
1449
  if (answer === '1' || answer === 'share') { await panelShare(); }
1418
1450
  else if (answer === '2' || answer === 'sync') { await panelSync(); }
@@ -1424,12 +1456,12 @@ async function runPanel(username, plan) {
1424
1456
  process.removeListener('SIGINT', handleStop);
1425
1457
  process.removeListener('SIGTERM', handleStop);
1426
1458
  await logout();
1427
- console.log(`\n ${c(C.bGreen, '')} Déconnecté avec succès. À bientôt !\n`);
1459
+ console.log(`\n ${c(C.bGreen, '+')} Deconnecte avec succes. A bientot !\n`);
1428
1460
  process.exit(0);
1429
1461
  } else if (answer === '') {
1430
- // reboucle silencieusement
1462
+ // reboucle
1431
1463
  } else {
1432
- statusMsg = c(C.bRed, `✘ Option inconnue : "${answer}" · choisis 16`);
1464
+ statusMsg = c(C.bRed, `x Inconnu : "${answer}" ── choisis 1 a 6`);
1433
1465
  }
1434
1466
  }
1435
1467
  }
@@ -1469,17 +1501,28 @@ async function main() {
1469
1501
  const server = getServer(flags, config);
1470
1502
  const token = getToken(config);
1471
1503
  let username = String(flags.username || '').trim();
1472
- // Récupère le username depuis le serveur si possible
1473
- if (!username && token) {
1504
+ let plan = '';
1505
+ // Récupère le username et le plan depuis le serveur si possible
1506
+ if (token) {
1474
1507
  try {
1475
- const data = await httpJson(`${server}/api/auth/verify`, {
1476
- method: 'POST',
1508
+ const data = await httpJson(`${server}/api/auth/profile`, {
1509
+ method: 'GET',
1477
1510
  headers: { Authorization: `Bearer ${token}` }
1478
1511
  });
1479
- username = data.user?.username || data.user?.userId || '';
1480
- } catch (_) {}
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
+ }
1481
1524
  }
1482
- await runPanel(username);
1525
+ await runPanel(username, plan);
1483
1526
  }
1484
1527
  return;
1485
1528
  }
@@ -1525,27 +1568,7 @@ async function main() {
1525
1568
  throw new Error(`Commande inconnue: ${command}`);
1526
1569
  }
1527
1570
 
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
- }
1571
+
1549
1572
 
1550
1573
  main().catch((error) => {
1551
1574
  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.47",
4
4
  "description": "CLI pour synchroniser un dossier local avec Shard",
5
5
  "bin": {
6
6
  "shard": "bin/shard.js"