@qzhuli/qzhuli-cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,38 +28,35 @@ npx qz --version
28
28
  The CLI ships with a `SKILL.md` that teaches AI agents how to use it. During `npm install -g`, the skill is
29
29
  automatically installed to your agent's skill directory.
30
30
 
31
- ### Supported agents (18)
31
+ ### Install skill to your agent
32
32
 
33
- Claude Code, Cursor, Codex, Copilot, Windsurf, OpenClaw, OpenCode, Cline, Gemini CLI, Amp, Roo, Goose, Kiro, Qwen Code,
34
- Qoder, Trae, Augment, Zed.
33
+ ```bash
34
+ npx -p @qzhuli/qzhuli-cli qz-install-skill --target <your-agent-skill-dir>
35
+ ```
35
36
 
36
- ### Manual skill install
37
+ Replace `<your-agent-skill-dir>` with your agent's skill path (e.g. `~/.claude/skills` for Claude Code, `~/.agents/skills` as universal fallback).
37
38
 
38
- If the automatic install didn't work (e.g. local `npm install` without `-g`):
39
+ ### Check skill version
39
40
 
40
41
  ```bash
41
- # Install to all detected agents (global)
42
- npx -p @qzhuli/qzhuli-cli qz-install-skill
42
+ npx -p @qzhuli/qzhuli-cli qz-install-skill --version
43
+ ```
43
44
 
44
- # Install to current project's skill directory
45
- npx -p @qzhuli/qzhuli-cli qz-install-skill --project
45
+ ### Update installed skills
46
46
 
47
- # Install to a specific agent only
48
- npx -p @qzhuli/qzhuli-cli qz-install-skill --agent claude-code
47
+ If a newer version is available, update all installed skills:
49
48
 
50
- # Update existing installs if skill version changed
49
+ ```bash
51
50
  npx -p @qzhuli/qzhuli-cli qz-install-skill --update
52
51
  ```
53
52
 
54
- ### Updating
53
+ ### Verify installation
55
54
 
56
- ```bash
57
- npm update -g @qzhuli/qzhuli-cli
58
- npx -p @qzhuli/qzhuli-cli qz-install-skill --update
59
- ```
55
+ After install or update, confirm everything works:
60
56
 
61
- The postinstall script auto-installs the skill on `npm install -g`. Use the `npx` command above to update skills without
62
- reinstalling the whole package.
57
+ 1. `qz --version` CLI version matches the installed package
58
+ 2. `npx -p @qzhuli/qzhuli-cli qz-install-skill --version` — skill version is current
59
+ 3. Check that `SKILL.md` exists in your agent's skill directory and contains valid `name` and `version` fields
63
60
 
64
61
  ## Commands
65
62
 
@@ -92,7 +89,7 @@ qz conversation list [--limit <n>] [--offset <n>] # List conversations, def
92
89
  qz conversation profile <conversation-id> [--type <n>] # Get conversation details with user profiles
93
90
 
94
91
  # Messages
95
- qz message send <conversation-id> <target-cid> <content>
92
+ qz message send <conversation-id> <content> [--role <n>] # 0=Assistant(default), 1=User
96
93
  qz message history <conversation-id> [--from <id>] [--direction newer|older] [--limit <n>]
