teamai-cli 0.21.0-beta.3 → 0.21.0-beta.8
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/CHANGELOG.md +4 -0
- package/README.md +2 -1
- package/README.zh-CN.md +2 -1
- package/dist/index.js +115 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,10 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
22
22
|
- `teamai skill` 或 `teamai skill list`:等价 `teamai list skills --source all`
|
|
23
23
|
- `teamai skill show <name>`:查看单个 skill 的来源标签、命名空间、贡献者、`tags.yaml` 中的 tags、已安装的 agent 列表,以及 frontmatter 中的描述(最多 160 字符)。不渲染完整 SKILL.md 正文
|
|
24
24
|
|
|
25
|
+
### 🐛 修复
|
|
26
|
+
|
|
27
|
+
- **项目级 source 配置持久化**(#335):`teamai pull` 自动上报 usage 时不再丢弃 `source add`/`remove` 尚未提交的 `teamai.yaml` 改动,后续 pull 可正常部署订阅 skills 并写入安装清单
|
|
28
|
+
|
|
25
29
|
## [0.14.2] (2026-04-16)
|
|
26
30
|
|
|
27
31
|
### 🐛 修复
|
package/README.md
CHANGED
|
@@ -159,7 +159,8 @@ teamai source browse other-team # browse available skills
|
|
|
159
159
|
teamai source remove other-team
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
The add/remove change takes effect locally right away, and subscribed skills sync on the next
|
|
163
|
+
`teamai pull`. Run `teamai push` when you want to share the `teamai.yaml` change with teammates.
|
|
163
164
|
|
|
164
165
|
## Knowledge Base
|
|
165
166
|
|
package/README.zh-CN.md
CHANGED
|
@@ -159,7 +159,8 @@ teamai source browse other-team # 浏览可用 skills
|
|
|
159
159
|
teamai source remove other-team
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
添加/移除会立即在本机生效,订阅的 skills 会在下一次 `teamai pull` 时同步。需要将
|
|
163
|
+
`teamai.yaml` 的改动分享给团队成员时,再运行 `teamai push`。
|
|
163
164
|
|
|
164
165
|
## 知识库
|
|
165
166
|
|
package/dist/index.js
CHANGED
|
@@ -3918,6 +3918,13 @@ function assertSafePath(target, allowedRoots) {
|
|
|
3918
3918
|
`Path traversal detected: "${target}" is outside allowed directories: ${allowedRoots.join(", ")}`
|
|
3919
3919
|
);
|
|
3920
3920
|
}
|
|
3921
|
+
function assertWithinRoot(root, candidate, message) {
|
|
3922
|
+
const resolvedRoot = path11.resolve(root);
|
|
3923
|
+
const resolvedCandidate = path11.resolve(candidate);
|
|
3924
|
+
if (resolvedCandidate !== resolvedRoot && !resolvedCandidate.startsWith(resolvedRoot + path11.sep)) {
|
|
3925
|
+
throw new Error(message ?? `path traversal detected: "${candidate}" is outside "${root}"`);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3921
3928
|
function resolveReal(p) {
|
|
3922
3929
|
const expanded = p.startsWith("~") ? path11.join(os4.homedir(), p.slice(1)) : p;
|
|
3923
3930
|
const abs = path11.resolve(expanded);
|
|
@@ -8712,12 +8719,6 @@ async function resolveSkillNamespaces(localConfig) {
|
|
|
8712
8719
|
return [localConfig.primaryRole, ...localConfig.additionalRoles ?? []];
|
|
8713
8720
|
}
|
|
8714
8721
|
}
|
|
8715
|
-
function getSkillDestination(localConfig, skillName, namespace) {
|
|
8716
|
-
if (namespace) {
|
|
8717
|
-
return path24.join(localConfig.repo.localPath, "skills", namespace, skillName);
|
|
8718
|
-
}
|
|
8719
|
-
return path24.join(localConfig.repo.localPath, "skills", skillName);
|
|
8720
|
-
}
|
|
8721
8722
|
async function scanSkillsRecursively(dirPath) {
|
|
8722
8723
|
const results = /* @__PURE__ */ new Map();
|
|
8723
8724
|
async function walk2(currentPath) {
|
|
@@ -8754,6 +8755,7 @@ var init_skills = __esm({
|
|
|
8754
8755
|
init_builtin_skills();
|
|
8755
8756
|
init_openclaw_hooks();
|
|
8756
8757
|
init_roles();
|
|
8758
|
+
init_path_safety();
|
|
8757
8759
|
CONTRIBUTORS_FILE = "CONTRIBUTORS";
|
|
8758
8760
|
SKILL_MD = "SKILL.md";
|
|
8759
8761
|
FRONTMATTER_REGEX = /^---\n[\s\S]*?\n---/;
|
|
@@ -8917,7 +8919,13 @@ var init_skills = __esm({
|
|
|
8917
8919
|
* Copy a local skill to the team repo.
|
|
8918
8920
|
*/
|
|
8919
8921
|
async pushItem(item, _teamConfig, localConfig) {
|
|
8920
|
-
const
|
|
8922
|
+
const skillsRoot = path24.join(localConfig.repo.localPath, "skills");
|
|
8923
|
+
const dest = path24.resolve(localConfig.repo.localPath, item.relativePath);
|
|
8924
|
+
assertWithinRoot(
|
|
8925
|
+
skillsRoot,
|
|
8926
|
+
dest,
|
|
8927
|
+
`Invalid skill destination outside team repo skills directory: ${item.relativePath}`
|
|
8928
|
+
);
|
|
8921
8929
|
await copyDir(item.sourcePath, dest);
|
|
8922
8930
|
log.debug(`Copied skill ${item.name} \u2192 team repo`);
|
|
8923
8931
|
await ensureSkillFrontmatter(dest, item.name);
|
|
@@ -11159,6 +11167,7 @@ async function push(options) {
|
|
|
11159
11167
|
} else {
|
|
11160
11168
|
log.error(`Push failed: ${e.message}`);
|
|
11161
11169
|
}
|
|
11170
|
+
process.exitCode = 1;
|
|
11162
11171
|
}
|
|
11163
11172
|
return;
|
|
11164
11173
|
}
|
|
@@ -11175,6 +11184,7 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
11175
11184
|
const git = createGit2(repoPath);
|
|
11176
11185
|
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
11177
11186
|
pullSpin.fail("Cannot push: team repo path is not a dedicated git root. Run `teamai init` to re-clone the team repo before pushing.");
|
|
11187
|
+
process.exitCode = 1;
|
|
11178
11188
|
return;
|
|
11179
11189
|
}
|
|
11180
11190
|
const yamlPath = path35.join(repoPath, "teamai.yaml");
|
|
@@ -11295,6 +11305,25 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
11295
11305
|
allItems.length = 0;
|
|
11296
11306
|
allItems.push(matchedItem);
|
|
11297
11307
|
}
|
|
11308
|
+
if (options.role) {
|
|
11309
|
+
try {
|
|
11310
|
+
assertSafeResourceName(options.role);
|
|
11311
|
+
for (const item of allItems) {
|
|
11312
|
+
if (item.type === "skills") {
|
|
11313
|
+
assertSafeResourceName(item.name);
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
} catch (e) {
|
|
11317
|
+
log.error(`Invalid skill role or name: ${e.message}`);
|
|
11318
|
+
process.exitCode = 2;
|
|
11319
|
+
return;
|
|
11320
|
+
}
|
|
11321
|
+
for (const item of allItems) {
|
|
11322
|
+
if (item.type !== "skills") continue;
|
|
11323
|
+
item.namespace = options.role;
|
|
11324
|
+
item.relativePath = `skills/${options.role}/${item.name}`;
|
|
11325
|
+
}
|
|
11326
|
+
}
|
|
11298
11327
|
if (allItems.length === 0) {
|
|
11299
11328
|
if (pendingTeamConfig !== null) {
|
|
11300
11329
|
await pushTeamConfigOnly(localConfig, teamConfig, options);
|
|
@@ -11452,7 +11481,7 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
11452
11481
|
return;
|
|
11453
11482
|
}
|
|
11454
11483
|
pushSpin.succeed(`Pushed branch ${branchName}`);
|
|
11455
|
-
await createPrWithFallback(
|
|
11484
|
+
const prUrl = await createPrWithFallback(
|
|
11456
11485
|
teamConfig,
|
|
11457
11486
|
localConfig,
|
|
11458
11487
|
branchName,
|
|
@@ -11460,6 +11489,9 @@ async function pushCore(localConfig, teamConfig, options) {
|
|
|
11460
11489
|
`Pushed ${selectedItems.length} resource(s):
|
|
11461
11490
|
${selectedItems.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
11462
11491
|
);
|
|
11492
|
+
if (!prUrl) {
|
|
11493
|
+
process.exitCode = 1;
|
|
11494
|
+
}
|
|
11463
11495
|
await checkoutMaster(localConfig.repo.localPath);
|
|
11464
11496
|
} catch (e) {
|
|
11465
11497
|
pushSpin.fail(`Push failed: ${e.message}`);
|
|
@@ -11475,6 +11507,7 @@ ${selectedItems.map((i) => `- [${i.type}] ${i.name}`).join("\n")}`
|
|
|
11475
11507
|
);
|
|
11476
11508
|
}
|
|
11477
11509
|
}
|
|
11510
|
+
process.exitCode = 1;
|
|
11478
11511
|
return;
|
|
11479
11512
|
}
|
|
11480
11513
|
const state = await loadStateForScope(localConfig.scope, localConfig.projectRoot);
|
|
@@ -11516,16 +11549,20 @@ async function pushTeamConfigOnly(localConfig, teamConfig, options) {
|
|
|
11516
11549
|
return;
|
|
11517
11550
|
}
|
|
11518
11551
|
pushSpin.succeed(`Pushed branch ${branchName}`);
|
|
11519
|
-
await createPrWithFallback(
|
|
11552
|
+
const prUrl = await createPrWithFallback(
|
|
11520
11553
|
teamConfig,
|
|
11521
11554
|
localConfig,
|
|
11522
11555
|
branchName,
|
|
11523
11556
|
commitMsg,
|
|
11524
11557
|
"Updated team config (teamai.yaml)"
|
|
11525
11558
|
);
|
|
11559
|
+
if (!prUrl) {
|
|
11560
|
+
process.exitCode = 1;
|
|
11561
|
+
}
|
|
11526
11562
|
await checkoutMaster(localConfig.repo.localPath);
|
|
11527
11563
|
} catch (e) {
|
|
11528
11564
|
pushSpin.fail(`Push failed: ${e.message}`);
|
|
11565
|
+
process.exitCode = 1;
|
|
11529
11566
|
return;
|
|
11530
11567
|
}
|
|
11531
11568
|
}
|
|
@@ -11784,6 +11821,8 @@ async function pushRepoBranch(localPath, message, files, branchName) {
|
|
|
11784
11821
|
await git.add(files);
|
|
11785
11822
|
const status2 = await git.status();
|
|
11786
11823
|
if (status2.staged.length === 0) {
|
|
11824
|
+
await git.reset(["--hard", "HEAD"]);
|
|
11825
|
+
await git.clean("f", ["-d"]);
|
|
11787
11826
|
const defaultBranch = await getDefaultBranch(localPath);
|
|
11788
11827
|
log.debug(`Nothing to commit, switching back to ${defaultBranch}`);
|
|
11789
11828
|
await switchToDefaultBranch(git, defaultBranch);
|
|
@@ -11792,6 +11831,8 @@ async function pushRepoBranch(localPath, message, files, branchName) {
|
|
|
11792
11831
|
}
|
|
11793
11832
|
const diffOutput = await git.diff(["--cached", "--unified=0"]);
|
|
11794
11833
|
if (isMetadataOnlyDiff(diffOutput)) {
|
|
11834
|
+
await git.reset(["--hard", "HEAD"]);
|
|
11835
|
+
await git.clean("f", ["-d"]);
|
|
11795
11836
|
const defaultBranch = await getDefaultBranch(localPath);
|
|
11796
11837
|
log.debug(`Only metadata/timestamp changes detected, switching back to ${defaultBranch}`);
|
|
11797
11838
|
await switchToDefaultBranch(git, defaultBranch);
|
|
@@ -15740,8 +15781,18 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
15740
15781
|
log.debug(`Skipping report: ${repoPath} is not a dedicated team-repo root (safety guard)`);
|
|
15741
15782
|
return;
|
|
15742
15783
|
}
|
|
15743
|
-
|
|
15744
|
-
await
|
|
15784
|
+
const yamlPath = path48.join(repoPath, "teamai.yaml");
|
|
15785
|
+
const workingContent = await readFileSafe(yamlPath);
|
|
15786
|
+
const committedContent = workingContent === null ? null : await getFileContentAtRev(repoPath, "HEAD", "teamai.yaml");
|
|
15787
|
+
const pendingTeamConfig = workingContent !== null && (committedContent === null || committedContent.toString() !== workingContent) ? workingContent : null;
|
|
15788
|
+
try {
|
|
15789
|
+
await resetToCleanMaster(git, repoPath);
|
|
15790
|
+
await pullRepo(repoPath);
|
|
15791
|
+
} finally {
|
|
15792
|
+
if (pendingTeamConfig !== null) {
|
|
15793
|
+
await writeFile(yamlPath, pendingTeamConfig);
|
|
15794
|
+
}
|
|
15795
|
+
}
|
|
15745
15796
|
}
|
|
15746
15797
|
if (hasUsage || hasInterventions || hasPromptTokens) {
|
|
15747
15798
|
const statsDir = path48.join(writeRoot, "stats");
|
|
@@ -16460,7 +16511,7 @@ async function scanRoleAwareSkills(localConfig, namespaces) {
|
|
|
16460
16511
|
}
|
|
16461
16512
|
return [...items.values()];
|
|
16462
16513
|
}
|
|
16463
|
-
async function cleanupInactiveNamespaceSkills(teamConfig, localConfig,
|
|
16514
|
+
async function cleanupInactiveNamespaceSkills(teamConfig, localConfig, retainedSkillNames, inactiveSkillNames) {
|
|
16464
16515
|
const baseDir = resolveBaseDir(localConfig);
|
|
16465
16516
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
16466
16517
|
if (isAgentDisabled(localConfig, tool)) continue;
|
|
@@ -16470,7 +16521,7 @@ async function cleanupInactiveNamespaceSkills(teamConfig, localConfig, activeSki
|
|
|
16470
16521
|
const localSkillNames = await listDirs(path50.join(baseDir, toolPath.skills));
|
|
16471
16522
|
for (const skillName of localSkillNames) {
|
|
16472
16523
|
if (BUILTIN_SKILL_NAMES.has(skillName)) continue;
|
|
16473
|
-
if (
|
|
16524
|
+
if (retainedSkillNames.has(skillName)) continue;
|
|
16474
16525
|
if (!inactiveSkillNames.has(skillName)) continue;
|
|
16475
16526
|
const localSkillDir = path50.join(baseDir, toolPath.skills, skillName);
|
|
16476
16527
|
await remove(localSkillDir);
|
|
@@ -16618,7 +16669,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
16618
16669
|
let tagIncluded = [];
|
|
16619
16670
|
if (hasActiveTagSubscriptions) {
|
|
16620
16671
|
const tagResult = filterByTags(allTeamSkills, tagsConfig, subscribedTags, "skills");
|
|
16621
|
-
|
|
16672
|
+
const subscribedTagSet = new Set(subscribedTags);
|
|
16673
|
+
tagIncluded = tagResult.included.filter((item) => {
|
|
16674
|
+
const itemTags = tagsConfig.skills[item.name];
|
|
16675
|
+
return itemTags?.some((tag) => subscribedTagSet.has(tag));
|
|
16676
|
+
});
|
|
16622
16677
|
skippedByTags = tagResult.skipped.length;
|
|
16623
16678
|
}
|
|
16624
16679
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -16718,7 +16773,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
16718
16773
|
await cleanupInactiveNamespaceSkills(
|
|
16719
16774
|
freshConfig,
|
|
16720
16775
|
localConfig,
|
|
16721
|
-
roleContext.activeSkillNames,
|
|
16776
|
+
desiredSkillNames ?? roleContext.activeSkillNames,
|
|
16722
16777
|
roleContext.inactiveSkillNames
|
|
16723
16778
|
);
|
|
16724
16779
|
}
|
|
@@ -18963,6 +19018,14 @@ async function collectTeamRuleNames(repoPath) {
|
|
|
18963
19018
|
files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""))
|
|
18964
19019
|
);
|
|
18965
19020
|
}
|
|
19021
|
+
async function collectTeamAgentNames(repoPath) {
|
|
19022
|
+
const teamAgentsDir = path58.join(repoPath, "agents");
|
|
19023
|
+
if (!await pathExists(teamAgentsDir)) return /* @__PURE__ */ new Set();
|
|
19024
|
+
const files = await listFiles(teamAgentsDir);
|
|
19025
|
+
return new Set(
|
|
19026
|
+
files.filter((file) => file.endsWith(".yaml") || file.endsWith(".md")).map((file) => path58.basename(file).replace(/\.(yaml|md)$/, ""))
|
|
19027
|
+
);
|
|
19028
|
+
}
|
|
18966
19029
|
function isEmptyHooksResidue(parsed) {
|
|
18967
19030
|
if (parsed == null || !("hooks" in parsed) || typeof parsed.hooks !== "object" || parsed.hooks == null) return false;
|
|
18968
19031
|
const entries = Object.values(parsed.hooks);
|
|
@@ -18976,7 +19039,7 @@ function opencodePluginTargets(baseDir, scope) {
|
|
|
18976
19039
|
}
|
|
18977
19040
|
return targets;
|
|
18978
19041
|
}
|
|
18979
|
-
async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, managedHooksPath, scope) {
|
|
19042
|
+
async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, teamAgentNames, managedHooksPath, scope) {
|
|
18980
19043
|
const res = {
|
|
18981
19044
|
hookFiles: [],
|
|
18982
19045
|
openclawHookDirs: [],
|
|
@@ -19022,12 +19085,18 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
19022
19085
|
}
|
|
19023
19086
|
}
|
|
19024
19087
|
if (toolPath.skills) {
|
|
19025
|
-
const
|
|
19026
|
-
if (
|
|
19027
|
-
const
|
|
19028
|
-
|
|
19029
|
-
|
|
19030
|
-
|
|
19088
|
+
const skillRoots = /* @__PURE__ */ new Set([path58.join(baseDir, toolPath.skills)]);
|
|
19089
|
+
if (tool === "openclaw") {
|
|
19090
|
+
const workspaceDir = await resolveOpenclawWorkspaceDir();
|
|
19091
|
+
if (workspaceDir) skillRoots.add(path58.join(workspaceDir, "skills"));
|
|
19092
|
+
}
|
|
19093
|
+
for (const skillsDir of skillRoots) {
|
|
19094
|
+
if (await pathExists(skillsDir)) {
|
|
19095
|
+
const dirs = await listDirs(skillsDir);
|
|
19096
|
+
for (const dir of dirs) {
|
|
19097
|
+
if (teamSkillNames.has(dir)) {
|
|
19098
|
+
res.skillDirs.push(path58.join(skillsDir, dir));
|
|
19099
|
+
}
|
|
19031
19100
|
}
|
|
19032
19101
|
}
|
|
19033
19102
|
}
|
|
@@ -19048,11 +19117,11 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
19048
19117
|
if (toolPath.agents) {
|
|
19049
19118
|
const agentsDir = path58.join(baseDir, toolPath.agents);
|
|
19050
19119
|
if (await pathExists(agentsDir)) {
|
|
19051
|
-
for (const
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19120
|
+
for (const file of await listFiles(agentsDir)) {
|
|
19121
|
+
if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
|
|
19122
|
+
const name = path58.basename(file).replace(/\.(md|toml)$/, "");
|
|
19123
|
+
if (!teamAgentNames.has(name) && !BUILTIN_AGENT_NAMES.has(name)) continue;
|
|
19124
|
+
res.agentFiles.push(path58.join(agentsDir, file));
|
|
19056
19125
|
}
|
|
19057
19126
|
}
|
|
19058
19127
|
}
|
|
@@ -19066,6 +19135,7 @@ async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
|
|
|
19066
19135
|
for (const name of BUILTIN_SKILL_NAMES) teamSkillNames.add(name);
|
|
19067
19136
|
const teamRuleNames = await collectTeamRuleNames(repoPath);
|
|
19068
19137
|
for (const name of BUILTIN_RULE_NAMES) teamRuleNames.add(name);
|
|
19138
|
+
const teamAgentNames = await collectTeamAgentNames(repoPath);
|
|
19069
19139
|
const localAgentManifestPath = path58.join(
|
|
19070
19140
|
getUserHome(),
|
|
19071
19141
|
".teamai",
|
|
@@ -19090,7 +19160,16 @@ async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
|
|
|
19090
19160
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
19091
19161
|
perTool.set(
|
|
19092
19162
|
tool,
|
|
19093
|
-
await discoverToolResources(
|
|
19163
|
+
await discoverToolResources(
|
|
19164
|
+
tool,
|
|
19165
|
+
toolPath,
|
|
19166
|
+
baseDir,
|
|
19167
|
+
teamSkillNames,
|
|
19168
|
+
teamRuleNames,
|
|
19169
|
+
teamAgentNames,
|
|
19170
|
+
managedHooksPath,
|
|
19171
|
+
localConfig.scope
|
|
19172
|
+
)
|
|
19094
19173
|
);
|
|
19095
19174
|
}
|
|
19096
19175
|
let includeShared;
|
|
@@ -19207,7 +19286,10 @@ function printSummary(plan, agentFilter) {
|
|
|
19207
19286
|
console.log("");
|
|
19208
19287
|
}
|
|
19209
19288
|
if (plan.skillDirs.length > 0) {
|
|
19210
|
-
console.log(` Skills (${plan.skillDirs.length} directories)
|
|
19289
|
+
console.log(` Skills (${plan.skillDirs.length} directories):`);
|
|
19290
|
+
for (const skillDir of plan.skillDirs) {
|
|
19291
|
+
console.log(` ${skillDir}`);
|
|
19292
|
+
}
|
|
19211
19293
|
console.log("");
|
|
19212
19294
|
}
|
|
19213
19295
|
if (plan.ruleFiles.length > 0) {
|
|
@@ -19215,7 +19297,10 @@ function printSummary(plan, agentFilter) {
|
|
|
19215
19297
|
console.log("");
|
|
19216
19298
|
}
|
|
19217
19299
|
if (plan.agentFiles.length > 0) {
|
|
19218
|
-
console.log(` Agents (${plan.agentFiles.length} files)
|
|
19300
|
+
console.log(` Agents (${plan.agentFiles.length} files):`);
|
|
19301
|
+
for (const agentFile of plan.agentFiles) {
|
|
19302
|
+
console.log(` ${agentFile}`);
|
|
19303
|
+
}
|
|
19219
19304
|
console.log("");
|
|
19220
19305
|
}
|
|
19221
19306
|
if (plan.mcpServers.length > 0) {
|