@immich/cli 2.2.79 → 2.2.98

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 (4) hide show
  1. package/.nvmrc +1 -1
  2. package/README.md +19 -11
  3. package/dist/index.js +70 -17
  4. package/package.json +15 -16
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- 22.18.0
1
+ 24.11.0
package/README.md CHANGED
@@ -1,30 +1,38 @@
1
1
  A command-line interface for interfacing with the self-hosted photo manager [Immich](https://immich.app/).
2
2
 
3
- Please see the [Immich CLI documentation](https://immich.app/docs/features/command-line-interface).
3
+ Please see the [Immich CLI documentation](https://docs.immich.app/features/command-line-interface).
4
4
 
5
5
  # For developers
6
6
 
7
7
  Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder:
8
8
 
9
- $ npm install
10
- $ npm run build
9
+ $ pnpm install
10
+ $ pnpm run build
11
11
 
12
12
  Then, to build the open-api client run the following in the open-api folder:
13
13
 
14
14
  $ ./bin/generate-open-api.sh
15
15
 
16
- To run the Immich CLI from source, run the following in the cli folder:
16
+ ## Run from build
17
17
 
18
- $ npm install
19
- $ npm run build
20
- $ ts-node .
18
+ Go to the cli folder and build it:
21
19
 
22
- You'll need ts-node, the easiest way to install it is to use npm:
20
+ $ pnpm install
21
+ $ pnpm run build
22
+ $ node dist/index.js
23
23
 
24
- $ npm i -g ts-node
24
+ ## Run and Debug from source (VSCode)
25
+
26
+ With VScode you can run and debug the Immich CLI. Go to the launch.json file, find the Immich CLI config and change this with the command you need to debug
27
+
28
+ `"args": ["upload", "--help"],`
29
+
30
+ replace that for the command of your choice.
31
+
32
+ ## Install from build
25
33
 
26
34
  You can also build and install the CLI using
27
35
 
28
- $ npm run build
29
- $ npm install -g .
36
+ $ pnpm run build
37
+ $ pnpm install -g .
30
38
  ****
package/dist/index.js CHANGED
@@ -3289,6 +3289,8 @@ var NotificationType;
3289
3289
  NotificationType2["JobFailed"] = "JobFailed";
3290
3290
  NotificationType2["BackupFailed"] = "BackupFailed";
3291
3291
  NotificationType2["SystemMessage"] = "SystemMessage";
3292
+ NotificationType2["AlbumInvite"] = "AlbumInvite";
3293
+ NotificationType2["AlbumUpdate"] = "AlbumUpdate";
3292
3294
  NotificationType2["Custom"] = "Custom";
3293
3295
  })(NotificationType || (NotificationType = {}));
3294
3296
  var UserStatus;
@@ -3327,6 +3329,13 @@ var AssetTypeEnum;
3327
3329
  AssetTypeEnum2["Audio"] = "AUDIO";
3328
3330
  AssetTypeEnum2["Other"] = "OTHER";
3329
3331
  })(AssetTypeEnum || (AssetTypeEnum = {}));
3332
+ var BulkIdErrorReason;
3333
+ (function(BulkIdErrorReason2) {
3334
+ BulkIdErrorReason2["Duplicate"] = "duplicate";
3335
+ BulkIdErrorReason2["NoPermission"] = "no_permission";
3336
+ BulkIdErrorReason2["NotFound"] = "not_found";
3337
+ BulkIdErrorReason2["Unknown"] = "unknown";
3338
+ })(BulkIdErrorReason || (BulkIdErrorReason = {}));
3330
3339
  var Error$1;