97
94
  ```
98
95
 
package/dist/cmd.js CHANGED
@@ -11529,12 +11529,14 @@ init_cjs_shims();
11529
11529
  var TEST_CONFIG = {
11530
11530
  name: "test",
11531
11531
  baseURL: "https://test.client.qzhuli.com",
11532
- wsURL: "wss://test.im.qzhuli.com/ws"
11532
+ imBaseURL: "https://test.im.qzhuli.com",
11533
+ imWsURL: "wss://test.im.qzhuli.com/ws"
11533
11534
  };
11534
11535
  var PRODUCTION_CONFIG = {
11535
11536
  name: "production",
11536
11537
  baseURL: "https://client.qzhuli.com",
11537
- wsURL: "wss://im.qzhuli.com/ws"
11538
+ imBaseURL: "https://im.qzhuli.com",
11539
+ imWsURL: "wss://im.qzhuli.com/ws"
11538
11540
  };
11539
11541
  var ENV_MAP = {
11540
11542
  test: TEST_CONFIG,
@@ -11616,7 +11618,8 @@ function loadAppConfig(env) {
11616
11618
  locale: prefs.locale ?? "en",
11617
11619
  debug: prefs.debug ?? false,
11618
11620
  baseURL: envConfig.baseURL,
11619
- wsURL: envConfig.wsURL
11621
+ imBaseURL: envConfig.imBaseURL,
11622
+ imWsURL: envConfig.imWsURL
11620
11623
  };
11621
11624
  }
11622
11625
 
@@ -12829,12 +12832,8 @@ async function messageHistoryRun(factory, opts) {
12829
12832
  // src/commands/message/send.ts
12830
12833
  init_cjs_shims();
12831
12834
  async function messageSendRun(factory, opts) {
12832
- const result = await factory.imGateway.sendMessage(
12833
- opts.conversationId,
12834
- opts.targetCid,
12835
- opts.content
12836
- );
12837
- if (result.status === "error" && result.code === "AUTH_FAILED" /* AUTH_FAILED */) {
12835
+ const cid = factory.config.cid;
12836
+ if (!cid) {
12838
12837
  return {
12839
12838
  status: "error",
12840
12839
  code: "AUTH_FAILED" /* AUTH_FAILED */,
@@ -12842,14 +12841,39 @@ async function messageSendRun(factory, opts) {
12842
12841
  data: null
12843
12842
  };
12844
12843
  }
12845
- return result;
12844
+ const req = {
12845
+ sender_cid: cid,
12846
+ conv_id: opts.conversationId,
12847
+ content: opts.content,
12848
+ role: opts.role ?? 0,
12849
+ msg_type: 0 /* Text */
12850
+ };
12851
+ const result = await factory.gateway.pushMessage(req);
12852
+ if (!result.ok) {
12853
+ return {
12854
+ status: "error",
12855
+ code: result.code,
12856
+ message: result.message,
12857
+ data: null
12858
+ };
12859
+ }
12860
+ return {
12861
+ status: "success",
12862
+ code: "MESSAGE_SENT" /* MESSAGE_SENT */,
12863
+ message: result.data.message,
12864
+ data: null
12865
+ };
12846
12866
  }
12847
12867
 
12848
12868
  // src/commands/message/index.ts
12849
12869
  function NewCmdMessage(factory) {
12850
12870
  const cmd = new import_commander6.Command("message").description(t("commands.message.desc"));
12851
- cmd.command("send <conversation-id> <target-cid> <content>").description(t("commands.message.sendDesc")).action(async (conversationId, targetCid, content) => {
12852
- const result = await messageSendRun(factory, { conversationId, targetCid, content });
12871
+ cmd.command("send <conversation-id> <content>").description(t("commands.message.sendDesc")).option("--role <n>", "Sender role: 0=Assistant, 1=User (default 0)", "0").action(async (conversationId, content, options3) => {
12872
+ const result = await messageSendRun(factory, {
12873
+ conversationId,
12874
+ content,
12875
+ role: parseInt(options3.role, 10)
12876
+ });
12853
12877
  handleCommand(result);
12854
12878
  });
12855
12879
  cmd.command("history <conversation-id>").description(t("commands.message.historyDesc")).option("--from <id>", t("commands.message.fromOption")).option("--direction <newer|older>", t("commands.message.directionOption"), "older").option("--limit <n>", t("commands.message.limitOption"), "20").action(async (conversationId, options3) => {
@@ -13055,150 +13079,7 @@ function NewCmdUser(factory) {
13055
13079
  // src/factory.ts
13056
13080
  init_cjs_shims();
13057
13081
 
13058
- // src/internal/api/api-gateway.ts
13059
- init_cjs_shims();
13060
- var ApiGateway = class {
13061
- constructor(client) {
13062
- this.client = client;
13063
- }
13064
- client;
13065
- setDryRun(enabled) {
13066
- this.client.setDryRun(enabled);
13067
- }
13068
- /** Check auth and return uid or a fail result. */
13069
- requireAuth() {
13070
- const uid = this.client.getAuthUid();
13071
- if (!uid) {
13072
- return {
13073
- ok: false,
13074
- code: "AUTH_FAILED" /* AUTH_FAILED */,
13075
- message: "Authentication required."
13076
- };
13077
- }
13078
- return { ok: true, data: uid };
13079
- }
13080
- // MARK: Friends / Contacts
13081
- /** POST /user/get_links_contacts — auth required */
13082
- getLinksContacts() {
13083
- const auth3 = this.requireAuth();
13084
- if (!auth3.ok) return Promise.resolve(auth3);
13085
- return this.client.performRequest("/user/get_links_contacts", "POST", {
13086
- uid: auth3.data
13087
- });
13088
- }
13089
- // MARK: Relation
13090
- /** POST /user/get_link_name_type — auth required, body: uid + friend_uid */
13091
- getLinkNameType(friendUid) {
13092
- const auth3 = this.requireAuth();
13093
- if (!auth3.ok) return Promise.resolve(auth3);
13094
- return this.client.performRequest("/user/get_link_name_type", "POST", {
13095
- uid: auth3.data,
13096
- friend_uid: friendUid
13097
- });
13098
- }
13099
- /** POST /user/update_link_name — auth required */
13100
- updateLinkName(friendUid, remark) {
13101
- const auth3 = this.requireAuth();
13102
- if (!auth3.ok) return Promise.resolve(auth3);
13103
- return this.client.performSimpleRequest("/user/update_link_name", "POST", {
13104
- uid: auth3.data,
13105
- friend_uid: friendUid,
13106
- link_name: remark
13107
- });
13108
- }
13109
- /** POST /user/update_link_type — auth required, body: friend_uid + link_type (string) */
13110
- updateLinkType(friendUid, groupType) {
13111
- const auth3 = this.requireAuth();
13112
- if (!auth3.ok) return Promise.resolve(auth3);
13113
- return this.client.performSimpleRequest("/user/update_link_type", "POST", {
13114
- friend_uid: friendUid,
13115
- link_type: String(groupType)
13116
- });
13117
- }
13118
- // MARK: User
13119
- /** POST /user/user_agent_profile — auth required, body: friend_uid + uid (+ agent_id) */
13120
- findUserById(id) {
13121
- const auth3 = this.requireAuth();
13122
- if (!auth3.ok) return Promise.resolve(auth3);
13123
- return this.client.performRequest("/user/user_agent_profile", "POST", {
13124
- friend_uid: id,
13125
- uid: auth3.data
13126
- });
13127
- }
13128
- /** POST /user/user_agent_profile — auth required, body: friend_uid + uid (+ agent_id) */
13129
- getUserAgentProfile(friendUid, agentId) {
13130
- const auth3 = this.requireAuth();
13131
- if (!auth3.ok) return Promise.resolve(auth3);
13132
- const params = {
13133
- friend_uid: friendUid,
13134
- uid: auth3.data
13135
- };
13136
- if (agentId) {
13137
- params.agent_id = agentId;
13138
- }
13139
- return this.client.performRequest("/user/user_agent_profile", "POST", params);
13140
- }
13141
- // MARK: QR Login
13142
- /** POST /user/qr_login_create/3 — no auth required */
13143
- qrLoginCreate() {
13144
- return this.client.performUnauthenticated(
13145
- "/user/qr_login_create/3",
13146
- "POST",
13147
- {}
13148
- );
13149
- }
13150
- /** POST /user/qr_login_status — no auth required */
13151
- qrLoginStatus(sceneId) {
13152
- return this.client.performUnauthenticated("/user/qr_login_status", "POST", {
13153
- scene_id: sceneId
13154
- });
13155
- }
13156
- // MARK: Connectivity
13157
- /** Quick connectivity check — calls the simplest endpoint. */
13158
- async ping() {
13159
- const result = await this.getLinksContacts();
13160
- if (result.ok) {
13161
- return { ok: true, data: true };
13162
- }
13163
- return result;
13164
- }
13165
- // MARK: Friend Add
13166
- /** POST /user/find_user_by_id — search any user by Q助号 */
13167
- searchUserById(id) {
13168
- return this.client.performRequest("/user/find_user_by_id", "POST", {
13169
- id
13170
- });
13171
- }
13172
- /** POST /user/create_conversation — auth required, body: uid + friend_uid + friend_agent_id */
13173
- createConversation(friendUid, friendAgentId) {
13174
- const auth3 = this.requireAuth();
13175
- if (!auth3.ok) return Promise.resolve(auth3);
13176
- return this.client.performRequest("/user/create_conversation", "POST", {
13177
- uid: auth3.data,
13178
- friend_uid: friendUid,
13179
- friend_agent_id: friendAgentId
13180
- });
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
- }
13199
- };
13200
-
13201
- // src/internal/api-client.ts
13082
+ // src/internal/api/api-client.ts
13202
13083
  init_cjs_shims();
13203
13084
  var import_node_crypto = require("crypto");
13204
13085
  var APIClient = class {
@@ -13378,6 +13259,238 @@ function printDryRun(url, method, body) {
13378
13259
  console.log();
13379
13260
  }
13380
13261
 
13262
+ // src/internal/api/api-gateway.ts
13263
+ init_cjs_shims();
13264
+ var ApiGateway = class {
13265
+ constructor(client) {
13266
+ this.client = client;
13267
+ }
13268
+ client;
13269
+ imApiClient = null;
13270
+ /** Inject the IM HTTP client after gateway creation. */
13271
+ setIMApiClient(client) {
13272
+ this.imApiClient = client;
13273
+ }
13274
+ setDryRun(enabled) {
13275
+ this.client.setDryRun(enabled);
13276
+ this.imApiClient?.setDryRun(enabled);
13277
+ }
13278
+ /** Check auth and return uid or a fail result. */
13279
+ requireAuth() {
13280
+ const uid = this.client.getAuthUid();
13281
+ if (!uid) {
13282
+ return {
13283
+ ok: false,
13284
+ code: "AUTH_FAILED" /* AUTH_FAILED */,
13285
+ message: "Authentication required."
13286
+ };
13287
+ }
13288
+ return { ok: true, data: uid };
13289
+ }
13290
+ // MARK: Friends / Contacts
13291
+ /** POST /user/get_links_contacts — auth required */
13292
+ getLinksContacts() {
13293
+ const auth3 = this.requireAuth();
13294
+ if (!auth3.ok) return Promise.resolve(auth3);
13295
+ return this.client.performRequest("/user/get_links_contacts", "POST", {
13296
+ uid: auth3.data
13297
+ });
13298
+ }
13299
+ // MARK: Relation
13300
+ /** POST /user/get_link_name_type — auth required, body: uid + friend_uid */
13301
+ getLinkNameType(friendUid) {
13302
+ const auth3 = this.requireAuth();
13303
+ if (!auth3.ok) return Promise.resolve(auth3);
13304
+ return this.client.performRequest("/user/get_link_name_type", "POST", {
13305
+ uid: auth3.data,
13306
+ friend_uid: friendUid
13307
+ });
13308
+ }
13309
+ /** POST /user/update_link_name — auth required */
13310
+ updateLinkName(friendUid, remark) {
13311
+ const auth3 = this.requireAuth();
13312
+ if (!auth3.ok) return Promise.resolve(auth3);
13313
+ return this.client.performSimpleRequest("/user/update_link_name", "POST", {
13314
+ uid: auth3.data,
13315
+ friend_uid: friendUid,
13316
+ link_name: remark
13317
+ });
13318
+ }
13319
+ /** POST /user/update_link_type — auth required, body: friend_uid + link_type (string) */
13320
+ updateLinkType(friendUid, groupType) {
13321
+ const auth3 = this.requireAuth();
13322
+ if (!auth3.ok) return Promise.resolve(auth3);
13323
+ return this.client.performSimpleRequest("/user/update_link_type", "POST", {
13324
+ friend_uid: friendUid,
13325
+ link_type: String(groupType)
13326
+ });
13327
+ }
13328
+ // MARK: User
13329
+ /** POST /user/user_agent_profile — auth required, body: friend_uid + uid (+ agent_id) */
13330
+ findUserById(id) {
13331
+ const auth3 = this.requireAuth();
13332
+ if (!auth3.ok) return Promise.resolve(auth3);
13333
+ return this.client.performRequest("/user/user_agent_profile", "POST", {
13334
+ friend_uid: id,
13335
+ uid: auth3.data
13336
+ });
13337
+ }
13338
+ /** POST /user/user_agent_profile — auth required, body: friend_uid + uid (+ agent_id) */
13339
+ getUserAgentProfile(friendUid, agentId) {
13340
+ const auth3 = this.requireAuth();
13341
+ if (!auth3.ok) return Promise.resolve(auth3);
13342
+ const params = {
13343
+ friend_uid: friendUid,
13344
+ uid: auth3.data
13345
+ };
13346
+ if (agentId) {
13347
+ params.agent_id = agentId;
13348
+ }
13349
+ return this.client.performRequest("/user/user_agent_profile", "POST", params);
13350
+ }
13351
+ // MARK: QR Login
13352
+ /** POST /user/qr_login_create/3 — no auth required */
13353
+ qrLoginCreate() {
13354
+ return this.client.performUnauthenticated(
13355
+ "/user/qr_login_create/3",
13356
+ "POST",
13357
+ {}
13358
+ );
13359
+ }
13360
+ /** POST /user/qr_login_status — no auth required */
13361
+ qrLoginStatus(sceneId) {
13362
+ return this.client.performUnauthenticated("/user/qr_login_status", "POST", {
13363
+ scene_id: sceneId
13364
+ });
13365
+ }
13366
+ // MARK: Connectivity
13367
+ /** Quick connectivity check — calls the simplest endpoint. */
13368
+ async ping() {
13369
+ const result = await this.getLinksContacts();
13370
+ if (result.ok) {
13371
+ return { ok: true, data: true };
13372
+ }
13373
+ return result;
13374
+ }
13375
+ // MARK: Friend Add
13376
+ /** POST /user/find_user_by_id — search any user by Q助号 */
13377
+ searchUserById(id) {
13378
+ return this.client.performRequest("/user/find_user_by_id", "POST", {
13379
+ id
13380
+ });
13381
+ }
13382
+ /** POST /user/create_conversation — auth required, body: uid + friend_uid + friend_agent_id */
13383
+ createConversation(friendUid, friendAgentId) {
13384
+ const auth3 = this.requireAuth();
13385
+ if (!auth3.ok) return Promise.resolve(auth3);
13386
+ return this.client.performRequest("/user/create_conversation", "POST", {
13387
+ uid: auth3.data,
13388
+ friend_uid: friendUid,
13389
+ friend_agent_id: friendAgentId
13390
+ });
13391
+ }
13392
+ /** POST /user/get_profile_by_conversation_id — auth required */
13393
+ getProfileByConversationId(conversationId, conversationType) {
13394
+ const auth3 = this.requireAuth();
13395
+ if (!auth3.ok) return Promise.resolve(auth3);
13396
+ const params = {
13397
+ uid: auth3.data,
13398
+ conversation_id: conversationId
13399
+ };
13400
+ if (conversationType !== void 0) {
13401
+ params.conversation_type = conversationType;
13402
+ }
13403
+ return this.client.performRequest(
13404
+ "/user/get_profile_by_conversation_id",
13405
+ "POST",
13406
+ params
13407
+ );
13408
+ }
13409
+ // MARK: IM
13410
+ /** POST /api/v1/conversations/push_message — JSON body, no HMAC signature */
13411
+ async pushMessage(req) {
13412
+ if (!this.imApiClient) {
13413
+ return {
13414
+ ok: false,
13415
+ code: "INTERNAL_ERROR" /* INTERNAL_ERROR */,
13416
+ message: "IM API client not configured."
13417
+ };
13418
+ }
13419
+ return this.imApiClient.pushMessage(req);
13420
+ }
13421
+ };
13422
+
13423
+ // src/internal/api/im-api-client.ts
13424
+ init_cjs_shims();
13425
+ var IMApiClient = class {
13426
+ baseURL;
13427
+ debugEnabled = false;
13428
+ dryRun = false;
13429
+ constructor(baseURL, debugEnabled = false) {
13430
+ this.baseURL = baseURL;
13431
+ this.debugEnabled = debugEnabled;
13432
+ }
13433
+ setDryRun(enabled) {
13434
+ this.dryRun = enabled;
13435
+ }
13436
+ setDebug(enabled) {
13437
+ this.debugEnabled = enabled;
13438
+ }
13439
+ async pushMessage(req) {
13440
+ const url = `${this.baseURL}/api/v1/conversations/push_message`;
13441
+ if (this.dryRun) {
13442
+ this.log("[dry-run]", url, req);
13443
+ return ok({
13444
+ status: "success",
13445
+ sender_cid: req.sender_cid ?? "",
13446
+ conv_id: req.conv_id,
13447
+ message: "Message sent successfully"
13448
+ });
13449
+ }
13450
+ this.log("POST", url, req);
13451
+ try {
13452
+ const response = await fetch(url, {
13453
+ method: "POST",
13454
+ headers: {
13455
+ "Content-Type": "application/json"
13456
+ },
13457
+ body: JSON.stringify(req)
13458
+ });
13459
+ const text = await response.clone().text();
13460
+ this.log(`${response.status} ${response.statusText}`, text);
13461
+ if (!response.ok) {
13462
+ return fail(
13463
+ response.status === 401 || response.status === 403 ? "AUTH_FAILED" /* AUTH_FAILED */ : "NETWORK_ERROR" /* NETWORK_ERROR */,
13464
+ `HTTP ${response.status}: ${response.statusText}`
13465
+ );
13466
+ }
13467
+ let json;
13468
+ try {
13469
+ json = JSON.parse(text);
13470
+ } catch {
13471
+ return fail("DECODE_ERROR" /* DECODE_ERROR */, "Failed to parse push_message response");
13472
+ }
13473
+ if (json.code !== 200) {
13474
+ return fail("API_ERROR" /* API_ERROR */, json.msg || `API error (code: ${json.code})`);
13475
+ }
13476
+ if (json.data === null) {
13477
+ return fail("NOT_FOUND" /* NOT_FOUND */, "push_message returned empty data");
13478
+ }
13479
+ return ok(json.data);
13480
+ } catch (error) {
13481
+ return fail(
13482
+ "NETWORK_ERROR" /* NETWORK_ERROR */,
13483
+ error instanceof Error ? error.message : "Failed to push message"
13484
+ );
13485
+ }
13486
+ }
13487
+ log(...args) {
13488
+ if (this.debugEnabled) {
13489
+ console.log("[PUSH]", ...args);
13490
+ }
13491
+ }
13492
+ };
13493
+
13381
13494
  // src/internal/im/im-gateway.ts
13382
13495
  init_cjs_shims();
13383
13496
 
@@ -13738,7 +13851,7 @@ var IMClient = class {
13738
13851
  }
13739
13852
  // MARK: - Private
13740
13853
  buildWsUrl() {
13741
- const httpUrl = this.config.wsUrl;
13854
+ const httpUrl = this.config.imWsURL;
13742
13855
  let wsUrl = httpUrl.replace(/^http/, "ws");
13743
13856
  wsUrl = wsUrl.replace(/\/+$/, "");
13744
13857
  if (!wsUrl.endsWith("/ws")) {
@@ -14736,8 +14849,9 @@ function createFactory(config) {
14736
14849
  client.setAuth({ uid: config.uid, tk: config.tk, token: config.token });
14737
14850
  }
14738
14851
  const gateway = new ApiGateway(client);
14852
+ gateway.setIMApiClient(new IMApiClient(config.imBaseURL, config.debug));
14739
14853
  const imGateway = new IMGateway({
14740
- wsUrl: config.wsURL,
14854
+ imWsURL: config.imWsURL,
14741
14855
  cid: config.cid,
14742
14856
  debugEnabled: config.debug
14743
14857
  });
@@ -14807,7 +14921,7 @@ async function main() {
14807
14921
  ${t("cli.banner")}` : t("cli.banner");
