digitalsee-ai-flow-cli 0.7.6 → 0.7.24-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 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
@@ -188,6 +188,22 @@ ai-flow profile show --name prod # 指定 Profile
188
188
  ai-flow profile show --json
189
189
  ```
190
190
 
191
+ 输出示例(当前 Profile):
192
+ ```
193
+ ==================== Profile: dev ====================
194
+ baseUrl: https://dev.example.com
195
+ token: abcd****1234
196
+ insecure: false
197
+ Node Package: 支持
198
+ ```
199
+
200
+ `--json` 输出示例:
201
+ ```json
202
+ { "ok": true, "data": { "baseUrl": "https://dev.example.com", "token": "abcd****1234", "insecure": false, "nodePackageSupported": true } }
203
+ ```
204
+
205
+ > Node Package 状态在登录后自动检测缓存。查看非当前 Profile 时该字段为 `undefined`(不检测)。
206
+
191
207
  ### profile delete / rename
192
208
 
193
209
  删除或重命名 Profile。
@@ -1284,6 +1300,57 @@ ai-flow sync status --id <connectorId> --json
1284
1300
 
1285
1301
  ---
1286
1302
 
1303
+ ## 运维日志
1304
+
1305
+ 查询和实时跟踪运维服务日志。
1306
+
1307
+ ### service-log search
1308
+
1309
+ 查询历史运维日志,按时间范围、级别、关键词等条件筛选。
1310
+
1311
+ ```bash
1312
+ ai-flow service-log search --time-range 1h
1313
+ ai-flow service-log search --time-range 3h --level ERROR
1314
+ ai-flow service-log search --time-range 7d --keyword "timeout" --service-type iam
1315
+ ai-flow service-log search --start-time "2026-07-01 00:00:00" --end-time "2026-07-20 23:59:59"
1316
+ ai-flow service-log search --time-range 24h --limit 100 --json
1317
+ ```
1318
+
1319
+ | 选项 | 必填 | 默认值 | 说明 |
1320
+ |------|------|--------|------|
1321
+ | `--time-range` | 二选一 | — | 相对时间范围,格式: `<N>s\|m\|h\|d`(如 `30s`, `15m`, `3h`, `7d`),与 `--start-time` 互斥 |
1322
+ | `--start-time` | 二选一 | — | 开始时间 `yyyy-MM-dd HH:mm:ss`,与 `--time-range` 互斥 |
1323
+ | `--end-time` | 否 | 请求时刻 | 结束时间 `yyyy-MM-dd HH:mm:ss` |
1324
+ | `--limit` | 否 | `50` | 最大返回条数(上限 500) |
1325
+ | `--level` | 否 | — | 日志级别:`INFO` / `WARN` / `ERROR` |
1326
+ | `--request-id` | 否 | — | 链路 trace id(精确匹配) |
1327
+ | `--keyword` | 否 | — | 日志正文关键词 |
1328
+ | `--service-type` | 否 | — | 服务类型:`iam` / `acm` / `ncm` / `adm` / `mdm` / `nginx` 等 |
1329
+ | `--json` | 否 | — | 以 JSON 格式输出 `{ ok, data, total }` |
1330
+
1331
+ ### service-log stream
1332
+
1333
+ 通过 WebSocket 实时跟随运维日志,按 `Ctrl+C` 退出。连接前自动校验 token 可用性。
1334
+
1335
+ ```bash
1336
+ ai-flow service-log stream
1337
+ ai-flow service-log stream --level ERROR --service-type iam
1338
+ ai-flow service-log stream --keyword "exception" --max 100
1339
+ ai-flow service-log stream --json # NDJSON 输出(每行一条 { ok, data })
1340
+ ```
1341
+
1342
+ | 选项 | 必填 | 默认值 | 说明 |
1343
+ |------|------|--------|------|
1344
+ | `--level` | 否 | — | 日志级别:`INFO` / `WARN` / `ERROR` |
1345
+ | `--request-id` | 否 | — | 链路 trace id(精确匹配) |
1346
+ | `--keyword` | 否 | — | 日志正文关键词 |
1347
+ | `--service-type` | 否 | — | 服务类型:`iam` / `acm` / `ncm` / `adm` / `mdm` / `nginx` 等 |
1348
+ | `--limit` | 否 | `20` | 单次 ES 拉取批量 |
1349
+ | `--max` | 否 | — | 收到 N 条后自动退出(默认不限) |
1350
+ | `--json` | 否 | — | NDJSON 输出,每行一条 `{ ok, data }` |
1351
+
1352
+ ---
1353
+
1287
1354
  ## 知识库
1288
1355
 
1289
1356
  ### knowledge categories
@@ -1292,8 +1359,7 @@ ai-flow sync status --id <connectorId> --json
1292
1359
 
1293
1360
  ```bash
