@qzhuli/qzhuli-cli 0.2.0 → 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 = {
@@ -11435,14 +11469,191 @@ function tf(key, values) {
11435
11469
 
11436
11470
  // src/iostreams.ts
11437
11471
  init_cjs_shims();
11472
+
11473
+ // src/internal/config/index.ts
11474
+ init_cjs_shims();
11475
+
11476
+ // src/internal/config/credentials.ts
11477
+ init_cjs_shims();
11478
+ var import_node_fs = require("fs");
11479
+ var import_node_path = require("path");
11480
+ function loadCredentials(configDir) {
11481
+ const path = (0, import_node_path.join)(configDir, "credentials.json");
11482
+ if (!(0, import_node_fs.existsSync)(path)) {
11483
+ return null;
11484
+ }
11485
+ try {
11486
+ const raw = (0, import_node_fs.readFileSync)(path, "utf-8");
11487
+ return JSON.parse(raw);
11488
+ } catch {
11489
+ console.error("[WARN] credentials.json is corrupted, skipping \u2014 please re-authenticate");
11490
+ return null;
11491
+ }
11492
+ }
11493
+ function saveCredentials(configDir, creds) {
11494
+ const path = (0, import_node_path.join)(configDir, "credentials.json");
11495
+ ensureConfigDirectory(configDir);
11496
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(creds, null, 2), {
11497
+ encoding: "utf-8",
11498
+ mode: 384
11499
+ });
11500
+ }
11501
+ function ensureConfigDirectory(configDir) {
11502
+ if (!(0, import_node_fs.existsSync)(configDir)) {
11503
+ (0, import_node_fs.mkdirSync)(configDir, { recursive: true });
11504
+ }
11505
+ }
11506
+ function clearCredentials(configDir) {
11507
+ const path = (0, import_node_path.join)(configDir, "credentials.json");
11508
+ if ((0, import_node_fs.existsSync)(path)) {
11509
+ (0, import_node_fs.rmSync)(path);
11510
+ }
11511
+ }
11512
+ function hasCredentials(configDir) {
11513
+ const creds = loadCredentials(configDir);
11514
+ return !!(creds?.uid && creds?.tk && creds?.token);
11515
+ }
11516
+
11517
+ // src/internal/config/directory.ts
11518
+ init_cjs_shims();
11519
+ var import_node_os = require("os");
11520
+ var import_node_path2 = require("path");
11521
+ var PROJECT_CONFIG_DIR = (0, import_node_path2.resolve)(process.cwd(), ".qzhuli-cli");
11522
+ var USER_CONFIG_DIR = (0, import_node_path2.resolve)((0, import_node_os.homedir)(), ".qzhuli-cli");
11523
+ function getConfigDirectory(env) {
11524
+ return env === "production" ? USER_CONFIG_DIR : PROJECT_CONFIG_DIR;
11525
+ }
11526
+
11527
+ // src/internal/config/environment.ts
11528
+ init_cjs_shims();
11529
+ var TEST_CONFIG = {
11530
+ name: "test",
11531
+ baseURL: "https://test.client.qzhuli.com",
11532
+ wsURL: "wss://test.im.qzhuli.com/ws"
11533
+ };
11534
+ var PRODUCTION_CONFIG = {
11535
+ name: "production",
11536
+ baseURL: "https://client.qzhuli.com",
11537
+ wsURL: "wss://im.qzhuli.com/ws"
11538
+ };
11539
+ var ENV_MAP = {
11540
+ test: TEST_CONFIG,
11541
+ production: PRODUCTION_CONFIG
11542
+ };
11543
+ function getEnvironmentConfig(name) {
11544
+ return ENV_MAP[name];
11545
+ }
11546
+ function detectEnvironment() {
11547
+ return false ? "test" : "production";
11548
+ }
11549
+
11550
+ // src/internal/config/preferences.ts
11551
+ init_cjs_shims();
11552
+ var import_node_fs2 = require("fs");
11553
+ var import_node_path3 = require("path");
11554
+ var DEFAULTS = {
11555
+ locale: "zh"
11556
+ };
11557
+ function loadPreferences(configDir) {
11558
+ const path = (0, import_node_path3.join)(configDir, "preferences.json");
11559
+ if (!(0, import_node_fs2.existsSync)(path)) {
11560
+ return { ...DEFAULTS };
11561
+ }
11562
+ try {
11563
+ const raw = (0, import_node_fs2.readFileSync)(path, "utf-8");
11564
+ const parsed = JSON.parse(raw);
11565
+ return { ...DEFAULTS, ...parsed };
11566
+ } catch {
11567
+ return { ...DEFAULTS };
11568
+ }
11569
+ }
11570
+ function savePreferences(configDir, updates) {
11571
+ if (isDryRun()) {
11572
+ return;
11573
+ }
11574
+ const current = loadPreferences(configDir);
11575
+ const merged = { ...current, ...updates };
11576
+ const path = (0, import_node_path3.join)(configDir, "preferences.json");
11577
+ ensureConfigDirectory2(configDir);
11578
+ (0, import_node_fs2.writeFileSync)(path, JSON.stringify(merged, null, 2), {
11579
+ encoding: "utf-8",
11580
+ mode: 384
11581
+ });
11582
+ }
11583
+ function ensureConfigDirectory2(configDir) {
11584
+ if (!(0, import_node_fs2.existsSync)(configDir)) {
11585
+ (0, import_node_fs2.mkdirSync)(configDir, { recursive: true });
11586
+ }
11587
+ }
11588
+ function initDefaultPreferences(configDir) {
11589
+ const path = (0, import_node_path3.join)(configDir, "preferences.json");
11590
+ if (!(0, import_node_fs2.existsSync)(path)) {
11591
+ ensureConfigDirectory2(configDir);
11592
+ (0, import_node_fs2.writeFileSync)(path, JSON.stringify(DEFAULTS, null, 2), {
11593
+ encoding: "utf-8",
11594
+ mode: 384
11595
+ });
11596
+ }
11597
+ }
11598
+
11599
+ // src/internal/config/index.ts
11600
+ function loadAppConfig(env) {
11601
+ const envName = env ?? detectEnvironment();
11602
+ const configDir = getConfigDirectory(envName);
11603
+ initDefaultPreferences(configDir);
11604
+ const envConfig = getEnvironmentConfig(envName);
11605
+ const prefs = loadPreferences(configDir);
11606
+ const creds = loadCredentials(configDir);
11607
+ const uid = creds?.uid || null;
11608
+ const tk = creds?.tk || null;
11609
+ const token = creds?.token || null;
11610
+ const cid = creds?.cid || null;
11611
+ return {
11612
+ uid,
11613
+ tk,
11614
+ token,
11615
+ cid,
11616
+ locale: prefs.locale ?? "en",
11617
+ debug: prefs.debug ?? false,
11618
+ baseURL: envConfig.baseURL,
11619
+ wsURL: envConfig.wsURL
11620
+ };
11621
+ }
11622
+
11623
+ // src/internal/config/logs.ts
11624
+ init_cjs_shims();
11625
+ var import_node_fs3 = require("fs");
11626
+ var import_node_path4 = require("path");
11627
+ function appendCliRunLog(configDir, entry, now = /* @__PURE__ */ new Date()) {
11628
+ const logDir = (0, import_node_path4.join)(configDir, "logs");
11629
+ (0, import_node_fs3.mkdirSync)(logDir, { recursive: true });
11630
+ (0, import_node_fs3.appendFileSync)(
11631
+ (0, import_node_path4.join)(logDir, `${formatLocalDate(now)}.log`),
11632
+ `${JSON.stringify(entry)}
11633
+ `,
11634
+ "utf-8"
11635
+ );
11636
+ }
11637
+ function formatLocalDate(value) {
11638
+ const year = value.getFullYear();
11639
+ const month = String(value.getMonth() + 1).padStart(2, "0");
11640
+ const day = String(value.getDate()).padStart(2, "0");
11641
+ return `${year}-${month}-${day}`;
11642
+ }
11643
+
11644
+ // src/iostreams.ts
11438
11645
  var jqFilter = null;
11439
11646
  var dryRun = false;
11647
+ var logDebug = false;
11440
11648
  function setJqFilter(expr) {
11441
11649
  jqFilter = expr;
11442
11650
  }
11443
11651
  function setDryRun(enabled) {
11444
11652
  dryRun = enabled;
11445
11653
  }
11654
+ function setLogDebug(enabled) {
11655
+ logDebug = enabled;
11656
+ }
11446
11657
  function isDryRun() {
11447
11658
  return dryRun;
11448
11659
  }
@@ -11473,11 +11684,34 @@ function printStatus(status) {
11473
11684
  `);
11474
11685
  }
11475
11686
  function handleCommand(result) {
11687
+ appendStatusLog(result);
11476
11688
  printStatus(result);
11477
11689
  if (!isDryRun() && result.status === "error") {
11478
11690
  process.exit(1);
11479
11691
  }
11480
11692
  }
11693
+ function appendStatusLog(status) {
11694
+ const argv = process.argv.slice(2);
11695
+ try {
11696
+ appendCliRunLog(getConfigDirectory(detectEnvironment()), {
11697
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11698
+ argv,
11699
+ command: argv.join(" "),
11700
+ status: status.status,
11701
+ code: status.code,
11702
+ message: status.message,
11703
+ data: status.data,
11704
+ dryRun
11705
+ });
11706
+ } catch (error) {
11707
+ if (logDebug) {
11708
+ process.stderr.write(
11709
+ `[DEBUG] Failed to write CLI log: ${error instanceof Error ? error.message : String(error)}
11710
+ `
11711
+ );
11712
+ }
11713
+ }
11714
+ }
11481
11715
 
11482
11716
  // src/commands/auth/login.ts
11483
11717
  init_cjs_shims();
@@ -11914,156 +12148,6 @@ function promptRefresh(controller, onReset) {
11914
12148
  });
11915
12149
  }
11916
12150
 
11917
- // src/internal/config/index.ts
11918
- init_cjs_shims();
11919
-
11920
- // src/internal/config/credentials.ts
11921
- init_cjs_shims();
11922
- var import_node_fs = require("fs");
11923
- var import_node_path = require("path");
11924
- function loadCredentials(configDir) {
11925
- const path = (0, import_node_path.join)(configDir, "credentials.json");
11926
- if (!(0, import_node_fs.existsSync)(path)) {
11927
- return null;
11928
- }
11929
- try {
11930
- const raw = (0, import_node_fs.readFileSync)(path, "utf-8");
11931
- return JSON.parse(raw);
11932
- } catch {
11933
- console.error("[WARN] credentials.json is corrupted, skipping \u2014 please re-authenticate");
11934
- return null;
11935
- }
11936
- }
11937
- function saveCredentials(configDir, creds) {
11938
- const path = (0, import_node_path.join)(configDir, "credentials.json");
11939
- ensureConfigDirectory(configDir);
11940
- (0, import_node_fs.writeFileSync)(path, JSON.stringify(creds, null, 2), {
11941
- encoding: "utf-8",
11942
- mode: 384
11943
- });
11944
- }
11945
- function ensureConfigDirectory(configDir) {
11946
- if (!(0, import_node_fs.existsSync)(configDir)) {
11947
- (0, import_node_fs.mkdirSync)(configDir, { recursive: true });
11948
- }
11949
- }
11950
- function clearCredentials(configDir) {
11951
- const path = (0, import_node_path.join)(configDir, "credentials.json");
11952
- if ((0, import_node_fs.existsSync)(path)) {
11953
- (0, import_node_fs.rmSync)(path);
11954
- }
11955
- }
11956
- function hasCredentials(configDir) {
11957
- const creds = loadCredentials(configDir);
11958
- return !!(creds?.uid && creds?.tk && creds?.token);
11959
- }
11960
-
11961
- // src/internal/config/directory.ts
11962
- init_cjs_shims();
11963
- var import_node_os = require("os");
11964
- var import_node_path2 = require("path");
11965
- var PROJECT_CONFIG_DIR = (0, import_node_path2.resolve)(process.cwd(), ".qzhuli-cli");
11966
- var USER_CONFIG_DIR = (0, import_node_path2.resolve)((0, import_node_os.homedir)(), ".qzhuli-cli");
11967
- function getConfigDirectory(env) {
11968
- return env === "production" ? USER_CONFIG_DIR : PROJECT_CONFIG_DIR;
11969
- }
11970
-
11971
- // src/internal/config/environment.ts
11972
- init_cjs_shims();
11973
- var TEST_CONFIG = {
11974
- name: "test",
11975
- baseURL: "https://test.client.qzhuli.com",
11976
- wsURL: "wss://test.im.qzhuli.com/ws"
11977
- };
11978
- var PRODUCTION_CONFIG = {
11979
- name: "production",
11980
- baseURL: "https://client.qzhuli.com",
11981
- wsURL: "wss://im.qzhuli.com/ws"
11982
- };
11983
- var ENV_MAP = {
11984
- test: TEST_CONFIG,
11985
- production: PRODUCTION_CONFIG
11986
- };
11987
- function getEnvironmentConfig(name) {
11988
- return ENV_MAP[name];
11989
- }
11990
- function detectEnvironment() {
11991
- return false ? "test" : "production";
11992
- }
11993
-
11994
- // src/internal/config/preferences.ts
11995
- init_cjs_shims();
11996
- var import_node_fs2 = require("fs");
11997
- var import_node_path3 = require("path");
11998
- var DEFAULTS = {
11999
- locale: "zh"
12000
- };
12001
- function loadPreferences(configDir) {
12002
- const path = (0, import_node_path3.join)(configDir, "preferences.json");
12003
- if (!(0, import_node_fs2.existsSync)(path)) {
12004
- return { ...DEFAULTS };
12005
- }
12006
- try {
12007
- const raw = (0, import_node_fs2.readFileSync)(path, "utf-8");
12008
- const parsed = JSON.parse(raw);
12009
- return { ...DEFAULTS, ...parsed };
12010
- } catch {
12011
- return { ...DEFAULTS };
12012
- }
12013
- }
12014
- function savePreferences(configDir, updates) {
12015
- if (isDryRun()) {
12016
- return;
12017
- }
12018
- const current = loadPreferences(configDir);
12019
- const merged = { ...current, ...updates };
12020
- const path = (0, import_node_path3.join)(configDir, "preferences.json");
12021
- ensureConfigDirectory2(configDir);
12022
- (0, import_node_fs2.writeFileSync)(path, JSON.stringify(merged, null, 2), {
12023
- encoding: "utf-8",
12024
- mode: 384
12025
- });
12026
- }
12027
- function ensureConfigDirectory2(configDir) {
12028
- if (!(0, import_node_fs2.existsSync)(configDir)) {
12029
- (0, import_node_fs2.mkdirSync)(configDir, { recursive: true });
12030
- }
12031
- }
12032
- function initDefaultPreferences(configDir) {
12033
- const path = (0, import_node_path3.join)(configDir, "preferences.json");
12034
- if (!(0, import_node_fs2.existsSync)(path)) {
12035
- ensureConfigDirectory2(configDir);
12036
- (0, import_node_fs2.writeFileSync)(path, JSON.stringify(DEFAULTS, null, 2), {
12037
- encoding: "utf-8",
12038
- mode: 384
12039
- });
12040
- }
12041
- }
12042
-
12043
- // src/internal/config/index.ts
12044
- function loadAppConfig(env) {
12045
- const envName = env ?? detectEnvironment();
12046
- const configDir = getConfigDirectory(envName);
12047
- initDefaultPreferences(configDir);
12048
- const envConfig = getEnvironmentConfig(envName);
12049
- const prefs = loadPreferences(configDir);
12050
- const creds = loadCredentials(configDir);
12051
- const uid = creds?.uid || null;
12052
- const tk = creds?.tk || null;
12053
- const token = creds?.token || null;
12054
- const cid = creds?.cid || null;
12055
- return {
12056
- uid,
12057
- tk,
12058
- token,
12059
- cid,
12060
- locale: prefs.locale ?? "en",
12061
- debug: prefs.debug ?? false,
12062
- baseURL: envConfig.baseURL,
12063
- wsURL: envConfig.wsURL
12064
- };
12065
- }
12066
-
12067
12151
  // src/commands/auth/login.ts
12068
12152
  async function authLoginRun(factory, opts) {
12069
12153
  try {
@@ -12192,10 +12276,121 @@ function NewCmdAuth(factory) {
12192
12276
  return cmd;
12193
12277
  }
12194
12278
 
12195
- // src/commands/config/index.ts
12279
+ // src/commands/cache/index.ts
12196
12280
  init_cjs_shims();
12197
12281
  var import_commander2 = require("commander");
12198
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
+
12199
12394
  // src/commands/config/get.ts
12200
12395
  init_cjs_shims();
12201
12396
  function configGetRun(_factory, opts) {
@@ -12223,7 +12418,7 @@ function configGetRun(_factory, opts) {
12223
12418
 
12224
12419
  // src/commands/config/index.ts
12225
12420
  function NewCmdConfig(factory) {
12226
- 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"));
12227
12422
  cmd.option("--locale <language>", t("commands.config.localeOption")).option("--debug", t("commands.config.debugOn")).option("--no-debug", t("commands.config.debugOff")).action((opts) => {
12228
12423
  const result = configGetRun(factory, {
12229
12424
  locale: opts.locale,
@@ -12236,7 +12431,7 @@ function NewCmdConfig(factory) {
12236
12431
 
12237
12432
  // src/commands/conversation/index.ts
12238
12433
  init_cjs_shims();
12239
- var import_commander3 = require("commander");
12434
+ var import_commander4 = require("commander");
12240
12435
 
12241
12436
  // src/commands/conversation/create.ts
12242
12437
  init_cjs_shims();
@@ -12257,28 +12452,166 @@ async function conversationCreateRun(factory, opts) {
12257
12452
  data: result.data
12258
12453
  };
12259
12454
  }
12260
-
12261
- // src/commands/conversation/list.ts
12262
- init_cjs_shims();
12263
- async function conversationListRun(factory, opts) {
12264
- const result = await factory.imGateway.queryConversations({ limit: opts.limit });
12265
- if (result.status === "error" && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12455
+
12456
+ // src/commands/conversation/list.ts
12457
+ init_cjs_shims();
12458
+ async function conversationListRun(factory, opts) {
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 */) {
12464
+ return {
12465
+ status: "error",
12466
+ code: "AUTH_FAILED" /* AUTH_FAILED */,
12467
+ message: t("auth.notAuthenticated"),
12468
+ data: null
12469
+ };
12470
+ }
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) {
12266
12559
  return {
12267
12560
  status: "error",
12268
- code: "AUTH_FAILED" /* AUTH_FAILED */,
12269
- message: t("auth.notAuthenticated"),
12561
+ code: userResult.code,
12562
+ message: userResult.code === "AUTH_FAILED" /* AUTH_FAILED */ ? t("auth.notAuthenticated") : userResult.message,
12270
12563
  data: null
12271
12564
  };
12272
12565
  }
12273
- return result;
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
+ };
12274
12599
  }
12275
12600
 
12276
12601
  // src/commands/conversation/index.ts
12277
12602
  function NewCmdConversation(factory) {
12278
- const cmd = new import_commander3.Command("conversation").description(t("commands.conversation.desc"));
12279
- 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) => {
12280
12605
  const result = await conversationListRun(factory, {
12281
- 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
12282
12615
  });
12283
12616
  handleCommand(result);
12284
12617
  });
@@ -12286,17 +12619,21 @@ function NewCmdConversation(factory) {
12286
12619
  const result = await conversationCreateRun(factory, { uid, agentId: opts.agentId });
12287
12620
  handleCommand(result);
12288
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
+ });
12289
12626
  return cmd;
12290
12627
  }
12291
12628
 
12292
12629
  // src/commands/friend/index.ts
12293
12630
  init_cjs_shims();
12294
- var import_commander4 = require("commander");
12631
+ var import_commander5 = require("commander");
12295
12632
 
12296
12633
  // src/commands/friend/list.ts
12297
12634
  init_cjs_shims();
12298
12635
  async function friendListRun(factory) {
12299
- const result = await factory.gateway.getLinksContacts();
12636
+ const result = await factory.repos.contact.getLinksContacts();
12300
12637
  if (!result.ok) {
12301
12638
  return {
12302
12639
  status: "error",
@@ -12326,7 +12663,7 @@ async function friendListRun(factory) {
12326
12663
  // src/commands/friend/profile.ts
12327
12664
  init_cjs_shims();
12328
12665
  async function friendProfileRun(factory, opts) {
12329
- const listResult = await factory.gateway.getLinksContacts();
12666
+ const listResult = await factory.repos.contact.getLinksContacts();
12330
12667
  if (!listResult.ok) {
12331
12668
  return {
12332
12669
  status: "error",
@@ -12411,7 +12748,7 @@ async function friendProfileRun(factory, opts) {
12411
12748
  }
12412
12749
  };
12413
12750
  }
12414
- const profileResult = await factory.gateway.getUserAgentProfile(match.uid);
12751
+ const profileResult = await factory.repos.user.getUserAgentProfile(match.uid);
12415
12752
  if (!profileResult.ok) {
12416
12753
  return {
12417
12754
  status: "error",
@@ -12444,7 +12781,7 @@ async function friendProfileRun(factory, opts) {
12444
12781
 
12445
12782
  // src/commands/friend/index.ts
12446
12783
  function NewCmdFriend(factory) {
12447
- 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"));
12448
12785
  cmd.command("list").description(t("commands.friend.listDesc")).action(async () => {
12449
12786
  const result = await friendListRun(factory);
12450
12787
  handleCommand(result);
@@ -12462,7 +12799,7 @@ function NewCmdFriend(factory) {
12462
12799
 
12463
12800
  // src/commands/message/index.ts
12464
12801
  init_cjs_shims();
12465
- var import_commander5 = require("commander");
12802
+ var import_commander6 = require("commander");
12466
12803
 
12467
12804
  // src/commands/message/history.ts
12468
12805
  init_cjs_shims();
@@ -12472,8 +12809,8 @@ async function messageHistoryRun(factory, opts) {
12472
12809
  direction: opts.direction,
12473
12810
  limit: opts.limit
12474
12811
  };
12475
- const result = await factory.imGateway.pullMessages(opts.conversationId, pullOpts);
12476
- 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 */) {
12477
12814
  return {
12478
12815
  status: "error",
12479
12816
  code: "AUTH_FAILED" /* AUTH_FAILED */,
@@ -12481,7 +12818,12 @@ async function messageHistoryRun(factory, opts) {
12481
12818
  data: null
12482
12819
  };
12483
12820
  }
12484
- 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
+ };
12485
12827
  }
12486
12828
 
12487
12829
  // src/commands/message/send.ts
@@ -12505,7 +12847,7 @@ async function messageSendRun(factory, opts) {
12505
12847
 
12506
12848
  // src/commands/message/index.ts
12507
12849
  function NewCmdMessage(factory) {
12508
- 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"));
12509
12851
  cmd.command("send <conversation-id> <target-cid> <content>").description(t("commands.message.sendDesc")).action(async (conversationId, targetCid, content) => {
12510
12852
  const result = await messageSendRun(factory, { conversationId, targetCid, content });
12511
12853
  handleCommand(result);
@@ -12524,12 +12866,12 @@ function NewCmdMessage(factory) {
12524
12866
 
12525
12867
  // src/commands/relation/index.ts
12526
12868
  init_cjs_shims();
12527
- var import_commander6 = require("commander");
12869
+ var import_commander7 = require("commander");
12528
12870
 
12529
12871
  // src/commands/relation/get.ts
12530
12872
  init_cjs_shims();
12531
12873
  async function relationGetRun(factory, opts) {
12532
- const result = await factory.gateway.getLinkNameType(opts.friendUid);
12874
+ const result = await factory.repos.relation.getLinkNameType(opts.friendUid);
12533
12875
  if (!result.ok) {
12534
12876
  return {
12535
12877
  status: "error",
@@ -12597,7 +12939,7 @@ async function relationSetRun(factory, opts) {
12597
12939
 
12598
12940
  // src/commands/relation/index.ts
12599
12941
  function NewCmdRelation(factory) {
12600
- 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"));
12601
12943
  cmd.command("get <uid>").description(t("commands.relation.getDesc")).action(async (uid) => {
12602
12944
  const result = await relationGetRun(factory, { friendUid: uid });
12603
12945
  handleCommand(result);
@@ -12615,7 +12957,7 @@ function NewCmdRelation(factory) {
12615
12957
 
12616
12958
  // src/commands/user/index.ts
12617
12959
  init_cjs_shims();
12618
- var import_commander7 = require("commander");
12960
+ var import_commander8 = require("commander");
12619
12961
 
12620
12962
  // src/commands/user/add.ts
12621
12963
  init_cjs_shims();
@@ -12670,7 +13012,7 @@ async function userAddRun(factory, opts) {
12670
13012
  // src/commands/user/search.ts
12671
13013
  init_cjs_shims();
12672
13014
  async function userSearchRun(factory, opts) {
12673
- 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);
12674
13016
  if (!result.ok) {
12675
13017
  return {
12676
13018
  status: "error",
@@ -12698,7 +13040,7 @@ async function userSearchRun(factory, opts) {
12698
13040
 
12699
13041
  // src/commands/user/index.ts
12700
13042
  function NewCmdUser(factory) {
12701
- 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"));
12702
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) => {
12703
13045
  const result = await userSearchRun(factory, { query, byUid: opts.uid });
12704
13046
  handleCommand(result);
@@ -12837,6 +13179,23 @@ var ApiGateway = class {
12837
13179
  friend_agent_id: friendAgentId
12838
13180
  });
12839
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
+ }
12840
13199
  };
12841
13200
 
12842
13201
  // src/internal/api-client.ts
@@ -13049,6 +13408,33 @@ var IMClient = class {
13049
13408
  console.log("[DEBUG]", ...args);
13050
13409
  }
13051
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
+ }
13052
13438
  isConnected() {
13053
13439
  return this.connected;
13054
13440
  }
@@ -13056,9 +13442,11 @@ var IMClient = class {
13056
13442
  async connect() {
13057
13443
  return new Promise((resolve2, reject) => {
13058
13444
  const wsUrl = this.buildWsUrl();
13445
+ this.debug(`\u2192 CONNECT ${wsUrl}, cid=${this.config.cid}`);
13059
13446
  this.ws = new import_ws.default(wsUrl);
13060
13447
  this.ws.binaryType = "arraybuffer";
13061
13448
  this.ws.on("open", () => {
13449
+ this.debug(" \u2713 Connected");
13062
13450
  clearTimeout(timeout);
13063
13451
  this.connected = true;
13064
13452
  this.onEvent({ type: "connected" });
@@ -13066,13 +13454,17 @@ var IMClient = class {
13066
13454
  resolve2();
13067
13455
  });
13068
13456
  this.ws.on("message", (data) => {
13069
- 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);
13070
13460
  });
13071
- this.ws.on("close", (_code, reason) => {
13461
+ this.ws.on("close", (code, reason) => {
13462
+ this.debug(` \u2717 Closed (code: ${code}, reason: ${reason.toString()})`);
13072
13463
  this.connected = false;
13073
13464
  this.onEvent({ type: "disconnected", reason: reason.toString() });
13074
13465
  });
13075
13466
  this.ws.on("error", (err) => {
13467
+ this.debug(` \u2717 Error: ${err instanceof Error ? err.message : String(err)}`);
13076
13468
  this.onEvent({ type: "error", error: err });
13077
13469
  reject(err);
13078
13470
  });
@@ -13085,6 +13477,7 @@ var IMClient = class {
13085
13477
  /** Close the IM connection. */
13086
13478
  close() {
13087
13479
  if (this.ws) {
13480
+ this.debug(`\u2192 CLOSE`);
13088
13481
  this.ws.close();
13089
13482
  this.ws = null;
13090
13483
  this.connected = false;
@@ -13124,13 +13517,21 @@ var IMClient = class {
13124
13517
  const handler = (ack) => {
13125
13518
  clearTimeout(timeout);
13126
13519
  this.ws?.off("message", onMessage);
13520
+ this.debug(
13521
+ ` \u2713 Ack, msg=${ack.messageId}, blocked=${ack.blocked}, moderated=${ack.moderated}`
13522
+ );
13127
13523
  resolve2(ack);
13128
13524
  };
13129
13525
  const onMessage = (data2) => {
13130
13526
  const response = this.root.IMResponse.decode(new Uint8Array(data2));
13131
- if (response.operation === this.root.IMOperation.Ack || response.operation === this.root.IMOperation.ConversationMessageSend) {
13132
- 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) {
13133
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
+ );
13134
13535
  handler({
13135
13536
  messageId: String(ack.id),
13136
13537
  conversationId: ack.conversationId,
@@ -13142,14 +13543,15 @@ var IMClient = class {
13142
13543
  ackTs: ack.ackTs ? Number(ack.ackTs) : 0
13143
13544
  });
13144
13545
  } else {
13145
- this.debug("sendMessage ack received with empty blob, operation:", response.operation);
13546
+ this.debug(` \u2190 ${this.opName(op)} (empty blob, ignored)`);
13146
13547
  }
13147
13548
  } else {
13148
- this.debug("sendMessage ignored message, operation:", response.operation);
13549
+ this.debug(` \u2190 ${this.opName(op)} ${this.size(blobLen)} (ignored, not ack)`);
13149
13550
  }
13150
13551
  };
13151
13552
  this.ws?.on("message", onMessage);
13152
13553
  const data = this.root.IMRequest.encode(request).finish();
13554
+ this.debug(`\u2192 ${this.opName(2e3)} ${this.size(data.length)} to: ${targetCid}`);
13153
13555
  this.ws?.send(Buffer.from(data));
13154
13556
  });
13155
13557
  }
@@ -13180,6 +13582,7 @@ var IMClient = class {
13180
13582
  clearTimeout(timeout);
13181
13583
  this.ws?.off("message", onMessage);
13182
13584
  if (!response.blob || response.blob.length === 0) {
13585
+ this.debug(` \u2190 ${this.opName(2001)} (empty, no messages)`);
13183
13586
  resolve2({
13184
13587
  messages: [],
13185
13588
  finished: true,
@@ -13194,6 +13597,9 @@ var IMClient = class {
13194
13597
  return;
13195
13598
  }
13196
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
+ );
13197
13603
  const debug = this.debugEnabled;
13198
13604
  const messages3 = [];
13199
13605
  for (const msg of msgList.msgs) {
@@ -13221,6 +13627,9 @@ var IMClient = class {
13221
13627
  timestamp: msg.ackTs ? Number(msg.ackTs) : msg.jetTs ? Number(msg.jetTs) : 0
13222
13628
  };
13223
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
+ );
13224
13633
  if (msg.blob && msg.blob.length > 0)
13225
13634
  pulledMsg.rawBlob = Buffer.from(msg.blob).toString("base64");
13226
13635
  if (msg.blobExtra && msg.blobExtra.length > 0)
@@ -13228,6 +13637,7 @@ var IMClient = class {
13228
13637
  }
13229
13638
  messages3.push(pulledMsg);
13230
13639
  }
13640
+ this.debug(` \u2713 ${messages3.length} messages resolved`);
13231
13641
  resolve2({
13232
13642
  messages: messages3,
13233
13643
  finished: msgList.finished,
@@ -13251,13 +13661,11 @@ var IMClient = class {
13251
13661
  });
13252
13662
  }
13253
13663
  /** Query all conversations for the user. */
13254
- async queryConversations(options3) {
13664
+ async queryConversations() {
13255
13665
  if (!this.ws || !this.connected) {
13256
13666
  throw new Error("Not connected to IM server");
13257
13667
  }
13258
- const queryBlob = this.root.ConversationQueryParams.create({
13259
- limit: options3?.limit ?? 0
13260
- });
13668
+ const queryBlob = this.root.ConversationQueryParams.create({});
13261
13669
  const request = this.root.IMRequest.create({
13262
13670
  operation: this.root.IMOperation.ConversationQuery,
13263
13671
  cid: this.config.cid,
@@ -13274,6 +13682,7 @@ var IMClient = class {
13274
13682
  clearTimeout(timeout);
13275
13683
  this.ws?.off("message", onMessage);
13276
13684
  if (!response.blob || response.blob.length === 0) {
13685
+ this.debug(` \u2190 ${this.opName(1003)} (empty, no conversations)`);
13277
13686
  resolve2({
13278
13687
  conversations: [],
13279
13688
  limit: 0,
@@ -13284,6 +13693,7 @@ var IMClient = class {
13284
13693
  return;
13285
13694
  }
13286
13695
  const list = this.root.ConversationList.decode(new Uint8Array(response.blob));
13696
+ this.debug(` \u2190 ${this.opName(1003)} ${list.convs?.length ?? 0} conversations`);
13287
13697
  const debug = this.debugEnabled;
13288
13698
  const conversations3 = [];
13289
13699
  for (const conv of list.convs ?? []) {
@@ -13344,13 +13754,18 @@ var IMClient = class {
13344
13754
  ts: Date.now()
13345
13755
  });
13346
13756
  const data = this.root.IMRequest.encode(request).finish();
13757
+ this.debug(`\u2192 ${this.opName(1e3)} ${this.size(data.length)}, cid=${this.config.cid}`);
13347
13758
  this.ws.send(Buffer.from(data));
13348
13759
  }
13349
13760
  handleResponse(data) {
13350
13761
  try {
13351
13762
  const response = this.root.IMResponse.decode(new Uint8Array(data));
13352
- if (response.operation === this.root.IMOperation.ConversationMessagePush) {
13763
+ const op = response.operation;
13764
+ if (op === this.root.IMOperation.ConversationMessagePush) {
13353
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
+ );
13354
13769
  const debug = this.debugEnabled;
13355
13770
  for (const msg of msgList.msgs) {
13356
13771
  const extraContents = (msg.extraContents ?? []).map((ec) => {
@@ -13377,6 +13792,9 @@ var IMClient = class {
13377
13792
  timestamp: msg.ackTs ? Number(msg.ackTs) : msg.jetTs ? Number(msg.jetTs) : 0
13378
13793
  };
13379
13794
  if (debug) {
13795
+ this.debug(
13796
+ ` #${pulledMsg.id} ${pulledMsg.type} ${pulledMsg.role} from ${pulledMsg.senderCid} (${pulledMsg.content.length} chars)`
13797
+ );
13380
13798
  if (msg.blob && msg.blob.length > 0)
13381
13799
  pulledMsg.rawBlob = Buffer.from(msg.blob).toString("base64");
13382
13800
  if (msg.blobExtra && msg.blobExtra.length > 0)
@@ -13387,6 +13805,11 @@ var IMClient = class {
13387
13805
  message: pulledMsg
13388
13806
  });