14808
14922
  program.addHelpText("beforeAll", `${banner}
14809
14923
  `);
14810
- program.name("qz").version(`v${"0.4.2"}`, "-v, --version", t("options.version")).helpOption("-h, --help", t("options.help")).option("-q, --jq <expr>", t("options.jq")).option("--dry-run", t("options.dryRun"));
14924
+ program.name("qz").version(`v${"0.5.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"));
14811
14925
  program.usage("<command> [subcommand] [options]");
14812
14926
  program.hook("preAction", () => {
14813
14927
  const opts = program.opts();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qzhuli/qzhuli-cli",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "CLI tool for Q助理 (QZhuli)",
5
5
  "main": "dist/cmd.js",
6
6
  "bin": {
@@ -6,6 +6,7 @@
6
6
  * node scripts/install-skill.mjs # install to all detected agents (global)
7
7
  * node scripts/install-skill.mjs --project # install to current project's skills dir
8
8
  * node scripts/install-skill.mjs --agent claude-code # install to specific agent only
9
+ * node scripts/install-skill.mjs --target /path/to/skills # install to custom directory
9
10
  * node scripts/install-skill.mjs --help # show help
10
11
  *
11
12
  * Works with:
@@ -51,8 +52,27 @@ function getPkgVersion() {
51
52
 
52
53
  // ── CLI parsing ────────────────────────────────────────────────────
53
54
 
55
+ function printVersion(sourceDir) {
56
+ const pkgVersion = getPkgVersion();
57
+ const skillVersion = sourceDir ? readSkillVersion(sourceDir) : 'n/a';
58
+ console.log(`@qzhuli/qzhuli-cli v${pkgVersion}`);
59
+ console.log(`qzhuli skill v${skillVersion}`);
60
+ }
61
+
62
+ function readSkillVersion(sourceDir) {
63
+ const skillMd = join(sourceDir, 'SKILL.md');
64
+ if (!existsSync(skillMd)) return 'n/a';
65
+ try {
66
+ const content = readFileSync(skillMd, 'utf8');
67
+ const match = content.match(/^version:\s*(.+)$/m);
68
+ return match ? match[1].trim() : 'n/a';
69
+ } catch {
70
+ return 'n/a';
71
+ }
72
+ }
73
+
54
74
  function parseArgs(argv) {
55
- const args = { project: false, agent: null, help: false, update: false };
75
+ const args = { project: false, agent: null, help: false, update: false, target: null, version: false };
56
76
  for (let i = 0; i < argv.length; i++) {
57
77
  switch (argv[i]) {
58
78
  case '--project':
@@ -64,6 +84,12 @@ function parseArgs(argv) {
64
84
  case '--update':
65
85
  args.update = true;
66
86
  break;
87
+ case '--target':
88
+ args.target = argv[++i];
89
+ break;
90
+ case '--version':
91
+ args.version = true;
92
+ break;
67
93
  case '--help':
68
94
  case '-h':
69
95
  args.help = true;
@@ -80,13 +106,16 @@ Usage: node install-skill.mjs [options]
80
106
  Options:
81
107
  --project Install to current project's .claude/skills (or equivalent)
82
108
  --agent <name> Install to a specific agent only (e.g. claude-code, cursor)
109
+ --target <path> Install to a custom directory path (skips agent detection)
83
110
  --update Check for updates and reinstall if version changed
111
+ --version Show package and skill version
84
112
  --help, -h Show this help message
85
113
 
86
114
  Examples:
87
115
  node scripts/install-skill.mjs # global, all agents
88
116
  node scripts/install-skill.mjs --project # project-level, all agents
89
117
  node scripts/install-skill.mjs --agent claude-code # global, claude-code only
118
+ node scripts/install-skill.mjs --target /path/to/skills # custom directory
90
119
  node scripts/install-skill.mjs --update # update all installed agents
91
120
  npx @qzhuli/qzhuli-cli scripts/install-skill.mjs # from published npm
92
121
  `);
