@karakeep/cli 0.27.1 → 0.29.1

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/dist/index.mjs +166 -17
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import * as require$$3 from "node:fs";
3
- import require$$3__default from "node:fs";
4
2
  import process$1, { stdout, stdin } from "node:process";
5
3
  import os from "node:os";
6
4
  import tty from "node:tty";
7
5
  import require$$0 from "node:events";
8
6
  import require$$1, { spawn } from "node:child_process";
9
7
  import path from "node:path";
8
+ import * as require$$3 from "node:fs";
9
+ import require$$3__default from "node:fs";
10
10
  import fsp from "node:fs/promises";
11
11
  import readline from "node:readline/promises";
12
12
  let globalOpts = void 0;
@@ -11216,6 +11216,70 @@ function requireSrc() {
11216
11216
  return src;
11217
11217
  }
11218
11218
  var srcExports = /* @__PURE__ */ requireSrc();
11219
+ const adminCmd = new Command().name("admin").description("admin commands");
11220
+ function toHumanReadableSize(size) {
11221
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
11222
+ if (size === 0) return "0 Bytes";
11223
+ const i = Math.floor(Math.log(size) / Math.log(1024));
11224
+ return (size / Math.pow(1024, i)).toFixed(2) + " " + sizes[i];
11225
+ }
11226
+ const usersCmd = new Command().name("users").description("user management commands");
11227
+ usersCmd.command("list").description("list all users").action(async () => {
11228
+ const api2 = getAPIClient();
11229
+ try {
11230
+ const [usersResp, userStats] = await Promise.all([
11231
+ api2.users.list.query(),
11232
+ api2.admin.userStats.query()
11233
+ ]);
11234
+ if (getGlobalOptions().json) {
11235
+ printObject({
11236
+ users: usersResp.users.map((u) => ({
11237
+ ...u,
11238
+ numBookmarks: userStats[u.id]?.numBookmarks ?? 0,
11239
+ assetSizes: userStats[u.id]?.assetSizes ?? 0
11240
+ }))
11241
+ });
11242
+ } else {
11243
+ const data = [
11244
+ [
11245
+ "Name",
11246
+ "Email",
11247
+ "Num Bookmarks",
11248
+ "Asset Sizes",
11249
+ "Role",
11250
+ "Local User"
11251
+ ]
11252
+ ];
11253
+ usersResp.users.forEach((user) => {
11254
+ const stats = userStats[user.id] ?? {
11255
+ numBookmarks: 0,
11256
+ assetSizes: 0
11257
+ };
11258
+ const numBookmarksDisplay = `${stats.numBookmarks} / ${user.bookmarkQuota?.toString() ?? "Unlimited"}`;
11259
+ const assetSizesDisplay = `${toHumanReadableSize(stats.assetSizes)} / ${user.storageQuota ? toHumanReadableSize(user.storageQuota) : "Unlimited"}`;
11260
+ data.push([
11261
+ user.name,
11262
+ user.email,
11263
+ numBookmarksDisplay,
11264
+ assetSizesDisplay,
11265
+ user.role ?? "",
11266
+ user.localUser ? "✓" : "✗"
11267
+ ]);
11268
+ });
11269
+ console.log(
11270
+ srcExports.table(data, {
11271
+ border: srcExports.getBorderCharacters("ramac"),
11272
+ drawHorizontalLine: (lineIndex, rowCount) => {
11273
+ return lineIndex === 0 || lineIndex === 1 || lineIndex === rowCount;
11274
+ }
11275
+ })
11276
+ );
11277
+ }
11278
+ } catch (error2) {
11279
+ printErrorMessageWithReason("Failed to list all users", error2);
11280
+ }
11281
+ });
11282
+ adminCmd.addCommand(usersCmd);
11219
11283
  function listsToTree(lists) {
11220
11284
  const idToList = lists.reduce((acc, list) => {
11221
11285
  acc[list.id] = list;
@@ -15408,6 +15472,7 @@ const zCursorV2 = z.object({
15408
15472
  function normalizeTagName(raw) {
15409
15473
  return raw.trim().replace(/^#+/, "");
15410
15474
  }
15475
+ const MAX_NUM_TAGS_PER_PAGE = 1e3;
15411
15476
  const zTagNameSchemaWithValidation = z.string().transform((s) => normalizeTagName(s).trim()).pipe(z.string().min(1));
15412
15477
  z.object({
15413
15478
  name: zTagNameSchemaWithValidation
@@ -15418,7 +15483,7 @@ const zBookmarkTagSchema = z.object({
15418
15483
  name: z.string(),
15419
15484
  attachedBy: zAttachedByEnumSchema
15420
15485
  });
15421
- z.object({
15486
+ const zGetTagResponseSchema = z.object({
15422
15487
  id: z.string(),
15423
15488
  name: z.string(),
15424
15489
  numBookmarks: z.number(),
@@ -15432,6 +15497,49 @@ z.object({
15432
15497
  id: z.string(),
15433
15498
  name: z.string()
15434
15499
  });
15500
+ const zTagCursorSchema = z.object({
15501
+ page: z.number().int().min(0)
15502
+ });
15503
+ const zTagListRequestSchema = z.object({
15504
+ nameContains: z.string().optional(),
15505
+ attachedBy: z.enum([...zAttachedByEnumSchema.options, "none"]).optional(),
15506
+ sortBy: z.enum(["name", "usage", "relevance"]).optional().default("usage"),
15507
+ cursor: zTagCursorSchema.nullish().default({ page: 0 }),
15508
+ // TODO: Remove the optional to enforce a limit after the next release
15509
+ limit: z.number().int().min(1).max(MAX_NUM_TAGS_PER_PAGE).optional()
15510
+ });
15511
+ zTagListRequestSchema.refine(
15512
+ (val) => val.sortBy != "relevance" || val.nameContains !== void 0,
15513
+ {
15514
+ message: "Relevance sorting requires a nameContains filter",
15515
+ path: ["sortBy"]
15516
+ }
15517
+ );
15518
+ const zTagListResponseSchema = z.object({
15519
+ tags: z.array(zGetTagResponseSchema),
15520
+ nextCursor: zTagCursorSchema.nullish()
15521
+ });
15522
+ z.object({
15523
+ nameContains: zTagListRequestSchema.shape.nameContains,
15524
+ sort: zTagListRequestSchema.shape.sortBy,
15525
+ attachedBy: zTagListRequestSchema.shape.attachedBy,
15526
+ cursor: z.string().transform((val, ctx) => {
15527
+ try {
15528
+ return JSON.parse(Buffer.from(val, "base64url").toString("utf8"));
15529
+ } catch {
15530
+ ctx.addIssue({
15531
+ code: "custom",
15532
+ message: "Invalid cursor"
15533
+ });
15534
+ return z.NEVER;
15535
+ }
15536
+ }).optional().pipe(zTagListRequestSchema.shape.cursor),
15537
+ limit: z.coerce.number().optional()
15538
+ });
15539
+ z.object({
15540
+ tags: zTagListResponseSchema.shape.tags,
15541
+ nextCursor: z.string().nullish()
15542
+ });
15435
15543
  const MAX_BOOKMARK_TITLE_LENGTH = 1e3;
15436
15544
  var BookmarkTypes = /* @__PURE__ */ ((BookmarkTypes2) => {
15437
15545
  BookmarkTypes2["LINK"] = "link";
@@ -15450,11 +15558,13 @@ const zAssetTypesSchema = z.enum([
15450
15558
  "video",
15451
15559
  "bookmarkAsset",
15452
15560
  "precrawledArchive",
15561
+ "userUploaded",
15453
15562
  "unknown"
15454
15563
  ]);
15455
15564
  const zAssetSchema = z.object({
15456
15565
  id: z.string(),
15457
- assetType: zAssetTypesSchema
15566
+ assetType: zAssetTypesSchema,
15567
+ fileName: z.string().nullish()
15458
15568
  });
15459
15569
  const zBookmarkedLinkSchema = z.object({
15460
15570
  type: z.literal(
@@ -15508,6 +15618,16 @@ const zBookmarkContentSchema = z.discriminatedUnion("type", [
15508
15618
  /* UNKNOWN */
15509
15619
  ) })
15510
15620
  ]);
15621
+ const zBookmarkSourceSchema = z.enum([
15622
+ "api",
15623
+ "web",
15624
+ "cli",
15625
+ "mobile",
15626
+ "extension",
15627
+ "singlefile",
15628
+ "rss",
15629
+ "import"
15630
+ ]);
15511
15631
  const zBareBookmarkSchema = z.object({
15512
15632
  id: z.string(),
15513
15633
  createdAt: z.date(),
@@ -15518,7 +15638,9 @@ const zBareBookmarkSchema = z.object({
15518
15638
  taggingStatus: z.enum(["success", "failure", "pending"]).nullable(),
15519
15639
  summarizationStatus: z.enum(["success", "failure", "pending"]).nullable(),
15520
15640
  note: z.string().nullish(),
15521
- summary: z.string().nullish()
15641
+ summary: z.string().nullish(),
15642
+ source: zBookmarkSourceSchema.nullish(),
15643
+ userId: z.string()
15522
15644
  });
15523
15645
  const zBookmarkSchema = zBareBookmarkSchema.merge(
15524
15646
  z.object({
@@ -15557,7 +15679,9 @@ z.object({
15557
15679
  createdAt: z.coerce.date().optional(),
15558
15680
  // A mechanism to prioritize crawling of bookmarks depending on whether
15559
15681
  // they were created by a user interaction or by a bulk import.
15560
- crawlPriority: z.enum(["low", "normal"]).optional()
15682
+ crawlPriority: z.enum(["low", "normal"]).optional(),
15683
+ importSessionId: z.string().optional(),
15684
+ source: zBookmarkSourceSchema.optional()
15561
15685
  }).and(
15562
15686
  z.discriminatedUnion("type", [
15563
15687
  z.object({
@@ -15734,12 +15858,22 @@ bookmarkCmd.command("add").description("creates a new bookmark").option(
15734
15858
  const results = [];
15735
15859
  const promises = [
15736
15860
  ...opts.link.map(
15737
- (url) => api2.bookmarks.createBookmark.mutate({ type: BookmarkTypes.LINK, url, title: opts.title }).then((bookmark) => {
15861
+ (url) => api2.bookmarks.createBookmark.mutate({
15862
+ type: BookmarkTypes.LINK,
15863
+ url,
15864
+ title: opts.title,
15865
+ source: "cli"
15866
+ }).then((bookmark) => {
15738
15867
  results.push(normalizeBookmark(bookmark));
15739
15868
  }).catch(printError(`Failed to add a link bookmark for url "${url}"`))
15740
15869
  ),
15741
15870
  ...opts.note.map(
15742
- (text) => api2.bookmarks.createBookmark.mutate({ type: BookmarkTypes.TEXT, text, title: opts.title }).then((bookmark) => {
15871
+ (text) => api2.bookmarks.createBookmark.mutate({
15872
+ type: BookmarkTypes.TEXT,
15873
+ text,
15874
+ title: opts.title,
15875
+ source: "cli"
15876
+ }).then((bookmark) => {
15743
15877
  results.push(normalizeBookmark(bookmark));
15744
15878
  }).catch(
15745
15879
  printError(
@@ -15751,7 +15885,12 @@ bookmarkCmd.command("add").description("creates a new bookmark").option(
15751
15885
  if (opts.stdin) {
15752
15886
  const text = require$$3.readFileSync(0, "utf-8");
15753
15887
  promises.push(
15754
- api2.bookmarks.createBookmark.mutate({ type: BookmarkTypes.TEXT, text, title: opts.title }).then((bookmark) => {
15888
+ api2.bookmarks.createBookmark.mutate({
15889
+ type: BookmarkTypes.TEXT,
15890
+ text,
15891
+ title: opts.title,
15892
+ source: "cli"
15893
+ }).then((bookmark) => {
15755
15894
  results.push(normalizeBookmark(bookmark));
15756
15895
  }).catch(
15757
15896
  printError(
@@ -15989,9 +16128,18 @@ const dumpCmd = new Command().name("dump").description("dump all account data an
15989
16128
  }
15990
16129
  if (!opts.excludeTags) {
15991
16130
  stepStart$2("Exporting tags");
15992
- const { tags } = await api2.tags.list.query();
15993
- await writeJson(path.join(workRoot, "tags", "index.json"), tags);
15994
- manifest.counts.tags = tags.length;
16131
+ let cursor = null;
16132
+ let allTags = [];
16133
+ do {
16134
+ const { tags, nextCursor } = await api2.tags.list.query({
16135
+ limit: MAX_NUM_TAGS_PER_PAGE,
16136
+ cursor
16137
+ });
16138
+ allTags.push(...tags);
16139
+ cursor = nextCursor;
16140
+ } while (cursor);
16141
+ await writeJson(path.join(workRoot, "tags", "index.json"), allTags);
16142
+ manifest.counts.tags = allTags.length;
15995
16143
  stepEndSuccess$2();
15996
16144
  }
15997
16145
  if (!opts.excludeRules) {
@@ -16539,7 +16687,7 @@ async function migrateWebhooks(src2, dest, onProgress) {
16539
16687
  }
16540
16688
  async function migrateTags(src2, dest, onProgress) {
16541
16689
  try {
16542
- const { tags: srcTags } = await src2.tags.list.query();
16690
+ const { tags: srcTags } = await src2.tags.list.query({});
16543
16691
  let ensured = 0;
16544
16692
  for (const t of srcTags) {
16545
16693
  try {
@@ -16549,7 +16697,7 @@ async function migrateTags(src2, dest, onProgress) {
16549
16697
  ensured++;
16550
16698
  onProgress?.(ensured, srcTags.length);
16551
16699
  }
16552
- const { tags: destTags } = await dest.tags.list.query();
16700
+ const { tags: destTags } = await dest.tags.list.query({});
16553
16701
  const nameToDestId = destTags.reduce((acc, t) => {
16554
16702
  acc[t.name] = t.id;
16555
16703
  return acc;
@@ -16802,7 +16950,7 @@ const tagsCmd = new Command().name("tags").description("manipulating tags");
16802
16950
  tagsCmd.command("list").description("lists all tags").action(async () => {
16803
16951
  const api2 = getAPIClient();
16804
16952
  try {
16805
- const tags = (await api2.tags.list.query()).tags;
16953
+ const tags = (await api2.tags.list.query({})).tags;
16806
16954
  tags.sort((a, b) => b.numBookmarks - a.numBookmarks);
16807
16955
  if (getGlobalOptions().json) {
16808
16956
  printObject(tags);
@@ -17153,7 +17301,7 @@ async function wipeTags(api2) {
17153
17301
  throw error2;
17154
17302
  }
17155
17303
  }
17156
- const __vite_import_meta_env__ = { "BASE_URL": "/", "CLI_VERSION": "0.27.1", "DEV": false, "MODE": "production", "PROD": true, "SSR": true };
17304
+ const __vite_import_meta_env__ = { "BASE_URL": "/", "CLI_VERSION": "0.29.1", "DEV": false, "MODE": "production", "PROD": true, "SSR": true };
17157
17305
  const program = new Command().name("karakeep").description("A CLI interface to interact with the karakeep api").addOption(
17158
17306
  new Option("--api-key <key>", "the API key to interact with the API").makeOptionMandatory(true).env("KARAKEEP_API_KEY")
17159
17307
  ).addOption(
@@ -17162,8 +17310,9 @@ const program = new Command().name("karakeep").description("A CLI interface to i
17162
17310
  "the address of the server to connect to"
17163
17311
  ).makeOptionMandatory(true).env("KARAKEEP_SERVER_ADDR")
17164
17312
  ).addOption(new Option("--json", "to output the result as JSON")).version(
17165
- __vite_import_meta_env__ && "CLI_VERSION" in __vite_import_meta_env__ ? "0.27.1" : "0.0.0"
17313
+ __vite_import_meta_env__ && "CLI_VERSION" in __vite_import_meta_env__ ? "0.29.1" : "0.0.0"
17166
17314
  );
17315
+ program.addCommand(adminCmd);
17167
17316
  program.addCommand(bookmarkCmd);
17168
17317
  program.addCommand(listsCmd);
17169
17318
  program.addCommand(tagsCmd);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@karakeep/cli",
4
- "version": "0.27.1",
4
+ "version": "0.29.1",
5
5
  "description": "Command Line Interface (CLI) for Karakeep",
6
6
  "license": "GNU Affero General Public License version 3",
7
7
  "type": "module",