digitalsee-ai-flow-cli 0.7.6 → 0.7.23-beta.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/README.md +54 -1
- package/dist/api/client.js +34 -4
- package/dist/api/refresh.js +81 -0
- package/dist/api/service-log.js +125 -0
- package/dist/commands/auth.js +37 -15
- package/dist/commands/config.js +5 -3
- package/dist/commands/profile.js +6 -3
- package/dist/commands/service-log.js +286 -0
- package/dist/index.js +2 -0
- package/dist/utils/common.js +16 -0
- package/dist/utils/config.js +9 -2
- package/dist/utils/oauth.js +27 -8
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ ai-flow auth status --json
|
|
|
98
98
|
|
|
99
99
|
`--json` 输出示例:
|
|
100
100
|
```json
|
|
101
|
-
{ "ok": true, "loggedIn": true, "activeProfile": "dev", "token": "abcd****1234", "baseUrl": "https://dev.example.com" }
|
|
101
|
+
{ "ok": true, "data": { "loggedIn": true, "activeProfile": "dev", "token": "abcd****1234", "baseUrl": "https://dev.example.com" } }
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
### auth logout
|
|
@@ -1284,6 +1284,57 @@ ai-flow sync status --id <connectorId> --json
|
|
|
1284
1284
|
|
|
1285
1285
|
---
|
|
1286
1286
|
|
|
1287
|
+
## 运维日志
|
|
1288
|
+
|
|
1289
|
+
查询和实时跟踪运维服务日志。
|
|
1290
|
+
|
|
1291
|
+
### service-log search
|
|
1292
|
+
|
|
1293
|
+
查询历史运维日志,按时间范围、级别、关键词等条件筛选。
|
|
1294
|
+
|
|
1295
|
+
```bash
|
|
1296
|
+
ai-flow service-log search --time-range 1h
|
|
1297
|
+
ai-flow service-log search --time-range 3h --level ERROR
|
|
1298
|
+
ai-flow service-log search --time-range 7d --keyword "timeout" --service-type iam
|
|
1299
|
+
ai-flow service-log search --start-time "2026-07-01 00:00:00" --end-time "2026-07-20 23:59:59"
|
|
1300
|
+
ai-flow service-log search --time-range 24h --limit 100 --json
|
|
1301
|
+
```
|
|
1302
|
+
|
|
1303
|
+
| 选项 | 必填 | 默认值 | 说明 |
|
|
1304
|
+
|------|------|--------|------|
|
|
1305
|
+
| `--time-range` | 二选一 | — | 相对时间范围,格式: `<N>s\|m\|h\|d`(如 `30s`, `15m`, `3h`, `7d`),与 `--start-time` 互斥 |
|
|
1306
|
+
| `--start-time` | 二选一 | — | 开始时间 `yyyy-MM-dd HH:mm:ss`,与 `--time-range` 互斥 |
|
|
1307
|
+
| `--end-time` | 否 | 请求时刻 | 结束时间 `yyyy-MM-dd HH:mm:ss` |
|
|
1308
|
+
| `--limit` | 否 | `50` | 最大返回条数(上限 500) |
|
|
1309
|
+
| `--level` | 否 | — | 日志级别:`INFO` / `WARN` / `ERROR` |
|
|
1310
|
+
| `--request-id` | 否 | — | 链路 trace id(精确匹配) |
|
|
1311
|
+
| `--keyword` | 否 | — | 日志正文关键词 |
|
|
1312
|
+
| `--service-type` | 否 | — | 服务类型:`iam` / `acm` / `ncm` / `adm` / `mdm` / `nginx` 等 |
|
|
1313
|
+
| `--json` | 否 | — | 以 JSON 格式输出 `{ ok, data, total }` |
|
|
1314
|
+
|
|
1315
|
+
### service-log stream
|
|
1316
|
+
|
|
1317
|
+
通过 WebSocket 实时跟随运维日志,按 `Ctrl+C` 退出。连接前自动校验 token 可用性。
|
|
1318
|
+
|
|
1319
|
+
```bash
|
|
1320
|
+
ai-flow service-log stream
|
|
1321
|
+
ai-flow service-log stream --level ERROR --service-type iam
|
|
1322
|
+
ai-flow service-log stream --keyword "exception" --max 100
|
|
1323
|
+
ai-flow service-log stream --json # NDJSON 输出(每行一条 { ok, data })
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
| 选项 | 必填 | 默认值 | 说明 |
|
|
1327
|
+
|------|------|--------|------|
|
|
1328
|
+
| `--level` | 否 | — | 日志级别:`INFO` / `WARN` / `ERROR` |
|
|
1329
|
+
| `--request-id` | 否 | — | 链路 trace id(精确匹配) |
|
|
1330
|
+
| `--keyword` | 否 | — | 日志正文关键词 |
|
|
1331
|
+
| `--service-type` | 否 | — | 服务类型:`iam` / `acm` / `ncm` / `adm` / `mdm` / `nginx` 等 |
|
|
1332
|
+
| `--limit` | 否 | `20` | 单次 ES 拉取批量 |
|
|
1333
|
+
| `--max` | 否 | — | 收到 N 条后自动退出(默认不限) |
|
|
1334
|
+
| `--json` | 否 | — | NDJSON 输出,每行一条 `{ ok, data }` |
|
|
1335
|
+
|
|
1336
|
+
---
|
|
1337
|
+
|
|
1287
1338
|
## 知识库
|
|
1288
1339
|
|
|
1289
1340
|
### knowledge categories
|
|
@@ -1563,6 +1614,7 @@ src/
|
|
|
1563
1614
|
│ │ ├── cancel-sync.ts
|
|
1564
1615
|
│ │ └── status.ts
|
|
1565
1616
|
│ ├── org.ts # ai-flow org(组织架构)
|
|
1617
|
+
│ ├── service-log.ts # ai-flow service-log(运维日志)
|
|
1566
1618
|
│ └── knowledge.ts # ai-flow knowledge
|
|
1567
1619
|
├── api/
|
|
1568
1620
|
│ ├── client.ts # axios 实例(SSL、Token)
|
|
@@ -1574,6 +1626,7 @@ src/
|
|
|
1574
1626
|
│ ├── link.ts # 上游连接器 API
|
|
1575
1627
|
│ ├── mapping.ts # 属性映射 API
|
|
1576
1628
|
│ ├── sync.ts # 下游连接器 API
|
|
1629
|
+
│ ├── service-log.ts # 运维日志 API(search + stream)
|
|
1577
1630
|
│ └── org.ts # 组织架构 API
|
|
1578
1631
|
├── services/
|
|
1579
1632
|
│ ├── flowAnalyzer.ts # DAG 拓扑分析
|
package/dist/api/client.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.apiDelete = apiDelete;
|
|
|
14
14
|
const https_1 = __importDefault(require("https"));
|
|
15
15
|
const axios_1 = __importDefault(require("axios"));
|
|
16
16
|
const config_1 = require("@/utils/config");
|
|
17
|
+
const refresh_1 = require("@/api/refresh");
|
|
17
18
|
let clientInstance = null;
|
|
18
19
|
function classifyError(error) {
|
|
19
20
|
if (error && typeof error === 'object') {
|
|
@@ -116,10 +117,19 @@ function buildClient() {
|
|
|
116
117
|
},
|
|
117
118
|
timeout: 30000,
|
|
118
119
|
});
|
|
119
|
-
instance.interceptors.request.use((reqConfig) => {
|
|
120
|
+
instance.interceptors.request.use(async (reqConfig) => {
|
|
120
121
|
const cfg = (0, config_1.loadConfig)();
|
|
121
|
-
if (cfg.
|
|
122
|
-
|
|
122
|
+
if (cfg.auth?.access_token) {
|
|
123
|
+
// 临近过期时主动刷新,避免发送已知过期的 token
|
|
124
|
+
if ((0, refresh_1.isTokenExpired)() && cfg.auth.refresh_token) {
|
|
125
|
+
const newAuth = await (0, refresh_1.attemptTokenRefresh)();
|
|
126
|
+
if (newAuth) {
|
|
127
|
+
reqConfig.headers.Authorization = `${newAuth.token_type ?? 'Bearer'} ${newAuth.access_token}`;
|
|
128
|
+
return reqConfig;
|
|
129
|
+
}
|
|
130
|
+
// 刷新失败不阻塞,继续用当前 token,401 interceptor 会再尝试
|
|
131
|
+
}
|
|
132
|
+
reqConfig.headers.Authorization = `${cfg.auth.token_type ?? 'Bearer'} ${cfg.auth.access_token}`;
|
|
123
133
|
}
|
|
124
134
|
return reqConfig;
|
|
125
135
|
});
|
|
@@ -127,9 +137,29 @@ function buildClient() {
|
|
|
127
137
|
const body = response.data;
|
|
128
138
|
if (body && typeof body === 'object' && 'data' in body) {
|
|
129
139
|
response.data = body.data;
|
|
140
|
+
//钉钉网关可能会触发 code: -1, Access Denied
|
|
141
|
+
if (body.data === null && body.code === '-1') {
|
|
142
|
+
throw new Error(humanizeError(body.message ?? body));
|
|
143
|
+
}
|
|
130
144
|
}
|
|
131
145
|
return response;
|
|
132
|
-
}, (error) => {
|
|
146
|
+
}, async (error) => {
|
|
147
|
+
const errObj = error;
|
|
148
|
+
const originalRequest = errObj.config;
|
|
149
|
+
if (errObj.response?.status === 401 &&
|
|
150
|
+
originalRequest &&
|
|
151
|
+
!originalRequest._retry) {
|
|
152
|
+
const cfg = (0, config_1.loadConfig)();
|
|
153
|
+
if (cfg.auth?.refresh_token) {
|
|
154
|
+
originalRequest._retry = true;
|
|
155
|
+
const newAuth = await (0, refresh_1.attemptTokenRefresh)();
|
|
156
|
+
if (newAuth) {
|
|
157
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
158
|
+
originalRequest.headers.Authorization = `${newAuth.token_type ?? 'Bearer'} ${newAuth.access_token}`;
|
|
159
|
+
return instance.request(originalRequest);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
133
163
|
throw new Error(humanizeError(error));
|
|
134
164
|
});
|
|
135
165
|
return instance;
|
|
@@ -0,0 +1,81 @@
|
|
|
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.attemptTokenRefresh = attemptTokenRefresh;
|
|
7
|
+
exports.isTokenExpired = isTokenExpired;
|
|
8
|
+
const https_1 = __importDefault(require("https"));
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = require("@/utils/config");
|
|
11
|
+
/**
|
|
12
|
+
* 并发控制:多个 401 同时到达时,只发一次 refresh 请求,
|
|
13
|
+
* 其他调用复用同一个 Promise。
|
|
14
|
+
*/
|
|
15
|
+
let isRefreshing = false;
|
|
16
|
+
let pendingRefresh = null;
|
|
17
|
+
const EXPIRY_BUFFER_MS = 120000; // 提前 120 秒刷新,避免临界区过期
|
|
18
|
+
/**
|
|
19
|
+
* 使用 refresh_token 向 /iam/token 获取新 access_token,
|
|
20
|
+
* 成功后自动写入 global config 并返回新的 AuthConfig。
|
|
21
|
+
* 无 refresh_token 或刷新失败返回 null。
|
|
22
|
+
*/
|
|
23
|
+
async function attemptTokenRefresh() {
|
|
24
|
+
const cfg = (0, config_1.loadConfig)();
|
|
25
|
+
if (!cfg.auth?.refresh_token) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
// 去重:如果已有正在进行的刷新,复用同一个 Promise
|
|
29
|
+
if (isRefreshing && pendingRefresh) {
|
|
30
|
+
return pendingRefresh;
|
|
31
|
+
}
|
|
32
|
+
isRefreshing = true;
|
|
33
|
+
pendingRefresh = (async () => {
|
|
34
|
+
try {
|
|
35
|
+
const httpsAgent = new https_1.default.Agent({ rejectUnauthorized: !cfg.insecure });
|
|
36
|
+
const res = await axios_1.default.post(`${cfg.baseUrl}/iam/token`, {
|
|
37
|
+
grant_type: 'refresh_token',
|
|
38
|
+
client_id: 'usercenter',
|
|
39
|
+
refresh_token: cfg.auth.refresh_token,
|
|
40
|
+
}, {
|
|
41
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
42
|
+
httpsAgent,
|
|
43
|
+
timeout: 15000,
|
|
44
|
+
// 不使用 getClient(),避免触发 request/response 拦截器
|
|
45
|
+
});
|
|
46
|
+
const { access_token, refresh_token: newRt, token_type, expires_in } = res.data;
|
|
47
|
+
const newAuth = {
|
|
48
|
+
access_token,
|
|
49
|
+
refresh_token: newRt || cfg.auth.refresh_token,
|
|
50
|
+
token_type: token_type || cfg.auth.token_type || 'Bearer',
|
|
51
|
+
// 使用客户端本地时间计算过期时刻,服务端不支持返回绝对过期时间
|
|
52
|
+
expires_at: Date.now() + (expires_in || 3600) * 1000,
|
|
53
|
+
};
|
|
54
|
+
(0, config_1.saveGlobalConfig)({ auth: newAuth });
|
|
55
|
+
return newAuth;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
const detail = err instanceof Error ? err.message : JSON.stringify(err);
|
|
59
|
+
process.stderr.write(`[refresh] token 刷新失败: ${detail}\n`);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
isRefreshing = false;
|
|
64
|
+
pendingRefresh = null;
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
return pendingRefresh;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 基于 config.auth.expires_at 判断 token 是否已(或即将)过期。
|
|
71
|
+
* 包含 120 秒缓冲,避免 token 在飞行期间过期。
|
|
72
|
+
* 无 expires_at 时默认返回 false(不阻塞,让 401 响应来驱动刷新)。
|
|
73
|
+
*/
|
|
74
|
+
function isTokenExpired() {
|
|
75
|
+
const cfg = (0, config_1.loadConfig)();
|
|
76
|
+
if (!cfg.auth?.expires_at) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return Date.now() >= cfg.auth.expires_at - EXPIRY_BUFFER_MS;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1,125 @@
|
|
|
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.verifyToken = verifyToken;
|
|
7
|
+
exports.searchServiceLogs = searchServiceLogs;
|
|
8
|
+
exports.openServiceLogStream = openServiceLogStream;
|
|
9
|
+
const https_1 = __importDefault(require("https"));
|
|
10
|
+
const axios_1 = __importDefault(require("axios"));
|
|
11
|
+
const ws_1 = __importDefault(require("ws"));
|
|
12
|
+
const config_1 = require("@/utils/config");
|
|
13
|
+
const client_1 = require("@/api/client");
|
|
14
|
+
const SEARCH_PATH = '/iam/api/service-log/search';
|
|
15
|
+
const STREAM_PATH = '/iam/api/service-log/stream';
|
|
16
|
+
/**
|
|
17
|
+
* 验证当前 token 是否可用(发起轻量 GET 请求)
|
|
18
|
+
* token 无效时抛出 axios interceptor 格式化的错误
|
|
19
|
+
*/
|
|
20
|
+
async function verifyToken() {
|
|
21
|
+
const client = (0, client_1.getClient)();
|
|
22
|
+
await client.get('/iam/api/connectors');
|
|
23
|
+
}
|
|
24
|
+
function buildHttpClient() {
|
|
25
|
+
const cfg = (0, config_1.loadConfig)();
|
|
26
|
+
return axios_1.default.create({
|
|
27
|
+
baseURL: cfg.baseUrl,
|
|
28
|
+
httpsAgent: new https_1.default.Agent({ rejectUnauthorized: !cfg.insecure }),
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
Authorization: `${cfg.auth?.token_type ?? 'Bearer'} ${cfg.auth?.access_token}`,
|
|
32
|
+
},
|
|
33
|
+
timeout: 30000,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 本地校验 envelope:现有 client.ts 拦截器只解包 data、不检查 result 字段,
|
|
38
|
+
* 而文档 2.6 规定业务错误(如 "limit 不能超过 500")以 HTTP 200 + result=false 返回,
|
|
39
|
+
* 因此本函数不复用 apiPost,独立 POST 并自行检查 envelope。
|
|
40
|
+
*/
|
|
41
|
+
async function searchServiceLogs(req) {
|
|
42
|
+
const client = buildHttpClient();
|
|
43
|
+
const response = await client.post(SEARCH_PATH, req);
|
|
44
|
+
const body = response.data;
|
|
45
|
+
if (Array.isArray(body)) {
|
|
46
|
+
return body;
|
|
47
|
+
}
|
|
48
|
+
if (body && typeof body === 'object' && 'result' in body) {
|
|
49
|
+
if (body.result === false) {
|
|
50
|
+
throw new Error(body.error_description || body.error || '日志查询失败');
|
|
51
|
+
}
|
|
52
|
+
return body.data ?? [];
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
function toWsUrl(baseUrl) {
|
|
57
|
+
const trimmed = baseUrl.replace(/\/+$/, '');
|
|
58
|
+
if (trimmed.startsWith('https://')) {
|
|
59
|
+
return `wss://${trimmed.slice(8)}${STREAM_PATH}`;
|
|
60
|
+
}
|
|
61
|
+
if (trimmed.startsWith('http://')) {
|
|
62
|
+
return `ws://${trimmed.slice(7)}${STREAM_PATH}`;
|
|
63
|
+
}
|
|
64
|
+
return `${trimmed}${STREAM_PATH}`;
|
|
65
|
+
}
|
|
66
|
+
function openServiceLogStream(filters, handlers) {
|
|
67
|
+
const cfg = (0, config_1.loadConfig)();
|
|
68
|
+
if (!cfg.auth?.access_token) {
|
|
69
|
+
throw new Error('未登录,请先执行 ai-flow auth login');
|
|
70
|
+
}
|
|
71
|
+
const url = toWsUrl(cfg.baseUrl);
|
|
72
|
+
const ws = new ws_1.default(url, {
|
|
73
|
+
headers: { Authorization: `${cfg.auth.token_type ?? 'Bearer'} ${cfg.auth.access_token}` },
|
|
74
|
+
rejectUnauthorized: !cfg.insecure,
|
|
75
|
+
});
|
|
76
|
+
let cancelled = false;
|
|
77
|
+
ws.on('open', () => {
|
|
78
|
+
const subscribeFrame = {
|
|
79
|
+
action: 'subscribe',
|
|
80
|
+
...filters,
|
|
81
|
+
};
|
|
82
|
+
ws.send(JSON.stringify(subscribeFrame));
|
|
83
|
+
handlers.onOpen?.();
|
|
84
|
+
});
|
|
85
|
+
ws.on('message', (raw) => {
|
|
86
|
+
let msg;
|
|
87
|
+
try {
|
|
88
|
+
msg = JSON.parse(raw.toString());
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (msg.type === 'log' && msg.data) {
|
|
94
|
+
handlers.onLog(msg.data);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (msg.type === 'error' && msg.message) {
|
|
98
|
+
handlers.onError(msg.message);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
ws.on('close', (code, reasonBuf) => {
|
|
102
|
+
const reason = reasonBuf?.toString?.() ?? '';
|
|
103
|
+
handlers.onClose(code, reason);
|
|
104
|
+
});
|
|
105
|
+
ws.on('error', (err) => {
|
|
106
|
+
handlers.onError(err.message || 'WebSocket 连接错误');
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
cancel() {
|
|
110
|
+
if (cancelled)
|
|
111
|
+
return;
|
|
112
|
+
cancelled = true;
|
|
113
|
+
if (ws.readyState === ws_1.default.OPEN) {
|
|
114
|
+
ws.send(JSON.stringify({ action: 'cancel' }));
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
ws.close();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=service-log.js.map
|
package/dist/commands/auth.js
CHANGED
|
@@ -29,14 +29,14 @@ function registerAuthCommand(program) {
|
|
|
29
29
|
(0, output_1.printInfo)(`使用 Profile: ${(0, config_1.getActiveProfile)()}`);
|
|
30
30
|
const protocol = options.https ? 'https' : 'http';
|
|
31
31
|
(0, output_1.printInfo)(`正在准备 OAuth 登录 (${protocol.toUpperCase()})...`);
|
|
32
|
-
const { server, port,
|
|
32
|
+
const { server, port, waitForResult } = options.https
|
|
33
33
|
? await (0, oauth_1.createAuthServerHttps)()
|
|
34
34
|
: await (0, oauth_1.createAuthServer)();
|
|
35
35
|
const authorizeUrl = (0, oauth_1.buildAuthorizeUrl)(config.baseUrl, port, protocol);
|
|
36
36
|
(0, output_1.printHeader)('OAuth 2 登录');
|
|
37
37
|
(0, output_1.printInfo)('打开下方链接,授权登录');
|
|
38
38
|
console.log(`\n${chalk_1.default.cyan(authorizeUrl)}\n`);
|
|
39
|
-
const
|
|
39
|
+
const resultPromise = waitForResult();
|
|
40
40
|
(0, output_1.printInfo)('等待授权回调...');
|
|
41
41
|
rl = readline.createInterface({ input, output });
|
|
42
42
|
rl.question('按 Enter 在浏览器中打开...\n', () => {
|
|
@@ -50,9 +50,17 @@ function registerAuthCommand(program) {
|
|
|
50
50
|
rl.close();
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
const
|
|
53
|
+
const { access_token, refresh_token, id_token, token_type, expires_in } = await resultPromise;
|
|
54
|
+
// 使用客户端本地时间计算过期时刻,服务端不支持返回绝对过期时间
|
|
55
|
+
const expires_at = new Date().getTime() + expires_in * 1000;
|
|
54
56
|
const saveData = {
|
|
55
|
-
|
|
57
|
+
auth: {
|
|
58
|
+
access_token,
|
|
59
|
+
refresh_token,
|
|
60
|
+
id_token,
|
|
61
|
+
token_type,
|
|
62
|
+
expires_at,
|
|
63
|
+
},
|
|
56
64
|
};
|
|
57
65
|
(0, config_1.saveGlobalConfig)(saveData);
|
|
58
66
|
(0, output_1.printSuccess)('登录成功');
|
|
@@ -75,11 +83,20 @@ function registerAuthCommand(program) {
|
|
|
75
83
|
if (options.json)
|
|
76
84
|
(0, output_1.setJsonMode)(true);
|
|
77
85
|
const scope = options.project ? 'project' : 'global';
|
|
86
|
+
const defaultConfig = {
|
|
87
|
+
auth: {
|
|
88
|
+
access_token: '',
|
|
89
|
+
refresh_token: '',
|
|
90
|
+
id_token: '',
|
|
91
|
+
token_type: '',
|
|
92
|
+
expires_at: 0,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
78
95
|
if (options.project) {
|
|
79
|
-
(0, config_1.saveProjectConfig)(
|
|
96
|
+
(0, config_1.saveProjectConfig)(defaultConfig);
|
|
80
97
|
}
|
|
81
98
|
else {
|
|
82
|
-
(0, config_1.saveGlobalConfig)(
|
|
99
|
+
(0, config_1.saveGlobalConfig)(defaultConfig);
|
|
83
100
|
}
|
|
84
101
|
if (options.json) {
|
|
85
102
|
(0, output_1.printStructuredResult)({ ok: true, scope, activeProfile: (0, config_1.getActiveProfile)() });
|
|
@@ -105,9 +122,12 @@ function registerAuthCommand(program) {
|
|
|
105
122
|
if (options.json)
|
|
106
123
|
(0, output_1.setJsonMode)(true);
|
|
107
124
|
const config = (0, config_1.loadConfig)();
|
|
108
|
-
if (!config.
|
|
125
|
+
if (!config.auth || !config.auth.access_token) {
|
|
109
126
|
if (options.json) {
|
|
110
|
-
(0, output_1.printStructuredResult)({
|
|
127
|
+
(0, output_1.printStructuredResult)({
|
|
128
|
+
ok: true,
|
|
129
|
+
data: { loggedIn: false, activeProfile: (0, config_1.getActiveProfile)() },
|
|
130
|
+
});
|
|
111
131
|
return;
|
|
112
132
|
}
|
|
113
133
|
(0, output_1.printHeader)('认证状态');
|
|
@@ -116,18 +136,20 @@ function registerAuthCommand(program) {
|
|
|
116
136
|
(0, output_1.printInfo)('请执行 ai-flow auth login 登录');
|
|
117
137
|
return;
|
|
118
138
|
}
|
|
119
|
-
const maskedToken = config.
|
|
120
|
-
? config.
|
|
139
|
+
const maskedToken = config.auth.access_token.length > 8
|
|
140
|
+
? config.auth.access_token.substring(0, 4) +
|
|
121
141
|
'****' +
|
|
122
|
-
config.
|
|
142
|
+
config.auth.access_token.substring(config.auth.access_token.length - 4)
|
|
123
143
|
: '****';
|
|
124
144
|
if (options.json) {
|
|
125
145
|
(0, output_1.printStructuredResult)({
|
|
126
146
|
ok: true,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
data: {
|
|
148
|
+
loggedIn: true,
|
|
149
|
+
activeProfile: (0, config_1.getActiveProfile)(),
|
|
150
|
+
token: maskedToken,
|
|
151
|
+
baseUrl: config.baseUrl,
|
|
152
|
+
},
|
|
131
153
|
});
|
|
132
154
|
return;
|
|
133
155
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -15,7 +15,7 @@ function registerConfigCommand(program) {
|
|
|
15
15
|
(0, output_1.printKeyValue)([
|
|
16
16
|
['Profile', (0, config_1.getActiveProfile)()],
|
|
17
17
|
['baseUrl', config.baseUrl],
|
|
18
|
-
['token', (0, output_1.truncateToken)(
|
|
18
|
+
['token', (0, output_1.truncateToken)((0, config_1.getAccessToken)(config)) || '(未设置)'],
|
|
19
19
|
['insecure', String(config.insecure)],
|
|
20
20
|
]);
|
|
21
21
|
});
|
|
@@ -31,8 +31,10 @@ function registerConfigCommand(program) {
|
|
|
31
31
|
const updates = {};
|
|
32
32
|
if (options.baseUrl)
|
|
33
33
|
updates.baseUrl = options.baseUrl.replace(/\/+$/, '');
|
|
34
|
-
if (options.token)
|
|
35
|
-
|
|
34
|
+
if (options.token) {
|
|
35
|
+
const currentConfig = (0, config_1.loadConfig)(options.profile || undefined);
|
|
36
|
+
updates.auth = { ...(currentConfig.auth || {}), access_token: options.token };
|
|
37
|
+
}
|
|
36
38
|
if (options.insecure !== undefined)
|
|
37
39
|
updates.insecure = true;
|
|
38
40
|
if (Object.keys(updates).length === 0) {
|
package/dist/commands/profile.js
CHANGED
|
@@ -82,7 +82,7 @@ function registerProfileCommand(program) {
|
|
|
82
82
|
ok: true,
|
|
83
83
|
profile: profileName,
|
|
84
84
|
baseUrl: config.baseUrl,
|
|
85
|
-
token:
|
|
85
|
+
token: (0, output_1.truncateToken)((0, config_1.getAccessToken)(config)),
|
|
86
86
|
insecure: config.insecure,
|
|
87
87
|
});
|
|
88
88
|
return;
|
|
@@ -90,7 +90,7 @@ function registerProfileCommand(program) {
|
|
|
90
90
|
(0, output_1.printHeader)(`Profile: ${profileName}`);
|
|
91
91
|
(0, output_1.printKeyValue)([
|
|
92
92
|
['baseUrl', config.baseUrl],
|
|
93
|
-
['token', (0, output_1.truncateToken)(
|
|
93
|
+
['token', (0, output_1.truncateToken)((0, config_1.getAccessToken)(config)) || '(未设置)'],
|
|
94
94
|
['insecure', String(config.insecure)],
|
|
95
95
|
]);
|
|
96
96
|
});
|
|
@@ -130,7 +130,10 @@ function registerProfileCommand(program) {
|
|
|
130
130
|
}
|
|
131
131
|
const baseUrl = options.baseUrl || '';
|
|
132
132
|
const token = options.token || '';
|
|
133
|
-
|
|
133
|
+
const auth = {
|
|
134
|
+
access_token: token,
|
|
135
|
+
};
|
|
136
|
+
(0, config_1.addProfile)(name, { baseUrl, auth, insecure: !!options.insecure });
|
|
134
137
|
if (options.setDefault) {
|
|
135
138
|
(0, config_1.setDefaultProfile)(name);
|
|
136
139
|
(0, config_1.setActiveProfile)(name);
|
|
@@ -0,0 +1,286 @@
|
|
|
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.registerServiceLogCommand = registerServiceLogCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const service_log_1 = require("@/api/service-log");
|
|
9
|
+
const output_1 = require("@/utils/output");
|
|
10
|
+
const exit_code_1 = require("@/constants/exit-code");
|
|
11
|
+
const common_1 = require("@/utils/common");
|
|
12
|
+
const config_1 = require("@/utils/config");
|
|
13
|
+
const DATETIME_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
14
|
+
function pad2(n) {
|
|
15
|
+
return n < 10 ? `0${n}` : String(n);
|
|
16
|
+
}
|
|
17
|
+
function formatDateTime(d) {
|
|
18
|
+
return (`${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ` +
|
|
19
|
+
`${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`);
|
|
20
|
+
}
|
|
21
|
+
function colorizeLevel(level) {
|
|
22
|
+
const upper = (level || '').toUpperCase();
|
|
23
|
+
if (upper === 'ERROR' || upper === 'FATAL')
|
|
24
|
+
return chalk_1.default.red(level);
|
|
25
|
+
if (upper === 'WARN' || upper === 'WARNING')
|
|
26
|
+
return chalk_1.default.yellow(level);
|
|
27
|
+
if (upper === 'INFO')
|
|
28
|
+
return chalk_1.default.green(level);
|
|
29
|
+
if (upper === 'DEBUG' || upper === 'TRACE')
|
|
30
|
+
return chalk_1.default.gray(level);
|
|
31
|
+
return level;
|
|
32
|
+
}
|
|
33
|
+
function pickLogTime(entry) {
|
|
34
|
+
return entry.log_time || entry.timestamp || '-';
|
|
35
|
+
}
|
|
36
|
+
function flattenMessage(message) {
|
|
37
|
+
return (message ?? '').replace(/\r?\n/g, ' ');
|
|
38
|
+
}
|
|
39
|
+
function registerServiceLogCommand(program) {
|
|
40
|
+
const cmd = program.command('service-log').description('运维日志查询');
|
|
41
|
+
registerSearch(cmd);
|
|
42
|
+
registerStream(cmd);
|
|
43
|
+
}
|
|
44
|
+
function registerSearch(serviceLogCmd) {
|
|
45
|
+
serviceLogCmd
|
|
46
|
+
.command('search')
|
|
47
|
+
.description('查询历史运维日志')
|
|
48
|
+
.option('--time-range <r>', '相对时间范围,格式: <N>s|m|h|d(如 30s, 15m, 3h, 7d)(与 --start-time 互斥)')
|
|
49
|
+
.option('--start-time <time>', '开始时间 yyyy-MM-dd HH:mm:ss(与 --time-range 互斥)')
|
|
50
|
+
.option('--end-time <time>', '结束时间 yyyy-MM-dd HH:mm:ss(不传则服务端用请求时刻)')
|
|
51
|
+
.option('--limit <n>', '最大返回条数(默认 50,上限 500)', '50')
|
|
52
|
+
.option('--level <level>', '日志级别: INFO / WARN / ERROR')
|
|
53
|
+
.option('--request-id <id>', '链路 trace id(精确匹配)')
|
|
54
|
+
.option('--keyword <text>', '日志正文关键词')
|
|
55
|
+
.option('--service-type <type>', '服务类型: iam / acm / ncm / adm / mdm / nginx 等')
|
|
56
|
+
.option('--json', '以 JSON 输出')
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
try {
|
|
59
|
+
if (options.json)
|
|
60
|
+
(0, output_1.setJsonMode)(true);
|
|
61
|
+
const req = buildSearchRequest(options);
|
|
62
|
+
if (req instanceof Error) {
|
|
63
|
+
if (options.json) {
|
|
64
|
+
(0, output_1.printStructuredResult)({ ok: false, message: req.message });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
(0, output_1.printError)(req.message);
|
|
68
|
+
}
|
|
69
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!options.json) {
|
|
73
|
+
(0, output_1.printInfo)('正在查询历史日志...');
|
|
74
|
+
}
|
|
75
|
+
const logs = await (0, service_log_1.searchServiceLogs)(req);
|
|
76
|
+
if (options.json) {
|
|
77
|
+
(0, output_1.printStructuredResult)({ ok: true, data: logs, total: logs.length });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (logs.length === 0) {
|
|
81
|
+
(0, output_1.printInfo)('暂无符合条件的日志');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
(0, output_1.printHeader)(`运维日志 (共 ${logs.length} 条,按时间倒序)`);
|
|
85
|
+
const rows = logs.map((entry) => [
|
|
86
|
+
pickLogTime(entry),
|
|
87
|
+
colorizeLevel(entry.level || '-'),
|
|
88
|
+
String(entry.host ?? '-'),
|
|
89
|
+
String(entry.trace_id ?? '-'),
|
|
90
|
+
flattenMessage(entry.message),
|
|
91
|
+
]);
|
|
92
|
+
(0, output_1.printTable)(['时间', '级别', '服务实例', 'Trace', '消息'], rows, {
|
|
93
|
+
wrapCols: [4],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (options.json) {
|
|
98
|
+
(0, output_1.printStructuredResult)({ ok: false, message: error.message });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
(0, output_1.printError)(`查询日志失败: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function buildSearchRequest(options) {
|
|
108
|
+
const hasRange = Boolean(options.timeRange);
|
|
109
|
+
const hasStart = Boolean(options.startTime);
|
|
110
|
+
if (!hasRange && !hasStart) {
|
|
111
|
+
return new Error('必须指定 --time-range 或 --start-time 之一');
|
|
112
|
+
}
|
|
113
|
+
if (hasRange && hasStart) {
|
|
114
|
+
return new Error('--time-range 与 --start-time 不可同时使用');
|
|
115
|
+
}
|
|
116
|
+
let start_time;
|
|
117
|
+
let end_time;
|
|
118
|
+
if (hasRange) {
|
|
119
|
+
const rangeKey = String(options.timeRange);
|
|
120
|
+
const ms = (0, common_1.parseTimeRange)(rangeKey);
|
|
121
|
+
if (ms === null) {
|
|
122
|
+
return new Error(`不支持的 --time-range 格式: "${rangeKey}",应为 <N>s|m|h|d(如 30s, 15m, 3h, 7d)`);
|
|
123
|
+
}
|
|
124
|
+
const now = new Date();
|
|
125
|
+
start_time = formatDateTime(new Date(now.getTime() - ms));
|
|
126
|
+
end_time = formatDateTime(now);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const st = String(options.startTime);
|
|
130
|
+
if (!DATETIME_REGEX.test(st)) {
|
|
131
|
+
return new Error('--start-time 格式应为 yyyy-MM-dd HH:mm:ss');
|
|
132
|
+
}
|
|
133
|
+
start_time = st;
|
|
134
|
+
if (options.endTime) {
|
|
135
|
+
const et = String(options.endTime);
|
|
136
|
+
if (!DATETIME_REGEX.test(et)) {
|
|
137
|
+
return new Error('--end-time 格式应为 yyyy-MM-dd HH:mm:ss');
|
|
138
|
+
}
|
|
139
|
+
end_time = et;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const limit = Number(options.limit);
|
|
143
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
144
|
+
return new Error('--limit 必须为正整数');
|
|
145
|
+
}
|
|
146
|
+
const req = { start_time, limit };
|
|
147
|
+
if (end_time)
|
|
148
|
+
req.end_time = end_time;
|
|
149
|
+
if (options.level)
|
|
150
|
+
req.log_level = String(options.level);
|
|
151
|
+
if (options.requestId)
|
|
152
|
+
req.request_id = String(options.requestId);
|
|
153
|
+
if (options.keyword)
|
|
154
|
+
req.keyword = String(options.keyword);
|
|
155
|
+
if (options.serviceType)
|
|
156
|
+
req.service_type = String(options.serviceType);
|
|
157
|
+
return req;
|
|
158
|
+
}
|
|
159
|
+
function registerStream(serviceLogCmd) {
|
|
160
|
+
serviceLogCmd
|
|
161
|
+
.command('stream')
|
|
162
|
+
.description('实时跟随运维日志(WebSocket,Ctrl+C 退出)')
|
|
163
|
+
.option('--level <level>', '日志级别: INFO / WARN / ERROR')
|
|
164
|
+
.option('--request-id <id>', '链路 trace id(精确匹配)')
|
|
165
|
+
.option('--keyword <text>', '日志正文关键词')
|
|
166
|
+
.option('--service-type <type>', '服务类型: iam / acm / ncm / adm / mdm / nginx 等')
|
|
167
|
+
.option('--limit <n>', '单次 ES 拉取批量(默认 20)', '20')
|
|
168
|
+
.option('--max <n>', '收到 N 条后自动退出(默认不限)')
|
|
169
|
+
.option('--json', 'NDJSON 输出(每行一条 { ok, data })')
|
|
170
|
+
.action(async (options) => {
|
|
171
|
+
if (options.json)
|
|
172
|
+
(0, output_1.setJsonMode)(true);
|
|
173
|
+
const cfg = (0, config_1.loadConfig)();
|
|
174
|
+
if (!cfg.auth || !cfg.auth.access_token) {
|
|
175
|
+
const msg = '未登录,请先执行 ai-flow auth login';
|
|
176
|
+
if (options.json) {
|
|
177
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
(0, output_1.printError)(msg);
|
|
181
|
+
}
|
|
182
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await (0, service_log_1.verifyToken)();
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
const msg = error.message || 'Token 校验失败';
|
|
190
|
+
if (options.json) {
|
|
191
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
(0, output_1.printError)(`Token 不可用: ${msg}`);
|
|
195
|
+
}
|
|
196
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const filters = buildStreamFilters(options);
|
|
200
|
+
if (filters instanceof Error) {
|
|
201
|
+
if (options.json) {
|
|
202
|
+
(0, output_1.printStructuredResult)({ ok: false, message: filters.message });
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
(0, output_1.printError)(filters.message);
|
|
206
|
+
}
|
|
207
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const maxCount = options.max ? Number(options.max) : 0;
|
|
211
|
+
if (options.max && (!Number.isFinite(maxCount) || maxCount <= 0)) {
|
|
212
|
+
const msg = '--max 必须为正整数';
|
|
213
|
+
if (options.json) {
|
|
214
|
+
(0, output_1.printStructuredResult)({ ok: false, message: msg });
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
(0, output_1.printError)(msg);
|
|
218
|
+
}
|
|
219
|
+
process.exitCode = exit_code_1.ExitCode.FAILURE;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
let received = 0;
|
|
223
|
+
let closed = false;
|
|
224
|
+
if (!options.json) {
|
|
225
|
+
(0, output_1.printInfo)('正在连接实时日志... (Ctrl+C 退出)');
|
|
226
|
+
}
|
|
227
|
+
const control = (0, service_log_1.openServiceLogStream)(filters, {
|
|
228
|
+
onLog: (entry) => {
|
|
229
|
+
received += 1;
|
|
230
|
+
if (options.json) {
|
|
231
|
+
(0, output_1.printStructuredResult)({ ok: true, data: entry });
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const time = pickLogTime(entry);
|
|
235
|
+
const level = colorizeLevel(entry.level || '-');
|
|
236
|
+
const host = entry.host ?? '-';
|
|
237
|
+
const msg = flattenMessage(entry.message);
|
|
238
|
+
console.log(`[${time}] [${level}] [${host}] ${msg}`);
|
|
239
|
+
}
|
|
240
|
+
if (maxCount > 0 && received >= maxCount) {
|
|
241
|
+
control.cancel();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
onError: (message) => {
|
|
245
|
+
if (options.json) {
|
|
246
|
+
(0, output_1.printStructuredResult)({ ok: false, message });
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
(0, output_1.printWarning)(message);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
onClose: (code) => {
|
|
253
|
+
if (closed)
|
|
254
|
+
return;
|
|
255
|
+
closed = true;
|
|
256
|
+
if (!options.json) {
|
|
257
|
+
(0, output_1.printInfo)(`实时日志连接已关闭 (code=${code})`);
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
const onSigInt = () => {
|
|
262
|
+
if (!closed) {
|
|
263
|
+
control.cancel();
|
|
264
|
+
}
|
|
265
|
+
process.removeListener('SIGINT', onSigInt);
|
|
266
|
+
};
|
|
267
|
+
process.on('SIGINT', onSigInt);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
function buildStreamFilters(options) {
|
|
271
|
+
const limit = Number(options.limit);
|
|
272
|
+
if (!Number.isFinite(limit) || limit <= 0) {
|
|
273
|
+
return new Error('--limit 必须为正整数');
|
|
274
|
+
}
|
|
275
|
+
const filters = { limit };
|
|
276
|
+
if (options.level)
|
|
277
|
+
filters.log_level = String(options.level);
|
|
278
|
+
if (options.requestId)
|
|
279
|
+
filters.request_id = String(options.requestId);
|
|
280
|
+
if (options.keyword)
|
|
281
|
+
filters.keyword = String(options.keyword);
|
|
282
|
+
if (options.serviceType)
|
|
283
|
+
filters.service_type = String(options.serviceType);
|
|
284
|
+
return filters;
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=service-log.js.map
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ const link_1 = require("@/commands/link");
|
|
|
57
57
|
const sync_1 = require("@/commands/sync");
|
|
58
58
|
const org_1 = require("@/commands/org");
|
|
59
59
|
const profile_1 = require("@/commands/profile");
|
|
60
|
+
const service_log_1 = require("@/commands/service-log");
|
|
60
61
|
const updateCheck_1 = require("@/utils/updateCheck");
|
|
61
62
|
const output_1 = require("@/utils/output");
|
|
62
63
|
const config_2 = require("@/utils/config");
|
|
@@ -76,6 +77,7 @@ async function main() {
|
|
|
76
77
|
(0, sync_1.registerSyncCommand)(program);
|
|
77
78
|
(0, org_1.registerOrgCommand)(program);
|
|
78
79
|
(0, profile_1.registerProfileCommand)(program);
|
|
80
|
+
(0, service_log_1.registerServiceLogCommand)(program);
|
|
79
81
|
program.hook('preAction', (thisCommand) => {
|
|
80
82
|
const opts = thisCommand.optsWithGlobals();
|
|
81
83
|
if (opts.quiet) {
|
package/dist/utils/common.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.safeParseJSON = safeParseJSON;
|
|
4
|
+
exports.parseTimeRange = parseTimeRange;
|
|
4
5
|
function safeParseJSON(value, fallback) {
|
|
5
6
|
if (typeof value !== 'string')
|
|
6
7
|
return fallback;
|
|
@@ -11,4 +12,19 @@ function safeParseJSON(value, fallback) {
|
|
|
11
12
|
return fallback;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
15
|
+
const UNIT_MS = {
|
|
16
|
+
s: 1000,
|
|
17
|
+
m: 60 * 1000,
|
|
18
|
+
h: 60 * 60 * 1000,
|
|
19
|
+
d: 24 * 60 * 60 * 1000,
|
|
20
|
+
};
|
|
21
|
+
function parseTimeRange(value) {
|
|
22
|
+
const trimmed = String(value).trim();
|
|
23
|
+
const match = trimmed.match(/^(\d+)([smhd])$/i);
|
|
24
|
+
if (!match)
|
|
25
|
+
return null;
|
|
26
|
+
const num = parseInt(match[1], 10);
|
|
27
|
+
const unit = match[2].toLowerCase();
|
|
28
|
+
return num * (UNIT_MS[unit] ?? 0);
|
|
29
|
+
}
|
|
14
30
|
//# sourceMappingURL=common.js.map
|
package/dist/utils/config.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getAccessToken = getAccessToken;
|
|
36
37
|
exports.setActiveProfile = setActiveProfile;
|
|
37
38
|
exports.getActiveProfile = getActiveProfile;
|
|
38
39
|
exports.loadConfig = loadConfig;
|
|
@@ -55,8 +56,8 @@ const PROJECT_RC = '.ai-flowrc';
|
|
|
55
56
|
const DEFAULT_PROFILE_NAME = 'default';
|
|
56
57
|
const DEFAULT_CONFIG = {
|
|
57
58
|
baseUrl: 'https://poc.digitalsee.cn',
|
|
58
|
-
token: '',
|
|
59
59
|
insecure: false,
|
|
60
|
+
auth: null,
|
|
60
61
|
};
|
|
61
62
|
let activeProfileOverride = null;
|
|
62
63
|
function ensureConfigDir() {
|
|
@@ -88,12 +89,18 @@ function buildProfileStoreFromFlat(raw) {
|
|
|
88
89
|
profiles: {
|
|
89
90
|
[DEFAULT_PROFILE_NAME]: {
|
|
90
91
|
baseUrl: raw.baseUrl || DEFAULT_CONFIG.baseUrl,
|
|
91
|
-
token: raw.token
|
|
92
|
+
auth: raw.auth ?? (raw.token ? { access_token: raw.token } : DEFAULT_CONFIG.auth),
|
|
92
93
|
insecure: raw.insecure ?? DEFAULT_CONFIG.insecure,
|
|
93
94
|
},
|
|
94
95
|
},
|
|
95
96
|
};
|
|
96
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* 从 FlowConfig 中提取 access_token,不存在时返回空字符串。
|
|
100
|
+
*/
|
|
101
|
+
function getAccessToken(config) {
|
|
102
|
+
return config.auth?.access_token ?? '';
|
|
103
|
+
}
|
|
97
104
|
function setActiveProfile(name) {
|
|
98
105
|
activeProfileOverride = name;
|
|
99
106
|
try {
|
package/dist/utils/oauth.js
CHANGED
|
@@ -219,8 +219,8 @@ function createAuthServer() {
|
|
|
219
219
|
return;
|
|
220
220
|
}
|
|
221
221
|
const port = addr.port;
|
|
222
|
-
const
|
|
223
|
-
resolve({ server, port,
|
|
222
|
+
const waitForResult = createWaitForCode(server, 'http', port);
|
|
223
|
+
resolve({ server, port, waitForResult });
|
|
224
224
|
});
|
|
225
225
|
});
|
|
226
226
|
}
|
|
@@ -236,8 +236,8 @@ function createAuthServerHttps() {
|
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
238
|
const port = addr.port;
|
|
239
|
-
const
|
|
240
|
-
resolve({ server, port,
|
|
239
|
+
const waitForResult = createWaitForCode(server, 'https', port);
|
|
240
|
+
resolve({ server, port, waitForResult });
|
|
241
241
|
});
|
|
242
242
|
});
|
|
243
243
|
}
|
|
@@ -300,14 +300,22 @@ function createWaitForCode(server, protocol, _port) {
|
|
|
300
300
|
server.on('request', (req, res) => {
|
|
301
301
|
const url = new URL(req.url || '/', `${protocol}://127.0.0.1`);
|
|
302
302
|
const error = url.searchParams.get('error');
|
|
303
|
-
const
|
|
304
|
-
|
|
303
|
+
const accessToken = url.searchParams.get('access_token');
|
|
304
|
+
const refreshToken = url.searchParams.get('refresh_token');
|
|
305
|
+
const idToken = url.searchParams.get('id_token');
|
|
306
|
+
const tokenType = url.searchParams.get('token_type');
|
|
307
|
+
const expiresIn = url.searchParams.get('expires_in');
|
|
308
|
+
if (!accessToken && !error) {
|
|
305
309
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
306
|
-
const str = "`${location.href.substring(0, location.href.indexOf('#'))}?
|
|
310
|
+
const str = "`${location.href.substring(0, location.href.indexOf('#'))}?access_token=${token}&refresh_token=${refresh_token}&id_token=${id_token}&token_type=${token_type}&expires_in=${expires_in}`";
|
|
307
311
|
res.end(renderPage('等待授权回调', `<p>正在等待浏览器授权,请勿关闭此页面...</p><div class="spinner"></div>`, '').replace('</body>', `<script>
|
|
308
312
|
(function(){
|
|
309
313
|
var params = new URLSearchParams(location.hash.substring(1));
|
|
310
314
|
var token = params.get('access_token');
|
|
315
|
+
var refresh_token = params.get('refresh_token');
|
|
316
|
+
var id_token = params.get('id_token');
|
|
317
|
+
var token_type = params.get('token_type');
|
|
318
|
+
var expires_in = params.get('expires_in');
|
|
311
319
|
location.href = ${str};
|
|
312
320
|
})();
|
|
313
321
|
</script></body>`));
|
|
@@ -325,7 +333,18 @@ function createWaitForCode(server, protocol, _port) {
|
|
|
325
333
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
326
334
|
res.end(renderPage('登录成功', '<p>请关闭此页面返回终端</p>', '').replace('card">', 'card success">'));
|
|
327
335
|
server.close();
|
|
328
|
-
|
|
336
|
+
const result = {
|
|
337
|
+
access_token: accessToken ?? '',
|
|
338
|
+
refresh_token: refreshToken ?? '',
|
|
339
|
+
id_token: idToken ?? '',
|
|
340
|
+
token_type: tokenType ?? '',
|
|
341
|
+
expires_in: expiresIn ? Number(expiresIn) : 0,
|
|
342
|
+
};
|
|
343
|
+
if (!accessToken) {
|
|
344
|
+
rejectCode(new Error('授权回调缺少 access_token'));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
resolveCode(result);
|
|
329
348
|
});
|
|
330
349
|
server.on('error', (err) => {
|
|
331
350
|
clearTimeout(timer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "digitalsee-ai-flow-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.23-beta.1",
|
|
4
4
|
"description": "AI Flow CLI - manage connection flows and node configurations",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
"axios": "^1.18.0",
|
|
18
18
|
"chalk": "^4.1.2",
|
|
19
19
|
"commander": "^12.1.0",
|
|
20
|
-
"form-data": "^4.0.6"
|
|
20
|
+
"form-data": "^4.0.6",
|
|
21
|
+
"ws": "^8.21.1"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@commitlint/cli": "^21.0.2",
|
|
24
25
|
"@commitlint/config-conventional": "^21.0.2",
|
|
25
26
|
"@types/node": "^20.14.0",
|
|
27
|
+
"@types/ws": "^8.18.1",
|
|
26
28
|
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
|
27
29
|
"@typescript-eslint/parser": "^8.60.1",
|
|
28
30
|
"eslint": "^10.4.1",
|