@cvtoolman/huawei-lts-cli 1.0.3 → 2.1.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024
3
+ Copyright (c) 2024 cvtoolman
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  **简体中文** | [English](README_en.md)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@cvtoolman/huawei-lts-cli.svg)](https://www.npmjs.com/package/@cvtoolman/huawei-lts-cli)
6
+ [![CI](https://github.com/jcxy/huawei-lts-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/jcxy/huawei-lts-cli/actions/workflows/ci.yml)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
8
 
8
9
  > 让你的 AI 编程助手(Claude Code、Cursor、Copilot、Qoder 等)通过自然语言查询华为云日志服务(LTS)。
@@ -24,11 +25,26 @@ AI 编程助手的上下文窗口很宝贵——一边要装代码库、一边
24
25
  |---|---|---|
25
26
  | **上下文开销** | **低** — skill 就一个短 markdown 文件,CLI 输出是纯文本 | **高** — 工具 schema + 快照数据常驻上下文 |
26
27
  | **AI 使用方式** | 执行 shell 命令,读取结果 | 通过协议调用结构化工具 |
27
- | **配置难度** | `npm install -g`、一个 skill 文件 | 服务进程、配置连线 |
28
+ | **配置难度** | `npm install -g`、一个 skill 目录 | 服务进程、配置连线 |
28
29
  | **适用场景** | 编码过程中顺手查日志 | 长时间运行的自动化循环 |
29
30
 
30
31
  > **一句话:** 如果你想让 AI 在写代码时顺嘴帮你查日志——CLI + SKILL 是更轻、更省钱的方案。
31
32
 
33
+ ## 核心设计:先发现,再查询
34
+
35
+ 字段名、字段类型、是否大小写敏感、分词符——**这些全是每条日志流各自的配置**,不是 LTS 的通用事实。任何写死在文档里的字段表,换个环境就是错的,而且不会报错,只会返回空结果。
36
+
37
+ 所以这个工具的答案永远来自运行时:
38
+
39
+ ```bash
40
+ lts-cli groups # 我有哪些日志组
41
+ lts-cli streams --group <组名> # 这个组下有哪些流
42
+ lts-cli fields # 这条流有哪些字段、什么类型、大小写敏感吗
43
+ lts-cli query --last 2h -q '...' # 这才开始查
44
+ ```
45
+
46
+ `fields` 是 2.0 新增的命令,也是 skill 敢不写任何字段表的前提。SKILL.md 里明确要求 AI:任何 `field:value` 里的字段,必须来自 `fields` 的实际输出;凭印象构造的字段查询在 LTS 上不会报错,只会返回空——然后 AI 会误判"没有这条日志"。
47
+
32
48
  ## 快速开始
33
49
 
34
50
  ### 1. 安装 CLI
@@ -37,208 +53,203 @@ AI 编程助手的上下文窗口很宝贵——一边要装代码库、一边
37
53
  npm install -g @cvtoolman/huawei-lts-cli
38
54
  ```
39
55
 
56
+ 需要 Node.js >= 18。
57
+
40
58
  ### 2. 配置凭证
41
59
 
42
60
  ```bash
43
61
  lts-cli init
44
62
  ```
45
63
 
46
- 向导会依次询问 AK、SK、Project ID、Region,一次配好。配置存储在 `~/.lts-cli/config.json`。
64
+ 向导会依次询问 AK、SK、Project ID、Region,一次配好,只写一个文件:
47
65
 
48
- ### 3. 安装 Skill(给 AI 工具用)
66
+ | 文件 | 内容 | 谁读 |
67
+ |---|---|---|
68
+ | `~/.lts-cli/config.json` | 凭证、默认日志组/流、`traceField`(权限 0600) | 仅 CLI |
49
69
 
50
- **Claude Code:**
51
- ```bash
52
- mkdir -p .claude/skills/huawei-lts-log-query
53
- curl -o .claude/skills/huawei-lts-log-query/SKILL.md \
54
- https://raw.githubusercontent.com/jcxy/huawei-lts-cli/main/skills/huawei-lts-log-query/SKILL.md
55
- ```
70
+ **这个目录不要整个交给 AI**,里面是明文 AK/SK。要看配置用 `lts-cli config --list`(已脱敏),不需要打开文件。
56
71
 
57
- **Qoder:**
58
- ```bash
59
- mkdir -p .qoder/skills/huawei-lts-log-query
60
- curl -o .qoder/skills/huawei-lts-log-query/SKILL.md \
61
- https://raw.githubusercontent.com/jcxy/huawei-lts-cli/main/skills/huawei-lts-log-query/SKILL.md
62
- ```
63
-
64
- **手动安装(任意 AI 工具):** 将 [skills/huawei-lts-log-query/SKILL.md](skills/huawei-lts-log-query/SKILL.md) 复制到对应 AI 工具的 skills 目录下(`.claude/skills/`、`.qoder/skills/`、`.cursor/skills/` 等)。
72
+ 日志组有哪些、字段叫什么、值长什么样——这些事实全部靠 `lts-cli groups` / `streams` / `fields` 现查,仓库和配置里都不存第二份。
65
73
 
66
- ### 4. 命令行直接使用(不依赖 AI)
74
+ 拿到控制台日志搜索页的 URL 时,可以直接导入 region / groupId / streamId:
67
75
 
68
76
  ```bash
69
- # 基础查询
70
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z
71
-
72
- # 简单关键词搜索(旧版,向后兼容)
73
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z -k "ERROR"
74
-
75
- # 高级查询:多字段组合(推荐)
76
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
77
- -q "content:error AND appName:myapp"
77
+ lts-cli init --from-url "<控制台URL>"
78
+ ```
78
79
 
79
- # 复杂逻辑查询
80
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
81
- -q "(level:ERROR OR level:WARN) NOT env:test"
80
+ ### 3. 安装 Skill(给 AI 工具用)
82
81
 
83
- # SQL 分析查询
84
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
85
- -q "SELECT count(*) as cnt, level FROM log GROUP BY level" \
86
- -f json
82
+ 用 [skills CLI](https://skills.sh) 一条命令装到对应工具的目录(支持 75+ 种 agent):
87
83
 
88
- # JSON 格式输出,管道给 jq 处理
89
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z -f json | jq .
84
+ ```bash
85
+ npx skills add jcxy/huawei-lts-cli -a qoder # Qoder
86
+ npx skills add jcxy/huawei-lts-cli -a claude-code # Claude Code
87
+ npx skills add jcxy/huawei-lts-cli -a cursor # Cursor
88
+ npx skills add jcxy/huawei-lts-cli # 交互式选 agent
90
89
  ```
91
90
 
92
- > 💡 **提示**:使用 `-q` 参数可实现与 LTS 控制台相同的多条件组合查询功能。详见 [查询语法速查表](skills/huawei-lts-log-query/references/cheatsheet.md) 和 [高级查询指南](skills/huawei-lts-log-query/references/advanced-query-guide.md)。
93
-
94
- ## 命令参考
91
+ 装完重启 AI 工具:skill 在会话启动时读进上下文,已经开着的会话不会热加载。日常用 `npx skills update` 更新、`npx skills remove` 卸载。
95
92
 
96
- ### `lts-cli query` — 查询日志
93
+ 不想用 npx 也可以手动复制(任何工具都适用):
97
94
 
98
95
  ```bash
99
- lts-cli query [选项]
100
-
101
- 选项:
102
- -g, --group-id <groupId> 日志组 ID(可在配置中预设)
103
- -s, --stream-id <streamId> 日志流 ID(可在配置中预设)
104
- --start-time, --st <time> 开始时间,ISO 8601 格式(必填)
105
- --end-time, --et <time> 结束时间,ISO 8601 格式(必填)
106
- -k, --keyword <keyword> 简单关键词搜索(旧版,不推荐)
107
- -q, --query <query> 高级查询表达式(推荐):
108
- - 字段查询: content:error AND appName:myapp
109
- - 比较运算: time>60 AND region:r
110
- - SQL语法: SELECT * FROM log WHERE ...
111
- - 逻辑运算符: AND, OR, NOT(需空格分隔)
112
- -l, --limit <limit> 每页条数(默认: 100)
113
- -o, --offset <offset> 分页偏移量(默认: 0)
114
- -r, --reverse 按时间倒序
115
- -f, --format <format> 输出格式: pretty、json、table(默认: pretty)
116
- --no-paginate 关闭自动翻页
96
+ git clone --depth 1 https://github.com/jcxy/huawei-lts-cli.git /tmp/huawei-lts-cli
97
+ cp -r /tmp/huawei-lts-cli/skills/huawei-lts-log-query <目标 skills 目录>/
117
98
  ```
118
99
 
119
- #### 高级查询语法示例
100
+ > `npx skills` 默认用软链指向一份 git clone,更新时一起同步。用 `--copy`(Windows 无软链权限时的退路)或插件方式安装时,升级会整目录替换——如果你在里面放了业务词表,先备份。
120
101
 
121
- **字段查询:**
122
- ```bash
123
- # 单字段匹配
124
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
125
- -q "content:error"
102
+ ### 4. 业务词表(可选)
126
103
 
127
- # 多字段组合(且关系)
128
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
129
- -q "content:error AND appName:myapp AND node:server1"
104
+ AI 不知道「订单服务」在你的日志里叫什么。两条路,都不需要配置文件:
130
105
 
131
- # 多字段组合(或关系)
132
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
133
- -q "level:ERROR OR level:WARN"
106
+ - **默认行为**:AI 用管道 SQL 实时枚举候选值(`* | SELECT appName, count(*) GROUP BY appName`),再问你挑哪一个。不落盘,也不会过期。
107
+ - **想省这一步**:在 skill 的 `references/vocab.md` 里写映射。这个文件默认不存在,自己建;已被 `.gitignore` 排除,不会被误提交。
134
108
 
135
- # 排除条件
136
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
137
- -q "level:DEBUG NOT module:test"
109
+ ### 5. 命令行直接使用(不依赖 AI)
138
110
 
139
- # 数值比较
140
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
141
- -q "response_time>1000 AND status_code!=200"
111
+ ```bash
112
+ # 最近 2 小时最新 50 条
113
+ lts-cli query --last 2h -r -l 50
142
114
 
143
- # 复杂组合
144
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
145
- -q "(level:ERROR OR level:WARN) NOT env:test"
146
- ```
115
+ # 组合条件
116
+ lts-cli query --last 2h --app <appName> --content timeout -r
147
117
 
148
- **SQL 查询:**
149
- ```bash
150
- # 统计各应用错误数量
151
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
152
- -q "SELECT count(*) as cnt, appName FROM log WHERE content LIKE '%error%' GROUP BY appName"
118
+ # 任意表达式
119
+ lts-cli query --last 1d -q '(level:ERROR OR level:WARN) NOT env:test' -r
120
+
121
+ # 管道分析:先过滤再统计
122
+ lts-cli query --last 1d -q 'level:ERROR | SELECT count(*) AS cnt, appName GROUP BY appName' -f json
153
123
 
154
- # 平均响应时间
155
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z \
156
- -q "SELECT avg(response_time) as avg_time FROM log WHERE status_code=200"
124
+ # 机器处理
125
+ lts-cli query --last 1h -f json | jq '.logs[].content'
157
126
  ```
158
127
 
159
- > ⚠️ **注意**:AND、OR、NOT 前后必须有**空格**!
128
+ > 💡 语法细节见 [查询语法](skills/huawei-lts-log-query/references/query-syntax.md),命令全貌见 [CLI 参考](skills/huawei-lts-log-query/references/cli-reference.md)。
160
129
 
161
- 详细文档请查看:
162
- - [查询语法速查表](skills/huawei-lts-log-query/references/cheatsheet.md)
163
- - [高级查询指南](skills/huawei-lts-log-query/references/advanced-query-guide.md)
164
- - [控制台 vs CLI 对照](skills/huawei-lts-log-query/references/query-comparison.md)
130
+ ## 命令参考
165
131
 
166
- ### `lts-cli config` — 管理配置
132
+ | 命令 | 作用 |
133
+ |---|---|
134
+ | `lts-cli groups` | 列出日志组 |
135
+ | `lts-cli streams` | 列出日志流(`--group <组名>` 过滤) |
136
+ | `lts-cli fields` | 读取日志流的索引配置 |
137
+ | `lts-cli query` | 查询日志 |
138
+ | `lts-cli trace <id>` | 按 ID 还原完整请求链路 |
139
+ | `lts-cli context` | 查看某条日志的前后文 |
140
+ | `lts-cli config` | 管理配置 |
141
+ | `lts-cli init` | 配置向导 |
142
+ | `lts-cli completion` | 生成 shell 补全 |
143
+
144
+ ### `lts-cli query`
167
145
 
168
146
  ```
169
- lts-cli config [选项]
170
-
171
- 选项:
172
- --set <key=value> 设置配置项
173
- --get <key> 查看配置项
174
- --list 列出所有配置
175
- --unset <key> 删除配置项
147
+ -g, --group-id <id> 日志组 ID(可在配置中预设)
148
+ -s, --stream-id <id> 日志流 ID(可在配置中预设)
149
+ --last <时长> 30m / 2h / 1d / 7d,与 --st/--et 互斥
150
+ --st, --start-time <ISO> 开始时间
151
+ --et, --end-time <ISO> 结束时间
152
+ -q, --query <expr> 查询表达式或管道分析
153
+ --app <取值> appName:"<取值>",与 -q 用 AND 合并
154
+ --content <关键词> content:"<关键词>",与 -q 用 AND 合并
155
+ --request-id <id> REQUEST_ID:"<id>",按时间正序输出
156
+ -l, --limit <n> 每页条数(默认 100),--all 时即单页大小
157
+ -r, --reverse 最新优先
158
+ -f, --format <fmt> pretty(默认)/ table / json
159
+ --all 沿游标读到末尾(上限 10000 条)
176
160
  ```
177
161
 
178
- ### `lts-cli init` — 配置向导
162
+ `--app` / `--content` / `--request-id` 自动为值加双引号——含连字符的 UUID 不加引号会被分词导致匹配不到。
179
163
 
180
- ```
181
- lts-cli init
164
+ ### `lts-cli trace`
182
165
 
183
- 交互式向导,引导设置 AK、SK、Project ID、Region、endpoint,
184
- 以及可选的默认日志组和日志流。
166
+ ```bash
167
+ lts-cli trace <id> [--field <name>] [--last 24h] [-l 200] [-f fmt]
185
168
  ```
186
169
 
187
- ### `lts-cli completion` — Shell 补全
188
-
189
- ```
190
- lts-cli completion <bash|zsh|powershell>
170
+ 按时间正序拉出一条 ID 的完整链路。默认走全文检索(不依赖任何特定字段命名);如果你的日志流有专用字段,配一次即可:
191
171
 
192
- 生成 shell 补全脚本。
172
+ ```bash
173
+ lts-cli config --set traceField=REQUEST_ID
193
174
  ```
194
175
 
195
176
  ## 配置说明
196
177
 
197
- 配置存储在 `~/.lts-cli/config.json`(Linux/macOS)或 `%USERPROFILE%\.lts-cli\config.json`(Windows)。
178
+ 配置文件 `~/.lts-cli/config.json`(Windows 为 `%USERPROFILE%\.lts-cli\config.json`):
198
179
 
199
180
  | 键 | 必填 | 说明 |
200
181
  |---|---|---|
201
182
  | `ak` | 是 | 华为云访问密钥 |
202
183
  | `sk` | 是 | 华为云私有密钥 |
203
184
  | `projectId` | 是 | 项目 ID |
204
- | `region` | 否 | 如 `cn-south-1` |
185
+ | `region` | 否 | 如 `cn-north-4` |
205
186
  | `endpoint` | 否 | 需包含 `https://` |
206
187
  | `groupId` | 否 | 默认日志组 ID |
207
188
  | `streamId` | 否 | 默认日志流 ID |
189
+ | `traceField` | 否 | `trace` 默认匹配的字段名,缺省为 `content` |
190
+
191
+ `lts-cli config --list` 会把 `ak` / `sk` 脱敏输出,避免凭证进入终端滚屏、shell 历史或 AI 上下文。
208
192
 
209
193
  ### 环境变量
210
194
 
211
- 所有配置项也支持环境变量(适用于 CI/CD):
195
+ 所有配置项都支持环境变量(适用于 CI/CD),优先级高于配置文件:
196
+
197
+ `LTS_AK` `LTS_SK` `LTS_PROJECT_ID` `LTS_REGION` `LTS_ENDPOINT` `LTS_GROUP_ID` `LTS_STREAM_ID`
212
198
 
213
- | 配置键 | 环境变量 |
199
+ ## 支持的区域
200
+
201
+ 以下是 `lts-cli init` 内置的 region → endpoint 映射:
202
+
203
+ | 区域 ID | Endpoint |
214
204
  |---|---|
215
- | `ak` | `LTS_AK` |
216
- | `sk` | `LTS_SK` |
217
- | `projectId` | `LTS_PROJECT_ID` |
218
- | `region` | `LTS_REGION` |
219
- | `endpoint` | `LTS_ENDPOINT` |
220
- | `groupId` | `LTS_GROUP_ID` |
221
- | `streamId` | `LTS_STREAM_ID` |
205
+ | `cn-north-1` | `lts.cn-north-1.myhuaweicloud.com` |
206
+ | `cn-north-2` | `lts.cn-north-2.myhuaweicloud.com` |
207
+ | `cn-north-4` | `lts.cn-north-4.myhuaweicloud.com` |
208
+ | `cn-north-5` | `lts.cn-north-5.myhuaweicloud.com` |
209
+ | `cn-north-9` | `lts.cn-north-9.myhuaweicloud.com` |
210
+ | `cn-east-2` | `lts.cn-east-2.myhuaweicloud.com` |
211
+ | `cn-east-3` | `lts.cn-east-3.myhuaweicloud.com` |
212
+ | `cn-east-4` | `lts.cn-east-4.myhuaweicloud.com` |
213
+ | `cn-south-1` | `lts.cn-south-1.myhuaweicloud.com` |
214
+ | `cn-south-2` | `lts.cn-south-2.myhuaweicloud.com` |
215
+ | `cn-south-4` | `lts.cn-south-4.myhuaweicloud.com` |
216
+ | `cn-southwest-2` | `lts.cn-southwest-2.myhuaweicloud.com` |
217
+ | `ap-southeast-1` | `lts.ap-southeast-1.myhuaweicloud.com` |
218
+ | `ap-southeast-2` | `lts.ap-southeast-2.myhuaweicloud.com` |
219
+ | `ap-southeast-3` | `lts.ap-southeast-3.myhuaweicloud.com` |
220
+ | `af-south-1` | `lts.af-south-1.myhuaweicloud.com` |
221
+ | `la-south-2` | `lts.la-south-2.myhuaweicloud.com` |
222
+ | `la-north-2` | `lts.la-north-2.myhuaweicloud.com` |
223
+ | `sa-brazil-1` | `lts.sa-brazil-1.myhuaweicloud.com` |
224
+ | `na-mexico-1` | `lts.na-mexico-1.myhuaweicloud.com` |
225
+
226
+ 不在表里时用 `--set endpoint=https://...` 手动指定,或参考 [华为云 LTS 终端节点](https://support.huaweicloud.com/api-lts/lts_api_0011.html)。
227
+
228
+ ## 升级说明
229
+
230
+ ### 从 2.0 升级到 2.1
231
+
232
+ | 变更 | 说明 |
233
+ |---|---|
234
+ | `init` 不再生成 `~/.lts-cli/profile.md` | 业务词表改为 skill 目录下的 `references/vocab.md`。旧文件可以删,留着也没影响(CLI 从来没读过它) |
235
+ | SKILL.md 增加运行时枚举候选值 | 没有词表也能定位应用名 |
236
+ | 新增 `npx skills` 安装方式 | 见上文「安装 Skill」 |
222
237
 
223
- 环境变量优先级高于配置文件。
238
+ 命令行参数没有变化,脚本不需要改。
224
239
 
225
- ## 支持的区域
240
+ ### 从 1.x 升级到 2.0
241
+
242
+ 2.0 修了三个会静默出错的问题,因此有破坏性变更:
226
243
 
227
- | 区域 ID | 区域名称 | Endpoint |
244
+ | 变更 | 原因 | 迁移 |
228
245
  |---|---|---|
229
- | `cn-north-1` | 华北-北京一 | `lts.cn-north-1.myhuaweicloud.com` |
230
- | `cn-north-4` | 华北-北京四 | `lts.cn-north-4.myhuaweicloud.com` |
231
- | `cn-east-2` | 华东-上海二 | `lts.cn-east-2.myhuaweicloud.com` |
232
- | `cn-east-3` | 华东-上海三 | `lts.cn-east-3.myhuaweicloud.com` |
233
- | `cn-south-1` | 华南-广州 | `lts.cn-south-1.myhuaweicloud.com` |
234
- | `cn-south-4` | 华南-深圳 | `lts.cn-south-4.myhuaweicloud.com` |
235
- | `cn-southwest-2` | 西南-贵阳一 | `lts.cn-southwest-2.myhuaweicloud.com` |
236
- | `ap-southeast-1` | 亚太-新加坡 | `lts.ap-southeast-1.myhuaweicloud.com` |
237
- | `ap-southeast-2` | 亚太-曼谷 | `lts.ap-southeast-2.myhuaweicloud.com` |
238
- | `ap-southeast-3` | 亚太-雅加达 | `lts.ap-southeast-3.myhuaweicloud.com` |
239
- | `af-south-1` | 非洲-约翰内斯堡 | `lts.af-south-1.myhuaweicloud.com` |
240
-
241
- > 完整列表请参考 [华为云 LTS 终端节点](https://support.huaweicloud.com/api-lts/lts_api_0011.html)。
246
+ | 删除 `--offset` | LTS 的 SDK 根本没有 offset 字段,该参数一直静默失效;`--all` 会重复拉同一页直到塞满 10000 条 | 用 `--all`,或缩小时间范围 |
247
+ | 删除 `-k, --keyword` | 与 `-q` 行为完全等价 | 改成 `-q "<关键词>"` |
248
+ | 删除 `--no-paginate` | 单页本来就是默认行为,该参数从未被读取 | 直接删掉 |
249
+ | `trace` 默认字段由 `REQUEST_ID` 改为 `content` | `REQUEST_ID` 只是某类应用的命名约定,不是 LTS 通用概念 | 需要精确字段时 `lts-cli config --set traceField=REQUEST_ID` |
250
+ | `-f json` 输出字段 `logTime` 改名为 `lineNum` | LTS 的日志记录只有 `content` / `line_num` / `labels`,没有时间戳,原名是错的 | 更新解析脚本 |
251
+
252
+ 此外,结果不完整时会向 stderr 输出警告(`isQueryComplete=false` 或触达 10000 条上限)。**这些警告不能忽略**——此时结果就是残缺的。
242
253
 
243
254
  ## 开发指南
244
255
 
@@ -246,16 +257,23 @@ lts-cli completion <bash|zsh|powershell>
246
257
  git clone https://github.com/jcxy/huawei-lts-cli.git
247
258
  cd huawei-lts-cli
248
259
  npm install
249
- npm run build
250
-
251
- # 开发模式运行(无需编译)
252
- npm run dev -- query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z
253
260
 
254
- # 本地测试
255
- npm link
256
- lts-cli query --st 2024-01-01T00:00:00Z --et 2024-01-02T00:00:00Z
261
+ npm run build # 编译到 dist/
262
+ npm run dev -- query --last 2h # 直接从源码跑
263
+ npm test # node:test,覆盖纯函数与游标推进
264
+ npm run typecheck # tsc --noEmit
257
265
  ```
258
266
 
267
+ 测试使用 Node 内置的 `node:test` + `tsx`,不引入额外测试依赖。跑测试需要 Node >= 22——`node --test` 的通配符支持直到 22 才落地,20 上会报 `Could not find`。CLI 本身在 Node 18 上可以正常运行,CI 的 typecheck/build 覆盖 18/20/22。
268
+
269
+ 架构约定见 [CONTEXT.md](CONTEXT.md) 与 [docs/adr/](docs/adr/)。提交前请读 [CONTRIBUTING.md](CONTRIBUTING.md)——里面写了这个仓库唯一不能被破坏的原则:**同一个事实只允许有一个来源**。
270
+
271
+ ## 参与
272
+
273
+ - 报告问题:[issue 模板](https://github.com/jcxy/huawei-lts-cli/issues/new/choose)(请勿贴真实凭证、日志组 ID 或内部业务名)
274
+ - 安全问题:[SECURITY.md](SECURITY.md),走私密通道,不要开公开 issue
275
+ - 版本变更:[CHANGELOG.md](CHANGELOG.md)
276
+
259
277
  ## License
260
278
 
261
279
  [MIT](LICENSE)
@@ -5,6 +5,7 @@ exports.resolveGroupStream = resolveGroupStream;
5
5
  exports.resolveTimeParams = resolveTimeParams;
6
6
  exports.buildQueryExpression = buildQueryExpression;
7
7
  exports.resolveFormat = resolveFormat;
8
+ exports.warnIfIncomplete = warnIfIncomplete;
8
9
  exports.handleCommandError = handleCommandError;
9
10
  const config_1 = require("../../config");
10
11
  /** 加载配置并校验凭证,缺失时打印引导并退出 */
@@ -75,6 +76,17 @@ function buildQueryExpression(options) {
75
76
  function resolveFormat(value) {
76
77
  return ['json', 'table', 'pretty'].includes(value) ? value : 'pretty';
77
78
  }
79
+ /**
80
+ * LTS 在时间跨度过大时会返回不完整结果(isQueryComplete=false)。
81
+ * 结果照样输出(残缺比没有好),但必须在 stderr 明确告警——安静地给出半个答案,
82
+ * 比直接报错更危险:调用方会基于残缺数据下结论。
83
+ */
84
+ function warnIfIncomplete(result) {
85
+ if (result.isQueryComplete === false) {
86
+ console.error('Warning: LTS reported an incomplete result set (isQueryComplete=false). ' +
87
+ 'Narrow the time range (e.g. --last 1h) or add a filter before trusting totals.');
88
+ }
89
+ }
78
90
  /** 统一错误处理:常见错误给出友好提示 */
79
91
  function handleCommandError(error) {
80
92
  const msg = error.message || String(error);
@@ -3,6 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createConfigCommand = createConfigCommand;
4
4
  const commander_1 = require("commander");
5
5
  const config_1 = require("../../config");
6
+ /** 凭证类字段一律脱敏:`config --list` 会把输出留在终端滚屏、shell 历史、agent 上下文里。 */
7
+ const SECRET_KEYS = new Set(['ak', 'sk']);
8
+ function maskSecret(value) {
9
+ if (value.length <= 4)
10
+ return '****';
11
+ return `${value.slice(0, 2)}${'*'.repeat(Math.max(4, value.length - 4))}${value.slice(-2)}`;
12
+ }
6
13
  function createConfigCommand() {
7
14
  const cmd = new commander_1.Command('config');
8
15
  cmd.description('Manage LTS CLI configuration')
@@ -24,7 +31,7 @@ function createConfigCommand() {
24
31
  else if (options.get) {
25
32
  const value = (0, config_1.getConfig)(options.get);
26
33
  if (value !== undefined) {
27
- console.log(value);
34
+ console.log(SECRET_KEYS.has(options.get) ? maskSecret(value) : value);
28
35
  }
29
36
  else {
30
37
  console.error(`Config key '${options.get}' not found`);
@@ -39,7 +46,7 @@ function createConfigCommand() {
39
46
  }
40
47
  else {
41
48
  for (const [key, value] of entries) {
42
- console.log(`${key}=${value}`);
49
+ console.log(`${key}=${SECRET_KEYS.has(key) ? maskSecret(value) : value}`);
43
50
  }
44
51
  }
45
52
  }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createFieldsCommand = createFieldsCommand;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const cli_table3_1 = __importDefault(require("cli-table3"));
10
+ const client_1 = require("../../lts/client");
11
+ const common_1 = require("./common");
12
+ /**
13
+ * 输出日志流的索引配置。
14
+ *
15
+ * 存在的理由:字段名、字段类型、大小写是否敏感、分词符,全都是**每条日志流各自一份**
16
+ * 的配置(SDK 里是 LTSFullTextIndexInfo / LTSFieldsInfo)。任何写进文档的字段表都只对
17
+ * 某一条流成立,换个环境就是错的。所以答案必须在运行时从 API 取。
18
+ */
19
+ function boolLabel(value) {
20
+ if (value === undefined)
21
+ return '-';
22
+ return value ? 'yes' : 'no';
23
+ }
24
+ function formatJson(index) {
25
+ return JSON.stringify(index, null, 2);
26
+ }
27
+ function formatTable(index) {
28
+ const lines = [];
29
+ const summary = new cli_table3_1.default({ head: ['Full-text index', 'Value'] });
30
+ summary.push(['enabled', boolLabel(index.fullText.enabled)], ['caseSensitive', boolLabel(index.fullText.caseSensitive)], ['includeChinese', boolLabel(index.fullText.includeChinese)], ['tokenizer', index.fullText.tokenizer || '-']);
31
+ lines.push(summary.toString());
32
+ if (index.fields.length === 0) {
33
+ lines.push('');
34
+ lines.push('No field index configured — only full-text search is available on this stream.');
35
+ return lines.join('\n');
36
+ }
37
+ lines.push('');
38
+ const fields = new cli_table3_1.default({
39
+ head: ['Field', 'Type', 'Case-sensitive', 'Chinese', 'Tokenizer', 'Quick analysis'],
40
+ });
41
+ for (const f of index.fields) {
42
+ fields.push([
43
+ f.name,
44
+ f.type || '-',
45
+ boolLabel(f.caseSensitive),
46
+ boolLabel(f.includeChinese),
47
+ f.tokenizer || '-',
48
+ boolLabel(f.quickAnalysis),
49
+ ]);
50
+ }
51
+ lines.push(fields.toString());
52
+ return lines.join('\n');
53
+ }
54
+ function formatPretty(index) {
55
+ const lines = [];
56
+ lines.push(chalk_1.default.bold(`Stream ${index.streamId}`));
57
+ lines.push('');
58
+ lines.push(chalk_1.default.bold('Full-text index'));
59
+ lines.push(` enabled: ${boolLabel(index.fullText.enabled)}`);
60
+ lines.push(` caseSensitive: ${boolLabel(index.fullText.caseSensitive)}`);
61
+ lines.push(` includeChinese: ${boolLabel(index.fullText.includeChinese)}`);
62
+ lines.push(` tokenizer: ${index.fullText.tokenizer || '-'}`);
63
+ lines.push('');
64
+ if (index.fields.length === 0) {
65
+ lines.push(chalk_1.default.yellow('No field index configured — only full-text search is available.'));
66
+ return lines.join('\n');
67
+ }
68
+ lines.push(chalk_1.default.bold(`Fields (${index.fields.length})`));
69
+ for (const f of index.fields) {
70
+ const flags = [
71
+ f.caseSensitive ? 'case-sensitive' : 'case-insensitive',
72
+ f.includeChinese ? 'chinese' : 'no-chinese',
73
+ f.quickAnalysis ? 'quick-analysis' : null,
74
+ ].filter(Boolean);
75
+ lines.push(` ${chalk_1.default.cyan(f.name)} ${chalk_1.default.gray(f.type || '-')} ${chalk_1.default.gray(flags.join(', '))}`);
76
+ if (f.tokenizer) {
77
+ lines.push(chalk_1.default.gray(` tokenizer: ${f.tokenizer}`));
78
+ }
79
+ }
80
+ return lines.join('\n');
81
+ }
82
+ function createFieldsCommand() {
83
+ const cmd = new commander_1.Command('fields');
84
+ cmd
85
+ .description('Show the index config of a log stream (fields, types, case sensitivity, tokenizer)')
86
+ .option('-g, --group-id <groupId>', 'Log group ID (can also be set via config)')
87
+ .option('-s, --stream-id <streamId>', 'Log stream ID (can also be set via config)')
88
+ .option('-f, --format <format>', 'Output format: json, table, pretty', 'pretty')
89
+ .action(async (options) => {
90
+ const config = (0, common_1.loadCheckedConfig)();
91
+ const { groupId, streamId } = (0, common_1.resolveGroupStream)(options, config);
92
+ const format = (0, common_1.resolveFormat)(options.format);
93
+ const client = new client_1.LTSClient(config);
94
+ try {
95
+ const index = await client.getStreamIndex(groupId, streamId);
96
+ const output = format === 'json'
97
+ ? formatJson(index)
98
+ : format === 'table'
99
+ ? formatTable(index)
100
+ : formatPretty(index);
101
+ console.log(output);
102
+ }
103
+ catch (error) {
104
+ (0, common_1.handleCommandError)(error);
105
+ }
106
+ });
107
+ return cmd;
108
+ }
@@ -35,13 +35,29 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.createInitCommand = createInitCommand;
37
37
  const commander_1 = require("commander");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
38
40
  const readline = __importStar(require("readline"));
39
41
  const config_1 = require("../../config");
42
+ const store_1 = require("../../config/store");
40
43
  function ask(rl, question) {
41
44
  return new Promise((resolve) => {
42
45
  rl.question(question, (answer) => resolve(answer.trim()));
43
46
  });
44
47
  }
48
+ /**
49
+ * 2.0 及更早版本会在凭证目录里生成一份业务词表骨架,那是把 AI 引进
50
+ * 明文 AK/SK 所在目录的入口。2.1 起不再生成,业务词表改由 skill 的
51
+ * `references/` 承载。这里只对老用户的残留文件做一次性提示,绝不代删。
52
+ */
53
+ function reportLegacyProfile() {
54
+ const legacy = path.join((0, store_1.getConfigDir)(), 'profile.md');
55
+ if (!fs.existsSync(legacy)) {
56
+ return;
57
+ }
58
+ console.log(` note: ${legacy} is no longer used since 2.1 — safe to delete.`);
59
+ console.log(' Business vocabulary now lives in the skill: references/vocab.md');
60
+ }
45
61
  const ENDPOINT_MAP = {
46
62
  'cn-north-1': 'lts.cn-north-1.myhuaweicloud.com',
47
63
  'cn-north-2': 'lts.cn-north-2.myhuaweicloud.com',
@@ -144,13 +160,17 @@ function createInitCommand() {
144
160
  }
145
161
  console.log('');
146
162
  console.log('✅ Configuration saved!');
163
+ reportLegacyProfile();
147
164
  console.log('');
148
165
  console.log('Try it out:');
149
166
  if (groupId && streamId) {
150
- console.log(' lts-cli query --start-time 2024-01-01T00:00:00Z --end-time 2024-01-02T00:00:00Z');
167
+ console.log(' lts-cli query --last 2h');
168
+ console.log(' lts-cli fields');
151
169
  }
152
170
  else {
153
- console.log(' lts-cli query -g <group-id> -s <stream-id> --start-time 2024-01-01T00:00:00Z --end-time 2024-01-02T00:00:00Z');
171
+ console.log(' lts-cli groups # find your log group ID');
172
+ console.log(' lts-cli streams --group <group-name> # find your log stream ID');
173
+ console.log(' lts-cli query -g <groupId> -s <streamId> --last 2h');
154
174
  }
155
175
  console.log('');
156
176
  }
@@ -203,9 +223,11 @@ function importFromConsoleUrl(raw) {
203
223
  }
204
224
  console.log('');
205
225
  console.log('✅ Configuration saved!');
226
+ reportLegacyProfile();
206
227
  console.log('');
207
228
  console.log('Next steps:');
208
229
  console.log(' 1. Configure credentials if not done yet: lts-cli init');
209
- console.log(' 2. Query logs: lts-cli query --last 2h --content "error"');
230
+ console.log(' 2. Check which fields this stream indexes: lts-cli fields');
231
+ console.log(' 3. Query logs: lts-cli query --last 2h');
210
232
  console.log('');
211
233
  }