@cnbcool/cnb-api-generate 2.14.0 → 2.14.2

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.
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { resolveToken } from '../utils/resolve-token';
3
+ import { getGitRemoteHosts } from '../utils/resolve-platform-host';
3
4
 
4
5
  /**
5
6
  * 用作 git credential helper。
@@ -11,7 +12,7 @@ import { resolveToken } from '../utils/resolve-token';
11
12
  *
12
13
  * git 在执行需要凭据的操作时,会以 `get`、`store`、`erase` 之一作为 action 调用本命令,
13
14
  * 并通过 stdin 传入若干 `key=value` 形式的字段(protocol/host/path 等)。
14
- * 我们仅处理 `get`:根据 host 白名单校验后,从后端获取凭据,按 git-credential
15
+ * 我们仅处理 `get`:校验 host 属于当前仓库 git remote 的域名后,从后端获取凭据,按 git-credential
15
16
  * 协议把 `username=...` / `password=...` 写到 stdout;`store` / `erase` 直接忽略。
16
17
  */
17
18
 
@@ -27,8 +28,6 @@ interface Credential {
27
28
  password: string;
28
29
  }
29
30
 
30
- const ALLOWED_HOSTS = ['cnb.cool', 'cnb.woa.com'];
31
-
32
31
  /** git-credential get 等待 stdin 的最长时间,避免 git 无限挂起。 */
33
32
  const GET_INPUT_TIMEOUT_MS = 8000;
34
33
 
@@ -73,7 +72,7 @@ export function registerGitCredentialCommand(program: Command): void {
73
72
  }
74
73
 
75
74
  /**
76
- * 校验白名单并向后端获取凭据,按照 git-credential 协议输出 username/password。
75
+ * 校验 remote 域名并向后端获取凭据,按照 git-credential 协议输出 username/password。
77
76
  */