@@ -97,6 +126,7 @@ Examples:
97
126
  function main() {
98
127
  const opts = parseArgs(process.argv.slice(2));
99
128
  if (opts.help) { printHelp(); process.exit(0); }
129
+ if (opts.version) { printVersion(findSourceDir()); process.exit(0); }
100
130
 
101
131
  const sourceDir = findSourceDir();
102
132
  if (!sourceDir) {
@@ -109,7 +139,11 @@ function main() {
109
139
 
110
140
  // Resolve target agents
111
141
  let targets = [];
112
- if (opts.update) {
142
+ if (opts.target) {
143
+ // --target: install to an arbitrary directory path
144
+ const targetPath = resolve(opts.target);
145
+ targets.push({ cfg: { name: `custom:${targetPath}`, global: targetPath }, isProject: false, isCustom: true });
146
+ } else if (opts.update) {
113
147
  // --update: check agents that already have the skill installed
114
148
  const candidates = opts.agent
115
149
  ? [AGENT_PATHS.find(a => a.name === opts.agent)]
@@ -150,15 +184,18 @@ function main() {
150
184
  targets.push({ cfg, isProject: opts.project });
151
185
  } else {
152
186
  const detected = detectAgents();
153
- const fallback = AGENT_PATHS.find(a => a.name === 'claude-code');
154
187
  if (detected.length === 0) {
155
- targets.push({ cfg: fallback, isProject: false });
188
+ const fallback = AGENT_PATHS.find(a => a.name === 'claude-code');
189
+ if (fallback) targets.push({ cfg: fallback, isProject: false });
156
190
  } else {
157
191
  for (const name of detected) {
158
192
  const cfg = AGENT_PATHS.find(a => a.name === name);
159
193
  if (cfg) targets.push({ cfg, isProject: false });
160
194
  }
161
195
  }
196
+ // Always install to agents as a universal fallback
197
+ const universal = AGENT_PATHS.find(a => a.name === 'agents');
198
+ if (universal) targets.push({ cfg: universal, isProject: false });
162
199
  }
163
200
 
164
201
  let installed = 0;
@@ -94,6 +94,11 @@ function install() {
94
94
  const detectedAgents = detectAgents();
95
95
  const targets = detectedAgents.length > 0 ? detectedAgents : ['claude-code'];
96
96
 
97
+ // Always install to agents as a universal fallback
98
+ if (!targets.includes('agents')) {
99
+ targets.push('agents');
100
+ }
101
+
97
102
  const results = [];
98
103
  const updated = [];
99
104
 
@@ -37,6 +37,8 @@ export const AGENT_PATHS = [
37
37
  { name: 'trae', global: '~/.trae/skills', project: '.trae/skills' },
38
38
  { name: 'augment', global: '~/.augment/skills', project: '.augment/skills' },
39
39
  { name: 'zed', global: '~/.config/zed/skills', project: '.zed/skills' },
40
+ // Universal fallback — always installed regardless of agent detection
41
+ { name: 'agents', global: '~/.agents/skills', project: '.agents/skills' },
40
42
  ];
41
43
 
42
44
  export const SKILL_NAME = 'qzhuli-cli';
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: qzhuli-cli
3
3
  description: Use when operating the QZhuli CLI (`qz`), including login, auth status, config, friends, relations, users, conversations, messages, cache management, JSON filtering, dry-run, command help, and interpreting test-environment banners or config files.
4
- version: 3
4
+ version: 0.1.0
5
5
  ---
6
6
 
7
7
  # QZhuli CLI
@@ -38,9 +38,8 @@ When ANY of the following applies, STOP and ask the user first:
38
38
  - **Ambiguous search results**: `status: "needs_resolution"` — show options and ask.
39
39
  - **Friend operations**: Before `user add`, show the target profile and confirm.
40
40
  - **Relation changes**: Before `relation set`, show the current value and the new value, then confirm.
41
- - **Message sending**: Before `message send`, show the target conversation, recipient, and message content.
42
41
  - **Cache clearing**: Before `cache clear`, confirm scope (all tables vs single table).
43
- - **Any write operation** (`user add`, `relation set`, `message send`, `conversation create`, `cache clear`): confirm
42
+ - **Any write operation** (`user add`, `relation set`, `conversation create`, `cache clear`): confirm
44
43
  with user.
45
44
 
46
45
  ### Use --dry-run for Preview
@@ -48,34 +47,39 @@ When ANY of the following applies, STOP and ask the user first:
48
47
  Before any write operation the user hasn't explicitly confirmed, run with `--dry-run` first:
49
48
 
50
49
  ```bash
51
- qz --dry-run message send <id> <cid> "hello"
50
+ qz --dry-run message send <id> "hello"
52
51
  qz --dry-run relation set <uid> --remark "New Name"
53
52
  ```
54
53
 
55
54
  ### Least-Surprise Principle
56
55
 
57
- - Never auto-send messages without explicit content approval.
58
56
  - Never change a friend's remark without showing both old and new.
59
57
  - Never delete cache data without confirming scope.
60
58
  - If the user says "send a message" but doesn't specify content, draft it and ask before sending.
61
59
 
60
+ ### Message Role Detection
61
+
62
+ When sending messages, determine the role based on the user's wording:
63
+
64
+ - If the user says something like **"以我的名义"** (in my name), **"帮我发给"** (help me send to), **"替我发送"** (send
65
+ on my behalf), or similar phrasing indicating they want to send as themselves → use `--role 1` (User).
66
+ - Otherwise, send normally without `--role` (defaults to `0` = Assistant).
67
+
62
68
  ## ID Reference
63
69
 
64
- The CLI uses 5 distinct ID types. **Using the wrong type will fail silently or hit the wrong target.**
70
+ The CLI uses 4 distinct ID types. **Using the wrong type will fail silently or hit the wrong target.**
65
71
 
66
- | ID Type | Field Name | Format | Example | Used By |
67
- |-----------------|------------------|-------------------------|----------------------------------------|---------------------------------------------------------------------------------------------------------------------|
68
- | Q助号 | `id` | Number, short | `10003` | `user add <q-number>`, `user search`, `conversation search` (default) |
69
- | UID | `uid` | 32-char hex string | `d5b6308e3abad6bc96573c58` | `relation get/set`, `friend profile --uid`, `user search --uid`, `conversation search --uid`, `conversation create` |
70
- | CID | `cid` | UUID | `5c2f46c2-b0d3-405d-ad21-d833538b77f7` | `message send <target-cid>` **use the OTHER participant's cid, not your own** |
71
- | Conversation ID | `conversationId` | Base64-like long string | `9boGaR7iii2Jdjhmb5LSo37...` | `message send`, `message history`, `conversation profile` |
72
- | Agent ID | `agent.id` | Number | `5` | `conversation create --agent-id` |
72
+ | ID Type | Field Name | Format | Example | Used By |
73
+ |-----------------|------------------|-------------------------|------------------------------|---------------------------------------------------------------------------------------------------------------------|
74
+ | Q助号 | `id` | Number, short | `10003` | `user add <q-number>`, `user search`, `conversation search` (default) |
75
+ | UID | `uid` | 32-char hex string | `d5b6308e3abad6bc96573c58` | `relation get/set`, `friend profile --uid`, `user search --uid`, `conversation search --uid`, `conversation create` |
76
+ | Conversation ID | `conversationId` | Base64-like long string | `9boGaR7iii2Jdjhmb5LSo37...` | `message send`, `message history`, `conversation profile` |
77
+ | Agent ID | `agent.id` | Number | `5` | `conversation create --agent-id` |
73
78
 
74
79
  **Quick identification by format**:
75
80
 
76
81
  - A short integer → Q助号
77
82
  - A 32-char hex string → UID
78
- - A UUID with dashes → CID
79
83
  - A long Base64-like string → conversationId
80
84
 
81
85
  **Common mistake**: Using UID for `message send` instead of conversationId. Always resolve via `conversation search` or
@@ -138,7 +142,7 @@ login/logout, and preference writes.
138
142
  | Create conversation | `qz conversation create <uid> --agent-id <id>` |
139
143
  | Search conversations (Q助号) | `qz conversation search <q-number>` |
140
144
  | Search conversations (UID) | `qz conversation search <uid> --uid` |
141
- | Send message | `qz message send <conversation-id> <target-cid> <content>` |
145
+ | Send message | `qz message send <conversation-id> <content> [--role <n>]` |
142
146
  | Read message history | `qz message history <conversation-id> [--from <id>] [--direction newer\|older] [--limit <n>]` |
143
147
  | Sync cache | `qz cache sync` |
144
148
  | Cache status | `qz cache status` |
@@ -184,10 +188,10 @@ entry contains `conversationId`, `isGroup`, `users`, and `visitors`.
184
188
  1. Confirm auth: `qz auth status`
185
189
  2. Get conversations: `qz conversation list --limit 10`
186
190
  3. Pick the conversation `id` (conversationId).
187
- 4. Pick `target-cid` from that conversation's `users` array **use the OTHER participant's cid**.
188
- 5. Show recipient name and message content to user, confirm.
189
- 6. Send: `qz message send <conversation-id> <target-cid> "message text"`
190
- 7. Verify: `qz message history <conversation-id> --limit 5`
191
+ 4. Determine role: if the user says "以我的名义" / "帮我发给" / "替我发送" etc., add `--role 1`; otherwise omit (
192
+ defaults to Assistant).
193
+ 5. Send: `qz message send <conversation-id> "message text"`
194
+ 6. Verify: `qz message history <conversation-id> --limit 5`
191
195
 
192
196
  ### Page Through Message History
193
197
 
@@ -219,15 +223,15 @@ Tables: `conversations_index`, `conversation_profiles`, `contacts_cache`, `user_
219
223
 
220
224
  ## Troubleshooting
221
225
 
222
- | Symptom | Action |
223
- |---------------------------------|---------------------------------------------------------------------------------------------------------------|
224
- | Command not found | Confirm `qz` is on PATH. Install: `npm install -g @qzhuli/qzhuli-cli` |
225
- | Auth failure | `qz auth status`; then `qz auth login` if needed |
226
- | Unexpected language | `qz config --locale en` or `--locale zh` |
227
- | Too much JSON | Use `--jq ".data"` or another simple dot path |
228
- | Need no-op preview | Use `--dry-run` |
229
- | Message send cid error | Re-check `auth status`, choose `target-cid` from `conversation list` — it must be the other participant's cid |
230
- | Slow queries | Run `qz cache sync` first (incremental, fast), then retry |
231
- | Cache corrupted | `qz cache clear` to reset, then retry (falls back to API) |
232
- | Ambiguous search | `status: "needs_resolution"` — refine query with `--uid` or `--remark` flag |
233
- | `relation set` INVALID_ARGUMENT | Must include at least one of `--remark` or `--type` |
226
+ | Symptom | Action |
227
+ |---------------------------------|-----------------------------------------------------------------------------|
228
+ | Command not found | Confirm `qz` is on PATH. Install: `npm install -g @qzhuli/qzhuli-cli` |
229
+ | Auth failure | `qz auth status`; then `qz auth login` if needed |
230
+ | Unexpected language | `qz config --locale en` or `--locale zh` |
231
+ | Too much JSON | Use `--jq ".data"` or another simple dot path |
232
+ | Need no-op preview | Use `--dry-run` |
233
+ | Message send fails | Re-check `auth status`, verify conversationId via `conversation list` |
234
+ | Slow queries | Run `qz cache sync` first (incremental, fast), then retry |
235
+ | Cache corrupted | `qz cache clear` to reset, then retry (falls back to API) |
236
+ | Ambiguous search | `status: "needs_resolution"` — refine query with `--uid` or `--remark` flag |
237
+ | `relation set` INVALID_ARGUMENT | Must include at least one of `--remark` or `--type` |