diff --git a/docs/usage-guide.md b/docs/usage-guide.md index 9b421036e..e821f3ff4 100644 --- a/docs/usage-guide.md +++ b/docs/usage-guide.md @@ -171,6 +171,8 @@ If the repo has role-based skills enabled (i.e. `manifest/roles.yaml` exists), ` - `primaryRole`: the target namespace for skill sync and push by default - `additionalRoles`: additional skill namespaces to sync +At the role prompt, enter one or more comma-separated role numbers. The first number becomes `primaryRole` and the remaining numbers become `additionalRoles` (for example, `1,3`). + You can also skip the interactive prompts via CLI flags for a fully non-interactive init (suitable for CI/CD or AI agents): ```bash diff --git a/docs/usage-guide.zh-CN.md b/docs/usage-guide.zh-CN.md index 7d8666d94..0cba6f1f5 100644 --- a/docs/usage-guide.zh-CN.md +++ b/docs/usage-guide.zh-CN.md @@ -165,6 +165,8 @@ teamai init https://github.com/yourorg/yourrepo - `primaryRole`:默认 skill 同步和推送的目标 namespace - `additionalRoles`:额外需要同步的 skill namespace +角色提示中可以输入一个或多个用逗号分隔的角色编号。第一个编号会保存为 `primaryRole`,后续编号会保存为 `additionalRoles`(例如 `1,3`)。 + 也可以通过 CLI 参数跳过交互,实现完全非交互式初始化(适合 CI/CD 或 AI agent): ```bash diff --git a/skill-data/setup/references/setup-admin.md b/skill-data/setup/references/setup-admin.md index acabd7b99..a9a5fd05f 100644 --- a/skill-data/setup/references/setup-admin.md +++ b/skill-data/setup/references/setup-admin.md @@ -187,9 +187,11 @@ If the repo does not exist yet, `init` offers to create it — accept the prompt - If `init` detects an unknown GitLab host, it stops and asks you to set `GITLAB_URL` + `GITLAB_TOKEN`, then retry. -If the repo has roles enabled, `init` may ask for a primary role — pick one with -the user, or pass `--role ` for a non-interactive run. Without a terminal -(or with `CI` / `TEAMAI_NONINTERACTIVE` set) `init` never waits: a provider that +If the repo has roles enabled, `init` asks for one or more comma-separated role +numbers when running interactively. The first number is the primary role and the +remaining numbers become additional roles; pass `--role ` for a +non-interactive run when only a primary role is needed. Without a terminal (or +with `CI` / `TEAMAI_NONINTERACTIVE` set) `init` never waits: a provider that would need a browser login fails at once and names the credential to prepare — a token for GitHub / CNB / GitLab / GitCode, and for TGit a prior `gf auth login` run in an interactive shell (see Step 3). diff --git a/src/__tests__/init.test.ts b/src/__tests__/init.test.ts index fc82d453e..9b8260764 100644 --- a/src/__tests__/init.test.ts +++ b/src/__tests__/init.test.ts @@ -561,6 +561,58 @@ describe('init', () => { resourceProfileVersion: 1, })); }); + + it('persists later selections as additional roles', async () => { + let cloneDone = false; + pathExistsFn = (p: string) => { + if (p === localPath) return cloneDone; + if (p === path.join(localPath, 'members', 'testuser.yaml')) return false; + return false; + }; + + mockGfRepoClone.mockImplementation(() => { + cloneDone = true; + }); + + const mockedLoadTeamConfig = vi.mocked(await import('../config.js')).loadTeamConfig; + mockedLoadTeamConfig + .mockResolvedValueOnce({ + team: 'my-team', + repo: 'https://git.woa.com/HyperAI/teamai-test.git', + provider: 'tgit', + reviewers: [], + sharing: { + skills: {}, + rules: { enforced: [] }, + docs: { localDir: '~/.teamai/docs' }, + env: { injectShellProfile: true }, + }, + toolPaths: {}, + } as never) + .mockResolvedValueOnce({ + team: 'my-team', + repo: 'https://git.woa.com/HyperAI/teamai-test.git', + provider: 'tgit', + reviewers: [], + sharing: { + skills: {}, + rules: { enforced: [] }, + docs: { localDir: '~/.teamai/docs' }, + env: { injectShellProfile: true }, + }, + toolPaths: {}, + } as never); + + questionAnswers = ['n', '1,3']; + + await init({ repo: 'https://git.woa.com/HyperAI/teamai-test.git', scope: 'user' }); + + expect(saveLocalConfig).toHaveBeenCalledWith(expect.objectContaining({ + primaryRole: 'hai', + additionalRoles: ['thpc'], + resourceProfileVersion: 1, + })); + }); }); describe('deploys built-in skills after init', () => { diff --git a/src/init.ts b/src/init.ts index 14a6f4674..d172b29e1 100644 --- a/src/init.ts +++ b/src/init.ts @@ -108,13 +108,14 @@ async function promptForRoleProfile( log.info(` ${index + 1}. ${label}`); }); - const primaryAnswer = await askQuestion('Primary role (number): ').catch(() => { + const primaryAnswer = await askQuestion('Primary role (number or comma-separated numbers, primary first): ').catch(() => { throw new Error( 'This team repo has several roles and there is no terminal to pick one. ' + `Pass --role (one of: ${listRoleIds(manifest).join(', ')}).`, ); }); - const [primaryIndex] = parseRoleSelection(primaryAnswer, manifest.roles.length); + const selectedIndexes = parseRoleSelection(primaryAnswer, manifest.roles.length); + const [primaryIndex, ...additionalIndexes] = selectedIndexes; if (!primaryIndex) { throw new NoRoleSelectedError('A primary role is required.'); } @@ -123,7 +124,7 @@ async function promptForRoleProfile( return { primaryRole: primaryRole.id, - additionalRoles: [], + additionalRoles: additionalIndexes.map((index) => manifest.roles[index - 1].id), resourceProfileVersion: manifest.version, }; }