1294
1361
  ai-flow knowledge categories
1295
- ai-flow knowledge categories --type action
1296
- ai-flow knowledge categories --type trigger
1362
+ ai-flow knowledge categories --trigger
1297
1363
  ai-flow knowledge categories --json
1298
1364
  ```
1299
1365
 
@@ -1304,7 +1370,7 @@ ai-flow knowledge categories --json
1304
1370
  ```bash
1305
1371
  ai-flow knowledge nodes
1306
1372
  ai-flow knowledge nodes <category>
1307
- ai-flow knowledge nodes --name "sql" --flow-id <flowId>
1373
+ ai-flow knowledge nodes --name "sql"
1308
1374
  ai-flow knowledge nodes --name "HTTP" --trigger --page 1 --size 20
1309
1375
  ai-flow knowledge nodes --json
1310
1376
  ```
@@ -1344,7 +1410,7 @@ ai-flow knowledge actions <linkId> --json # JSON 格式输出(含 outpu
1344
1410
 
1345
1411
  ### knowledge generate-skills
1346
1412
 
1347
- 遍历所有分类(action / trigger / tool / mcp / model / memory)下的所有节点和动作,为每个动作生成 Markdown 文档和 JSON 结构化数据文件,按分类类型存储。
1413
+ 遍历所有分类下的所有节点和动作,为每个动作生成 Markdown 文档和 JSON 结构化数据文件,按分类类型和分类名组织存储。支持 `node_schema` 模式(包含 input/output fields 详情)。
1348
1414
 
1349
1415
  ```bash
1350
1416
  ai-flow knowledge generate-skills ./docs
@@ -1356,26 +1422,20 @@ ai-flow knowledge generate-skills ./docs --json
1356
1422
  输出:
1357
1423
  ```
1358
1424
  docs/
1359
- ├── action/
1360
- │ ├── markdown/
1425
+ ├── normal/
1426
+ │ ├── HTTP/
1361
1427
  │ │ ├── HTTP_发送请求.md
1362
- │ │ └── ...
1363
- │ └── json/
1364
- │ ├── HTTP_发送请求.json
1365
- │ └── ...
1366
- ├── trigger/
1367
- │ ├── markdown/
1368
- │ │ ├── Webhook_接收数据v2.md
1369
- │ │ └── ...
1370
- │ └── json/
1371
- │ └── ...
1372
- ├── tool/
1373
- │ └── ...
1428
+ │ │ └── HTTP_发送请求.json
1429
+ │ └── MySQL/
1430
+ │ ├── MySQL_执行SQL查询.md
1431
+ │ └── MySQL_执行SQL查询.json
1432
+ ├── normal-trigger/
1433
+ │ └── Webhook/
1434
+ │ ├── Webhook_接收数据v2.md
1435
+ │ └── Webhook_接收数据v2.json
1374
1436
  ├── mcp/
1375
1437
  │ └── ...
1376
- ├── model/
1377
- │ └── ...
1378
- └── memory/
1438
+ └── mcp-trigger/
1379
1439
  └── ...
1380
1440
  ```
1381
1441
 
@@ -1563,6 +1623,7 @@ src/
1563
1623
  │ │ ├── cancel-sync.ts
1564
1624
  │ │ └── status.ts
1565
1625
  │ ├── org.ts # ai-flow org(组织架构)
1626
+ │ ├── service-log.ts # ai-flow service-log(运维日志)
1566
1627
  │ └── knowledge.ts # ai-flow knowledge
1567
1628
  ├── api/
1568
1629
  │ ├── client.ts # axios 实例(SSL、Token)
@@ -1574,6 +1635,7 @@ src/
1574
1635
  │ ├── link.ts # 上游连接器 API
1575
1636
  │ ├── mapping.ts # 属性映射 API
1576
1637
  │ ├── sync.ts # 下游连接器 API
1638
+ │ ├── service-log.ts # 运维日志 API(search + stream)
1577
1639
  │ └── org.ts # 组织架构 API
1578
1640
  ├── services/
1579
1641
  │ ├── flowAnalyzer.ts # DAG 拓扑分析
