@immich/cli 2.5.6 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/.nvmrc +1 -1
  2. package/dist/index.js +163 -78
  3. package/package.json +14 -14
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- 24.13.0
1
+ 24.13.1
package/dist/index.js CHANGED
@@ -886,12 +886,12 @@ function requireSuggestSimilar() {
886
886
  function editDistance(a2, b) {
887
887
  if (Math.abs(a2.length - b.length) > maxDistance)
888
888
  return Math.max(a2.length, b.length);
889
- const d = [];
889
+ const d2 = [];
890
890
  for (let i = 0; i <= a2.length; i++) {
891
- d[i] = [i];
891
+ d2[i] = [i];
892
892
  }
893
893
  for (let j2 = 0; j2 <= b.length; j2++) {
894
- d[0][j2] = j2;
894
+ d2[0][j2] = j2;
895
895
  }
896
896
  for (let j2 = 1; j2 <= b.length; j2++) {
897
897
  for (let i = 1; i <= a2.length; i++) {
@@ -901,20 +901,20 @@ function requireSuggestSimilar() {
901
901
  } else {
902
902
  cost = 1;
903
903
  }
904
- d[i][j2] = Math.min(
905
- d[i - 1][j2] + 1,
904
+ d2[i][j2] = Math.min(
905
+ d2[i - 1][j2] + 1,
906
906
  // deletion
907
- d[i][j2 - 1] + 1,
907
+ d2[i][j2 - 1] + 1,
908
908
  // insertion
909
- d[i - 1][j2 - 1] + cost
909
+ d2[i - 1][j2 - 1] + cost
910
910
  // substitution
911
911
  );
912
912
  if (i > 1 && j2 > 1 && a2[i - 1] === b[j2 - 2] && a2[i - 2] === b[j2 - 1]) {
913
- d[i][j2] = Math.min(d[i][j2], d[i - 2][j2 - 2] + 1);
913
+ d2[i][j2] = Math.min(d2[i][j2], d2[i - 2][j2 - 2] + 1);
914
914
  }
915
915
  }
916
916
  }
917
- return d[a2.length][b.length];
917
+ return d2[a2.length][b.length];
918
918
  }
919
919
  function suggestSimilar$1(word, candidates) {
920
920
  if (!candidates || candidates.length === 0) return "";
@@ -1570,8 +1570,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1570
1570
  } else if (fn instanceof RegExp) {
1571
1571
  const regex = fn;
1572
1572
  fn = (val, def) => {
1573
- const m2 = regex.exec(val);
1574
- return m2 ? m2[0] : def;
1573
+ const m = regex.exec(val);
1574
+ return m ? m[0] : def;
1575
1575
  };
1576
1576
  option2.default(defaultValue).argParser(fn);
1577
1577
  } else {
@@ -3039,31 +3039,34 @@ const {
3039
3039
  Help
3040
3040
  } = commander;
3041
3041
  const a = [encodeURIComponent, encodeURIComponent];
3042
- function f(r, n = ",") {
3043
- const t2 = (e, o2) => {
3044
- const c = r[o2 % r.length];
3045
- return typeof e > "u" ? "" : typeof e == "object" ? Array.isArray(e) ? e.map(c).join(n) : Object.entries(e).reduce(
3046
- (i, d) => [...i, ...d],
3042
+ function d(e, t2 = ",") {
3043
+ const o2 = (n, r) => {
3044
+ const c = e[r % e.length];
3045
+ return typeof n > "u" ? "" : typeof n == "object" ? Array.isArray(n) ? n.map(c).join(t2) : Object.entries(n).reduce(
3046
+ (i, f) => [...i, ...f],
3047
3047
  []
3048
- ).map(c).join(n) : c(String(e));
3048
+ ).map(c).join(t2) : c(l$1(n));
3049
3049
  };
3050
- return (e, ...o2) => e.reduce((c, u, i) => `${c}${u}${t2(o2[i], i)}`, "");
3050
+ return (n, ...r) => n.reduce((c, u, i) => `${c}${u}${o2(r[i], i)}`, "");
3051
3051
  }
3052
- function l$1(r = ",") {
3053
- return (n, t2 = a) => Object.entries(n).filter(([, e]) => e !== void 0).map(([e, o2]) => f(t2, r)`${e}=${o2}`).join("&");
3052
+ function R(e = ",") {
3053
+ return (t2, o2 = a) => Object.entries(t2).filter(([, n]) => n !== void 0).map(([n, r]) => d(o2, e)`${n}=${r}`).join("&");
3054
3054
  }
3055
- function j$1(...r) {
3056
- return r.filter(Boolean).map((n, t2) => t2 === 0 ? n : n.replace(/^\/+/, "")).map((n, t2, e) => t2 === e.length - 1 ? n : n.replace(/\/+$/, "")).join("/");
3055
+ function j$1(...e) {
3056
+ return e.filter(Boolean).map((t2, o2) => o2 === 0 ? t2 : t2.replace(/^\/+/, "")).map((t2, o2, n) => o2 === n.length - 1 ? t2 : t2.replace(/\/+$/, "")).join("/");
3057
+ }
3058
+ function l$1(e) {
3059
+ return typeof e == "string" || typeof e == "number" || typeof e == "boolean" ? e : String(e);
3057
3060
  }
3058
3061
  function q(...r) {
3059
3062
  const n = r.filter(Boolean).join("&");
3060
3063
  return n && `?${n}`;
3061
3064
  }
3062
- function m(r, n = a) {
3063
- const o2 = f(n);
3064
- return Object.entries(r).filter(([, t2]) => t2 !== void 0).map(([t2, e]) => Array.isArray(e) ? e.map((i) => o2`${t2}=${i}`).join("&") : typeof e == "object" ? m(e, n) : o2`${t2}=${e}`).join("&");
3065
+ function y$1(r, n = a) {
3066
+ const o2 = d(n);
3067
+ return Object.entries(r).filter(([, t2]) => t2 !== void 0).map(([t2, e]) => Array.isArray(e) ? e.map((i) => o2`${t2}=${i}`).join("&") : typeof e == "object" ? y$1(e, n) : o2`${t2}=${e}`).join("&");
3065
3068
  }
3066
- const O = l$1();
3069
+ const A = R();
3067
3070
  function o(e, r) {
3068
3071
  const n = t(e);
3069
3072
  return t(r).forEach((i, s2) => {
@@ -3108,7 +3111,7 @@ function D(a2 = {}) {
3108
3111
  data: c ? JSON.parse(c) : null
3109
3112
  } : { status: t2, headers: s2, data: c };
3110
3113
  }
3111
- async function f2(e, n = {}) {
3114
+ async function f(e, n = {}) {
3112
3115
  const t2 = await p(e, n);
3113
3116
  let s2;
3114
3117
  try {
@@ -3133,7 +3136,7 @@ function D(a2 = {}) {
3133
3136
  ok: y,
3134
3137
  fetchText: r,
3135
3138
  fetchJson: i,
3136
- fetchBlob: f2,
3139
+ fetchBlob: f,
3137
3140
  mergeHeaders: o,
3138
3141
  json({ body: e, headers: n, ...t2 }) {
3139
3142
  return {
@@ -3150,7 +3153,7 @@ function D(a2 = {}) {
3150
3153
  form({ body: e, headers: n, ...t2 }) {
3151
3154
  return {
3152
3155
  ...t2,
3153
- ...e != null && { body: O(e) },
3156
+ ...e != null && { body: A(e) },
3154
3157
  headers: o(
3155
3158
  {
3156
3159
  "Content-Type": "application/x-www-form-urlencoded"
@@ -3169,7 +3172,7 @@ function D(a2 = {}) {
3169
3172
  );
3170
3173
  };
3171
3174
  return Object.entries(e).forEach(([c, o2]) => {
3172
- Array.isArray(o2) ? o2.forEach((m2) => u(c, m2)) : u(c, o2);
3175
+ Array.isArray(o2) ? o2.forEach((m) => u(c, m)) : u(c, o2);
3173
3176
  }), {
3174
3177
  ...t$1,
3175
3178
  body: s2,
@@ -3188,8 +3191,8 @@ class l extends Error {
3188
3191
  status;
3189
3192
  data;
3190
3193
  headers;
3191
- constructor(r, i, f2) {
3192
- super(`Error: ${r}`), this.status = r, this.data = i, this.headers = f2;
3194
+ constructor(r, i, f) {
3195
+ super(`Error: ${r}`), this.status = r, this.data = i, this.headers = f;
3193
3196
  }
3194
3197
  }
3195
3198
  const defaults = {
@@ -3198,7 +3201,7 @@ const defaults = {
3198
3201
  };
3199
3202
  const oazapfts = D(defaults);
3200
3203
  function getAllAlbums({ assetId, shared }, opts) {
3201
- return oazapfts.ok(oazapfts.fetchJson(`/albums${q(m({
3204
+ return oazapfts.ok(oazapfts.fetchJson(`/albums${q(y$1({
3202
3205
  assetId,
3203
3206
  shared
3204
3207
  }))}`, {
@@ -3213,7 +3216,7 @@ function createAlbum({ createAlbumDto }, opts) {
3213
3216
  })));
3214
3217
  }
3215
3218
  function addAssetsToAlbum({ id, key, slug, bulkIdsDto }, opts) {
3216
- return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets${q(m({
3219
+ return oazapfts.ok(oazapfts.fetchJson(`/albums/${encodeURIComponent(id)}/assets${q(y$1({
3217
3220
  key,
3218
3221
  slug
3219
3222
  }))}`, oazapfts.json({
@@ -3222,6 +3225,11 @@ function addAssetsToAlbum({ id, key, slug, bulkIdsDto }, opts) {
3222
3225
  body: bulkIdsDto
3223
3226
  })));
3224
3227
  }
3228
+ function getMyApiKey(opts) {
3229
+ return oazapfts.ok(oazapfts.fetchJson("/api-keys/me", {
3230
+ ...opts
3231
+ }));
3232
+ }
3225
3233
  function checkBulkUpload({ assetBulkUploadCheckDto }, opts) {
3226
3234
  return oazapfts.ok(oazapfts.fetchJson("/assets/bulk-upload-check", oazapfts.json({
3227
3235
  ...opts,
@@ -3230,7 +3238,7 @@ function checkBulkUpload({ assetBulkUploadCheckDto }, opts) {
3230
3238
  })));
3231
3239
  }
3232
3240
  function getAssetStatistics({ isFavorite, isTrashed, visibility }, opts) {
3233
- return oazapfts.ok(oazapfts.fetchJson(`/assets/statistics${q(m({
3241
+ return oazapfts.ok(oazapfts.fetchJson(`/assets/statistics${q(y$1({
3234
3242
  isFavorite,
3235
3243
  isTrashed,
3236
3244
  visibility
@@ -3718,6 +3726,8 @@ var SyncEntityType;
3718
3726
  SyncEntityType2["AssetV1"] = "AssetV1";
3719
3727
  SyncEntityType2["AssetDeleteV1"] = "AssetDeleteV1";
3720
3728
  SyncEntityType2["AssetExifV1"] = "AssetExifV1";
3729
+ SyncEntityType2["AssetEditV1"] = "AssetEditV1";
3730
+ SyncEntityType2["AssetEditDeleteV1"] = "AssetEditDeleteV1";
3721
3731
  SyncEntityType2["AssetMetadataV1"] = "AssetMetadataV1";
3722
3732
  SyncEntityType2["AssetMetadataDeleteV1"] = "AssetMetadataDeleteV1";
3723
3733
  SyncEntityType2["PartnerV1"] = "PartnerV1";
@@ -3753,6 +3763,7 @@ var SyncEntityType;
3753
3763
  SyncEntityType2["PersonV1"] = "PersonV1";
3754
3764
  SyncEntityType2["PersonDeleteV1"] = "PersonDeleteV1";
3755
3765
  SyncEntityType2["AssetFaceV1"] = "AssetFaceV1";
3766
+ SyncEntityType2["AssetFaceV2"] = "AssetFaceV2";
3756
3767
  SyncEntityType2["AssetFaceDeleteV1"] = "AssetFaceDeleteV1";
3757
3768
  SyncEntityType2["UserMetadataV1"] = "UserMetadataV1";
3758
3769
  SyncEntityType2["UserMetadataDeleteV1"] = "UserMetadataDeleteV1";
@@ -3769,6 +3780,7 @@ var SyncRequestType;
3769
3780
  SyncRequestType2["AlbumAssetExifsV1"] = "AlbumAssetExifsV1";
3770
3781
  SyncRequestType2["AssetsV1"] = "AssetsV1";
3771
3782
  SyncRequestType2["AssetExifsV1"] = "AssetExifsV1";
3783
+ SyncRequestType2["AssetEditsV1"] = "AssetEditsV1";
3772
3784
  SyncRequestType2["AssetMetadataV1"] = "AssetMetadataV1";
3773
3785
  SyncRequestType2["AuthUsersV1"] = "AuthUsersV1";
3774
3786
  SyncRequestType2["MemoriesV1"] = "MemoriesV1";
@@ -3781,6 +3793,7 @@ var SyncRequestType;
3781
3793
  SyncRequestType2["UsersV1"] = "UsersV1";
3782
3794
  SyncRequestType2["PeopleV1"] = "PeopleV1";
3783
3795
  SyncRequestType2["AssetFacesV1"] = "AssetFacesV1";
3796
+ SyncRequestType2["AssetFacesV2"] = "AssetFacesV2";
3784
3797
  SyncRequestType2["UserMetadataV1"] = "UserMetadataV1";
3785
3798
  })(SyncRequestType || (SyncRequestType = {}));
3786
3799
  var TranscodeHWAccel;
@@ -3796,6 +3809,7 @@ var AudioCodec;
3796
3809
  AudioCodec2["Mp3"] = "mp3";
3797
3810
  AudioCodec2["Aac"] = "aac";
3798
3811
  AudioCodec2["Libopus"] = "libopus";
3812
+ AudioCodec2["Opus"] = "opus";
3799
3813
  AudioCodec2["PcmS16Le"] = "pcm_s16le";
3800
3814
  })(AudioCodec || (AudioCodec = {}));
3801
3815
  var VideoContainer;
@@ -4033,7 +4047,7 @@ const normalizeFilter = (filter) => {
4033
4047
  }
4034
4048
  if (Array.isArray(filter)) {
4035
4049
  const trItems = filter.map((item) => item.trim());
4036
- return (entry2) => trItems.some((f2) => entry2.basename === f2);
4050
+ return (entry2) => trItems.some((f) => entry2.basename === f);
4037
4051
  }
4038
4052
  return emptyFn;
4039
4053
  };
@@ -8544,10 +8558,10 @@ function requireParse() {
8544
8558
  };
8545
8559
  if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
8546
8560
  let backslashes = false;
8547
- let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m2, esc, chars, first, rest, index) => {
8561
+ let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
8548
8562
  if (first === "\\") {
8549
8563
  backslashes = true;
8550
- return m2;
8564
+ return m;
8551
8565
  }
8552
8566
  if (first === "?") {
8553
8567
  if (esc) {
@@ -8567,14 +8581,14 @@ function requireParse() {
8567
8581
  }
8568
8582
  return star;
8569
8583
  }
8570
- return esc ? m2 : `\\${m2}`;
8584
+ return esc ? m : `\\${m}`;
8571
8585
  });
8572
8586
  if (backslashes === true) {
8573
8587
  if (opts.unescape === true) {
8574
8588
  output = output.replace(/\\/g, "");
8575
8589
  } else {
8576
- output = output.replace(/\\+/g, (m2) => {
8577
- return m2.length % 2 === 0 ? "\\\\" : m2 ? "\\" : "";
8590
+ output = output.replace(/\\+/g, (m) => {
8591
+ return m.length % 2 === 0 ? "\\\\" : m ? "\\" : "";
8578
8592
  });
8579
8593
  }
8580
8594
  }
@@ -14647,8 +14661,8 @@ function requireStringifyNumber() {
14647
14661
  i = n.length;
14648
14662
  n += ".";
14649
14663
  }
14650
- let d = minFractionDigits - (n.length - i - 1);
14651
- while (d-- > 0)
14664
+ let d2 = minFractionDigits - (n.length - i - 1);
14665
+ while (d2-- > 0)
14652
14666
  n += "0";
14653
14667
  }
14654
14668
  return n;
@@ -15132,9 +15146,9 @@ function requireFloat() {
15132
15146
  const node = new Scalar2.Scalar(parseFloat(str.replace(/_/g, "")));
15133
15147
  const dot = str.indexOf(".");
15134
15148
  if (dot !== -1) {
15135
- const f2 = str.substring(dot + 1).replace(/_/g, "");
15136
- if (f2[f2.length - 1] === "0")
15137
- node.minFractionDigits = f2.length;
15149
+ const f = str.substring(dot + 1).replace(/_/g, "");
15150
+ if (f[f.length - 1] === "0")
15151
+ node.minFractionDigits = f.length;
15138
15152
  }
15139
15153
  return node;
15140
15154
  },
@@ -15387,10 +15401,10 @@ function requireTimestamp() {
15387
15401
  let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec);
15388
15402
  const tz = match[8];
15389
15403
  if (tz && tz !== "Z") {
15390
- let d = parseSexagesimal(tz, false);
15391
- if (Math.abs(d) < 30)
15392
- d *= 60;
15393
- date -= 6e4 * d;
15404
+ let d2 = parseSexagesimal(tz, false);
15405
+ if (Math.abs(d2) < 30)
15406
+ d2 *= 60;
15407
+ date -= 6e4 * d2;
15394
15408
  }
15395
15409
  return new Date(date);
15396
15410
  },
@@ -16868,8 +16882,8 @@ function requireResolveBlockScalar() {
16868
16882
  function splitLines(source) {
16869
16883
  const split = source.split(/\n( *)/);
16870
16884
  const first = split[0];
16871
- const m2 = first.match(/^( *)/);
16872
- const line0 = m2?.[1] ? [m2[1], first.slice(m2[1].length)] : ["", first];
16885
+ const m = first.match(/^( *)/);
16886
+ const line0 = m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first];
16873
16887
  const lines = [line0];
16874
16888
  for (let i = 1; i < split.length; i += 2)
16875
16889
  lines.push([split[i], split[i + 1]]);
@@ -19603,6 +19617,30 @@ const authenticate = async (options2) => {
19603
19617
  }
19604
19618
  return auth;
19605
19619
  };
19620
+ const s = (count) => count === 1 ? "" : "s";
19621
+ let _apiKey;
19622
+ const requirePermissions = async (permissions) => {
19623
+ if (!_apiKey) {
19624
+ _apiKey = await getMyApiKey();
19625
+ }
19626
+ if (_apiKey.permissions.includes(Permission.All)) {
19627
+ return;
19628
+ }
19629
+ const missing = [];
19630
+ for (const permission of permissions) {
19631
+ if (!_apiKey.permissions.includes(permission)) {
19632
+ missing.push(permission);
19633
+ }
19634
+ }
19635
+ if (missing.length > 0) {
19636
+ const combined = missing.map((permission) => `"${permission}"`).join(", ");
19637
+ console.log(
19638
+ `Missing required permission${s(missing.length)}: ${combined}.
19639
+ Please make sure your API key has the correct permissions.`
19640
+ );
19641
+ process.exit(1);
19642
+ }
19643
+ };
19606
19644
  const connect = async (url, key) => {
19607
19645
  const wellKnownUrl = new URL(".well-known/immich", url);
19608
19646
  try {
@@ -19617,7 +19655,7 @@ const connect = async (url, key) => {
19617
19655
  init({ baseUrl: url, apiKey: key });
19618
19656
  const [error2] = await withError(getMyUser());
19619
19657
  if (isHttpError(error2)) {
19620
- logError(error2, "Failed to connect to server");
19658
+ logError(error2, `Failed to connect to server ${url}`);
19621
19659
  process.exit(1);
19622
19660
  }
19623
19661
  return { url, key };
@@ -19760,7 +19798,6 @@ class Batcher {
19760
19798
  }
19761
19799
  const UPLOAD_WATCH_BATCH_SIZE = 100;
19762
19800
  const UPLOAD_WATCH_DEBOUNCE_TIME_MS = 1e4;
19763
- const s = (count) => count === 1 ? "" : "s";
19764
19801
  class UploadFile extends File {
19765
19802
  constructor(filepath, _size) {
19766
19803
  super([], basename(filepath));
@@ -19830,6 +19867,7 @@ const startWatch = async (paths, options2, {
19830
19867
  };
19831
19868
  const upload = async (paths, baseOptions, options2) => {
19832
19869
  await authenticate(baseOptions);
19870
+ await requirePermissions([Permission.AssetUpload]);
19833
19871
  const scanFiles = await scan(paths, options2);
19834
19872
  if (scanFiles.length === 0) {
19835
19873
  if (options2.watch) {
@@ -19863,16 +19901,43 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
19863
19901
  return { newFiles: files, duplicates: [] };
19864
19902
  }
19865
19903
  let multiBar2;
19904
+ let totalSize = 0;
19905
+ const statsMap = /* @__PURE__ */ new Map();
19906
+ for (const filepath of files) {
19907
+ const stats = await stat(filepath);
19908
+ statsMap.set(filepath, stats);
19909
+ totalSize += stats.size;
19910
+ }
19866
19911
  if (progress) {
19867
19912
  multiBar2 = new cliProgressExports.MultiBar(
19868
- { format: "{message} | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets" },
19913
+ {
19914
+ format: "{message} | {bar} | {percentage}% | ETA: {eta_formatted} | {value}/{total}",
19915
+ formatValue: (v, options2, type2) => {
19916
+ if (type2 === "percentage") {
19917
+ return v.toString();
19918
+ }
19919
+ return byteSize(v).toString();
19920
+ },
19921
+ etaBuffer: 100
19922
+ // Increase samples for ETA calculation
19923
+ },
19869
19924
  cliProgressExports.Presets.shades_classic
19870
19925
  );
19926
+ process.on("SIGINT", () => {
19927
+ if (multiBar2) {
19928
+ multiBar2.stop();
19929
+ }
19930
+ process.exit(0);
19931
+ });
19871
19932
  } else {
19872
- console.log(`Received ${files.length} files, hashing...`);
19933
+ console.log(`Received ${files.length} files (${byteSize(totalSize)}), hashing...`);
19873
19934
  }
19874
- const hashProgressBar = multiBar2?.create(files.length, 0, { message: "Hashing files " });
19875
- const checkProgressBar = multiBar2?.create(files.length, 0, { message: "Checking for duplicates" });
19935
+ const hashProgressBar = multiBar2?.create(totalSize, 0, {
19936
+ message: "Hashing files "
19937
+ });
19938
+ const checkProgressBar = multiBar2?.create(totalSize, 0, {
19939
+ message: "Checking for duplicates"
19940
+ });
19876
19941
  const newFiles = [];
19877
19942
  const duplicates = [];
19878
19943
  const checkBulkUploadQueue = new Queue(
@@ -19886,7 +19951,12 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
19886
19951
  duplicates.push({ id: assetId, filepath });
19887
19952
  }
19888
19953
  }
19889
- checkProgressBar?.increment(assets.length);
19954
+ let processedSize = 0;
19955
+ for (const asset of assets) {
19956
+ const stats = statsMap.get(asset.id);
19957
+ processedSize += stats?.size || 0;
19958
+ }
19959
+ checkProgressBar?.increment(processedSize);
19890
19960
  },
19891
19961
  { concurrency, retry: 3 }
19892
19962
  );
@@ -19894,6 +19964,10 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
19894
19964
  let checkBulkUploadRequests = [];
19895
19965
  const queue2 = new Queue(
19896
19966
  async (filepath) => {
19967
+ const stats = statsMap.get(filepath);
19968
+ if (!stats) {
19969
+ throw new Error(`Stats not found for ${filepath}`);
19970
+ }
19897
19971
  const dto = { id: filepath, checksum: await sha1(filepath) };
19898
19972
  results.push(dto);
19899
19973
  checkBulkUploadRequests.push(dto);
@@ -19902,7 +19976,7 @@ const checkForDuplicates = async (files, { concurrency, skipHash, progress }) =>
19902
19976
  checkBulkUploadRequests = [];
19903
19977
  void checkBulkUploadQueue.push(batch);
19904
19978
  }
19905
- hashProgressBar?.increment();
19979
+ hashProgressBar?.increment(stats.size);
19906
19980
  return results;
19907
19981
  },
19908
19982
  { concurrency, retry: 3 }
@@ -20000,20 +20074,6 @@ const uploadFiles = async (files, { dryRun, concurrency, progress }) => {
20000
20074
  };
20001
20075
  const uploadFile = async (input, stats) => {
20002
20076
  const { baseUrl, headers } = defaults;
20003
- const assetPath = path$1.parse(input);
20004
- const noExtension = path$1.join(assetPath.dir, assetPath.name);
20005
- const sidecarsFiles = await Promise.all(
20006
- // XMP sidecars can come in two filename formats. For a photo named photo.ext, the filenames are photo.ext.xmp and photo.xmp
20007
- [`${noExtension}.xmp`, `${input}.xmp`].map(async (sidecarPath) => {
20008
- try {
20009
- const stats2 = await stat(sidecarPath);
20010
- return new UploadFile(sidecarPath, stats2.size);
20011
- } catch {
20012
- return false;
20013
- }
20014
- })
20015
- );
20016
- const sidecarData = sidecarsFiles.find((file) => file !== false);
20017
20077
  const formData = new FormData();
20018
20078
  formData.append("deviceAssetId", `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, ""));
20019
20079
  formData.append("deviceId", "CLI");
@@ -20022,8 +20082,14 @@ const uploadFile = async (input, stats) => {
20022
20082
  formData.append("fileSize", String(stats.size));
20023
20083
  formData.append("isFavorite", "false");
20024
20084
  formData.append("assetData", new UploadFile(input, stats.size));
20025
- if (sidecarData) {
20026
- formData.append("sidecarData", sidecarData);
20085
+ const sidecarPath = findSidecar(input);
20086
+ if (sidecarPath) {
20087
+ try {
20088
+ const stats2 = await stat(sidecarPath);
20089
+ const sidecarData = new UploadFile(sidecarPath, stats2.size);
20090
+ formData.append("sidecarData", sidecarData);
20091
+ } catch {
20092
+ }
20027
20093
  }
20028
20094
  const response = await fetch(`${baseUrl}/assets`, {
20029
20095
  method: "post",
@@ -20036,6 +20102,15 @@ const uploadFile = async (input, stats) => {
20036
20102
  }
20037
20103
  return response.json();
20038
20104
  };
20105
+ const findSidecar = (filepath) => {
20106
+ const assetPath = path$1.parse(filepath);
20107
+ const noExtension = path$1.join(assetPath.dir, assetPath.name);
20108
+ for (const sidecarPath of [`${noExtension}.xmp`, `${filepath}.xmp`]) {
20109
+ if (existsSync(sidecarPath)) {
20110
+ return sidecarPath;
20111
+ }
20112
+ }
20113
+ };
20039
20114
  const deleteFiles = async (uploaded, duplicates, options2) => {
20040
20115
  let fileCount = 0;
20041
20116
  if (options2.delete) {
@@ -20059,7 +20134,15 @@ const deleteFiles = async (uploaded, duplicates, options2) => {
20059
20134
  deletionProgress.start(fileCount, 0);
20060
20135
  const chunkDelete = async (files) => {
20061
20136
  for (const assetBatch of chunk(files, options2.concurrency)) {
20062
- await Promise.all(assetBatch.map((input) => unlink(input.filepath)));
20137
+ await Promise.all(
20138
+ assetBatch.map(async (input) => {
20139
+ await unlink(input.filepath);
20140
+ const sidecarPath = findSidecar(input.filepath);
20141
+ if (sidecarPath) {
20142
+ await unlink(sidecarPath);
20143
+ }
20144
+ })
20145
+ );
20063
20146
  deletionProgress.update(assetBatch.length);
20064
20147
  }
20065
20148
  };
@@ -20150,6 +20233,7 @@ const login = async (url, key, options2) => {
20150
20233
  console.log(`Logging in to ${url}`);
20151
20234
  const { configDirectory: configDir } = options2;
20152
20235
  await connect(url, key);
20236
+ await requirePermissions([Permission.UserRead]);
20153
20237
  const [error2, user] = await withError(getMyUser());
20154
20238
  if (error2) {
20155
20239
  logError(error2, "Failed to load user info");
@@ -20178,6 +20262,7 @@ const logout = async (options2) => {
20178
20262
  };
20179
20263
  const serverInfo = async (options2) => {
20180
20264
  const { url } = await authenticate(options2);
20265
+ await requirePermissions([Permission.ServerAbout, Permission.AssetStatistics, Permission.UserRead]);
20181
20266
  const [versionInfo, mediaTypes, stats, userInfo] = await Promise.all([
20182
20267
  getServerVersion(),
20183
20268
  getSupportedMediaTypes(),
@@ -20195,7 +20280,7 @@ const serverInfo = async (options2) => {
20195
20280
  console.log(` Videos: ${stats.videos}`);
20196
20281
  console.log(` Total: ${stats.total}`);
20197
20282
  };
20198
- const version = "2.5.6";
20283
+ const version = "2.6.0";
20199
20284
  const defaultConfigDirectory = path$1.join(os.homedir(), ".config/immich/");
20200
20285
  const defaultConcurrency = Math.max(1, os.cpus().length - 1);
20201
20286
  const program = new Command().name("immich").version(version).description("Command line interface for Immich").addOption(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/cli",
3
- "version": "2.5.6",
3
+ "version": "2.6.0",
4
4
  "description": "Command Line Interface (CLI) for Immich",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",
@@ -13,23 +13,22 @@
13
13
  "cli"
14
14
  ],
15
15
  "devDependencies": {
16
- "@eslint/js": "^9.8.0",
17
- "@immich/sdk": "file:../open-api/typescript-sdk",
16
+ "@eslint/js": "^10.0.0",
18
17
  "@types/byte-size": "^8.1.0",
19
18
  "@types/cli-progress": "^3.11.0",
20
19
  "@types/lodash-es": "^4.17.12",
21
20
  "@types/micromatch": "^4.0.9",
22
21
  "@types/mock-fs": "^4.13.1",
23
- "@types/node": "^24.10.11",
24
- "@vitest/coverage-v8": "^3.0.0",
22
+ "@types/node": "^24.11.0",
23
+ "@vitest/coverage-v8": "^4.0.0",
25
24
  "byte-size": "^9.0.0",
26
25
  "cli-progress": "^3.12.0",
27
26
  "commander": "^12.0.0",
28
- "eslint": "^9.14.0",
27
+ "eslint": "^10.0.0",
29
28
  "eslint-config-prettier": "^10.1.8",
30
29
  "eslint-plugin-prettier": "^5.1.3",
31
- "eslint-plugin-unicorn": "^62.0.0",
32
- "globals": "^16.0.0",
30
+ "eslint-plugin-unicorn": "^63.0.0",
31
+ "globals": "^17.0.0",
33
32
  "mock-fs": "^5.2.0",
34
33
  "prettier": "^3.7.4",
35
34
  "prettier-plugin-organize-imports": "^4.0.0",
@@ -37,9 +36,10 @@
37
36
  "typescript-eslint": "^8.28.0",
38
37
  "vite": "^7.0.0",
39
38
  "vite-tsconfig-paths": "^6.0.0",
40
- "vitest": "^3.0.0",
39
+ "vitest": "^4.0.0",
41
40
  "vitest-fetch-mock": "^0.4.0",
42
- "yaml": "^2.3.1"
41
+ "yaml": "^2.3.1",
42
+ "@immich/sdk": "2.6.0"
43
43
  },
44
44
  "repository": {
45
45
  "type": "git",
@@ -57,17 +57,17 @@
57
57
  "micromatch": "^4.0.8"
58
58
  },
59
59
  "volta": {
60
- "node": "24.13.0"
60
+ "node": "24.13.1"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "vite build",
64
64
  "build:dev": "vite build --sourcemap true",
65
65
  "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
66
- "lint:fix": "npm run lint -- --fix",
66
+ "lint:fix": "pnpm run lint --fix",
67
67
  "test": "vitest",
68
68
  "test:cov": "vitest --coverage",
69
- "format": "prettier --check .",
70
- "format:fix": "prettier --write .",
69
+ "format": "prettier --cache --check .",
70
+ "format:fix": "prettier --cache --write --list-different .",
71
71
  "check": "tsc --noEmit"
72
72
  }
73
73
  }