78
77
  async function auth(data: GitCredentialInput): Promise<void> {
79
78
  const { host } = data;
@@ -81,8 +80,11 @@ async function auth(data: GitCredentialInput): Promise<void> {
81
80
  `[git-credential]: for ${data.protocol}://${data.host}/${data.path ?? ''}`,
82
81
  );
83
82
 
84
- // 必须校验域名,否则传入第三方域名就会被盗密码
85
- if (!host || !ALLOWED_HOSTS.includes(host)) {
83
+ // 只向当前仓库 git remote 的域名提供凭据;remote 由用户配置(含子域名、api 子域),
84
+ // git-credential 调用链天然与仓库绑定,非本仓库 remote 的域名一律拒绝,
85
+ // 避免第三方域名盗取密码
86
+ const remoteHosts = getGitRemoteHosts();
87
+ if (!host || remoteHosts.length === 0 || !remoteHosts.includes(host)) {
86
88
  throw new Error(`unknown host: ${host}`);
87
89
  }
88
90
 
@@ -9,19 +9,20 @@ import {
9
9
  type TokenStore,
10
10
  } from '../utils/device-auth';
11
11
  import { tryOpenBrowser } from '../utils/open-browser';
12
+ import { resolvePlatformUrl } from '../utils/resolve-platform-host';
12
13
 
13
14
  export function registerLoginCommand(program: Command): void {
14
15
  program
15
16
  .command('login')
16
17
  .description('通过 OAuth2 设备授权流登录 CNB,获取并保存 access_token')
17
18
  .option('--client-id <string>', 'OAuth2 client_id', process.env.OAUTH2_CLIENT_ID || 'cnb_cli')
18
- .option('--woa', '使用内网环境 (https://cnb.woa.com)', false)
19
19
  .option('--debug', '打印调试信息', false)
20
20
  .helpOption('-h, --help', '显示帮助文档')
21
21
  .action(async (opts) => {
22
22
  const cfg: LoginConfig = {
23
23
  clientID: opts.clientId,
24
- platformURL: opts.woa ? 'https://cnb.woa.com' : 'https://cnb.cool',
24
+ // git remote -v 动态识别平台域名,兼容不同部署(公开 / 内网 / 自定义域名)
25
+ platformURL: resolvePlatformUrl(),
25
26
  debug: opts.debug,
26
27
  };
27
28
 
@@ -90,22 +90,24 @@ export interface SkillsListOptions {
90
90
  global?: boolean;
91
91
  /** 仅列出项目 scope(对应 skills list -p) */
92
92
  project?: boolean;
93
- /** 按 agent 过滤:仅保留 agents 中包含指定 agent 的条目 */
93
+ /** 按 agent 过滤:透传 -a 给底层 skills 命令(值会归一化为小写 key) */
94
94
  agent?: string;
95
95
  }
96
96
 
97
97
  /**
98
- * 运行原始 `skills list [scope] --json` 并返回解析后的 JSON 数组。
98
+ * 运行原始 `skills list [scope] [--agent] --json` 并返回解析后的 JSON 数组。
99
99
  *
100
- * scope 参数规则与 `skills list` 一致:
100
+ * 参数规则与 `skills list` 一致:
101
101
  * - 指定 `-g` → `-g`
102
102
  * - 指定 `-p` → `-p`
103
103
  * - 都不指定 → 不传 scope(由 skills 默认:项目优先,否则全局)
104
+ * - 指定 `-a/--agent` → 归一化为小写后透传(底层按 agent 目录 key 匹配)
104
105
  */
105
106
  export function runSkillsListJson(opts: SkillsListOptions): SkillEntry[] {
106
107
  const args = ['list'];
107
108
  if (opts.global) args.push('-g');
108
109
  if (opts.project) args.push('-p');
110
+ if (opts.agent) args.push('-a', opts.agent.toLowerCase());
109
111
  args.push('--json');
110
112
 
111
113
  let stdout: string;
@@ -132,24 +134,17 @@ export function runSkillsListJson(opts: SkillsListOptions): SkillEntry[] {
132
134
 
133
135
  /**
134
136
  * 对 `skills list --json` 输出做后处理:
135
- * 1. SKILL.md frontmatter 回填缺失的 description
136
- * 2. 按 agent 过滤(若指定 -a)。
137
+ * 仅从 SKILL.md frontmatter 回填缺失的 description
137
138
  *
139
+ * agent 过滤已下沉到底层 `skills list -a`(大小写归一化后透传),此处不再过滤。
138
140
  * 返回的是全新数组/对象,不修改原始输入。
139
141
  */
140
142
  export function decorateSkillEntries(
141
143
  entries: SkillEntry[],
142
- opts: SkillsListOptions,
143
144
  ): SkillEntry[] {
144
145
  const result: SkillEntry[] = [];
145
146
 
146
147
  for (const entry of entries) {
147
- // agent 过滤:保留 agents 中包含指定 agent 的条目
148
- if (opts.agent) {
149
- const agents = Array.isArray(entry.agents) ? entry.agents : [];
150
- if (!agents.includes(opts.agent)) continue;
151
- }
152
-
153
148
  const decorated: SkillEntry = { ...entry };
154
149
 
155
150
  // 回填 description:缺失或为空时从 path 下的 SKILL.md frontmatter 解析
@@ -171,7 +166,7 @@ export function decorateSkillEntries(
171
166
  */
172
167
  export function skillsList(opts: SkillsListOptions & { json?: boolean }): string {
173
168
  const raw = runSkillsListJson(opts);
174
- const decorated = decorateSkillEntries(raw, opts);
169
+ const decorated = decorateSkillEntries(raw);
175
170
 
176
171
  // 与 `skills list --json` 保持一致的 JSON 输出(默认即 JSON,字段结构不变)
177
172
  return JSON.stringify(decorated, null, 2);
@@ -0,0 +1,64 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ /**
4
+ * 从当前仓库的 `git remote -v` 动态识别平台域名。
5
+ *
6
+ * 统一以 git remote 解析为准,不再维护静态域名白名单,
7
+ * 天然兼容不同平台部署(公开 / 内网 / 自定义域名)。
8
+ */
9
+
10
+ /**
11
+ * 执行 `git remote -v` 并解析出所有 remote 的 host(去重)。
12
+ * 仅在当前目录是 git 仓库时返回非空数组;解析失败或非 git 仓库时返回空数组。
13
+ * @returns 去重后的 host 列表,如 ['cnb.cool']
14
+ */
15
+ export function getGitRemoteHosts(): string[] {
16
+ try {
17
+ const out = execSync('git remote -v', {
18
+ encoding: 'utf8',
19
+ stdio: ['ignore', 'pipe', 'ignore'],
20
+ });
21
+ const hosts = new Set<string>();
22
+ for (const line of out.split('\n')) {
23
+ // 每行形如:origin\thttps://cnb.cool/cnb/skills/cnb-skill.git (fetch)
24
+ const url = line.split(/\s+/)[1];
25
+ if (!url) continue;
26
+ const host = parseUrlHost(url);
27
+ if (host) hosts.add(host);
28
+ }
29
+ return [...hosts];
30
+ } catch {
31
+ // 非 git 仓库或 git 不可用等场景,静默返回空数组
32
+ return [];
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 从 remote URL 中解析出 host。
38
+ * 兼容 https://、http://、ssh:// 以及 scp-like 形式(git@host:path)。
39
+ * @param url
40
+ * @returns host,解析失败返回 null
41
+ */
42
+ function parseUrlHost(url: string): string | null {
43
+ try {
44
+ // scp-like: git@github.com:org/repo.git
45
+ if (/^[^@/]+@[^:/]+:/.test(url) && !/^[a-z]+:\/\//i.test(url)) {
46
+ const host = url.slice(url.indexOf('@') + 1, url.indexOf(':'));
47
+ return host || null;
48
+ }
49
+ return new URL(url).hostname || null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 解析当前平台的 base URL(形如 https://cnb.cool)。
57
+ * 优先取 git remote 中的 host 拼成 https://{host};
58
+ * 无 remote 或解析失败时兜底返回 https://cnb.cool。
59
+ * @returns 平台 base URL
60
+ */
61
+ export function resolvePlatformUrl(): string {
62
+ const host = getGitRemoteHosts()[0];
63
+ return host ? `https://${host}` : 'https://cnb.cool';
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnbcool/cnb-api-generate",
3
- "version": "2.14.0",
3
+ "version": "2.14.2",
4
4
  "main": "./built/index.js",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",