@@ -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.token) {
122
- reqConfig.headers.Authorization = `Bearer ${cfg.token}`;
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;
@@ -6,21 +6,23 @@ exports.getConnectorDetail = getConnectorDetail;
6
6
  exports.getAllConnectors = getAllConnectors;
7
7
  const client_1 = require("@/api/client");
8
8
  async function getNodeCategories(params) {
9
- return (0, client_1.apiGet)('/acm/links/domain/v2', { type: params?.type });
9
+ return (0, client_1.apiPost)('/acm/node-package/categories/connectable', {
10
+ scene: params?.scene || 'normal',
11
+ trigger: params?.trigger || false,
12
+ });
10
13
  }
11
14
  async function searchNodesByCategory(params) {
12
15
  const body = {
16
+ scene: 'normal',
13
17
  page: params.page || 1,
14
18
  size: params.size || 50,
15
19
  trigger: params.trigger,
16
20
  };
17
- if (params.name)
18
- body.name = params.name;
19
- if (params.flow_id)
20
- body.flow_id = params.flow_id;
21
- if (params.app_domain_category)
22
- body.app_domain_category = params.app_domain_category;
23
- const data = await (0, client_1.apiPost)('/acm/links/search', body);
21
+ if (params.keyword)
22
+ body.keyword = params.keyword;
23
+ if (params.category_name)
24
+ body.category_name = params.category_name;
25
+ const data = await (0, client_1.apiPost)('/acm/node-package/plugins/connectable', body);
24
26
  return data || { items: [], total: 0, page: params.page || 1, size: params.size || 50 };
25
27
  }
26
28
  async function getConnectorDetail(connectorId) {
package/dist/api/node.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getActions = getActions;
4
+ exports.searchActions = searchActions;
5
+ exports.isNodePackageSupported = isNodePackageSupported;
4
6
  exports.getNodeDetail = getNodeDetail;
5
7
  exports.createNode = createNode;
6
8
  exports.updateNode = updateNode;
@@ -9,6 +11,7 @@ exports.deleteNode = deleteNode;
9
11
  exports.testRunNode = testRunNode;
10
12
  exports.cancelDebugNode = cancelDebugNode;
11
13
  const client_1 = require("@/api/client");
14
+ const knowledge_1 = require("@/api/knowledge");
12
15
  const encoding_1 = require("@/utils/encoding");
13
16
  async function getActions(params) {
14
17
  const data = await (0, client_1.apiGet)('/acm/actions/v2', params);
@@ -22,6 +25,39 @@ async function getActions(params) {
22
25
  return data.data;
23
26
  return [];
24
27
  }
28
+ async function searchActions(params) {
29
+ const data = await (0, client_1.apiPost)('/acm/links/search', params);
30
+ const actions = [];
31
+ if (data && data.items && Array.isArray(data.items)) {
32
+ for (const item of data.items) {
33
+ if (item.matched_actions && Array.isArray(item.matched_actions)) {
34
+ actions.push(...item.matched_actions);
35
+ }
36
+ }
37
+ }
38
+ return actions;
39
+ }
40
+ async function isNodePackageSupported() {
41
+ try {
42
+ const categories = await (0, knowledge_1.getNodeCategories)();
43
+ const firstName = categories[0]?.name;
44
+ if (!firstName)
45
+ return false;
46
+ const { items } = await (0, knowledge_1.searchNodesByCategory)({
47
+ category_name: firstName,
48
+ trigger: true,
49
+ size: 1,
50
+ });
51
+ const linkId = items?.[0]?.connector?.id;
52
+ if (!linkId)
53
+ return false;
54
+ const actions = await getActions({ linkId });
55
+ return actions.some((action) => action.hasOwnProperty('node_schema'));
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
25
61
  async function getNodeDetail(nodeId) {
26
62
  return (0, client_1.apiGet)(`/acm/flows/nodes/${nodeId}`);
27
63
  }
@@ -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
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.registerAuthCommand = registerAuthCommand;
7
7
  const config_1 = require("@/utils/config");
8
+ const nodePackage_1 = require("@/utils/nodePackage");
8
9
  const oauth_1 = require("@/utils/oauth");
9
10
  const output_1 = require("@/utils/output");
10
11
  const chalk_1 = __importDefault(require("chalk"));
@@ -29,14 +30,14 @@ function registerAuthCommand(program) {
29
30
  (0, output_1.printInfo)(`使用 Profile: ${(0, config_1.getActiveProfile)()}`);
30
31
  const protocol = options.https ? 'https' : 'http';
31
32
  (0, output_1.printInfo)(`正在准备 OAuth 登录 (${protocol.toUpperCase()})...`);
32
- const { server, port, waitForCode } = options.https
33
+ const { server, port, waitForResult } = options.https
33
34
  ? await (0, oauth_1.createAuthServerHttps)()
34
35
  : await (0, oauth_1.createAuthServer)();
35
36
  const authorizeUrl = (0, oauth_1.buildAuthorizeUrl)(config.baseUrl, port, protocol);
36
37
  (0, output_1.printHeader)('OAuth 2 登录');
37
38
  (0, output_1.printInfo)('打开下方链接,授权登录');
38
39
  console.log(`\n${chalk_1.default.cyan(authorizeUrl)}\n`);
39
- const codePromise = waitForCode();
40
+ const resultPromise = waitForResult();
40
41
  (0, output_1.printInfo)('等待授权回调...');
41
42
  rl = readline.createInterface({ input, output });
42
43
  rl.question('按 Enter 在浏览器中打开...\n', () => {
@@ -50,12 +51,21 @@ function registerAuthCommand(program) {
50
51
  rl.close();
51
52
  }
52
53
  });
53
- const token = await codePromise;
54
+ const { access_token, refresh_token, id_token, token_type, expires_in } = await resultPromise;
55
+ // 使用客户端本地时间计算过期时刻,服务端不支持返回绝对过期时间
56
+ const expires_at = new Date().getTime() + expires_in * 1000;
54
57
  const saveData = {
55
- token,
58
+ auth: {
59
+ access_token,
60
+ refresh_token,
61
+ id_token,
62
+ token_type,
63
+ expires_at,
64
+ },
56
65
  };
57
66
  (0, config_1.saveGlobalConfig)(saveData);
58
67
  (0, output_1.printSuccess)('登录成功');
68
+ (0, nodePackage_1.checkAndCacheNodePackageSupport)().catch(() => { });
59
69
  server.close();
60
70
  }
61
71
  catch (error) {
@@ -75,11 +85,20 @@ function registerAuthCommand(program) {
75
85
  if (options.json)
76
86
  (0, output_1.setJsonMode)(true);
77
87
  const scope = options.project ? 'project' : 'global';
88
+ const defaultConfig = {
89
+ auth: {
90
+ access_token: '',
91
+ refresh_token: '',
92
+ id_token: '',
93
+ token_type: '',
94
+ expires_at: 0,
95
+ },
96
+ };
78
97
  if (options.project) {
79
- (0, config_1.saveProjectConfig)({ token: '' });
98
+ (0, config_1.saveProjectConfig)(defaultConfig);
80
99
  }
81
100
  else {
82
- (0, config_1.saveGlobalConfig)({ token: '' });
101
+ (0, config_1.saveGlobalConfig)(defaultConfig);
83
102
  }
84
103
  if (options.json) {
85
104
  (0, output_1.printStructuredResult)({ ok: true, scope, activeProfile: (0, config_1.getActiveProfile)() });
@@ -105,9 +124,12 @@ function registerAuthCommand(program) {
105
124
  if (options.json)
106
125
  (0, output_1.setJsonMode)(true);
107
126
  const config = (0, config_1.loadConfig)();
108
- if (!config.token) {
127
+ if (!config.auth || !config.auth.access_token) {
109
128
  if (options.json) {
110
- (0, output_1.printStructuredResult)({ ok: true, loggedIn: false, activeProfile: (0, config_1.getActiveProfile)() });
129
+ (0, output_1.printStructuredResult)({
130
+ ok: true,
131
+ data: { loggedIn: false, activeProfile: (0, config_1.getActiveProfile)() },
132
+ });
111
133
  return;
112
134
  }
113
135
  (0, output_1.printHeader)('认证状态');
@@ -116,18 +138,20 @@ function registerAuthCommand(program) {
116
138
  (0, output_1.printInfo)('请执行 ai-flow auth login 登录');
117
139
  return;
118
140
  }
119
- const maskedToken = config.token.length > 8
120
- ? config.token.substring(0, 4) +
141
+ const maskedToken = config.auth.access_token.length > 8
142
+ ? config.auth.access_token.substring(0, 4) +
121
143
  '****' +
122
- config.token.substring(config.token.length - 4)
144
+ config.auth.access_token.substring(config.auth.access_token.length - 4)
123
145
  : '****';
124
146
  if (options.json) {
125
147
  (0, output_1.printStructuredResult)({
126
148
  ok: true,
127
- loggedIn: true,
128
- activeProfile: (0, config_1.getActiveProfile)(),
129
- token: maskedToken,
130
- baseUrl: config.baseUrl,
149
+ data: {
150
+ loggedIn: true,
151
+ activeProfile: (0, config_1.getActiveProfile)(),
152
+ token: maskedToken,
153
+ baseUrl: config.baseUrl,
154
+ },
131
155
  });
132
156
  return;
133
157
  }
@@ -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)(config.token) || '(未设置)'],
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
- updates.token = options.token;
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) {