13389
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
+ );
13390
13813
  }
13391
13814
  } catch (err) {
13392
13815
  this.debug("handleResponse decode error:", err instanceof Error ? err.message : String(err));
@@ -13401,20 +13824,28 @@ var IMGateway = class {
13401
13824
  }
13402
13825
  config;
13403
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
+ }
13404
13833
  setDryRun(enabled) {
13405
13834
  this.dryRun = enabled;
13406
13835
  }
13407
13836
  /** Execute a function with an IM connection, ensuring connect/close lifecycle. */
13408
13837
  async withConnection(fn) {
13838
+ this.debug("\u2500\u2500 new connection \u2500\u2500");
13409
13839
  const client = new IMClient(this.config, (event) => {
13410
- if (event.type === "error" && this.config.debugEnabled) {
13411
- console.error("[IM] error event:", event.error.message);
13840
+ if (event.type === "error") {
13841
+ this.debug(` \u2717 IM error: ${event.error.message}`);
13412
13842
  }
13413
13843
  });
13414
13844
  try {
13415
13845
  await client.connect();
13416
13846
  return await fn(client);
13417
13847
  } finally {
13848
+ this.debug("\u2500\u2500 connection closed \u2500\u2500");
13418
13849
  client.close();
13419
13850
  }
13420
13851
  }
@@ -13509,7 +13940,7 @@ var IMGateway = class {
13509
13940
  };
13510
13941
  }