3331
3340
  (function(Error3) {
3332
3341
  Error3["Duplicate"] = "duplicate";
@@ -3355,6 +3364,7 @@ var Permission;
3355
3364
  Permission2["AssetDownload"] = "asset.download";
3356
3365
  Permission2["AssetUpload"] = "asset.upload";
3357
3366
  Permission2["AssetReplace"] = "asset.replace";
3367
+ Permission2["AssetCopy"] = "asset.copy";
3358
3368
  Permission2["AlbumCreate"] = "album.create";
3359
3369
  Permission2["AlbumRead"] = "album.read";
3360
3370
  Permission2["AlbumUpdate"] = "album.update";
@@ -3463,8 +3473,13 @@ var Permission;
3463
3473
  Permission2["AdminUserRead"] = "adminUser.read";
3464
3474
  Permission2["AdminUserUpdate"] = "adminUser.update";
3465
3475
  Permission2["AdminUserDelete"] = "adminUser.delete";
3476
+ Permission2["AdminSessionRead"] = "adminSession.read";
3466
3477
  Permission2["AdminAuthUnlinkAll"] = "adminAuth.unlinkAll";
3467
3478
  })(Permission || (Permission = {}));
3479
+ var AssetMetadataKey;
3480
+ (function(AssetMetadataKey2) {
3481
+ AssetMetadataKey2["MobileApp"] = "mobile-app";
3482
+ })(AssetMetadataKey || (AssetMetadataKey = {}));
3468
3483
  var AssetMediaStatus;
3469
3484
  (function(AssetMediaStatus2) {
3470
3485
  AssetMediaStatus2["Created"] = "created";
@@ -3520,6 +3535,7 @@ var JobName;
3520
3535
  JobName2["Library"] = "library";
3521
3536
  JobName2["Notifications"] = "notifications";
3522
3537
  JobName2["BackupDatabase"] = "backupDatabase";
3538
+ JobName2["Ocr"] = "ocr";
3523
3539
  })(JobName || (JobName = {}));
3524
3540
  var JobCommand;
3525
3541
  (function(JobCommand2) {
@@ -3545,6 +3561,7 @@ var SearchSuggestionType;
3545
3561
  SearchSuggestionType2["City"] = "city";
3546
3562
  SearchSuggestionType2["CameraMake"] = "camera-make";
3547
3563
  SearchSuggestionType2["CameraModel"] = "camera-model";
3564
+ SearchSuggestionType2["CameraLensModel"] = "camera-lens-model";
3548
3565
  })(SearchSuggestionType || (SearchSuggestionType = {}));
3549
3566
  var SharedLinkType;
3550
3567
  (function(SharedLinkType2) {
@@ -3565,6 +3582,8 @@ var SyncEntityType;
3565
3582
  SyncEntityType2["AssetV1"] = "AssetV1";
3566
3583
  SyncEntityType2["AssetDeleteV1"] = "AssetDeleteV1";
3567
3584
  SyncEntityType2["AssetExifV1"] = "AssetExifV1";
3585
+ SyncEntityType2["AssetMetadataV1"] = "AssetMetadataV1";
3586
+ SyncEntityType2["AssetMetadataDeleteV1"] = "AssetMetadataDeleteV1";
3568
3587
  SyncEntityType2["PartnerV1"] = "PartnerV1";
3569
3588
  SyncEntityType2["PartnerDeleteV1"] = "PartnerDeleteV1";
3570
3589
  SyncEntityType2["PartnerAssetV1"] = "PartnerAssetV1";
@@ -3603,6 +3622,7 @@ var SyncEntityType;
3603
3622
  SyncEntityType2["UserMetadataDeleteV1"] = "UserMetadataDeleteV1";
3604
3623
  SyncEntityType2["SyncAckV1"] = "SyncAckV1";
3605
3624
  SyncEntityType2["SyncResetV1"] = "SyncResetV1";
3625
+ SyncEntityType2["SyncCompleteV1"] = "SyncCompleteV1";
3606
3626
  })(SyncEntityType || (SyncEntityType = {}));
3607
3627
  var SyncRequestType;
