@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 +1 -1
- package/README.md +163 -145
- package/dist/cli/commands/common.js +12 -0
- package/dist/cli/commands/config.js +9 -2
- package/dist/cli/commands/fields.js +108 -0
- package/dist/cli/commands/init.js +25 -3
- package/dist/cli/commands/query.js +16 -35
- package/dist/cli/commands/streams.js +1 -1
- package/dist/cli/commands/trace.js +24 -20
- package/dist/cli/index.js +122 -14
- package/dist/config/store.js +7 -2
- package/dist/lts/client.js +44 -10
- package/dist/lts/pagination.js +56 -0
- package/dist/output/index.js +1 -1
- package/dist/output/table.js +2 -2
- package/package.json +12 -10
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**简体中文** | [English](README_en.md)
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@cvtoolman/huawei-lts-cli)
|
|
6
|
+
[](https://github.com/jcxy/huawei-lts-cli/actions/workflows/ci.yml)
|
|
6
7
|
[](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
|
|
64
|
+
向导会依次询问 AK、SK、Project ID、Region,一次配好,只写一个文件:
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
| 文件 | 内容 | 谁读 |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `~/.lts-cli/config.json` | 凭证、默认日志组/流、`traceField`(权限 0600) | 仅 CLI |
|
|
49
69
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
+
拿到控制台日志搜索页的 URL 时,可以直接导入 region / groupId / streamId:
|
|
67
75
|
|
|
68
76
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
## 命令参考
|
|
91
|
+
装完重启 AI 工具:skill 在会话启动时读进上下文,已经开着的会话不会热加载。日常用 `npx skills update` 更新、`npx skills remove` 卸载。
|
|
95
92
|
|
|
96
|
-
|
|
93
|
+
不想用 npx 也可以手动复制(任何工具都适用):
|
|
97
94
|
|
|
98
95
|
```bash
|
|
99
|
-
lts-cli
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
111
|
+
```bash
|
|
112
|
+
# 最近 2 小时最新 50 条
|
|
113
|
+
lts-cli query --last 2h -r -l 50
|
|
142
114
|
|
|
143
|
-
#
|
|
144
|
-
lts-cli query --
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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 --
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
162
|
+
`--app` / `--content` / `--request-id` 自动为值加双引号——含连字符的 UUID 不加引号会被分词导致匹配不到。
|
|
179
163
|
|
|
180
|
-
|
|
181
|
-
lts-cli init
|
|
164
|
+
### `lts-cli trace`
|
|
182
165
|
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
```bash
|
|
167
|
+
lts-cli trace <id> [--field <name>] [--last 24h] [-l 200] [-f fmt]
|
|
185
168
|
```
|
|
186
169
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
```
|
|
190
|
-
lts-cli completion <bash|zsh|powershell>
|
|
170
|
+
按时间正序拉出一条 ID 的完整链路。默认走全文检索(不依赖任何特定字段命名);如果你的日志流有专用字段,配一次即可:
|
|
191
171
|
|
|
192
|
-
|
|
172
|
+
```bash
|
|
173
|
+
lts-cli config --set traceField=REQUEST_ID
|
|
193
174
|
```
|
|
194
175
|
|
|
195
176
|
## 配置说明
|
|
196
177
|
|
|
197
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
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
|
-
|
|
|
244
|
+
| 变更 | 原因 | 迁移 |
|
|
228
245
|
|---|---|---|
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
|
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
256
|
-
|
|
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 --
|
|
167
|
+
console.log(' lts-cli query --last 2h');
|
|
168
|
+
console.log(' lts-cli fields');
|
|
151
169
|
}
|
|
152
170
|
else {
|
|
153
|
-
console.log(' lts-cli
|
|
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.
|
|
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
|
}
|