13511
13942
  try {
13512
- const result = await this.withConnection((client) => client.queryConversations(options3));
13943
+ const result = await this.withConnection((client) => client.queryConversations());
13513
13944
  let conversations3 = result.conversations.map((conv) => ({
13514
13945
  id: conv.id,
13515
13946
  name: conv.name,
@@ -13523,13 +13954,16 @@ var IMGateway = class {
13523
13954
  extraDecoded: conv.extraDecoded,
13524
13955
  rawExtra: conv.rawExtra
13525
13956
  }));
13957
+ if (options3?.offset && options3.offset > 0) {
13958
+ conversations3 = conversations3.slice(options3.offset);
13959
+ }
13526
13960
  if (options3?.limit && options3.limit > 0) {
13527
13961
  conversations3 = conversations3.slice(0, options3.limit);
13528
13962
  }
13529
13963
  return {
13530
13964
  status: "success",
13531
13965
  code: "CONVERSATION_LIST" /* CONVERSATION_LIST */,
13532
- message: conversations3.length > 0 ? "Conversations ({count}):" : "No conversations found.",
13966
+ message: conversations3.length > 0 ? `Conversations (${conversations3.length}):` : "No conversations found.",
13533
13967
  data: { conversations: conversations3 }
13534
13968
  };
13535
13969
  } catch (error) {
@@ -13571,25 +14005,708 @@ var IMGateway = class {
13571
14005
  }
13572
14006
  };
13573
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
+
13574
14685
  // src/factory.ts
13575
14686
  function createFactory(config) {
13576
14687
  const client = new APIClient(config.baseURL, config.debug);
13577
14688
  if (config.uid && config.tk && config.token) {
13578
14689
  client.setAuth({ uid: config.uid, tk: config.tk, token: config.token });
13579
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);
13580
14699
  return {
13581
- gateway: new ApiGateway(client),
13582
- imGateway: new IMGateway({
13583
- wsUrl: config.wsURL,
13584
- cid: config.cid,
13585
- debugEnabled: config.debug
13586
- }),
13587
- config
14700
+ gateway,
14701
+ imGateway,
14702
+ config,
14703
+ repos
13588
14704
  };
13589
14705
  }
13590
14706
 
13591
14707
  // src/cmd.ts
13592
- var LocalizedHelp = class extends import_commander8.Help {
14708
+ var logDebugEnabled = false;
14709
+ var LocalizedHelp = class extends import_commander9.Help {
13593
14710
  /** Format an argument name with <required> or [optional] brackets. */
13594
14711
  argDisplayName(arg) {
13595
14712
  const name = arg.name() + (arg.variadic === true ? "..." : "");
@@ -13629,8 +14746,10 @@ async function main() {
13629
14746
  const envName = detectEnvironment();
13630
14747
  const config = loadAppConfig(envName);
13631
14748
  setLocale(config.locale);
14749
+ logDebugEnabled = config.debug;
14750
+ setLogDebug(config.debug);
13632
14751
  const factory = createFactory(config);
13633
- const program = new import_commander8.Command();
14752
+ const program = new import_commander9.Command();
13634
14753
  const testModeBanner = `
13635
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
13636
14755
  \u2551 DEVELOPMENT BUILD \u2551
@@ -13641,7 +14760,7 @@ async function main() {
13641
14760
  ${t("cli.banner")}` : t("cli.banner");
13642
14761
  program.addHelpText("beforeAll", `${banner}
13643
14762
  `);
13644
- program.name("qz").version(`v${"0.2.0"}`, "-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"));
13645
14764
  program.usage("<command> [subcommand] [options]");
13646
14765
  program.hook("preAction", () => {
13647
14766
  const opts = program.opts();
@@ -13651,6 +14770,7 @@ ${t("cli.banner")}` : t("cli.banner");
13651
14770
  factory.gateway.setDryRun(dryRun2);
13652
14771
  factory.imGateway.setDryRun(dryRun2);
13653
14772
  });
14773
+ program.addCommand(NewCmdCache(factory));
13654
14774
  program.addCommand(NewCmdAuth(factory));
13655
14775
  program.addCommand(NewCmdConfig(factory));
13656
14776
  program.addCommand(NewCmdUser(factory));
@@ -13667,6 +14787,27 @@ ${t("messages.helpFooter")}
13667
14787
  await program.parseAsync(process.argv);
13668
14788
  }
13669
14789
  main().catch((err) => {
13670
- console.error(`Error: ${err.message}`);
14790
+ const envName = detectEnvironment();
14791
+ const message = err instanceof Error ? err.message : String(err);
14792
+ try {
14793
+ appendCliRunLog(getConfigDirectory(envName), {
14794
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14795
+ argv: process.argv.slice(2),
14796
+ command: process.argv.slice(2).join(" "),
14797
+ status: "error",
14798
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
14799
+ message,
14800
+ data: null,
14801
+ dryRun: process.argv.includes("--dry-run")
14802
+ });
14803
+ } catch (logError) {
14804
+ if (logDebugEnabled) {
14805
+ process.stderr.write(
14806
+ `[DEBUG] Failed to write CLI log: ${logError instanceof Error ? logError.message : String(logError)}
14807
+ `
14808
+ );
14809
+ }
14810
+ }
14811
+ console.error(`Error: ${message}`);
13671
14812
  process.exit(1);
13672
14813
  });