Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/usage-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions skill-data/setup/references/setup-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <id>` 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 <id>` 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).
Expand Down
52 changes: 52 additions & 0 deletions src/__tests__/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
7 changes: 4 additions & 3 deletions src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <id> (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.');
}
Expand All @@ -123,7 +124,7 @@ async function promptForRoleProfile(

return {
primaryRole: primaryRole.id,
additionalRoles: [],
additionalRoles: additionalIndexes.map((index) => manifest.roles[index - 1].id),
resourceProfileVersion: manifest.version,
};
}
Expand Down
Loading