@qzhuli/qzhuli-cli 0.2.1 → 0.3.0-rc.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.
package/dist/cmd.js CHANGED
@@ -11133,7 +11133,7 @@ var require_protos = __commonJS({
11133
11133
 
11134
11134
  // src/cmd.ts
11135
11135
  init_cjs_shims();
11136
- var import_commander8 = require("commander");
11136
+ var import_commander9 = require("commander");
11137
11137
 
11138
11138
  // src/commands/auth/index.ts
11139
11139
  init_cjs_shims();
@@ -11244,10 +11244,27 @@ var commands = {
11244
11244
  desc: "Conversation operations",
11245
11245
  listDesc: "List all conversations",
11246
11246
  limitOption: "Max conversations to retrieve",
11247
+ offsetOption: "Skip first N conversations",
11247
11248
  createDesc: "Create conversation",
11248
11249
  createArgDesc: "User UID",
11249
11250
  createAgentIdOption: "Agent ID",
11250
- createSuccess: "Conversation created."
11251
+ createSuccess: "Conversation created.",
11252
+ profileDesc: "Get conversation details",
11253
+ profileArgDesc: "Conversation ID",
11254
+ typeOption: "Conversation type (1=private, 2=group, auto-detect if omitted)",
11255
+ profileSuccess: "Conversation details retrieved.",
11256
+ searchDesc: "Find all conversations with a user by Q\u52A9\u53F7 or UID",
11257
+ searchArgDesc: "Query string (default: Q\u52A9\u53F7)",
11258
+ searchByUid: "Search by UID",
11259
+ searchNotFound: "No conversations found with {name}.",
11260
+ searchSuccess: "Found {count} conversation(s) with {name}."
11261
+ },
11262
+ cache: {
11263
+ desc: "Manage local cache",
11264
+ syncDesc: "Sync all data to local cache",
11265
+ statusDesc: "Show cache status",
11266
+ clearDesc: "Clear cache",
11267
+ clearTableOption: "Clear specific cache table"
11251
11268
  }
11252
11269
  };
11253
11270
  var messages = {
@@ -11374,10 +11391,27 @@ var commands2 = {
11374
11391
  desc: "\u4F1A\u8BDD\u64CD\u4F5C",
11375
11392
  listDesc: "\u5217\u51FA\u6240\u6709\u4F1A\u8BDD",
11376
11393
  limitOption: "\u6700\u5927\u4F1A\u8BDD\u6570",
11394
+ offsetOption: "\u8DF3\u8FC7\u524D N \u6761\u4F1A\u8BDD",
11377
11395
  createDesc: "\u521B\u5EFA\u4F1A\u8BDD",
11378
11396
  createArgDesc: "\u7528\u6237UID",
11379
11397
  createAgentIdOption: "\u52A9\u7406ID",
11380
- createSuccess: "\u4F1A\u8BDD\u521B\u5EFA\u6210\u529F\u3002"
11398
+ createSuccess: "\u4F1A\u8BDD\u521B\u5EFA\u6210\u529F\u3002",
11399
+ profileDesc: "\u67E5\u8BE2\u4F1A\u8BDD\u8BE6\u60C5",
11400
+ profileArgDesc: "\u4F1A\u8BDDID",
11401
+ typeOption: "\u4F1A\u8BDD\u7C7B\u578B\uFF081=\u79C1\u804A, 2=\u7FA4\u804A\uFF0C\u4E0D\u4F20\u81EA\u52A8\u5224\u65AD\uFF09",
11402
+ profileSuccess: "\u5DF2\u83B7\u53D6\u4F1A\u8BDD\u8BE6\u60C5\u3002",
11403
+ searchDesc: "\u901A\u8FC7Q\u52A9\u53F7\u6216UID\u67E5\u627E\u4E0E\u67D0\u7528\u6237\u7684\u6240\u6709\u4F1A\u8BDD",
11404
+ searchArgDesc: "\u67E5\u8BE2\u5185\u5BB9\uFF08\u9ED8\u8BA4\u6309 Q\u52A9\u53F7\uFF09",
11405
+ searchByUid: "\u6309 UID \u641C\u7D22",
11406
+ searchNotFound: "\u672A\u627E\u5230\u4E0E {name} \u7684\u4F1A\u8BDD\u3002",
11407
+ searchSuccess: "\u627E\u5230\u4E0E {name} \u7684 {count} \u4E2A\u4F1A\u8BDD\u3002"
11408
+ },
11409
+ cache: {
11410
+ desc: "\u7BA1\u7406\u672C\u5730\u7F13\u5B58",
11411
+ syncDesc: "\u540C\u6B65\u6240\u6709\u6570\u636E\u5230\u672C\u5730\u7F13\u5B58",
11412
+ statusDesc: "\u663E\u793A\u7F13\u5B58\u72B6\u6001",
11413
+ clearDesc: "\u6E05\u9664\u7F13\u5B58",
11414
+ clearTableOption: "\u6E05\u9664\u6307\u5B9A\u7F13\u5B58\u8868"
11381
11415
  }
11382
11416
  };
11383
11417
  var messages2 = {
@@ -12242,10 +12276,121 @@ function NewCmdAuth(factory) {
12242
12276
  return cmd;
12243
12277
  }
12244
12278
 
12245
- // src/commands/config/index.ts
12279
+ // src/commands/cache/index.ts
12246
12280
  init_cjs_shims();
12247
12281
  var import_commander2 = require("commander");
12248
12282
 
12283
+ // src/commands/cache/clear.ts
12284
+ init_cjs_shims();
12285
+ async function cacheClearRun(factory, opts) {
12286
+ if (opts.table) {
12287
+ switch (opts.table) {
12288
+ case "conversations":
12289
+ factory.repos.conversation.clear();
12290
+ break;
12291
+ case "contacts":
12292
+ factory.repos.contact.clear();
12293
+ break;
12294
+ case "users":
12295
+ factory.repos.user.clear();
12296
+ break;
12297
+ case "relations":
12298
+ factory.repos.relation.clear();
12299
+ break;
12300
+ case "messages":
12301
+ factory.repos.message.clear();
12302
+ break;
12303
+ default:
12304
+ return {
12305
+ status: "error",
12306
+ code: "INVALID_ARGUMENT" /* INVALID_ARGUMENT */,
12307
+ message: t("commands.cache.unknownTable").replace("{table}", opts.table),
12308
+ data: null
12309
+ };
12310
+ }
12311
+ } else {
12312
+ factory.repos.conversation.clear();
12313
+ factory.repos.contact.clear();
12314
+ factory.repos.user.clear();
12315
+ factory.repos.relation.clear();
12316
+ factory.repos.message.clear();
12317
+ }
12318
+ return {
12319
+ status: "success",
12320
+ code: "CONFIG_UPDATED" /* CONFIG_UPDATED */,
12321
+ message: opts.table ? t("commands.cache.clearTableSuccess").replace("{table}", opts.table) : t("commands.cache.clearSuccess"),
12322
+ data: null
12323
+ };
12324
+ }
12325
+
12326
+ // src/commands/cache/status.ts
12327
+ init_cjs_shims();
12328
+ async function cacheStatusRun(factory) {
12329
+ const [convStatus, contactStatus] = await Promise.all([
12330
+ factory.repos.conversation.getStatus(),
12331
+ factory.repos.contact.getStatus()
12332
+ ]);
12333
+ return {
12334
+ status: "success",
12335
+ code: "CONFIG_RETRIEVED" /* CONFIG_RETRIEVED */,
12336
+ message: "Cache status:",
12337
+ data: {
12338
+ conversations: {
12339
+ count: convStatus.count,
12340
+ lastSyncAt: convStatus.lastSyncAt ? new Date(convStatus.lastSyncAt).toISOString() : "never"
12341
+ },
12342
+ contacts: {
12343
+ count: contactStatus.count,
12344
+ lastSyncAt: contactStatus.lastSyncAt ? new Date(contactStatus.lastSyncAt).toISOString() : "never"
12345
+ }
12346
+ }
12347
+ };
12348
+ }
12349
+
12350
+ // src/commands/cache/sync.ts
12351
+ init_cjs_shims();
12352
+ async function cacheSyncRun(factory) {
12353
+ try {
12354
+ await factory.repos.contact.sync();
12355
+ await factory.repos.conversation.sync();
12356
+ } catch (error) {
12357
+ return {
12358
+ status: "error",
12359
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
12360
+ message: error instanceof Error ? error.message : "Cache sync failed.",
12361
+ data: null
12362
+ };
12363
+ }
12364
+ return {
12365
+ status: "success",
12366
+ code: "CONFIG_UPDATED" /* CONFIG_UPDATED */,
12367
+ message: t("commands.cache.syncSuccess"),
12368
+ data: null
12369
+ };
12370
+ }
12371
+
12372
+ // src/commands/cache/index.ts
12373
+ function NewCmdCache(factory) {
12374
+ const cmd = new import_commander2.Command("cache").description(t("commands.cache.desc"));
12375
+ cmd.command("sync").description(t("commands.cache.syncDesc")).action(async () => {
12376
+ const result = await cacheSyncRun(factory);
12377
+ handleCommand(result);
12378
+ });
12379
+ cmd.command("status").description(t("commands.cache.statusDesc")).action(async () => {
12380
+ const result = await cacheStatusRun(factory);
12381
+ handleCommand(result);
12382
+ });
12383
+ cmd.command("clear").description(t("commands.cache.clearDesc")).option("--table <table>", t("commands.cache.clearTableOption")).action(async (opts) => {
12384
+ const result = await cacheClearRun(factory, { table: opts.table });
12385
+ handleCommand(result);
12386
+ });
12387
+ return cmd;
12388
+ }
12389
+
12390
+ // src/commands/config/index.ts
12391
+ init_cjs_shims();
12392
+ var import_commander3 = require("commander");
12393
+
12249
12394
  // src/commands/config/get.ts
12250
12395
  init_cjs_shims();
12251
12396
  function configGetRun(_factory, opts) {
@@ -12273,7 +12418,7 @@ function configGetRun(_factory, opts) {
12273
12418
 
12274
12419
  // src/commands/config/index.ts
12275
12420
  function NewCmdConfig(factory) {
12276
- const cmd = new import_commander2.Command("config").description(t("commands.config.desc"));
12421
+ const cmd = new import_commander3.Command("config").description(t("commands.config.desc"));
12277
12422
  cmd.option("--locale <language>", t("commands.config.localeOption")).option("--debug", t("commands.config.debugOn")).option("--no-debug", t("commands.config.debugOff")).action((opts) => {
12278
12423
  const result = configGetRun(factory, {
12279
12424
  locale: opts.locale,
@@ -12286,7 +12431,7 @@ function NewCmdConfig(factory) {
12286
12431
 
12287
12432
  // src/commands/conversation/index.ts
12288
12433
  init_cjs_shims();
12289
- var import_commander3 = require("commander");
12434
+ var import_commander4 = require("commander");
12290
12435
 
12291
12436
  // src/commands/conversation/create.ts
12292
12437
  init_cjs_shims();
@@ -12311,8 +12456,11 @@ async function conversationCreateRun(factory, opts) {
12311
12456
  // src/commands/conversation/list.ts
12312
12457
  init_cjs_shims();
12313
12458
  async function conversationListRun(factory, opts) {
12314
- const result = await factory.imGateway.queryConversations({ limit: opts.limit });
12315
- if (result.status === "error" && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12459
+ const result = await factory.repos.conversation.queryAll({
12460
+ limit: opts.limit,
12461
+ offset: opts.offset
12462
+ });
12463
+ if (!result.ok && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12316
12464
  return {
12317
12465
  status: "error",
12318
12466
  code: "AUTH_FAILED" /* AUTH_FAILED */,
@@ -12320,15 +12468,150 @@ async function conversationListRun(factory, opts) {
12320
12468
  data: null
12321
12469
  };
12322
12470
  }
12323
- return result;
12471
+ return {
12472
+ status: result.ok ? "success" : "error",
12473
+ code: result.ok ? "CONVERSATION_LIST" /* CONVERSATION_LIST */ : result.code,
12474
+ message: result.ok ? `Conversations (${result.data.length}):` : result.message,
12475
+ data: result.ok ? { conversations: result.data } : null
12476
+ };
12477
+ }
12478
+
12479
+ // src/commands/conversation/profile.ts
12480
+ init_cjs_shims();
12481
+ async function conversationProfileRun(factory, opts) {
12482
+ const result = await factory.repos.conversation.getProfile(
12483
+ opts.conversationId,
12484
+ opts.conversationType
12485
+ );
12486
+ if (!result.ok) {
12487
+ return {
12488
+ status: "error",
12489
+ code: result.code,
12490
+ message: result.code === "AUTH_FAILED" /* AUTH_FAILED */ ? t("auth.notAuthenticated") : result.message,
12491
+ data: null
12492
+ };
12493
+ }
12494
+ return {
12495
+ status: "success",
12496
+ code: "CONVERSATION_LIST" /* CONVERSATION_LIST */,
12497
+ message: t("commands.conversation.profileSuccess"),
12498
+ data: result.data
12499
+ };
12500
+ }
12501
+
12502
+ // src/commands/conversation/search.ts
12503
+ init_cjs_shims();
12504
+ function convertUser(raw) {
12505
+ const user = {
12506
+ id: raw.id,
12507
+ cid: raw.cid,
12508
+ uid: raw.uid,
12509
+ nickname: raw.nickname,
12510
+ avatar: raw.avatar,
12511
+ status: raw.status
12512
+ };
12513
+ if (raw.link_name) user.linkName = raw.link_name;
12514
+ if (raw.member_delete_time) user.memberDeleteTime = raw.member_delete_time;
12515
+ if (raw.agent) {
12516
+ const a = raw.agent;
12517
+ const agentVoice = a.voice ? {
12518
+ code: a.voice.code,
12519
+ ...a.voice.voice_name ? { voiceName: a.voice.voice_name } : {},
12520
+ ...a.voice.file_link ? { fileLink: a.voice.file_link } : {},
12521
+ ...a.voice.text ? { text: a.voice.text } : {}
12522
+ } : void 0;
12523
+ user.agent = {
12524
+ id: a.id,
12525
+ title: a.title,
12526
+ avatar: a.avatar,
12527
+ packageIcon: a.package_icon,
12528
+ packageLevel: a.package_level,
12529
+ ...a.background !== void 0 ? { background: a.background } : {},
12530
+ ...a.agent_type !== void 0 ? { agentType: a.agent_type } : {},
12531
+ ...agentVoice ? { voice: agentVoice } : {}
12532
+ };
12533
+ }
12534
+ return user;
12535
+ }
12536
+ function convertVisitor(raw) {
12537
+ return { id: raw.id, cid: raw.cid };
12538
+ }
12539
+ function profileToItem(profile) {
12540
+ const convData = profile.conversation;
12541
+ return {
12542
+ conversationId: convData.conversation_id,
12543
+ blackStatus: convData.black_status,
12544
+ isGroup: convData.is_group,
12545
+ name: convData.name,
12546
+ avatar: convData.avatar,
12547
+ sourceType: convData.source_type,
12548
+ classesId: convData.classes_id,
12549
+ classesName: convData.classes_name,
12550
+ teamId: convData.team_id,
12551
+ teamName: convData.team_name,
12552
+ users: profile.users.map(convertUser),
12553
+ visitors: profile.visitors.map(convertVisitor)
12554
+ };
12555
+ }
12556
+ async function conversationSearchRun(factory, opts) {
12557
+ const userResult = opts.byUid ? await factory.repos.user.findUserById(opts.query) : await factory.repos.user.searchUserById(opts.query);
12558
+ if (!userResult.ok) {
12559
+ return {
12560
+ status: "error",
12561
+ code: userResult.code,
12562
+ message: userResult.code === "AUTH_FAILED" /* AUTH_FAILED */ ? t("auth.notAuthenticated") : userResult.message,
12563
+ data: null
12564
+ };
12565
+ }
12566
+ if (!userResult.data) {
12567
+ return {
12568
+ status: "error",
12569
+ code: "USER_NOT_FOUND_NEEDS_RESOLUTION" /* USER_NOT_FOUND_NEEDS_RESOLUTION */,
12570
+ message: t("messages.notFound"),
12571
+ data: null
12572
+ };
12573
+ }
12574
+ const targetId = userResult.data.id;
12575
+ const targetUid = userResult.data.uid;
12576
+ const nickname = userResult.data.nickname;
12577
+ const profilesResult = await factory.repos.conversation.searchByUid(targetUid);
12578
+ if (!profilesResult.ok || profilesResult.data.length === 0) {
12579
+ return {
12580
+ status: "success",
12581
+ code: "NOT_FOUND" /* NOT_FOUND */,
12582
+ message: t("commands.conversation.searchNotFound").replace("{name}", nickname),
12583
+ data: null
12584
+ };
12585
+ }
12586
+ const results = profilesResult.data.map(profileToItem);
12587
+ const result = {
12588
+ id: targetId,
12589
+ uid: targetUid,
12590
+ nickname,
12591
+ conversations: results
12592
+ };
12593
+ return {
12594
+ status: "success",
12595
+ code: "CONVERSATION_LIST" /* CONVERSATION_LIST */,
12596
+ message: t("commands.conversation.searchSuccess").replace("{name}", nickname).replace("{count}", String(results.length)),
12597
+ data: result
12598
+ };
12324
12599
  }
12325
12600
 
12326
12601
  // src/commands/conversation/index.ts
12327
12602
  function NewCmdConversation(factory) {
12328
- const cmd = new import_commander3.Command("conversation").description(t("commands.conversation.desc"));
12329
- cmd.command("list").description(t("commands.conversation.listDesc")).option("--limit <n>", t("commands.conversation.limitOption"), "50").action(async (options3) => {
12603
+ const cmd = new import_commander4.Command("conversation").description(t("commands.conversation.desc"));
12604
+ cmd.command("list").description(t("commands.conversation.listDesc")).option("--limit <n>", t("commands.conversation.limitOption"), "50").option("--offset <n>", t("commands.conversation.offsetOption"), "0").action(async (options3) => {
12330
12605
  const result = await conversationListRun(factory, {
12331
- limit: parseInt(options3.limit, 10)
12606
+ limit: parseInt(options3.limit, 10),
12607
+ offset: parseInt(options3.offset, 10)
12608
+ });
12609
+ handleCommand(result);
12610
+ });
12611
+ cmd.command("profile").description(t("commands.conversation.profileDesc")).argument("<id>", t("commands.conversation.profileArgDesc")).option("--type <n>", t("commands.conversation.typeOption")).action(async (id, opts) => {
12612
+ const result = await conversationProfileRun(factory, {
12613
+ conversationId: id,
12614
+ conversationType: opts.type ? parseInt(opts.type, 10) : void 0
12332
12615
  });
12333
12616
  handleCommand(result);
12334
12617
  });
@@ -12336,17 +12619,21 @@ function NewCmdConversation(factory) {
12336
12619
  const result = await conversationCreateRun(factory, { uid, agentId: opts.agentId });
12337
12620
  handleCommand(result);
12338
12621
  });
12622
+ cmd.command("search").description(t("commands.conversation.searchDesc")).argument("<query>", t("commands.conversation.searchArgDesc")).option("--uid", t("commands.conversation.searchByUid")).action(async (query, opts) => {
12623
+ const result = await conversationSearchRun(factory, { query, byUid: opts.uid });
12624
+ handleCommand(result);
12625
+ });
12339
12626
  return cmd;
12340
12627
  }
12341
12628
 
12342
12629
  // src/commands/friend/index.ts
12343
12630
  init_cjs_shims();
12344
- var import_commander4 = require("commander");
12631
+ var import_commander5 = require("commander");
12345
12632
 
12346
12633
  // src/commands/friend/list.ts
12347
12634
  init_cjs_shims();
12348
12635
  async function friendListRun(factory) {
12349
- const result = await factory.gateway.getLinksContacts();
12636
+ const result = await factory.repos.contact.getLinksContacts();
12350
12637
  if (!result.ok) {
12351
12638
  return {
12352
12639
  status: "error",
@@ -12376,7 +12663,7 @@ async function friendListRun(factory) {
12376
12663
  // src/commands/friend/profile.ts
12377
12664
  init_cjs_shims();
12378
12665
  async function friendProfileRun(factory, opts) {
12379
- const listResult = await factory.gateway.getLinksContacts();
12666
+ const listResult = await factory.repos.contact.getLinksContacts();
12380
12667
  if (!listResult.ok) {
12381
12668
  return {
12382
12669
  status: "error",
@@ -12461,7 +12748,7 @@ async function friendProfileRun(factory, opts) {
12461
12748
  }
12462
12749
  };
12463
12750
  }
12464
- const profileResult = await factory.gateway.getUserAgentProfile(match.uid);
12751
+ const profileResult = await factory.repos.user.getUserAgentProfile(match.uid);
12465
12752
  if (!profileResult.ok) {
12466
12753
  return {
12467
12754
  status: "error",
@@ -12494,7 +12781,7 @@ async function friendProfileRun(factory, opts) {
12494
12781
 
12495
12782
  // src/commands/friend/index.ts
12496
12783
  function NewCmdFriend(factory) {
12497
- const cmd = new import_commander4.Command("friend").description(t("commands.friend.desc"));
12784
+ const cmd = new import_commander5.Command("friend").description(t("commands.friend.desc"));
12498
12785
  cmd.command("list").description(t("commands.friend.listDesc")).action(async () => {
12499
12786
  const result = await friendListRun(factory);
12500
12787
  handleCommand(result);
@@ -12512,7 +12799,7 @@ function NewCmdFriend(factory) {
12512
12799
 
12513
12800
  // src/commands/message/index.ts
12514
12801
  init_cjs_shims();
12515
- var import_commander5 = require("commander");
12802
+ var import_commander6 = require("commander");
12516
12803
 
12517
12804
  // src/commands/message/history.ts
12518
12805
  init_cjs_shims();
@@ -12522,8 +12809,8 @@ async function messageHistoryRun(factory, opts) {
12522
12809
  direction: opts.direction,
12523
12810
  limit: opts.limit
12524
12811
  };
12525
- const result = await factory.imGateway.pullMessages(opts.conversationId, pullOpts);
12526
- if (result.status === "error" && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12812
+ const result = await factory.repos.message.pullMessages(opts.conversationId, pullOpts);
12813
+ if (!result.ok && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12527
12814
  return {
12528
12815
  status: "error",
12529
12816
  code: "AUTH_FAILED" /* AUTH_FAILED */,
@@ -12531,7 +12818,12 @@ async function messageHistoryRun(factory, opts) {
12531
12818
  data: null
12532
12819
  };
12533
12820
  }
12534
- return result;
12821
+ return {
12822
+ status: result.ok ? "success" : "error",
12823
+ code: result.ok ? "MESSAGE_HISTORY" /* MESSAGE_HISTORY */ : result.code,
12824
+ message: result.ok ? `Messages (${result.data.messages.length}):` : result.message,
12825
+ data: result.ok ? result.data : null
12826
+ };
12535
12827
  }
12536
12828
 
12537
12829
  // src/commands/message/send.ts
@@ -12555,7 +12847,7 @@ async function messageSendRun(factory, opts) {
12555
12847
 
12556
12848
  // src/commands/message/index.ts
12557
12849
  function NewCmdMessage(factory) {
12558
- const cmd = new import_commander5.Command("message").description(t("commands.message.desc"));
12850
+ const cmd = new import_commander6.Command("message").description(t("commands.message.desc"));
12559
12851
  cmd.command("send <conversation-id> <target-cid> <content>").description(t("commands.message.sendDesc")).action(async (conversationId, targetCid, content) => {
12560
12852
  const result = await messageSendRun(factory, { conversationId, targetCid, content });
12561
12853
  handleCommand(result);
@@ -12574,12 +12866,12 @@ function NewCmdMessage(factory) {
12574
12866
 
12575
12867
  // src/commands/relation/index.ts
12576
12868
  init_cjs_shims();
12577
- var import_commander6 = require("commander");
12869
+ var import_commander7 = require("commander");
12578
12870
 
12579
12871
  // src/commands/relation/get.ts
12580
12872
  init_cjs_shims();
12581
12873
  async function relationGetRun(factory, opts) {
12582
- const result = await factory.gateway.getLinkNameType(opts.friendUid);
12874
+ const result = await factory.repos.relation.getLinkNameType(opts.friendUid);
12583
12875
  if (!result.ok) {
12584
12876
  return {
12585
12877
  status: "error",
@@ -12647,7 +12939,7 @@ async function relationSetRun(factory, opts) {
12647
12939
 
12648
12940
  // src/commands/relation/index.ts
12649
12941
  function NewCmdRelation(factory) {
12650
- const cmd = new import_commander6.Command("relation").description(t("commands.relation.desc"));
12942
+ const cmd = new import_commander7.Command("relation").description(t("commands.relation.desc"));
12651
12943
  cmd.command("get <uid>").description(t("commands.relation.getDesc")).action(async (uid) => {
12652
12944
  const result = await relationGetRun(factory, { friendUid: uid });
12653
12945
  handleCommand(result);
@@ -12665,7 +12957,7 @@ function NewCmdRelation(factory) {
12665
12957
 
12666
12958
  // src/commands/user/index.ts
12667
12959
  init_cjs_shims();
12668
- var import_commander7 = require("commander");
12960
+ var import_commander8 = require("commander");
12669
12961
 
12670
12962
  // src/commands/user/add.ts
12671
12963
  init_cjs_shims();
@@ -12720,7 +13012,7 @@ async function userAddRun(factory, opts) {
12720
13012
  // src/commands/user/search.ts
12721
13013
  init_cjs_shims();
12722
13014
  async function userSearchRun(factory, opts) {
12723
- const result = opts.byUid ? await factory.gateway.findUserById(opts.query) : await factory.gateway.searchUserById(opts.query);
13015
+ const result = opts.byUid ? await factory.repos.user.findUserById(opts.query) : await factory.repos.user.searchUserById(opts.query);
12724
13016
  if (!result.ok) {
12725
13017
  return {
12726
13018
  status: "error",
@@ -12748,7 +13040,7 @@ async function userSearchRun(factory, opts) {
12748
13040
 
12749
13041
  // src/commands/user/index.ts
12750
13042
  function NewCmdUser(factory) {
12751
- const cmd = new import_commander7.Command("user").description(t("commands.user.desc"));
13043
+ const cmd = new import_commander8.Command("user").description(t("commands.user.desc"));
12752
13044
  cmd.command("search").description(t("commands.user.searchDesc")).argument("<query>", t("commands.user.searchArgDesc")).option("--uid", t("commands.user.searchByUid")).action(async (query, opts) => {
12753
13045
  const result = await userSearchRun(factory, { query, byUid: opts.uid });
12754
13046
  handleCommand(result);
@@ -12887,6 +13179,23 @@ var ApiGateway = class {
12887
13179
  friend_agent_id: friendAgentId
12888
13180
  });
12889
13181
  }
13182
+ /** POST /user/get_profile_by_conversation_id — auth required */
13183
+ getProfileByConversationId(conversationId, conversationType) {
13184
+ const auth3 = this.requireAuth();
13185
+ if (!auth3.ok) return Promise.resolve(auth3);
13186
+ const params = {
13187
+ uid: auth3.data,
13188
+ conversation_id: conversationId
13189
+ };
13190
+ if (conversationType !== void 0) {
13191
+ params.conversation_type = conversationType;
13192
+ }
13193
+ return this.client.performRequest(
13194
+ "/user/get_profile_by_conversation_id",
13195
+ "POST",
13196
+ params
13197
+ );
13198
+ }
12890
13199
  };
12891
13200
 
12892
13201
  // src/internal/api-client.ts
@@ -13099,6 +13408,33 @@ var IMClient = class {
13099
13408
  console.log("[DEBUG]", ...args);
13100
13409
  }
13101
13410
  }
13411
+ /** Convert a protobuf Long to a readable string. */
13412
+ longStr(v) {
13413
+ if (!v) return "0";
13414
+ if (typeof v === "object" && "low" in v && "high" in v) {
13415
+ return v.toString();
13416
+ }
13417
+ return String(v);
13418
+ }
13419
+ /** Map operation code to a human-readable name. */
13420
+ opName(code) {
13421
+ const map = {
13422
+ 1: "KeepAlive",
13423
+ 1e3: "ClientOpen",
13424
+ 1001: "ClientClose",
13425
+ 1003: "ConversationQuery",
13426
+ 2e3: "ConversationMessageSend",
13427
+ 2001: "ConversationMessagePull",
13428
+ 2005: "ConversationMessagePush",
13429
+ 3e3: "Ack"
13430
+ };
13431
+ return map[code] ?? `Op(${code})`;
13432
+ }
13433
+ /** Format byte size for display. */
13434
+ size(n) {
13435
+ if (n < 1024) return `${n}B`;
13436
+ return `${(n / 1024).toFixed(1)}KB`;
13437
+ }
13102
13438
  isConnected() {
13103
13439
  return this.connected;
13104
13440
  }
@@ -13106,9 +13442,11 @@ var IMClient = class {
13106
13442
  async connect() {
13107
13443
  return new Promise((resolve2, reject) => {
13108
13444
  const wsUrl = this.buildWsUrl();
13445
+ this.debug(`\u2192 CONNECT ${wsUrl}, cid=${this.config.cid}`);
13109
13446
  this.ws = new import_ws.default(wsUrl);
13110
13447
  this.ws.binaryType = "arraybuffer";
13111
13448
  this.ws.on("open", () => {
13449
+ this.debug(" \u2713 Connected");
13112
13450
  clearTimeout(timeout);
13113
13451
  this.connected = true;
13114
13452
  this.onEvent({ type: "connected" });
@@ -13116,13 +13454,17 @@ var IMClient = class {
13116
13454
  resolve2();
13117
13455
  });
13118
13456
  this.ws.on("message", (data) => {
13119
- this.handleResponse(data);
13457
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
13458
+ this.debug(` \u2190 ${this.size(buf.length)} received`);
13459
+ this.handleResponse(buf);
13120
13460
  });
13121
- this.ws.on("close", (_code, reason) => {
13461
+ this.ws.on("close", (code, reason) => {
13462
+ this.debug(` \u2717 Closed (code: ${code}, reason: ${reason.toString()})`);
13122
13463
  this.connected = false;
13123
13464
  this.onEvent({ type: "disconnected", reason: reason.toString() });
13124
13465
  });
13125
13466
  this.ws.on("error", (err) => {
13467
+ this.debug(` \u2717 Error: ${err instanceof Error ? err.message : String(err)}`);
13126
13468
  this.onEvent({ type: "error", error: err });
13127
13469
  reject(err);
13128
13470
  });
@@ -13135,6 +13477,7 @@ var IMClient = class {
13135
13477
  /** Close the IM connection. */
13136
13478
  close() {
13137
13479
  if (this.ws) {
13480
+ this.debug(`\u2192 CLOSE`);
13138
13481
  this.ws.close();
13139
13482
  this.ws = null;
13140
13483
  this.connected = false;
@@ -13174,13 +13517,21 @@ var IMClient = class {
13174
13517
  const handler = (ack) => {
13175
13518
  clearTimeout(timeout);
13176
13519
  this.ws?.off("message", onMessage);
13520
+ this.debug(
13521
+ ` \u2713 Ack, msg=${ack.messageId}, blocked=${ack.blocked}, moderated=${ack.moderated}`
13522
+ );
13177
13523
  resolve2(ack);
13178
13524
  };
13179
13525
  const onMessage = (data2) => {
13180
13526
  const response = this.root.IMResponse.decode(new Uint8Array(data2));
13181
- if (response.operation === this.root.IMOperation.Ack || response.operation === this.root.IMOperation.ConversationMessageSend) {
13182
- if (response.blob && response.blob.length > 0) {
13527
+ const op = response.operation;
13528
+ const blobLen = response.blob?.length ?? 0;
13529
+ if (op === this.root.IMOperation.Ack || op === this.root.IMOperation.ConversationMessageSend) {
13530
+ if (blobLen > 0) {
13183
13531
  const ack = this.root.MsgAck.decode(new Uint8Array(response.blob));
13532
+ this.debug(
13533
+ ` \u2190 ${this.opName(op)} ${this.size(blobLen)}, id=${this.longStr(ack.id)}, blocked=${ack.blocked}, ackTs=${this.longStr(ack.ackTs)}`
13534
+ );
13184
13535
  handler({
13185
13536
  messageId: String(ack.id),
13186
13537
  conversationId: ack.conversationId,
@@ -13192,14 +13543,15 @@ var IMClient = class {
13192
13543
  ackTs: ack.ackTs ? Number(ack.ackTs) : 0
13193
13544
  });
13194
13545
  } else {
13195
- this.debug("sendMessage ack received with empty blob, operation:", response.operation);
13546
+ this.debug(` \u2190 ${this.opName(op)} (empty blob, ignored)`);
13196
13547
  }
13197
13548
  } else {
13198
- this.debug("sendMessage ignored message, operation:", response.operation);
13549
+ this.debug(` \u2190 ${this.opName(op)} ${this.size(blobLen)} (ignored, not ack)`);
13199
13550
  }
13200
13551
  };
13201
13552
  this.ws?.on("message", onMessage);
13202
13553
  const data = this.root.IMRequest.encode(request).finish();
13554
+ this.debug(`\u2192 ${this.opName(2e3)} ${this.size(data.length)} to: ${targetCid}`);
13203
13555
  this.ws?.send(Buffer.from(data));
13204
13556
  });
13205
13557
  }
@@ -13230,6 +13582,7 @@ var IMClient = class {
13230
13582
  clearTimeout(timeout);
13231
13583
  this.ws?.off("message", onMessage);
13232
13584
  if (!response.blob || response.blob.length === 0) {
13585
+ this.debug(` \u2190 ${this.opName(2001)} (empty, no messages)`);
13233
13586
  resolve2({
13234
13587
  messages: [],
13235
13588
  finished: true,
@@ -13244,6 +13597,9 @@ var IMClient = class {
13244
13597
  return;
13245
13598
  }
13246
13599
  const msgList = this.root.MsgList.decode(new Uint8Array(response.blob));
13600
+ this.debug(
13601
+ ` \u2190 ${this.opName(2001)}, ${msgList.msgs?.length ?? 0} msgs, finished=${msgList.finished}, lastId=${this.longStr(msgList.lastId)}`
13602
+ );
13247
13603
  const debug = this.debugEnabled;
13248
13604
  const messages3 = [];
13249
13605
  for (const msg of msgList.msgs) {
@@ -13271,6 +13627,9 @@ var IMClient = class {
13271
13627
  timestamp: msg.ackTs ? Number(msg.ackTs) : msg.jetTs ? Number(msg.jetTs) : 0
13272
13628
  };
13273
13629
  if (debug) {
13630
+ this.debug(
13631
+ ` #${pulledMsg.id} ${pulledMsg.type} ${pulledMsg.role} from ${pulledMsg.senderCid} (${pulledMsg.content.length} chars, ${pulledMsg.extraContents.length} extras)`
13632
+ );
13274
13633
  if (msg.blob && msg.blob.length > 0)
13275
13634
  pulledMsg.rawBlob = Buffer.from(msg.blob).toString("base64");
13276
13635
  if (msg.blobExtra && msg.blobExtra.length > 0)
@@ -13278,6 +13637,7 @@ var IMClient = class {
13278
13637
  }
13279
13638
  messages3.push(pulledMsg);
13280
13639
  }
13640
+ this.debug(` \u2713 ${messages3.length} messages resolved`);
13281
13641
  resolve2({
13282
13642
  messages: messages3,
13283
13643
  finished: msgList.finished,
@@ -13301,13 +13661,11 @@ var IMClient = class {
13301
13661
  });
13302
13662
  }
13303
13663
  /** Query all conversations for the user. */
13304
- async queryConversations(options3) {
13664
+ async queryConversations() {
13305
13665
  if (!this.ws || !this.connected) {
13306
13666
  throw new Error("Not connected to IM server");
13307
13667
  }
13308
- const queryBlob = this.root.ConversationQueryParams.create({
13309
- limit: options3?.limit ?? 0
13310
- });
13668
+ const queryBlob = this.root.ConversationQueryParams.create({});
13311
13669
  const request = this.root.IMRequest.create({
13312
13670
  operation: this.root.IMOperation.ConversationQuery,
13313
13671
  cid: this.config.cid,
@@ -13324,6 +13682,7 @@ var IMClient = class {
13324
13682
  clearTimeout(timeout);
13325
13683
  this.ws?.off("message", onMessage);
13326
13684
  if (!response.blob || response.blob.length === 0) {
13685
+ this.debug(` \u2190 ${this.opName(1003)} (empty, no conversations)`);
13327
13686
  resolve2({
13328
13687
  conversations: [],
13329
13688
  limit: 0,
@@ -13334,6 +13693,7 @@ var IMClient = class {
13334
13693
  return;
13335
13694
  }
13336
13695
  const list = this.root.ConversationList.decode(new Uint8Array(response.blob));
13696
+ this.debug(` \u2190 ${this.opName(1003)} ${list.convs?.length ?? 0} conversations`);
13337
13697
  const debug = this.debugEnabled;
13338
13698
  const conversations3 = [];
13339
13699
  for (const conv of list.convs ?? []) {
@@ -13394,13 +13754,18 @@ var IMClient = class {
13394
13754
  ts: Date.now()
13395
13755
  });
13396
13756
  const data = this.root.IMRequest.encode(request).finish();
13757
+ this.debug(`\u2192 ${this.opName(1e3)} ${this.size(data.length)}, cid=${this.config.cid}`);
13397
13758
  this.ws.send(Buffer.from(data));
13398
13759
  }
13399
13760
  handleResponse(data) {
13400
13761
  try {
13401
13762
  const response = this.root.IMResponse.decode(new Uint8Array(data));
13402
- if (response.operation === this.root.IMOperation.ConversationMessagePush) {
13763
+ const op = response.operation;
13764
+ if (op === this.root.IMOperation.ConversationMessagePush) {
13403
13765
  const msgList = this.root.MsgList.decode(new Uint8Array(response.blob));
13766
+ this.debug(
13767
+ ` \u2190 ${this.opName(op)}, ${msgList.msgs?.length ?? 0} msgs, conv=${msgList.convId}`
13768
+ );
13404
13769
  const debug = this.debugEnabled;
13405
13770
  for (const msg of msgList.msgs) {
13406
13771
  const extraContents = (msg.extraContents ?? []).map((ec) => {
@@ -13427,6 +13792,9 @@ var IMClient = class {
13427
13792
  timestamp: msg.ackTs ? Number(msg.ackTs) : msg.jetTs ? Number(msg.jetTs) : 0
13428
13793
  };
13429
13794
  if (debug) {
13795
+ this.debug(
13796
+ ` #${pulledMsg.id} ${pulledMsg.type} ${pulledMsg.role} from ${pulledMsg.senderCid} (${pulledMsg.content.length} chars)`
13797
+ );
13430
13798
  if (msg.blob && msg.blob.length > 0)
13431
13799
  pulledMsg.rawBlob = Buffer.from(msg.blob).toString("base64");
13432
13800
  if (msg.blobExtra && msg.blobExtra.length > 0)
@@ -13437,6 +13805,11 @@ var IMClient = class {
13437
13805
  message: pulledMsg
13438
13806
  });
13439
13807
  }
13808
+ } else {
13809
+ const blobLen = response.blob?.length ?? 0;
13810
+ this.debug(
13811
+ ` \u2190 ${this.opName(op)} ${this.size(blobLen)} (unhandled, no handler registered)`
13812
+ );
13440
13813
  }
13441
13814
  } catch (err) {
13442
13815
  this.debug("handleResponse decode error:", err instanceof Error ? err.message : String(err));
@@ -13451,20 +13824,28 @@ var IMGateway = class {
13451
13824
  }
13452
13825
  config;
13453
13826
  dryRun = false;
13827
+ /** Log debug output when debug mode is enabled. */
13828
+ debug(...args) {
13829
+ if (this.config.debugEnabled) {
13830
+ console.log("[DEBUG]", ...args);
13831
+ }
13832
+ }
13454
13833
  setDryRun(enabled) {
13455
13834
  this.dryRun = enabled;
13456
13835
  }
13457
13836
  /** Execute a function with an IM connection, ensuring connect/close lifecycle. */
13458
13837
  async withConnection(fn) {
13838
+ this.debug("\u2500\u2500 new connection \u2500\u2500");
13459
13839
  const client = new IMClient(this.config, (event) => {
13460
- if (event.type === "error" && this.config.debugEnabled) {
13461
- console.error("[IM] error event:", event.error.message);
13840
+ if (event.type === "error") {
13841
+ this.debug(` \u2717 IM error: ${event.error.message}`);
13462
13842
  }
13463
13843
  });
13464
13844
  try {
13465
13845
  await client.connect();
13466
13846
  return await fn(client);
13467
13847
  } finally {
13848
+ this.debug("\u2500\u2500 connection closed \u2500\u2500");
13468
13849
  client.close();
13469
13850
  }
13470
13851
  }
@@ -13559,7 +13940,7 @@ var IMGateway = class {
13559
13940
  };
13560
13941
  }
13561
13942
  try {
13562
- const result = await this.withConnection((client) => client.queryConversations(options3));
13943
+ const result = await this.withConnection((client) => client.queryConversations());
13563
13944
  let conversations3 = result.conversations.map((conv) => ({
13564
13945
  id: conv.id,
13565
13946
  name: conv.name,
@@ -13573,6 +13954,9 @@ var IMGateway = class {
13573
13954
  extraDecoded: conv.extraDecoded,
13574
13955
  rawExtra: conv.rawExtra
13575
13956
  }));
13957
+ if (options3?.offset && options3.offset > 0) {
13958
+ conversations3 = conversations3.slice(options3.offset);
13959
+ }
13576
13960
  if (options3?.limit && options3.limit > 0) {
13577
13961
  conversations3 = conversations3.slice(0, options3.limit);
13578
13962
  }
@@ -13621,26 +14005,708 @@ var IMGateway = class {
13621
14005
  }
13622
14006
  };
13623
14007
 
14008
+ // src/internal/repository/index.ts
14009
+ init_cjs_shims();
14010
+
14011
+ // src/internal/repository/contact-repository.ts
14012
+ init_cjs_shims();
14013
+ var ApiContactRepository = class {
14014
+ constructor(gateway) {
14015
+ this.gateway = gateway;
14016
+ }
14017
+ gateway;
14018
+ async getLinksContacts() {
14019
+ return this.gateway.getLinksContacts();
14020
+ }
14021
+ invalidate() {
14022
+ }
14023
+ async sync() {
14024
+ }
14025
+ clear() {
14026
+ }
14027
+ async getStatus() {
14028
+ return { count: 0, lastSyncAt: null };
14029
+ }
14030
+ };
14031
+ var SqliteContactRepository = class {
14032
+ db;
14033
+ constructor(db) {
14034
+ this.db = db;
14035
+ }
14036
+ async getLinksContacts() {
14037
+ const rows = this.db.prepare(
14038
+ "SELECT owner_uid, data, cached_at FROM contacts_cache ORDER BY cached_at DESC LIMIT 1"
14039
+ ).all();
14040
+ if (!rows || rows.length === 0) {
14041
+ return fail("NOT_FOUND" /* NOT_FOUND */, "Contacts not found in cache");
14042
+ }
14043
+ return ok(JSON.parse(rows[0].data));
14044
+ }
14045
+ upsert(ownerUid, data) {
14046
+ this.db.prepare("INSERT INTO contacts_cache (owner_uid, data, cached_at) VALUES (?, ?, ?)").run(ownerUid, JSON.stringify(data), Date.now());
14047
+ }
14048
+ invalidate() {
14049
+ this.db.exec("DELETE FROM contacts_cache");
14050
+ }
14051
+ async sync() {
14052
+ }
14053
+ clear() {
14054
+ this.db.exec("DELETE FROM contacts_cache");
14055
+ this.db.exec("DELETE FROM cache_metadata WHERE key = 'last_sync_contacts'");
14056
+ }
14057
+ async getStatus() {
14058
+ const count = this.db.prepare("SELECT COUNT(*) as c FROM contacts_cache").get();
14059
+ const meta = this.db.prepare("SELECT value FROM cache_metadata WHERE key = 'last_sync_contacts'").get();
14060
+ return {
14061
+ count: count.c,
14062
+ lastSyncAt: meta ? parseInt(meta.value, 10) : null
14063
+ };
14064
+ }
14065
+ };
14066
+ var CachedContactRepository = class {
14067
+ constructor(local, remote, ttlMs = 5 * 60 * 1e3) {
14068
+ this.local = local;
14069
+ this.remote = remote;
14070
+ this.ttlMs = ttlMs;
14071
+ }
14072
+ local;
14073
+ remote;
14074
+ ttlMs;
14075
+ async getLinksContacts() {
14076
+ const cached = await this.local.getLinksContacts();
14077
+ if (cached.ok) {
14078
+ const status = await this.local.getStatus();
14079
+ if (status.lastSyncAt !== null && Date.now() - status.lastSyncAt < this.ttlMs) {
14080
+ return cached;
14081
+ }
14082
+ }
14083
+ const remote = await this.remote.getLinksContacts();
14084
+ if (remote.ok) {
14085
+ this.local.upsert("", remote.data);
14086
+ this.local.db.prepare("INSERT OR REPLACE INTO cache_metadata (key, value) VALUES (?, ?)").run("last_sync_contacts", String(Date.now()));
14087
+ }
14088
+ return remote;
14089
+ }
14090
+ invalidate() {
14091
+ this.local.invalidate();
14092
+ }
14093
+ async sync() {
14094
+ const result = await this.remote.getLinksContacts();
14095
+ if (result.ok) {
14096
+ this.local.upsert("", result.data);
14097
+ this.local.db.prepare("INSERT OR REPLACE INTO cache_metadata (key, value) VALUES (?, ?)").run("last_sync_contacts", String(Date.now()));
14098
+ }
14099
+ }
14100
+ clear() {
14101
+ this.local.clear();
14102
+ }
14103
+ async getStatus() {
14104
+ return this.local.getStatus();
14105
+ }
14106
+ };
14107
+
14108
+ // src/internal/repository/conversation-repository.ts
14109
+ init_cjs_shims();
14110
+ var ApiConversationRepository = class {
14111
+ constructor(gateway, imGateway) {
14112
+ this.gateway = gateway;
14113
+ this.imGateway = imGateway;
14114
+ }
14115
+ gateway;
14116
+ imGateway;
14117
+ async queryAll(options3) {
14118
+ const result = await this.imGateway.queryConversations(options3);
14119
+ if (result.status === "error") {
14120
+ return fail(result.code, result.message);
14121
+ }
14122
+ return ok(result.data?.conversations ?? []);
14123
+ }
14124
+ async getProfile(conversationId, conversationType) {
14125
+ return this.gateway.getProfileByConversationId(conversationId, conversationType);
14126
+ }
14127
+ async searchByUid(uid) {
14128
+ const listResult = await this.imGateway.queryConversations({ limit: 0, offset: 0 });
14129
+ if (listResult.status === "error") {
14130
+ return fail(listResult.code, listResult.message);
14131
+ }
14132
+ const conversations3 = listResult.data?.conversations ?? [];
14133
+ const results = [];
14134
+ for (const conv of conversations3) {
14135
+ const profileResult = await this.gateway.getProfileByConversationId(conv.id);
14136
+ if (!profileResult.ok) continue;
14137
+ const hasTarget = profileResult.data.users.some((u) => u.uid === uid);
14138
+ if (hasTarget) {
14139
+ results.push(profileResult.data);
14140
+ }
14141
+ }
14142
+ return ok(results);
14143
+ }
14144
+ invalidate(_conversationId) {
14145
+ }
14146
+ async sync() {
14147
+ }
14148
+ clear() {
14149
+ }
14150
+ async getStatus() {
14151
+ return { count: 0, lastSyncAt: null };
14152
+ }
14153
+ };
14154
+ var SqliteConversationRepository = class {
14155
+ db;
14156
+ constructor(db) {
14157
+ this.db = db;
14158
+ }
14159
+ queryAll(_options) {
14160
+ return Promise.resolve(ok([]));
14161
+ }
14162
+ getProfile(conversationId) {
14163
+ const row = this.db.prepare("SELECT data FROM conversation_profiles WHERE conversation_id = ?").get(conversationId);
14164
+ if (!row) {
14165
+ return Promise.resolve(
14166
+ fail("NOT_FOUND" /* NOT_FOUND */, "Conversation profile not found in cache")
14167
+ );
14168
+ }
14169
+ return Promise.resolve(ok(JSON.parse(row.data)));
14170
+ }
14171
+ searchByUid(uid) {
14172
+ const rows = this.db.prepare(
14173
+ "SELECT data FROM conversation_profiles WHERE user_ids LIKE ? OR user_ids LIKE ? OR user_ids LIKE ? OR user_ids = ?"
14174
+ ).all(`%${uid},%`, `%,${uid},%`, `%,${uid}`, uid);
14175
+ const results = rows.map((r) => JSON.parse(r.data));
14176
+ return Promise.resolve(ok(results));
14177
+ }
14178
+ upsertProfile(conversationId, profile) {
14179
+ const user_ids = profile.users.map((u) => u.uid).join(",");
14180
+ const cached_at = Date.now();
14181
+ const data = JSON.stringify(profile);
14182
+ this.db.prepare(
14183
+ "INSERT INTO conversation_profiles (conversation_id, data, user_ids, cached_at) VALUES (?, ?, ?, ?) ON CONFLICT(conversation_id) DO UPDATE SET data=?, user_ids=?, cached_at=?"
14184
+ ).run(conversationId, data, user_ids, cached_at, data, user_ids, cached_at);
14185
+ }
14186
+ invalidate(conversationId) {
14187
+ this.db.prepare("DELETE FROM conversation_profiles WHERE conversation_id = ?").run(conversationId);
14188
+ }
14189
+ sync() {
14190
+ return Promise.resolve();
14191
+ }
14192
+ clear() {
14193
+ this.db.exec("DELETE FROM conversation_profiles");
14194
+ this.db.exec("DELETE FROM cache_metadata WHERE key = 'last_sync_conversations'");
14195
+ }
14196
+ getStatus() {
14197
+ const count = this.db.prepare("SELECT COUNT(*) as c FROM conversation_profiles").get();
14198
+ const meta = this.db.prepare("SELECT value FROM cache_metadata WHERE key = 'last_sync_conversations'").get();
14199
+ return Promise.resolve({
14200
+ count: count.c,
14201
+ lastSyncAt: meta ? parseInt(meta.value, 10) : null
14202
+ });
14203
+ }
14204
+ };
14205
+ var CachedConversationRepository = class {
14206
+ constructor(local, remote) {
14207
+ this.local = local;
14208
+ this.remote = remote;
14209
+ }
14210
+ local;
14211
+ remote;
14212
+ syncInProgress = false;
14213
+ async queryAll(options3) {
14214
+ return this.remote.queryAll(options3);
14215
+ }
14216
+ async getProfile(conversationId, conversationType) {
14217
+ const cached = await this.local.getProfile(conversationId);
14218
+ if (cached.ok) return cached;
14219
+ const remote = await this.remote.getProfile(conversationId, conversationType);
14220
+ if (remote.ok) {
14221
+ this.local.upsertProfile(conversationId, remote.data);
14222
+ }
14223
+ return remote;
14224
+ }
14225
+ async searchByUid(uid) {
14226
+ const cached = await this.local.searchByUid(uid);
14227
+ if (cached.ok && cached.data.length > 0) return cached;
14228
+ await this.ensureSynced();
14229
+ const retry = await this.local.searchByUid(uid);
14230
+ return retry;
14231
+ }
14232
+ async ensureSynced() {
14233
+ if (this.syncInProgress) return;
14234
+ const status = await this.local.getStatus();
14235
+ const isFresh = status.lastSyncAt !== null && Date.now() - status.lastSyncAt < 5 * 60 * 1e3;
14236
+ if (isFresh) return;
14237
+ this.syncInProgress = true;
14238
+ try {
14239
+ await this.sync();
14240
+ } finally {
14241
+ this.syncInProgress = false;
14242
+ }
14243
+ }
14244
+ async sync() {
14245
+ const convResult = await this.remote.queryAll({ limit: 0, offset: 0 });
14246
+ if (!convResult.ok || !convResult.data) return;
14247
+ for (const conv of convResult.data) {
14248
+ const profileResult = await this.remote.getProfile(conv.id);
14249
+ if (profileResult.ok) {
14250
+ this.local.upsertProfile(conv.id, profileResult.data);
14251
+ }
14252
+ }
14253
+ this.local.db.prepare("INSERT OR REPLACE INTO cache_metadata (key, value) VALUES (?, ?)").run("last_sync_conversations", String(Date.now()));
14254
+ }
14255
+ invalidate(conversationId) {
14256
+ this.local.invalidate(conversationId);
14257
+ }
14258
+ clear() {
14259
+ this.local.clear();
14260
+ }
14261
+ async getStatus() {
14262
+ return this.local.getStatus();
14263
+ }
14264
+ };
14265
+
14266
+ // src/internal/repository/message-repository.ts
14267
+ init_cjs_shims();
14268
+ var ApiMessageRepository = class {
14269
+ constructor(imGateway) {
14270
+ this.imGateway = imGateway;
14271
+ }
14272
+ imGateway;
14273
+ async pullMessages(conversationId, options3) {
14274
+ const result = await this.imGateway.pullMessages(conversationId, options3);
14275
+ if (result.status === "error") {
14276
+ return fail(result.code, result.message);
14277
+ }
14278
+ return ok({
14279
+ messages: result.data?.messages ?? [],
14280
+ finished: result.data?.finished ?? true,
14281
+ lastId: result.data?.lastId ?? ""
14282
+ });
14283
+ }
14284
+ invalidate(_conversationId) {
14285
+ }
14286
+ async sync(_conversationId) {
14287
+ }
14288
+ clear() {
14289
+ }
14290
+ };
14291
+ var SqliteMessageRepository = class {
14292
+ db;
14293
+ constructor(db) {
14294
+ this.db = db;
14295
+ }
14296
+ async pullMessages(conversationId, options3) {
14297
+ const limit = options3?.limit ?? 50;
14298
+ const fromId = options3?.fromMessageId;
14299
+ let sql = "SELECT message_id, data FROM messages_cache WHERE conversation_id = ?";
14300
+ const params = [conversationId];
14301
+ if (fromId) {
14302
+ if (options3?.direction === "newer") {
14303
+ sql += " AND message_id > ?";
14304
+ params.push(fromId);
14305
+ } else {
14306
+ sql += " AND message_id < ?";
14307
+ params.push(fromId);
14308
+ }
14309
+ }
14310
+ sql += " ORDER BY message_id DESC LIMIT ?";
14311
+ params.push(limit);
14312
+ const rows = this.db.prepare(sql).all(...params);
14313
+ if (rows.length === 0) {
14314
+ return fail("NOT_FOUND" /* NOT_FOUND */, "Messages not found in cache");
14315
+ }
14316
+ return ok({
14317
+ messages: rows.map((r) => JSON.parse(r.data)),
14318
+ finished: rows.length < limit,
14319
+ lastId: rows[rows.length - 1]?.message_id ?? ""
14320
+ });
14321
+ }
14322
+ upsert(conversationId, messageId, data) {
14323
+ this.db.prepare(
14324
+ "INSERT INTO messages_cache (conversation_id, message_id, data, cached_at) VALUES (?, ?, ?, ?) ON CONFLICT(conversation_id, message_id) DO UPDATE SET data=?, cached_at=?"
14325
+ ).run(
14326
+ conversationId,
14327
+ messageId,
14328
+ JSON.stringify(data),
14329
+ Date.now(),
14330
+ JSON.stringify(data),
14331
+ Date.now()
14332
+ );
14333
+ }
14334
+ invalidate(conversationId) {
14335
+ this.db.prepare("DELETE FROM messages_cache WHERE conversation_id = ?").run(conversationId);
14336
+ }
14337
+ async sync(_conversationId) {
14338
+ }
14339
+ clear() {
14340
+ this.db.exec("DELETE FROM messages_cache");
14341
+ }
14342
+ };
14343
+ var CachedMessageRepository = class {
14344
+ constructor(local, remote) {
14345
+ this.local = local;
14346
+ this.remote = remote;
14347
+ }
14348
+ local;
14349
+ remote;
14350
+ async pullMessages(conversationId, options3) {
14351
+ const cached = await this.local.pullMessages(conversationId, options3);
14352
+ if (cached.ok) return cached;
14353
+ const remote = await this.remote.pullMessages(conversationId, options3);
14354
+ if (remote.ok) {
14355
+ for (const msg of remote.data.messages) {
14356
+ const messageId = msg.id;
14357
+ if (messageId) {
14358
+ this.local.upsert(conversationId, messageId, msg);
14359
+ }
14360
+ }
14361
+ }
14362
+ return remote;
14363
+ }
14364
+ invalidate(conversationId) {
14365
+ this.local.invalidate(conversationId);
14366
+ }
14367
+ async sync(conversationId) {
14368
+ const result = await this.remote.pullMessages(conversationId, { limit: 100 });
14369
+ if (result.ok) {
14370
+ for (const msg of result.data.messages) {
14371
+ const messageId = msg.id;
14372
+ if (messageId) {
14373
+ this.local.upsert(conversationId, messageId, msg);
14374
+ }
14375
+ }
14376
+ }
14377
+ }
14378
+ clear() {
14379
+ this.local.clear();
14380
+ }
14381
+ };
14382
+
14383
+ // src/internal/repository/relation-repository.ts
14384
+ init_cjs_shims();
14385
+ var ApiRelationRepository = class {
14386
+ constructor(gateway) {
14387
+ this.gateway = gateway;
14388
+ }
14389
+ gateway;
14390
+ async getLinkNameType(friendUid) {
14391
+ return this.gateway.getLinkNameType(friendUid);
14392
+ }
14393
+ invalidate(_friendUid) {
14394
+ }
14395
+ async sync() {
14396
+ }
14397
+ clear() {
14398
+ }
14399
+ };
14400
+ var SqliteRelationRepository = class {
14401
+ db;
14402
+ constructor(db) {
14403
+ this.db = db;
14404
+ }
14405
+ async getLinkNameType(friendUid) {
14406
+ const row = this.db.prepare("SELECT name, type, cached_at FROM relations_cache WHERE friend_uid = ?").get(friendUid);
14407
+ if (!row) return fail("NOT_FOUND" /* NOT_FOUND */, "Relation not found in cache");
14408
+ return ok({ name: row.name, type: row.type });
14409
+ }
14410
+ upsert(friendUid, name, type) {
14411
+ this.db.prepare(
14412
+ "INSERT OR REPLACE INTO relations_cache (friend_uid, name, type, cached_at) VALUES (?, ?, ?, ?)"
14413
+ ).run(friendUid, name, type, Date.now());
14414
+ }
14415
+ invalidate(friendUid) {
14416
+ this.db.prepare("DELETE FROM relations_cache WHERE friend_uid = ?").run(friendUid);
14417
+ }
14418
+ async sync() {
14419
+ }
14420
+ clear() {
14421
+ this.db.exec("DELETE FROM relations_cache");
14422
+ }
14423
+ };
14424
+ var CachedRelationRepository = class {
14425
+ constructor(local, remote, ttlMs = 5 * 60 * 1e3) {
14426
+ this.local = local;
14427
+ this.remote = remote;
14428
+ this.ttlMs = ttlMs;
14429
+ }
14430
+ local;
14431
+ remote;
14432
+ ttlMs;
14433
+ async getLinkNameType(friendUid) {
14434
+ const row = this.local.db.prepare("SELECT name, type, cached_at FROM relations_cache WHERE friend_uid = ?").get(friendUid);
14435
+ if (row && Date.now() - row.cached_at < this.ttlMs) {
14436
+ return ok({ name: row.name, type: row.type });
14437
+ }
14438
+ const remote = await this.remote.getLinkNameType(friendUid);
14439
+ if (remote.ok) {
14440
+ this.local.upsert(friendUid, remote.data.name, remote.data.type);
14441
+ }
14442
+ return remote;
14443
+ }
14444
+ invalidate(friendUid) {
14445
+ this.local.invalidate(friendUid);
14446
+ }
14447
+ async sync() {
14448
+ }
14449
+ clear() {
14450
+ this.local.clear();
14451
+ }
14452
+ };
14453
+
14454
+ // src/internal/repository/sqlite.ts
14455
+ init_cjs_shims();
14456
+ var import_node_path5 = require("path");
14457
+ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
14458
+ var SCHEMA_VERSION = 1;
14459
+ var CREATE_TABLES = `
14460
+ CREATE TABLE IF NOT EXISTS conversation_profiles (
14461
+ conversation_id TEXT PRIMARY KEY,
14462
+ data TEXT NOT NULL,
14463
+ user_ids TEXT NOT NULL,
14464
+ cached_at INTEGER NOT NULL
14465
+ );
14466
+ CREATE INDEX IF NOT EXISTS idx_conv_user_ids ON conversation_profiles(user_ids);
14467
+
14468
+ CREATE TABLE IF NOT EXISTS contacts_cache (
14469
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14470
+ owner_uid TEXT NOT NULL,
14471
+ data TEXT NOT NULL,
14472
+ cached_at INTEGER NOT NULL
14473
+ );
14474
+ CREATE INDEX IF NOT EXISTS idx_contacts_owner ON contacts_cache(owner_uid);
14475
+
14476
+ CREATE TABLE IF NOT EXISTS user_profiles (
14477
+ uid TEXT PRIMARY KEY,
14478
+ data TEXT NOT NULL,
14479
+ cached_at INTEGER NOT NULL
14480
+ );
14481
+
14482
+ CREATE TABLE IF NOT EXISTS relations_cache (
14483
+ friend_uid TEXT PRIMARY KEY,
14484
+ name TEXT,
14485
+ type INTEGER,
14486
+ cached_at INTEGER NOT NULL
14487
+ );
14488
+
14489
+ CREATE TABLE IF NOT EXISTS messages_cache (
14490
+ conversation_id TEXT NOT NULL,
14491
+ message_id TEXT NOT NULL,
14492
+ data TEXT NOT NULL,
14493
+ cached_at INTEGER NOT NULL,
14494
+ PRIMARY KEY (conversation_id, message_id)
14495
+ );
14496
+ CREATE INDEX IF NOT EXISTS idx_msg_conv ON messages_cache(conversation_id);
14497
+
14498
+ CREATE TABLE IF NOT EXISTS cache_metadata (
14499
+ key TEXT PRIMARY KEY,
14500
+ value TEXT
14501
+ );
14502
+ `;
14503
+ function resolveDbPaths(configDir, uid) {
14504
+ return {
14505
+ dbFile: (0, import_node_path5.join)(configDir, `cache-${uid}.db`)
14506
+ };
14507
+ }
14508
+ function openDatabase(dbPath) {
14509
+ const db = new import_better_sqlite3.default(dbPath);
14510
+ db.pragma("journal_mode = WAL");
14511
+ db.pragma("foreign_keys = ON");
14512
+ db.exec(CREATE_TABLES);
14513
+ ensureVersion(db);
14514
+ return db;
14515
+ }
14516
+ function closeDatabase(db) {
14517
+ db.close();
14518
+ }
14519
+ function ensureVersion(db) {
14520
+ const row = db.prepare("SELECT value FROM cache_metadata WHERE key = 'schema_version'").get();
14521
+ const current = row ? parseInt(row.value, 10) : 0;
14522
+ if (current < SCHEMA_VERSION) {
14523
+ db.prepare("INSERT OR REPLACE INTO cache_metadata (key, value) VALUES (?, ?)").run(
14524
+ "schema_version",
14525
+ String(SCHEMA_VERSION)
14526
+ );
14527
+ }
14528
+ }
14529
+
14530
+ // src/internal/repository/user-repository.ts
14531
+ init_cjs_shims();
14532
+ var ApiUserRepository = class {
14533
+ constructor(gateway) {
14534
+ this.gateway = gateway;
14535
+ }
14536
+ gateway;
14537
+ findUserById(id) {
14538
+ return this.gateway.findUserById(id);
14539
+ }
14540
+ searchUserById(id) {
14541
+ return this.gateway.searchUserById(id);
14542
+ }
14543
+ getUserAgentProfile(friendUid, agentId) {
14544
+ return this.gateway.getUserAgentProfile(friendUid, agentId);
14545
+ }
14546
+ invalidate(_uid) {
14547
+ }
14548
+ async sync() {
14549
+ }
14550
+ clear() {
14551
+ }
14552
+ };
14553
+ var SqliteUserRepository = class {
14554
+ db;
14555
+ constructor(db) {
14556
+ this.db = db;
14557
+ }
14558
+ async findUserById(id) {
14559
+ const row = this.db.prepare("SELECT data FROM user_profiles WHERE uid = ?").get(id);
14560
+ if (!row) return fail("NOT_FOUND" /* NOT_FOUND */, "User not found in cache");
14561
+ return ok(JSON.parse(row.data));
14562
+ }
14563
+ async searchUserById(id) {
14564
+ const row = this.db.prepare("SELECT data FROM user_profiles WHERE uid = ?").get(id);
14565
+ if (!row) return fail("NOT_FOUND" /* NOT_FOUND */, "User not found in cache");
14566
+ const data = JSON.parse(row.data);
14567
+ return ok({
14568
+ id: data.id,
14569
+ uid: data.uid,
14570
+ nickname: data.nickname,
14571
+ avatar: data.avatar,
14572
+ agent: data.agent
14573
+ });
14574
+ }
14575
+ async getUserAgentProfile(friendUid, _agentId) {
14576
+ const row = this.db.prepare("SELECT data FROM user_profiles WHERE uid = ?").get(friendUid);
14577
+ if (!row) return fail("NOT_FOUND" /* NOT_FOUND */, "User profile not found in cache");
14578
+ return ok(JSON.parse(row.data));
14579
+ }
14580
+ upsert(uid, data) {
14581
+ this.db.prepare("INSERT OR REPLACE INTO user_profiles (uid, data, cached_at) VALUES (?, ?, ?)").run(uid, JSON.stringify(data), Date.now());
14582
+ }
14583
+ invalidate(uid) {
14584
+ this.db.prepare("DELETE FROM user_profiles WHERE uid = ?").run(uid);
14585
+ }
14586
+ async sync() {
14587
+ }
14588
+ clear() {
14589
+ this.db.exec("DELETE FROM user_profiles");
14590
+ }
14591
+ };
14592
+ var CachedUserRepository = class {
14593
+ constructor(local, remote, ttlMs = 60 * 60 * 1e3) {
14594
+ this.local = local;
14595
+ this.remote = remote;
14596
+ this.ttlMs = ttlMs;
14597
+ }
14598
+ local;
14599
+ remote;
14600
+ ttlMs;
14601
+ async findUserById(id) {
14602
+ return this.fetchWithCache(
14603
+ id,
14604
+ () => this.remote.findUserById(id),
14605
+ (data) => this.local.upsert(id, data)
14606
+ );
14607
+ }
14608
+ async searchUserById(id) {
14609
+ return this.fetchWithCache(
14610
+ id,
14611
+ () => this.remote.searchUserById(id),
14612
+ (data) => {
14613
+ this.local.upsert(id, {
14614
+ id: data.id,
14615
+ uid: data.uid,
14616
+ nickname: data.nickname,
14617
+ avatar: data.avatar,
14618
+ title: "",
14619
+ link_type: 0,
14620
+ sex: 0,
14621
+ address: "",
14622
+ status: 0,
14623
+ agent: data.agent
14624
+ });
14625
+ }
14626
+ );
14627
+ }
14628
+ async getUserAgentProfile(friendUid, agentId) {
14629
+ return this.fetchWithCache(
14630
+ friendUid,
14631
+ () => this.remote.getUserAgentProfile(friendUid, agentId),
14632
+ (data) => this.local.upsert(friendUid, data)
14633
+ );
14634
+ }
14635
+ async fetchWithCache(uid, fetch2, store) {
14636
+ const row = this.local.db.prepare("SELECT data, cached_at FROM user_profiles WHERE uid = ?").get(uid);
14637
+ if (row && Date.now() - row.cached_at < this.ttlMs) {
14638
+ return ok(JSON.parse(row.data));
14639
+ }
14640
+ const remote = await fetch2();
14641
+ if (remote.ok) {
14642
+ store(remote.data);
14643
+ }
14644
+ return remote;
14645
+ }
14646
+ invalidate(uid) {
14647
+ this.local.invalidate(uid);
14648
+ }
14649
+ async sync() {
14650
+ }
14651
+ clear() {
14652
+ this.local.clear();
14653
+ }
14654
+ };
14655
+
14656
+ // src/internal/repository/index.ts
14657
+ function createRepositories(configDir, uid, gateway, imGateway) {
14658
+ const paths = resolveDbPaths(configDir, uid);
14659
+ const db = openDatabase(paths.dbFile);
14660
+ const sqliteConv = new SqliteConversationRepository(db);
14661
+ const sqliteContact = new SqliteContactRepository(db);
14662
+ const sqliteUser = new SqliteUserRepository(db);
14663
+ const sqliteRelation = new SqliteRelationRepository(db);
14664
+ const sqliteMessage = new SqliteMessageRepository(db);
14665
+ const apiConv = new ApiConversationRepository(gateway, imGateway);
14666
+ const apiContact = new ApiContactRepository(gateway);
14667
+ const apiUser = new ApiUserRepository(gateway);
14668
+ const apiRelation = new ApiRelationRepository(gateway);
14669
+ const apiMessage = new ApiMessageRepository(imGateway);
14670
+ const conversation = new CachedConversationRepository(sqliteConv, apiConv);
14671
+ const contact = new CachedContactRepository(sqliteContact, apiContact);
14672
+ const user = new CachedUserRepository(sqliteUser, apiUser);
14673
+ const relation = new CachedRelationRepository(sqliteRelation, apiRelation);
14674
+ const message = new CachedMessageRepository(sqliteMessage, apiMessage);
14675
+ return {
14676
+ conversation,
14677
+ contact,
14678
+ user,
14679
+ relation,
14680
+ message,
14681
+ close: () => closeDatabase(db)
14682
+ };
14683
+ }
14684
+
13624
14685
  // src/factory.ts
13625
14686
  function createFactory(config) {
13626
14687
  const client = new APIClient(config.baseURL, config.debug);
13627
14688
  if (config.uid && config.tk && config.token) {
13628
14689
  client.setAuth({ uid: config.uid, tk: config.tk, token: config.token });
13629
14690
  }
14691
+ const gateway = new ApiGateway(client);
14692
+ const imGateway = new IMGateway({
14693
+ wsUrl: config.wsURL,
14694
+ cid: config.cid,
14695
+ debugEnabled: config.debug
14696
+ });
14697
+ const configDir = getConfigDirectory(detectEnvironment());
14698
+ const repos = createRepositories(configDir, config.uid ?? "anonymous", gateway, imGateway);
13630
14699
  return {
13631
- gateway: new ApiGateway(client),
13632
- imGateway: new IMGateway({
13633
- wsUrl: config.wsURL,
13634
- cid: config.cid,
13635
- debugEnabled: config.debug
13636
- }),
13637
- config
14700
+ gateway,
14701
+ imGateway,
14702
+ config,
14703
+ repos
13638
14704
  };
13639
14705
  }
13640
14706
 
13641
14707
  // src/cmd.ts
13642
14708
  var logDebugEnabled = false;
13643
- var LocalizedHelp = class extends import_commander8.Help {
14709
+ var LocalizedHelp = class extends import_commander9.Help {
13644
14710
  /** Format an argument name with <required> or [optional] brackets. */
13645
14711
  argDisplayName(arg) {
13646
14712
  const name = arg.name() + (arg.variadic === true ? "..." : "");
@@ -13683,7 +14749,7 @@ async function main() {
13683
14749
  logDebugEnabled = config.debug;
13684
14750
  setLogDebug(config.debug);
13685
14751
  const factory = createFactory(config);
13686
- const program = new import_commander8.Command();
14752
+ const program = new import_commander9.Command();
13687
14753
  const testModeBanner = `
13688
14754
  \u2554\u2550\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2550\u2557
13689
14755
  \u2551 DEVELOPMENT BUILD \u2551
@@ -13694,7 +14760,7 @@ async function main() {
13694
14760
  ${t("cli.banner")}` : t("cli.banner");
13695
14761
  program.addHelpText("beforeAll", `${banner}
13696
14762
  `);
13697
- program.name("qz").version(`v${"0.2.1"}`, "-v, --version", t("options.version")).helpOption("-h, --help", t("options.help")).option("-q, --jq <expr>", t("options.jq")).option("--dry-run", t("options.dryRun"));
14763
+ program.name("qz").version(`v${"0.3.0-rc.1"}`, "-v, --version", t("options.version")).helpOption("-h, --help", t("options.help")).option("-q, --jq <expr>", t("options.jq")).option("--dry-run", t("options.dryRun"));
13698
14764
  program.usage("<command> [subcommand] [options]");
13699
14765
  program.hook("preAction", () => {
13700
14766
  const opts = program.opts();
@@ -13704,6 +14770,7 @@ ${t("cli.banner")}` : t("cli.banner");
13704
14770
  factory.gateway.setDryRun(dryRun2);
13705
14771
  factory.imGateway.setDryRun(dryRun2);
13706
14772
  });
14773
+ program.addCommand(NewCmdCache(factory));
13707
14774
  program.addCommand(NewCmdAuth(factory));
13708
14775
  program.addCommand(NewCmdConfig(factory));
13709
14776
  program.addCommand(NewCmdUser(factory));