3608
3628
  (function(SyncRequestType2) {
@@ -3613,6 +3633,7 @@ var SyncRequestType;
3613
3633
  SyncRequestType2["AlbumAssetExifsV1"] = "AlbumAssetExifsV1";
3614
3634
  SyncRequestType2["AssetsV1"] = "AssetsV1";
3615
3635
  SyncRequestType2["AssetExifsV1"] = "AssetExifsV1";
3636
+ SyncRequestType2["AssetMetadataV1"] = "AssetMetadataV1";
3616
3637
  SyncRequestType2["AuthUsersV1"] = "AuthUsersV1";
3617
3638
  SyncRequestType2["MemoriesV1"] = "MemoriesV1";
3618
3639
  SyncRequestType2["MemoryToAssetsV1"] = "MemoryToAssetsV1";
@@ -3703,9 +3724,12 @@ var OAuthTokenEndpointAuthMethod;
3703
3724
  function isHttpError(error2) {
3704
3725
  return error2 instanceof l;
3705
3726
  }
3706
- const init = ({ baseUrl, apiKey }) => {
3727
+ const init = ({ baseUrl, apiKey, headers }) => {
3707
3728
  setBaseUrl(baseUrl);
3708
3729
  setApiKey(apiKey);
3730
+ if (headers) {
3731
+ setHeaders(headers);
3732
+ }
3709
3733
  };
3710
3734
  const setBaseUrl = (baseUrl) => {
3711
3735
  defaults.baseUrl = baseUrl;
@@ -3714,6 +3738,18 @@ const setApiKey = (apiKey) => {
3714
3738
  defaults.headers = defaults.headers || {};
3715
3739
  defaults.headers["x-api-key"] = apiKey;
3716
3740
  };
3741
+ const setHeaders = (headers) => {
3742
+ defaults.headers = defaults.headers || {};
3743
+ for (const [key, value] of Object.entries(headers)) {
3744
+ assertNoApiKey(key);
3745
+ defaults.headers[key] = value;
3746
+ }
3747
+ };
3748
+ const assertNoApiKey = (headerKey) => {
3749
+ if (headerKey.toLowerCase() === "x-api-key") {
3750
+ throw new Error("The API key header can only be set using setApiKey().");
3751
+ }
3752
+ };
3717
3753
  let defaultOptions$1 = {};
3718
3754
  const _options = /* @__PURE__ */ new WeakMap();
3719
3755
  const referenceTables = {
@@ -6450,7 +6486,7 @@ function toFinite(value) {
6450
6486
  return value === 0 ? value : 0;
6451
6487
  }
6452
6488
  value = toNumber(value);
6453
- if (value === INFINITY || value === -Infinity) {
6489
+ if (value === INFINITY || value === -INFINITY) {
6454
6490
  var sign = value < 0 ? -1 : 1;
6455
6491
  return sign * MAX_INTEGER;
6456
6492
  }
@@ -13374,7 +13410,7 @@ ${indent}`) + "'";
13374
13410
  }
13375
13411
  function blockString({ comment, type: type2, value }, ctx, onComment, onChompKeep) {
13376
13412
  const { blockQuote, commentString, lineWidth } = ctx.options;
13377
- if (!blockQuote || /\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
13413
+ if (!blockQuote || /\n[\t ]+$/.test(value)) {
13378
13414
  return quotedString(value, ctx);
13379
13415
  }
13380
13416
  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : "");
@@ -19613,10 +19649,7 @@ const uploadBatch = async (files, options2) => {
19613
19649
  console.log(JSON.stringify({ newFiles, duplicates, newAssets }, void 0, 4));
19614
19650
  }
19615
19651
  await updateAlbums([...newAssets, ...duplicates], options2);
19616
- await deleteFiles(
19617
- newAssets.map(({ filepath }) => filepath),
19618
- options2
19619
- );
19652
+ await deleteFiles(newAssets, duplicates, options2);
19620
19653
  };
19621
19654
  const startWatch = async (paths, options2, {
19622
19655
  batchSize = UPLOAD_WATCH_BATCH_SIZE,
@@ -19871,12 +19904,19 @@ const uploadFile = async (input, stats) => {
19871
19904
  }
19872
19905
  return response.json();
19873
19906
  };
19874
- const deleteFiles = async (files, options2) => {
19875
- if (!options2.delete) {
19876
- return;
19907
+ const deleteFiles = async (uploaded, duplicates, options2) => {
19908
+ let fileCount = 0;
19909
+ if (options2.delete) {
19910
+ fileCount += uploaded.length;
19911
+ }
19912
+ if (options2.deleteDuplicates) {
19913
+ fileCount += duplicates.length;
19877
19914
  }
19878
19915
  if (options2.dryRun) {
19879
- console.log(`Would have deleted ${files.length} local asset${s(files.length)}`);
19916
+ console.log(`Would have deleted ${fileCount} local asset${s(fileCount)}`);
19917
+ return;
19918
+ }
19919
+ if (fileCount === 0) {
19880
19920
  return;
19881
19921
  }
19882
19922
  console.log("Deleting assets that have been uploaded...");
@@ -19884,12 +19924,20 @@ const deleteFiles = async (files, options2) => {
19884
19924
  { format: "Deleting local assets | {bar} | {percentage}% | ETA: {eta}s | {value}/{total} assets" },
19885
19925
  cliProgressExports.Presets.shades_classic
19886
19926
  );
19887
- deletionProgress.start(files.length, 0);
19888
- try {
19927
+ deletionProgress.start(fileCount, 0);
19928
+ const chunkDelete = async (files) => {
19889
19929
  for (const assetBatch of chunk(files, options2.concurrency)) {
19890
- await Promise.all(assetBatch.map((input) => unlink(input)));
19930
+ await Promise.all(assetBatch.map((input) => unlink(input.filepath)));
19891
19931
  deletionProgress.update(assetBatch.length);
19892
19932
  }
19933
+ };
19934
+ try {
19935
+ if (options2.delete) {
19936
+ await chunkDelete(uploaded);
19937
+ }
19938
+ if (options2.deleteDuplicates) {
19939
+ await chunkDelete(duplicates);
19940
+ }
19893
19941
  } finally {
19894
19942
  deletionProgress.stop();
19895
19943
  }
@@ -20015,8 +20063,9 @@ const serverInfo = async (options2) => {
20015
20063
  console.log(` Videos: ${stats.videos}`);
20016
20064
  console.log(` Total: ${stats.total}`);
20017
20065
  };
20018
- const version = "2.2.79";
20066
+ const version = "2.2.98";
20019
20067
  const defaultConfigDirectory = path$1.join(os.homedir(), ".config/immich/");
20068
+ const defaultConcurrency = Math.max(1, os.cpus().length - 1);
20020
20069
  const program = new Command().name("immich").version(version).description("Command line interface for Immich").addOption(
20021
20070
  new Option("-d, --config-directory <directory>", "Configuration directory where auth.yml will be stored").env("IMMICH_CONFIG_DIR").default(defaultConfigDirectory)
20022
20071
  ).addOption(new Option("-u, --url [url]", "Immich server URL").env("IMMICH_INSTANCE_URL")).addOption(new Option("-k, --key [key]", "Immich API key").env("IMMICH_API_KEY"));
@@ -20030,10 +20079,14 @@ program.command("upload").description("Upload assets").usage("[paths...] [option
20030
20079
  ).addOption(
20031
20080
  new Option("-n, --dry-run", "Don't perform any actions, just show what will be done").env("IMMICH_DRY_RUN").default(false).conflicts("skipHash")
20032
20081
  ).addOption(
20033
- new Option("-c, --concurrency <number>", "Number of assets to upload at the same time").env("IMMICH_UPLOAD_CONCURRENCY").default(4)
20082
+ new Option("-c, --concurrency <number>", "Number of assets to upload at the same time").env("IMMICH_UPLOAD_CONCURRENCY").default(defaultConcurrency)
20034
20083
  ).addOption(
20035
20084
  new Option("-j, --json-output", "Output detailed information in json format").env("IMMICH_JSON_OUTPUT").default(false)
20036
- ).addOption(new Option("--delete", "Delete local assets after upload").env("IMMICH_DELETE_ASSETS")).addOption(new Option("--no-progress", "Hide progress bars").env("IMMICH_PROGRESS_BAR").default(true)).addOption(
20085
+ ).addOption(new Option("--delete", "Delete local assets after upload").env("IMMICH_DELETE_ASSETS")).addOption(
20086
+ new Option("--delete-duplicates", "Delete local assets that are duplicates (already exist on server)").env(
20087
+ "IMMICH_DELETE_DUPLICATES"
20088
+ )
20089
+ ).addOption(new Option("--no-progress", "Hide progress bars").env("IMMICH_PROGRESS_BAR").default(true)).addOption(
20037
20090
  new Option("--watch", "Watch for changes and upload automatically").env("IMMICH_WATCH_CHANGES").default(false).implies({ progress: false })
20038
20091
  ).argument("[paths...]", "One or more paths to assets to be uploaded").action((paths, options2) => upload(paths, program.opts(), options2));
20039
20092
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/cli",
3
- "version": "2.2.79",
3
+ "version": "2.2.98",
4
4
  "description": "Command Line Interface (CLI) for Immich",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",
@@ -13,7 +13,6 @@
13
13
  "cli"
14
14
  ],
15
15
  "devDependencies": {
16
- "@eslint/eslintrc": "^3.1.0",
17
16
  "@eslint/js": "^9.8.0",
18
17
  "@immich/sdk": "file:../open-api/typescript-sdk",
19
18
  "@types/byte-size": "^8.1.0",
@@ -21,7 +20,7 @@
21
20
  "@types/lodash-es": "^4.17.12",
22
21
  "@types/micromatch": "^4.0.9",
23
22
  "@types/mock-fs": "^4.13.1",
24
- "@types/node": "^22.17.0",
23
+ "@types/node": "^22.18.12",
25
24
  "@vitest/coverage-v8": "^3.0.0",
26
25
  "byte-size": "^9.0.0",
27
26
  "cli-progress": "^3.12.0",
@@ -42,17 +41,6 @@
42
41
  "vitest-fetch-mock": "^0.4.0",
43
42
  "yaml": "^2.3.1"
44
43
  },
45
- "scripts": {
46
- "build": "vite build",
47
- "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
48
- "lint:fix": "npm run lint -- --fix",
49
- "prepack": "npm run build",
50
- "test": "vitest",
51
- "test:cov": "vitest --coverage",
52
- "format": "prettier --check .",
53
- "format:fix": "prettier --write .",
54
- "check": "tsc --noEmit"
55
- },
56
44
  "repository": {
57
45
  "type": "git",
58
46
  "url": "git+https://github.com/immich-app/immich.git",
@@ -69,6 +57,17 @@
69
57
  "micromatch": "^4.0.8"
70
58
  },
71
59
  "volta": {
72
- "node": "22.18.0"
60
+ "node": "24.11.0"
61
+ },
62
+ "scripts": {
63
+ "build": "vite build",
64
+ "build:dev": "vite build --sourcemap true",
65
+ "lint": "eslint \"src/**/*.ts\" --max-warnings 0",
66
+ "lint:fix": "npm run lint -- --fix",
67
+ "test": "vitest",
68
+ "test:cov": "vitest --coverage",
69
+ "format": "prettier --check .",
70
+ "format:fix": "prettier --write .",
71
+ "check": "tsc --noEmit"
73
72
  }
74
- }
73
+ }