teamai-cli 0.25.0-beta.0 → 0.25.0-beta.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.
- package/CHANGELOG.md +9 -0
- package/README.ja.md +278 -0
- package/README.ko.md +278 -0
- package/README.md +5 -4
- package/README.th.md +278 -0
- package/README.zh-CN.md +5 -4
- package/dist/index.js +1305 -689
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -503,6 +503,7 @@ __export(types_exports, {
|
|
|
503
503
|
CONTRIBUTE_SKILL_BONUS: () => CONTRIBUTE_SKILL_BONUS,
|
|
504
504
|
CONTRIBUTE_SMART_THRESHOLD: () => CONTRIBUTE_SMART_THRESHOLD,
|
|
505
505
|
CONTRIBUTE_TOOLERROR_TIERS: () => CONTRIBUTE_TOOLERROR_TIERS,
|
|
506
|
+
COPILOT_TOOL_ID: () => COPILOT_TOOL_ID,
|
|
506
507
|
CORRECTION_KEYWORDS: () => CORRECTION_KEYWORDS,
|
|
507
508
|
CORRECTION_WINDOW_MS: () => CORRECTION_WINDOW_MS,
|
|
508
509
|
CultureCompanySchema: () => CultureCompanySchema,
|
|
@@ -556,6 +557,7 @@ __export(types_exports, {
|
|
|
556
557
|
areTeamHooksDisabled: () => areTeamHooksDisabled,
|
|
557
558
|
emptyTokenUsage: () => emptyTokenUsage,
|
|
558
559
|
getConfigPath: () => getConfigPath,
|
|
560
|
+
getCopilotHome: () => getCopilotHome,
|
|
559
561
|
getDataHome: () => getDataHome,
|
|
560
562
|
getEnvBackupPath: () => getEnvBackupPath,
|
|
561
563
|
getHooksSharing: () => getHooksSharing,
|
|
@@ -590,6 +592,7 @@ __export(types_exports, {
|
|
|
590
592
|
resolveCoAuthor: () => resolveCoAuthor,
|
|
591
593
|
resolveHookScope: () => resolveHookScope,
|
|
592
594
|
resolveLegacyProjectHookScope: () => resolveLegacyProjectHookScope,
|
|
595
|
+
resolveToolBaseDir: () => resolveToolBaseDir,
|
|
593
596
|
scopedToolPaths: () => scopedToolPaths,
|
|
594
597
|
totalTokens: () => totalTokens,
|
|
595
598
|
usesReportsBranch: () => usesReportsBranch
|
|
@@ -699,6 +702,14 @@ function resolveBaseDir(localConfig) {
|
|
|
699
702
|
}
|
|
700
703
|
return getUserHome();
|
|
701
704
|
}
|
|
705
|
+
function getCopilotHome(env = process.env) {
|
|
706
|
+
const configured = env.COPILOT_HOME?.trim();
|
|
707
|
+
return configured ? path3.resolve(configured) : path3.join(getUserHome(), DEFAULT_COPILOT_HOME);
|
|
708
|
+
}
|
|
709
|
+
function resolveToolBaseDir(tool, localConfig) {
|
|
710
|
+
if (tool === COPILOT_TOOL_ID && localConfig.scope === "user") return getCopilotHome();
|
|
711
|
+
return resolveBaseDir(localConfig);
|
|
712
|
+
}
|
|
702
713
|
function isAgentDisabled(localConfig, tool) {
|
|
703
714
|
return localConfig.disabledAgents?.includes(tool) ?? false;
|
|
704
715
|
}
|
|
@@ -719,7 +730,8 @@ function scopedToolPaths(teamConfig, localConfig) {
|
|
|
719
730
|
...paths,
|
|
720
731
|
...us.skills !== void 0 ? { skills: us.skills } : {},
|
|
721
732
|
...us.rules !== void 0 ? { rules: us.rules } : {},
|
|
722
|
-
...us.agents !== void 0 ? { agents: us.agents } : {}
|
|
733
|
+
...us.agents !== void 0 ? { agents: us.agents } : {},
|
|
734
|
+
...us.hooks !== void 0 ? { hooks: us.hooks } : {}
|
|
723
735
|
};
|
|
724
736
|
}
|
|
725
737
|
return out;
|
|
@@ -795,7 +807,7 @@ function getPushignorePath() {
|
|
|
795
807
|
function areTeamHooksDisabled() {
|
|
796
808
|
return process.env.TEAMAI_HOOKS_DISABLED === "1" || process.env.TEAMAI_HOOKS_DISABLED === "true";
|
|
797
809
|
}
|
|
798
|
-
var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, UsageEventSchema, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, SEARCH_INDEX_VERSION, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
|
|
810
|
+
var ToolPathsSchema, ScopeEnum, SharingConfigSchema, SourceConfigSchema, SOURCE_PULL_TTL_MS, TEAMAI_SOURCES_DIR, TeamaiConfigSchema, MemberConfigSchema, LocalConfigSchema, PendingPushItemSchema, PendingPushSchema, StateSchema, RESOURCE_TYPES, TEAMAI_RULES_START, TEAMAI_RULES_END, TEAMAI_HOOK_DESCRIPTION_PREFIX, TEAMAI_CUSTOM_HOOK_PREFIX, TEAMAI_AGENT_HOOK_PREFIX, TEAMAI_ENV_START, TEAMAI_ENV_END, TEAMAI_CULTURE_START, TEAMAI_CULTURE_END, TEAMAI_CLAUDEMD_START, TEAMAI_CLAUDEMD_END, TEAMAI_RECALL_RULES_START, TEAMAI_RECALL_RULES_END, SKILL_NAME_REGEX, UsageEventSchema, DASHBOARD_DEFAULT_PORT, DASHBOARD_IDLE_TIMEOUT_MS, DASHBOARD_STALE_TIMEOUT_MS, DASHBOARD_COMPACTION_THRESHOLD, DASHBOARD_STOPPED_DISPLAY_MS, DASHBOARD_PID_CHECK_INTERVAL_MS, CORRECTION_WINDOW_MS, CORRECTION_KEYWORDS, INTERVENTION_SCAN_MAX_BYTES, TRANSCRIPT_INTERRUPT_PREFIX, TRANSCRIPT_SYSTEM_PREFIXES, TRANSCRIPT_REJECT_MARKERS, CONTRIBUTE_BASE_THRESHOLD, CONTRIBUTE_SMART_THRESHOLD, CONTRIBUTE_INTERRUPT_WEIGHT, CONTRIBUTE_REJECT_WEIGHT, CONTRIBUTE_CORRECTION_WEIGHT, CONTRIBUTE_TOOLERROR_TIERS, CONTRIBUTE_SKILL_BONUS, CONTRIBUTE_DIVERSITY_BONUS_MAX, CONTRIBUTE_FASTPATH_TTL_MS, CONTRIBUTE_KNOWLEDGE_GAP_BONUS, CONTRIBUTE_LOW_QUALITY_BONUS, CONTRIBUTE_LOW_QUALITY_THRESHOLD, CONTRIBUTE_GIT_COMMIT_DOWNWEIGHT, SEARCH_INDEX_VERSION, CultureCompanySchema, CultureTeamSchema, CultureFrontmatterSchema, COPILOT_TOOL_ID, DEFAULT_COPILOT_HOME, REPORTS_BRANCH, REPORTS_WORKTREE_DIRNAME, KNOWLEDGE_WORKTREE_DIRNAME, REPORTS_LOCK_FILENAME, BOOTSTRAP_LOCK_FILENAME, SYNC_LOCK_FILENAME;
|
|
799
811
|
var init_types = __esm({
|
|
800
812
|
"src/types.ts"() {
|
|
801
813
|
"use strict";
|
|
@@ -804,6 +816,8 @@ var init_types = __esm({
|
|
|
804
816
|
skills: z.string().optional(),
|
|
805
817
|
rules: z.string().optional(),
|
|
806
818
|
settings: z.string().optional(),
|
|
819
|
+
/** Standalone hooks file for tools that do not store hooks in settings. */
|
|
820
|
+
hooks: z.string().optional(),
|
|
807
821
|
claudemd: z.string().optional(),
|
|
808
822
|
/** Per-tool agents directory (Phase 1: teamai-recall subagent target).
|
|
809
823
|
* Optional — tools without subagent support omit this and agents sync skips them. */
|
|
@@ -826,7 +840,8 @@ var init_types = __esm({
|
|
|
826
840
|
userScope: z.object({
|
|
827
841
|
skills: z.string().optional(),
|
|
828
842
|
rules: z.string().optional(),
|
|
829
|
-
agents: z.string().optional()
|
|
843
|
+
agents: z.string().optional(),
|
|
844
|
+
hooks: z.string().optional()
|
|
830
845
|
}).optional()
|
|
831
846
|
});
|
|
832
847
|
ScopeEnum = z.enum(["user", "project"]);
|
|
@@ -954,6 +969,15 @@ var init_types = __esm({
|
|
|
954
969
|
tclaude: { skills: ".tclaude/skills", rules: ".tclaude/rules", settings: ".tclaude/settings.json", claudemd: ".tclaude/CLAUDE.md", agents: ".tclaude/agents", mcp: ".tclaude/.claude.json" },
|
|
955
970
|
tcodex: { skills: ".tcodex/skills", rules: ".tcodex/rules", settings: ".tcodex/hooks.json", agents: ".tcodex/agents" },
|
|
956
971
|
cursor: { skills: ".cursor/skills", rules: ".cursor/rules", settings: ".cursor/hooks.json", agents: ".cursor/agents", mcp: ".cursor/mcp.json", mcpProject: ".cursor/mcp.json" },
|
|
972
|
+
// GitHub Copilot CLI keeps project customizations under .github and moves
|
|
973
|
+
// the complete user customization root when COPILOT_HOME is set. Hooks are
|
|
974
|
+
// a standalone file; settings.json is deliberately never managed.
|
|
975
|
+
copilot: {
|
|
976
|
+
skills: ".github/skills",
|
|
977
|
+
rules: ".github/instructions",
|
|
978
|
+
hooks: ".github/hooks/teamai.json",
|
|
979
|
+
userScope: { skills: "skills", rules: "instructions", hooks: "hooks/teamai.json" }
|
|
980
|
+
},
|
|
957
981
|
// JoyCode: skills, rules (.mdc), and subagents are synced to .joycode/.
|
|
958
982
|
// JoyCode currently does not provide a lifecycle hooks system or startup
|
|
959
983
|
// adapter, so it intentionally has no `settings` path. Hook reconciliation
|
|
@@ -1238,6 +1262,8 @@ var init_types = __esm({
|
|
|
1238
1262
|
company: CultureCompanySchema.optional(),
|
|
1239
1263
|
team: CultureTeamSchema.optional()
|
|
1240
1264
|
});
|
|
1265
|
+
COPILOT_TOOL_ID = "copilot";
|
|
1266
|
+
DEFAULT_COPILOT_HOME = ".copilot";
|
|
1241
1267
|
REPORTS_BRANCH = "teamai-reports";
|
|
1242
1268
|
REPORTS_WORKTREE_DIRNAME = "reports-wt";
|
|
1243
1269
|
KNOWLEDGE_WORKTREE_DIRNAME = "knowledge-wt";
|
|
@@ -1344,7 +1370,8 @@ function resolveRoleResourceNamespaces(input) {
|
|
|
1344
1370
|
skills: [],
|
|
1345
1371
|
// Roles never contribute learnings namespaces; only projects do. Kept empty
|
|
1346
1372
|
// so the shape matches project resolution for a clean union at the call site.
|
|
1347
|
-
learnings: []
|
|
1373
|
+
learnings: [],
|
|
1374
|
+
agents: []
|
|
1348
1375
|
};
|
|
1349
1376
|
for (const type of ROLE_RESOURCE_TYPES) {
|
|
1350
1377
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1388,10 +1415,13 @@ var init_roles = __esm({
|
|
|
1388
1415
|
"use strict";
|
|
1389
1416
|
init_fs();
|
|
1390
1417
|
init_logger();
|
|
1391
|
-
ROLE_RESOURCE_TYPES = ["knowledge", "skills"];
|
|
1418
|
+
ROLE_RESOURCE_TYPES = ["knowledge", "skills", "agents"];
|
|
1392
1419
|
RoleResourceNamespacesSchema = z2.object({
|
|
1393
1420
|
knowledge: z2.array(z2.string().min(1)),
|
|
1394
1421
|
skills: z2.array(z2.string().min(1)),
|
|
1422
|
+
// Optional: a role without `agents` receives root-level agents only, which
|
|
1423
|
+
// is what every manifest written before this key existed already got.
|
|
1424
|
+
agents: z2.array(z2.string().min(1)).default([]),
|
|
1395
1425
|
// learnings is accepted for backward compatibility but ignored at runtime.
|
|
1396
1426
|
// All learnings are shared flat across the entire team (no namespace isolation).
|
|
1397
1427
|
learnings: z2.array(z2.string()).optional()
|
|
@@ -1583,7 +1613,7 @@ function getWrapperDispatchCommand(event, tool, matcher) {
|
|
|
1583
1613
|
return `PATH="$HOME/${TEAMAI_BIN_DIR}:$PATH" teamai hook-dispatch ${event} --tool ${tool}${matcherArg} 2>/dev/null || true`;
|
|
1584
1614
|
}
|
|
1585
1615
|
function builtinHookDefs(tool) {
|
|
1586
|
-
const withTimeout3 = tool === "cursor" || tool === "workbuddy" || tool === "codebuddy";
|
|
1616
|
+
const withTimeout3 = tool === "cursor" || tool === "copilot" || tool === "workbuddy" || tool === "codebuddy";
|
|
1587
1617
|
const buildCommand = tool === "zcode" ? getRawDispatchCommand : WRAPPER_TOOLS.has(tool) ? getWrapperDispatchCommand : getDispatchCommand;
|
|
1588
1618
|
return BUILTIN_HOOK_SPECS.map((spec) => ({
|
|
1589
1619
|
source: "builtin",
|
|
@@ -1635,10 +1665,18 @@ function toolInstallRoot(toolPath) {
|
|
|
1635
1665
|
}
|
|
1636
1666
|
return segments[0] ?? toolPath;
|
|
1637
1667
|
}
|
|
1668
|
+
async function isToolInstalledForConfig(tool, toolPath, localConfig) {
|
|
1669
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
1670
|
+
if (tool === COPILOT_TOOL_ID) {
|
|
1671
|
+
return localConfig.enabledAgents?.includes(COPILOT_TOOL_ID) === true || pathExists(getCopilotHome());
|
|
1672
|
+
}
|
|
1673
|
+
return ResourceHandler.isToolInstalled(toolPath, baseDir);
|
|
1674
|
+
}
|
|
1638
1675
|
var TOMBSTONE_FILE, ResourceHandler;
|
|
1639
1676
|
var init_base = __esm({
|
|
1640
1677
|
"src/resources/base.ts"() {
|
|
1641
1678
|
"use strict";
|
|
1679
|
+
init_types();
|
|
1642
1680
|
init_fs();
|
|
1643
1681
|
init_home();
|
|
1644
1682
|
TOMBSTONE_FILE = ".removed";
|
|
@@ -2505,6 +2543,7 @@ var init_hermes_hooks = __esm({
|
|
|
2505
2543
|
var hooks_exports = {};
|
|
2506
2544
|
__export(hooks_exports, {
|
|
2507
2545
|
AGENT_HOOK_EVENTS: () => AGENT_HOOK_EVENTS,
|
|
2546
|
+
CLAUDE_TO_COPILOT_EVENTS: () => CLAUDE_TO_COPILOT_EVENTS,
|
|
2508
2547
|
CLAUDE_TO_CURSOR_EVENTS: () => CLAUDE_TO_CURSOR_EVENTS,
|
|
2509
2548
|
OPENCLAW_TOOLS: () => OPENCLAW_TOOLS,
|
|
2510
2549
|
TEAMAI_HOOK_SUBCOMMANDS: () => TEAMAI_HOOK_SUBCOMMANDS,
|
|
@@ -2530,6 +2569,7 @@ __export(hooks_exports, {
|
|
|
2530
2569
|
import path14 from "path";
|
|
2531
2570
|
import { realpathSync } from "fs";
|
|
2532
2571
|
function detectFormat(tool) {
|
|
2572
|
+
if (tool === COPILOT_TOOL_ID) return "copilot";
|
|
2533
2573
|
if (CODEX_TOOLS.has(tool)) return "codex";
|
|
2534
2574
|
if (ZCODE_TOOLS.has(tool)) return "zcode";
|
|
2535
2575
|
return CURSOR_TOOLS.has(tool) ? "cursor" : "claude";
|
|
@@ -2619,6 +2659,20 @@ function toCodexEntry(def) {
|
|
|
2619
2659
|
if (def.matcher && def.matcher !== "*") entry.matcher = def.matcher;
|
|
2620
2660
|
return entry;
|
|
2621
2661
|
}
|
|
2662
|
+
function copilotPowershellCommand(command) {
|
|
2663
|
+
const match = command.match(COPILOT_BUILTIN_COMMAND_RE);
|
|
2664
|
+
return match ? `${match[1]} 2>$null; exit 0` : command;
|
|
2665
|
+
}
|
|
2666
|
+
function toCopilotEntry(def) {
|
|
2667
|
+
return {
|
|
2668
|
+
type: "command",
|
|
2669
|
+
bash: def.command,
|
|
2670
|
+
powershell: copilotPowershellCommand(def.command),
|
|
2671
|
+
command: def.command,
|
|
2672
|
+
...def.matcher && def.matcher !== "*" ? { matcher: def.matcher } : {},
|
|
2673
|
+
...def.timeout !== void 0 ? { timeoutSec: def.timeout } : {}
|
|
2674
|
+
};
|
|
2675
|
+
}
|
|
2622
2676
|
function toZcodeEntry(def) {
|
|
2623
2677
|
const ZCODE_TIMEOUT_MS = {
|
|
2624
2678
|
SessionStart: 18e4,
|
|
@@ -2626,6 +2680,7 @@ function toZcodeEntry(def) {
|
|
|
2626
2680
|
PostToolUse: 3e4,
|
|
2627
2681
|
UserPromptSubmit: 6e4
|
|
2628
2682
|
};
|
|
2683
|
+
const timeoutMs = def.timeout !== void 0 ? def.timeout * 1e3 : ZCODE_TIMEOUT_MS[def.event] ?? 6e4;
|
|
2629
2684
|
const entry = process.platform === "win32" ? {
|
|
2630
2685
|
// Windows must NOT spawn bare `bash`: CreateProcess resolves it to
|
|
2631
2686
|
// System32's WSL launcher before any PATH directory, and the WSL side
|
|
@@ -2635,7 +2690,7 @@ function toZcodeEntry(def) {
|
|
|
2635
2690
|
type: "process",
|
|
2636
2691
|
command: "cmd",
|
|
2637
2692
|
args: ["/c", def.command],
|
|
2638
|
-
timeoutMs
|
|
2693
|
+
timeoutMs
|
|
2639
2694
|
} : {
|
|
2640
2695
|
type: "process",
|
|
2641
2696
|
command: "bash",
|
|
@@ -2643,7 +2698,7 @@ function toZcodeEntry(def) {
|
|
|
2643
2698
|
// so managed-entry detection and the managed-hooks manifest share one
|
|
2644
2699
|
// command representation (the same invariant the Codex format keeps).
|
|
2645
2700
|
args: ["-lc", def.command],
|
|
2646
|
-
timeoutMs
|
|
2701
|
+
timeoutMs
|
|
2647
2702
|
};
|
|
2648
2703
|
const group = { hooks: [entry] };
|
|
2649
2704
|
if (def.matcher && def.matcher !== "*") group.matcher = def.matcher;
|
|
@@ -2763,6 +2818,61 @@ async function reconcileCursorFormat(hooksPath, tool, teamDefs, opts, priorTeamC
|
|
|
2763
2818
|
log.debug(`teamai hooks already up-to-date in ${hooksPath}`);
|
|
2764
2819
|
}
|
|
2765
2820
|
}
|
|
2821
|
+
function copilotEntryCommands(entry) {
|
|
2822
|
+
return [entry.bash, entry.powershell, entry.command].filter(Boolean);
|
|
2823
|
+
}
|
|
2824
|
+
async function reconcileCopilotFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2825
|
+
const expanded = expandHome(hooksPath);
|
|
2826
|
+
const existed = await pathExists(expanded);
|
|
2827
|
+
if (opts.removeAll && !existed) {
|
|
2828
|
+
log.debug(`No teamai hooks to remove from ${hooksPath}`);
|
|
2829
|
+
return;
|
|
2830
|
+
}
|
|
2831
|
+
await ensureDir(path14.dirname(expanded));
|
|
2832
|
+
const hooksJson = await readJson(expanded) ?? {
|
|
2833
|
+
version: COPILOT_HOOK_SCHEMA_VERSION,
|
|
2834
|
+
hooks: {}
|
|
2835
|
+
};
|
|
2836
|
+
let changed = hooksJson.version !== COPILOT_HOOK_SCHEMA_VERSION;
|
|
2837
|
+
hooksJson.version = COPILOT_HOOK_SCHEMA_VERSION;
|
|
2838
|
+
if (!hooksJson.hooks) hooksJson.hooks = {};
|
|
2839
|
+
const isManaged = (entry) => copilotEntryCommands(entry).some(
|
|
2840
|
+
(command) => TEAMAI_COMMAND_MARKERS.some((marker) => command.includes(marker)) || priorTeamCommands.has(command)
|
|
2841
|
+
);
|
|
2842
|
+
const defs = opts.removeAll ? [] : desiredDefs(tool, teamDefs, opts.builtinOverride);
|
|
2843
|
+
const desiredByEvent = {};
|
|
2844
|
+
for (const def of defs) {
|
|
2845
|
+
const event = CLAUDE_TO_COPILOT_EVENTS[def.event];
|
|
2846
|
+
if (!event) continue;
|
|
2847
|
+
(desiredByEvent[event] ??= []).push(toCopilotEntry(def));
|
|
2848
|
+
}
|
|
2849
|
+
for (const event of Object.keys(hooksJson.hooks)) {
|
|
2850
|
+
const existing = hooksJson.hooks[event] ?? [];
|
|
2851
|
+
const untouched = existing.filter((entry) => !isManaged(entry));
|
|
2852
|
+
const desired = desiredByEvent[event] ?? [];
|
|
2853
|
+
const next = [...untouched, ...desired];
|
|
2854
|
+
if (next.length === 0 && !opts.removeAll) {
|
|
2855
|
+
if (existing.length > 0) changed = true;
|
|
2856
|
+
delete hooksJson.hooks[event];
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2859
|
+
if (JSON.stringify(existing) !== JSON.stringify(next)) {
|
|
2860
|
+
hooksJson.hooks[event] = next;
|
|
2861
|
+
changed = true;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
for (const event of desiredEventOrder(defs, (value) => CLAUDE_TO_COPILOT_EVENTS[value])) {
|
|
2865
|
+
if (hooksJson.hooks[event]) continue;
|
|
2866
|
+
hooksJson.hooks[event] = desiredByEvent[event];
|
|
2867
|
+
changed = true;
|
|
2868
|
+
}
|
|
2869
|
+
if (changed || !existed) {
|
|
2870
|
+
await writeJson(expanded, hooksJson);
|
|
2871
|
+
log.success(`${opts.removeAll ? "Removed" : "Updated"} teamai hooks in ${hooksPath}`);
|
|
2872
|
+
} else {
|
|
2873
|
+
log.debug(`teamai hooks already up-to-date in ${hooksPath}`);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2766
2876
|
async function reconcileCodexFormat(hooksPath, tool, teamDefs, opts, priorTeamCommands) {
|
|
2767
2877
|
const expanded = expandHome(hooksPath);
|
|
2768
2878
|
await ensureDir(path14.dirname(expanded));
|
|
@@ -2949,6 +3059,8 @@ async function reconcileHooks(settingsPath, tool, teamDefs = [], opts = {}) {
|
|
|
2949
3059
|
const format = detectFormat(tool);
|
|
2950
3060
|
if (format === "cursor") {
|
|
2951
3061
|
await reconcileCursorFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
|
|
3062
|
+
} else if (format === "copilot") {
|
|
3063
|
+
await reconcileCopilotFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
|
|
2952
3064
|
} else if (format === "codex") {
|
|
2953
3065
|
await reconcileCodexFormat(settingsPath, tool, scopedDefs, opts, priorTeamCommands);
|
|
2954
3066
|
} else if (format === "zcode") {
|
|
@@ -3002,6 +3114,19 @@ async function getHookStatus(settingsPath, tool) {
|
|
|
3002
3114
|
});
|
|
3003
3115
|
return present2 ? "installed" : "missing";
|
|
3004
3116
|
}
|
|
3117
|
+
if (format === "copilot") {
|
|
3118
|
+
const hooksJson = await readJson(expanded);
|
|
3119
|
+
if (hooksJson?.version !== COPILOT_HOOK_SCHEMA_VERSION || !hooksJson.hooks) return "missing";
|
|
3120
|
+
const present2 = defs.every((def) => {
|
|
3121
|
+
const event = CLAUDE_TO_COPILOT_EVENTS[def.event];
|
|
3122
|
+
if (!event) return true;
|
|
3123
|
+
const want = toCopilotEntry(def);
|
|
3124
|
+
return (hooksJson.hooks[event] ?? []).some(
|
|
3125
|
+
(entry) => entry.type === want.type && entry.bash === want.bash && entry.powershell === want.powershell && entry.command === want.command && entry.matcher === want.matcher
|
|
3126
|
+
);
|
|
3127
|
+
});
|
|
3128
|
+
return present2 ? "installed" : "missing";
|
|
3129
|
+
}
|
|
3005
3130
|
if (format === "codex") {
|
|
3006
3131
|
const hooksJson = await readJson(expanded);
|
|
3007
3132
|
if (!hooksJson?.hooks) return "missing";
|
|
@@ -3045,6 +3170,15 @@ async function hasTeamaiHooks(settingsPath, tool, manifestPath) {
|
|
|
3045
3170
|
(entries) => (entries ?? []).some((e) => isTeamaiHookCommand(e.command) || priorTeamCommands.has(e.command))
|
|
3046
3171
|
);
|
|
3047
3172
|
}
|
|
3173
|
+
if (format === "copilot") {
|
|
3174
|
+
const j = await readJson(expanded);
|
|
3175
|
+
if (!j?.hooks) return false;
|
|
3176
|
+
return Object.values(j.hooks).some(
|
|
3177
|
+
(entries) => (entries ?? []).some((entry) => copilotEntryCommands(entry).some(
|
|
3178
|
+
(command) => TEAMAI_COMMAND_MARKERS.some((marker) => command.includes(marker)) || priorTeamCommands.has(command)
|
|
3179
|
+
))
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3048
3182
|
if (format === "codex") {
|
|
3049
3183
|
const j = await readJson(expanded);
|
|
3050
3184
|
if (!j?.hooks) return false;
|
|
@@ -3213,7 +3347,8 @@ async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
|
|
|
3213
3347
|
activeRoles: activeRoleIds(localConfig)
|
|
3214
3348
|
});
|
|
3215
3349
|
const { baseDir, manifestPath } = resolveHookScope(localConfig);
|
|
3216
|
-
|
|
3350
|
+
const explicitlySelectedAgents = opts.filterAgents ?? localConfig.enabledAgents;
|
|
3351
|
+
let filterAgents2 = explicitlySelectedAgents;
|
|
3217
3352
|
const disabled = localConfig.disabledAgents;
|
|
3218
3353
|
if (disabled && disabled.length > 0) {
|
|
3219
3354
|
const universe = filterAgents2 ?? Object.keys(teamConfig.toolPaths);
|
|
@@ -3226,10 +3361,29 @@ async function reconcileTeamHooksForConfig(teamConfig, localConfig, opts = {}) {
|
|
|
3226
3361
|
teamHookProjectRoot: localConfig.scope === "project" && !isSelfMode(localConfig) ? localConfig.projectRoot : void 0,
|
|
3227
3362
|
installedBaseDir: localConfig.scope === "project" ? localConfig.projectRoot ?? baseDir : void 0
|
|
3228
3363
|
});
|
|
3364
|
+
const copilotExcluded = disabled?.includes(COPILOT_TOOL_ID) ?? false;
|
|
3365
|
+
const copilotSelected = !copilotExcluded && (explicitlySelectedAgents?.includes(COPILOT_TOOL_ID) ?? false);
|
|
3366
|
+
const copilotEnabled = !copilotExcluded && (copilotSelected || explicitlySelectedAgents === void 0 && await pathExists(getCopilotHome()));
|
|
3367
|
+
const copilotPaths = scopedToolPaths(teamConfig, localConfig)[COPILOT_TOOL_ID];
|
|
3368
|
+
if (copilotEnabled && copilotPaths?.hooks) {
|
|
3369
|
+
const copilotBase = resolveToolBaseDir(COPILOT_TOOL_ID, localConfig);
|
|
3370
|
+
if (copilotSelected || await pathExists(getCopilotHome())) {
|
|
3371
|
+
await reconcileHooks(
|
|
3372
|
+
path14.join(copilotBase, copilotPaths.hooks),
|
|
3373
|
+
COPILOT_TOOL_ID,
|
|
3374
|
+
teamDefs,
|
|
3375
|
+
{
|
|
3376
|
+
manifestPath: getManagedHooksPath(localConfig.scope, localConfig.projectRoot),
|
|
3377
|
+
removeAll: opts.removeAll,
|
|
3378
|
+
builtinOverride: builtin
|
|
3379
|
+
}
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3229
3383
|
await sweepLegacyProjectHooks(teamConfig.toolPaths, localConfig);
|
|
3230
3384
|
return teamDefs;
|
|
3231
3385
|
}
|
|
3232
|
-
var OPENCLAW_TOOLS, TEAMAI_HOOK_SUBCOMMANDS, TEAMAI_LEGACY_HOOK_SUBCOMMANDS, CLAUDE_TO_CURSOR_EVENTS, CURSOR_TOOLS, CODEX_TOOLS, ZCODE_TOOLS, CODEX_TRUST_GATE_TOOLS, TEAMAI_COMMAND_MARKERS, AGENT_HOOK_EVENTS;
|
|
3386
|
+
var OPENCLAW_TOOLS, TEAMAI_HOOK_SUBCOMMANDS, TEAMAI_LEGACY_HOOK_SUBCOMMANDS, CLAUDE_TO_CURSOR_EVENTS, CLAUDE_TO_COPILOT_EVENTS, COPILOT_HOOK_SCHEMA_VERSION, CURSOR_TOOLS, CODEX_TOOLS, ZCODE_TOOLS, CODEX_TRUST_GATE_TOOLS, TEAMAI_COMMAND_MARKERS, COPILOT_BUILTIN_COMMAND_RE, AGENT_HOOK_EVENTS;
|
|
3233
3387
|
var init_hooks2 = __esm({
|
|
3234
3388
|
"src/hooks.ts"() {
|
|
3235
3389
|
"use strict";
|
|
@@ -3250,6 +3404,14 @@ var init_hooks2 = __esm({
|
|
|
3250
3404
|
PostToolUse: "postToolUse",
|
|
3251
3405
|
UserPromptSubmit: "beforeSubmitPrompt"
|
|
3252
3406
|
};
|
|
3407
|
+
CLAUDE_TO_COPILOT_EVENTS = {
|
|
3408
|
+
SessionStart: "SessionStart",
|
|
3409
|
+
Stop: "Stop",
|
|
3410
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
3411
|
+
PreToolUse: "PreToolUse",
|
|
3412
|
+
PostToolUse: "PostToolUse"
|
|
3413
|
+
};
|
|
3414
|
+
COPILOT_HOOK_SCHEMA_VERSION = 1;
|
|
3253
3415
|
CURSOR_TOOLS = /* @__PURE__ */ new Set(["cursor"]);
|
|
3254
3416
|
CODEX_TOOLS = /* @__PURE__ */ new Set(["codex", "codex-internal", "tcodex"]);
|
|
3255
3417
|
ZCODE_TOOLS = /* @__PURE__ */ new Set(["zcode"]);
|
|
@@ -3265,6 +3427,7 @@ var init_hooks2 = __esm({
|
|
|
3265
3427
|
"teamai mr-hint",
|
|
3266
3428
|
"teamai hook-dispatch"
|
|
3267
3429
|
];
|
|
3430
|
+
COPILOT_BUILTIN_COMMAND_RE = /^bash -lc "(teamai hook-dispatch [^"]+) 2>\/dev\/null" \|\| true$/;
|
|
3268
3431
|
AGENT_HOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
3269
3432
|
"SessionStart",
|
|
3270
3433
|
"UserPromptSubmit",
|
|
@@ -3370,12 +3533,17 @@ var init_pending_push = __esm({
|
|
|
3370
3533
|
|
|
3371
3534
|
// src/resources/rule-format.ts
|
|
3372
3535
|
function ruleFileExtensionForTool(tool) {
|
|
3373
|
-
|
|
3536
|
+
if (usesCursorMdcRules(tool)) return ".mdc";
|
|
3537
|
+
return usesCopilotInstructions(tool) ? ".instructions.md" : ".md";
|
|
3374
3538
|
}
|
|
3375
3539
|
function usesCursorMdcRules(tool) {
|
|
3376
3540
|
return CURSOR_MDC_RULE_TOOLS.has(tool);
|
|
3377
3541
|
}
|
|
3542
|
+
function usesCopilotInstructions(tool) {
|
|
3543
|
+
return COPILOT_INSTRUCTIONS_RULE_TOOLS.has(tool);
|
|
3544
|
+
}
|
|
3378
3545
|
function ruleStemFromFilename(filename) {
|
|
3546
|
+
if (filename.endsWith(".instructions.md")) return filename.slice(0, -".instructions.md".length);
|
|
3379
3547
|
if (filename.endsWith(".mdc")) return filename.slice(0, -".mdc".length);
|
|
3380
3548
|
if (filename.endsWith(".md")) return filename.slice(0, -".md".length);
|
|
3381
3549
|
return null;
|
|
@@ -3383,11 +3551,12 @@ function ruleStemFromFilename(filename) {
|
|
|
3383
3551
|
function isLegacyCursorRuleFile(tool, filename) {
|
|
3384
3552
|
return usesCursorMdcRules(tool) && filename.endsWith(".md");
|
|
3385
3553
|
}
|
|
3386
|
-
var CURSOR_MDC_RULE_TOOLS;
|
|
3554
|
+
var CURSOR_MDC_RULE_TOOLS, COPILOT_INSTRUCTIONS_RULE_TOOLS;
|
|
3387
3555
|
var init_rule_format = __esm({
|
|
3388
3556
|
"src/resources/rule-format.ts"() {
|
|
3389
3557
|
"use strict";
|
|
3390
3558
|
CURSOR_MDC_RULE_TOOLS = /* @__PURE__ */ new Set(["cursor", "joycode"]);
|
|
3559
|
+
COPILOT_INSTRUCTIONS_RULE_TOOLS = /* @__PURE__ */ new Set(["copilot"]);
|
|
3391
3560
|
}
|
|
3392
3561
|
});
|
|
3393
3562
|
|
|
@@ -8058,20 +8227,73 @@ var init_dashboard_collector = __esm({
|
|
|
8058
8227
|
}
|
|
8059
8228
|
});
|
|
8060
8229
|
|
|
8230
|
+
// src/utils/exec.ts
|
|
8231
|
+
import spawn from "cross-spawn";
|
|
8232
|
+
async function probeBinary(binary, args = ["--version"], executor = execCommand) {
|
|
8233
|
+
try {
|
|
8234
|
+
const result = await executor(binary, args, { timeoutMs: 5e3 });
|
|
8235
|
+
if (result.code !== 0) return "";
|
|
8236
|
+
return (result.stdout || result.stderr).trim();
|
|
8237
|
+
} catch {
|
|
8238
|
+
return "";
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
function formatCommand(command, args) {
|
|
8242
|
+
const quote = (value) => /^[a-zA-Z0-9_./:@=+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
8243
|
+
return [command, ...args.map(quote)].join(" ");
|
|
8244
|
+
}
|
|
8245
|
+
var execCommand;
|
|
8246
|
+
var init_exec = __esm({
|
|
8247
|
+
"src/utils/exec.ts"() {
|
|
8248
|
+
"use strict";
|
|
8249
|
+
execCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
8250
|
+
const child = spawn(command, args, {
|
|
8251
|
+
cwd: options.cwd,
|
|
8252
|
+
env: options.env,
|
|
8253
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
8254
|
+
shell: false
|
|
8255
|
+
});
|
|
8256
|
+
let stdout = "";
|
|
8257
|
+
let stderr = "";
|
|
8258
|
+
let settled = false;
|
|
8259
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
8260
|
+
const finish = (callback) => {
|
|
8261
|
+
if (settled) return;
|
|
8262
|
+
settled = true;
|
|
8263
|
+
clearTimeout(timer);
|
|
8264
|
+
callback();
|
|
8265
|
+
};
|
|
8266
|
+
child.stdout?.on("data", (chunk) => {
|
|
8267
|
+
const text = chunk.toString();
|
|
8268
|
+
stdout += text;
|
|
8269
|
+
if (options.stream) process.stdout.write(text);
|
|
8270
|
+
});
|
|
8271
|
+
child.stderr?.on("data", (chunk) => {
|
|
8272
|
+
const text = chunk.toString();
|
|
8273
|
+
stderr += text;
|
|
8274
|
+
if (options.stream) process.stderr.write(text);
|
|
8275
|
+
});
|
|
8276
|
+
const timer = setTimeout(() => {
|
|
8277
|
+
child.kill();
|
|
8278
|
+
finish(() => reject(new Error(
|
|
8279
|
+
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`
|
|
8280
|
+
)));
|
|
8281
|
+
}, timeoutMs);
|
|
8282
|
+
child.on("error", (error) => finish(() => reject(error)));
|
|
8283
|
+
child.on("close", (code) => finish(() => resolve({
|
|
8284
|
+
stdout,
|
|
8285
|
+
stderr,
|
|
8286
|
+
code: code ?? 1
|
|
8287
|
+
})));
|
|
8288
|
+
});
|
|
8289
|
+
}
|
|
8290
|
+
});
|
|
8291
|
+
|
|
8061
8292
|
// src/agent-version.ts
|
|
8062
|
-
import { execFile } from "child_process";
|
|
8063
8293
|
import { readFile } from "fs/promises";
|
|
8064
8294
|
import path22 from "path";
|
|
8065
8295
|
async function execVersion(bin, args = ["--version"]) {
|
|
8066
|
-
return
|
|
8067
|
-
execFile(bin, args, { timeout: 5e3 }, (err, stdout) => {
|
|
8068
|
-
if (err) {
|
|
8069
|
-
resolve("");
|
|
8070
|
-
return;
|
|
8071
|
-
}
|
|
8072
|
-
resolve(stdout.trim());
|
|
8073
|
-
});
|
|
8074
|
-
});
|
|
8296
|
+
return probeBinary(bin, args);
|
|
8075
8297
|
}
|
|
8076
8298
|
async function readPlistVersion(appPath) {
|
|
8077
8299
|
const plistPath = path22.join(appPath, "Contents", "Info.plist");
|
|
@@ -8145,6 +8367,7 @@ var init_agent_version = __esm({
|
|
|
8145
8367
|
"src/agent-version.ts"() {
|
|
8146
8368
|
"use strict";
|
|
8147
8369
|
init_logger();
|
|
8370
|
+
init_exec();
|
|
8148
8371
|
VERSION_CACHE = /* @__PURE__ */ new Map();
|
|
8149
8372
|
CODEBUDDY_IDE_PATHS = [
|
|
8150
8373
|
"/Applications/CodeBuddy.app",
|
|
@@ -8805,6 +9028,10 @@ function codexServerNames(source) {
|
|
|
8805
9028
|
for (const m of source.matchAll(/^\[mcp_servers\.([A-Za-z0-9_-]+)\]\s*$/gm)) names.add(m[1]);
|
|
8806
9029
|
return [...names];
|
|
8807
9030
|
}
|
|
9031
|
+
function mcpTargetExcluded(localConfig, target) {
|
|
9032
|
+
if (!isAgentExcluded(localConfig, target.tool)) return false;
|
|
9033
|
+
return !(target.projectScope && target.tool === "claude" && !isAgentExcluded(localConfig, "tclaude"));
|
|
9034
|
+
}
|
|
8808
9035
|
async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
8809
9036
|
const changes = [];
|
|
8810
9037
|
let wrote = false;
|
|
@@ -8843,6 +9070,7 @@ async function reconcileMcpForConfig(teamConfig, localConfig, options = {}) {
|
|
|
8843
9070
|
if (teamDefs.length === 0 && nothingOwned) return { changes, wrote };
|
|
8844
9071
|
const vars = await buildVarTable(localConfig);
|
|
8845
9072
|
for (const target of targets) {
|
|
9073
|
+
if (!removeAll && mcpTargetExcluded(localConfig, target)) continue;
|
|
8846
9074
|
const manifestKey = managedMcpManifestKey(target.tool, target.projectScope);
|
|
8847
9075
|
const owned = manifest[manifestKey] ?? [];
|
|
8848
9076
|
const ownedNames = new Set(owned.map((r) => r.name));
|
|
@@ -9305,7 +9533,7 @@ __export(local_agent_exports, {
|
|
|
9305
9533
|
import fs14 from "fs";
|
|
9306
9534
|
import os7 from "os";
|
|
9307
9535
|
import path28 from "path";
|
|
9308
|
-
import { execFile
|
|
9536
|
+
import { execFile } from "child_process";
|
|
9309
9537
|
import { promisify } from "util";
|
|
9310
9538
|
import fse4 from "fs-extra";
|
|
9311
9539
|
function modelAgentKind(tool) {
|
|
@@ -11544,7 +11772,7 @@ var init_local_agent = __esm({
|
|
|
11544
11772
|
init_types();
|
|
11545
11773
|
init_home();
|
|
11546
11774
|
init_git2();
|
|
11547
|
-
execFileAsync = promisify(
|
|
11775
|
+
execFileAsync = promisify(execFile);
|
|
11548
11776
|
LOCAL_AGENT_DIR = "local-agent";
|
|
11549
11777
|
CONFIG_FILE = "config.json";
|
|
11550
11778
|
MANIFEST_FILE = "manifest.json";
|
|
@@ -12272,9 +12500,9 @@ var init_skills = __esm({
|
|
|
12272
12500
|
sourceSkillNames = /* @__PURE__ */ new Set();
|
|
12273
12501
|
}
|
|
12274
12502
|
const candidates = /* @__PURE__ */ new Map();
|
|
12275
|
-
for (const [
|
|
12503
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12276
12504
|
if (!toolPath.skills) continue;
|
|
12277
|
-
const skillsDir = path30.join(
|
|
12505
|
+
const skillsDir = path30.join(resolveToolBaseDir(tool, localConfig), toolPath.skills);
|
|
12278
12506
|
if (!await pathExists(skillsDir)) continue;
|
|
12279
12507
|
const localSkills = await scanSkillsRecursively(skillsDir);
|
|
12280
12508
|
for (const [dir, localDirPath] of localSkills) {
|
|
@@ -12382,7 +12610,6 @@ var init_skills = __esm({
|
|
|
12382
12610
|
* Pull a skill from team repo to all configured AI tool directories.
|
|
12383
12611
|
*/
|
|
12384
12612
|
async pullItem(item, teamConfig, localConfig) {
|
|
12385
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
12386
12613
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12387
12614
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12388
12615
|
if (!toolPath.skills) continue;
|
|
@@ -12401,10 +12628,11 @@ var init_skills = __esm({
|
|
|
12401
12628
|
}
|
|
12402
12629
|
dest = path30.join(getHermesHome(), "skills", item.name);
|
|
12403
12630
|
} else {
|
|
12404
|
-
if (!await
|
|
12631
|
+
if (!await isToolInstalledForConfig(tool, toolPath.skills, localConfig)) {
|
|
12405
12632
|
log.debug(`Skipping skill sync for ${tool}: tool not installed`);
|
|
12406
12633
|
continue;
|
|
12407
12634
|
}
|
|
12635
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
12408
12636
|
dest = await resolveSkillDestination(tool, toolPath.skills, baseDir, item.name, item.sourcePath);
|
|
12409
12637
|
}
|
|
12410
12638
|
try {
|
|
@@ -12421,7 +12649,6 @@ var init_skills = __esm({
|
|
|
12421
12649
|
*/
|
|
12422
12650
|
async removeItem(name, teamConfig, localConfig) {
|
|
12423
12651
|
const removed = [];
|
|
12424
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
12425
12652
|
const scopedNamespaces = await resolveSkillNamespaces(localConfig);
|
|
12426
12653
|
if (scopedNamespaces.length > 0) {
|
|
12427
12654
|
for (const namespace of scopedNamespaces) {
|
|
@@ -12441,12 +12668,14 @@ var init_skills = __esm({
|
|
|
12441
12668
|
await this.addTombstone(name, localConfig);
|
|
12442
12669
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12443
12670
|
if (!toolPath.skills) continue;
|
|
12671
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12444
12672
|
let skillDir;
|
|
12445
12673
|
if (tool === "openclaw") {
|
|
12446
12674
|
const wsDir = await resolveOpenclawWorkspaceDir();
|
|
12447
12675
|
if (!wsDir) continue;
|
|
12448
12676
|
skillDir = path30.join(wsDir, "skills", name);
|
|
12449
12677
|
} else {
|
|
12678
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
12450
12679
|
const configuredDir = path30.join(baseDir, toolPath.skills, name);
|
|
12451
12680
|
skillDir = await resolveSkillDestination(tool, toolPath.skills, baseDir, name);
|
|
12452
12681
|
if (skillDir !== configuredDir && await pathExists(configuredDir) && await dirContentEqual(skillDir, configuredDir)) {
|
|
@@ -12475,6 +12704,55 @@ var init_skills = __esm({
|
|
|
12475
12704
|
}
|
|
12476
12705
|
});
|
|
12477
12706
|
|
|
12707
|
+
// src/resources/copilot-instructions.ts
|
|
12708
|
+
function normalizeBody2(body) {
|
|
12709
|
+
return body.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
12710
|
+
}
|
|
12711
|
+
function rulePaths(data) {
|
|
12712
|
+
const value = data.paths;
|
|
12713
|
+
if (Array.isArray(value)) {
|
|
12714
|
+
return value.map((entry) => String(entry).trim()).filter(Boolean);
|
|
12715
|
+
}
|
|
12716
|
+
if (typeof value === "string") {
|
|
12717
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
12718
|
+
}
|
|
12719
|
+
return [];
|
|
12720
|
+
}
|
|
12721
|
+
function teamRuleToCopilotInstructions(rawTeamRule) {
|
|
12722
|
+
const { data, body } = splitFrontmatter2(rawTeamRule);
|
|
12723
|
+
const paths = rulePaths(data);
|
|
12724
|
+
return stringifyFrontmatter(
|
|
12725
|
+
{ applyTo: paths.length > 0 ? paths.join(", ") : ALL_FILES_GLOB },
|
|
12726
|
+
`
|
|
12727
|
+
${normalizeBody2(body)}
|
|
12728
|
+
`
|
|
12729
|
+
);
|
|
12730
|
+
}
|
|
12731
|
+
function mergeCopilotBodyIntoTeamMd(rawCopilotInstructions, existingTeamMd) {
|
|
12732
|
+
const body = normalizeBody2(splitFrontmatter2(rawCopilotInstructions).body);
|
|
12733
|
+
if (existingTeamMd === null) return `${body}
|
|
12734
|
+
`;
|
|
12735
|
+
const existing = splitFrontmatter2(existingTeamMd);
|
|
12736
|
+
if (normalizeBody2(existing.body) === body) return existingTeamMd;
|
|
12737
|
+
if (!existing.raw) return `${body}
|
|
12738
|
+
`;
|
|
12739
|
+
return `${existing.raw.endsWith("\n") ? existing.raw : `${existing.raw}
|
|
12740
|
+
`}
|
|
12741
|
+
${body}
|
|
12742
|
+
`;
|
|
12743
|
+
}
|
|
12744
|
+
function copilotInstructionsBodyEqualsTeamMd(rawCopilotInstructions, rawTeamRule) {
|
|
12745
|
+
return normalizeBody2(splitFrontmatter2(rawCopilotInstructions).body) === normalizeBody2(splitFrontmatter2(rawTeamRule).body);
|
|
12746
|
+
}
|
|
12747
|
+
var ALL_FILES_GLOB;
|
|
12748
|
+
var init_copilot_instructions = __esm({
|
|
12749
|
+
"src/resources/copilot-instructions.ts"() {
|
|
12750
|
+
"use strict";
|
|
12751
|
+
init_frontmatter();
|
|
12752
|
+
ALL_FILES_GLOB = "**";
|
|
12753
|
+
}
|
|
12754
|
+
});
|
|
12755
|
+
|
|
12478
12756
|
// src/resources/opencode-config.ts
|
|
12479
12757
|
var opencode_config_exports = {};
|
|
12480
12758
|
__export(opencode_config_exports, {
|
|
@@ -12547,6 +12825,7 @@ var init_rules = __esm({
|
|
|
12547
12825
|
init_types();
|
|
12548
12826
|
init_builtin_rules();
|
|
12549
12827
|
init_cursor_mdc();
|
|
12828
|
+
init_copilot_instructions();
|
|
12550
12829
|
init_rule_format();
|
|
12551
12830
|
RulesHandler = class extends ResourceHandler {
|
|
12552
12831
|
type = "rules";
|
|
@@ -12574,10 +12853,11 @@ var init_rules = __esm({
|
|
|
12574
12853
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12575
12854
|
const rulesPath = toolPath.rules;
|
|
12576
12855
|
if (!rulesPath) continue;
|
|
12577
|
-
const rulesDir = path32.join(
|
|
12856
|
+
const rulesDir = path32.join(resolveToolBaseDir(tool, localConfig), rulesPath);
|
|
12578
12857
|
if (!await pathExists(rulesDir)) continue;
|
|
12579
12858
|
const ext = ruleFileExtensionForTool(tool);
|
|
12580
12859
|
const isMdcTool = usesCursorMdcRules(tool);
|
|
12860
|
+
const isCopilotTool = usesCopilotInstructions(tool);
|
|
12581
12861
|
const files = await listFilesRecursive(rulesDir);
|
|
12582
12862
|
for (const file of files) {
|
|
12583
12863
|
if (!file.endsWith(ext)) continue;
|
|
@@ -12588,10 +12868,12 @@ var init_rules = __esm({
|
|
|
12588
12868
|
const teamFileName = `${name}.md`;
|
|
12589
12869
|
if (teamRules.has(teamFileName)) {
|
|
12590
12870
|
const teamFilePath = path32.join(teamRulesDir, teamFileName);
|
|
12871
|
+
const localRule = await readFileSafe(localFilePath) ?? "";
|
|
12872
|
+
const teamRule = await readTeamRule(teamFilePath);
|
|
12591
12873
|
const equal = isMdcTool ? cursorMdcBodyEqualsTeamMd(
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
) : await fileContentEqual(localFilePath, teamFilePath);
|
|
12874
|
+
localRule,
|
|
12875
|
+
teamRule
|
|
12876
|
+
) : isCopilotTool ? copilotInstructionsBodyEqualsTeamMd(localRule, teamRule) : await fileContentEqual(localFilePath, teamFilePath);
|
|
12595
12877
|
if (equal) continue;
|
|
12596
12878
|
const mtime = await getFileMtime(localFilePath);
|
|
12597
12879
|
const existing = candidates.get(name);
|
|
@@ -12599,7 +12881,7 @@ var init_rules = __esm({
|
|
|
12599
12881
|
candidates.set(name, { sourcePath: localFilePath, mtime, status: "modified" });
|
|
12600
12882
|
}
|
|
12601
12883
|
} else {
|
|
12602
|
-
if (isMdcTool) continue;
|
|
12884
|
+
if (isMdcTool || isCopilotTool) continue;
|
|
12603
12885
|
const existing = candidates.get(name);
|
|
12604
12886
|
if (!existing) {
|
|
12605
12887
|
const mtime = await getFileMtime(localFilePath);
|
|
@@ -12645,6 +12927,12 @@ var init_rules = __esm({
|
|
|
12645
12927
|
throw new Error(`Cannot read rule source ${item.sourcePath}`);
|
|
12646
12928
|
}
|
|
12647
12929
|
await writeFile(dest, mergeCursorBodyIntoTeamMd(raw, await readFileSafe(dest)));
|
|
12930
|
+
} else if (item.sourcePath.endsWith(".instructions.md")) {
|
|
12931
|
+
const raw = await readFileSafe(item.sourcePath);
|
|
12932
|
+
if (raw === null) {
|
|
12933
|
+
throw new Error(`Cannot read rule source ${item.sourcePath}`);
|
|
12934
|
+
}
|
|
12935
|
+
await writeFile(dest, mergeCopilotBodyIntoTeamMd(raw, await readFileSafe(dest)));
|
|
12648
12936
|
} else {
|
|
12649
12937
|
await copyFile(item.sourcePath, dest);
|
|
12650
12938
|
}
|
|
@@ -12655,15 +12943,14 @@ var init_rules = __esm({
|
|
|
12655
12943
|
* Pull a single rule file to all configured AI tool rules/ directories.
|
|
12656
12944
|
*/
|
|
12657
12945
|
async pullItem(item, teamConfig, localConfig) {
|
|
12658
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
12659
12946
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12660
12947
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12661
12948
|
if (!toolPath.rules) continue;
|
|
12662
|
-
if (!await
|
|
12949
|
+
if (!await isToolInstalledForConfig(tool, toolPath.rules, localConfig)) {
|
|
12663
12950
|
log.debug(`Skipping rule sync for ${tool}: tool not installed`);
|
|
12664
12951
|
continue;
|
|
12665
12952
|
}
|
|
12666
|
-
const destDir = path32.join(
|
|
12953
|
+
const destDir = path32.join(resolveToolBaseDir(tool, localConfig), toolPath.rules);
|
|
12667
12954
|
await ensureDir(destDir);
|
|
12668
12955
|
const dest = path32.join(destDir, `${item.name}${ruleFileExtensionForTool(tool)}`);
|
|
12669
12956
|
try {
|
|
@@ -12674,6 +12961,13 @@ var init_rules = __esm({
|
|
|
12674
12961
|
}
|
|
12675
12962
|
await writeFile(dest, teamRuleToCursorMdc(raw));
|
|
12676
12963
|
await remove(path32.join(destDir, `${item.name}.md`));
|
|
12964
|
+
} else if (usesCopilotInstructions(tool)) {
|
|
12965
|
+
const raw = await readFileSafe(item.sourcePath);
|
|
12966
|
+
if (raw === null) {
|
|
12967
|
+
throw new Error(`Cannot read rule source ${item.sourcePath}`);
|
|
12968
|
+
}
|
|
12969
|
+
await writeFile(dest, teamRuleToCopilotInstructions(raw));
|
|
12970
|
+
await remove(path32.join(destDir, `${item.name}.md`));
|
|
12677
12971
|
} else {
|
|
12678
12972
|
await copyFile(item.sourcePath, dest);
|
|
12679
12973
|
}
|
|
@@ -12688,7 +12982,6 @@ var init_rules = __esm({
|
|
|
12688
12982
|
*/
|
|
12689
12983
|
async removeItem(name, teamConfig, localConfig) {
|
|
12690
12984
|
const removed = [];
|
|
12691
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
12692
12985
|
const teamFile = path32.join(localConfig.repo.localPath, "rules", `${name}.md`);
|
|
12693
12986
|
if (await pathExists(teamFile)) {
|
|
12694
12987
|
await remove(teamFile);
|
|
@@ -12697,6 +12990,8 @@ var init_rules = __esm({
|
|
|
12697
12990
|
await this.addTombstone(name, localConfig);
|
|
12698
12991
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12699
12992
|
if (!toolPath.rules) continue;
|
|
12993
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
12994
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
12700
12995
|
const extensions = /* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"]);
|
|
12701
12996
|
for (const extension of extensions) {
|
|
12702
12997
|
const filePath = path32.join(baseDir, toolPath.rules, `${name}${extension}`);
|
|
@@ -12735,10 +13030,11 @@ var init_rules = __esm({
|
|
|
12735
13030
|
}
|
|
12736
13031
|
const teamRuleNames = new Set(rules.map((r) => r.name));
|
|
12737
13032
|
const tombstones = await this.readTombstones(localConfig);
|
|
12738
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
12739
13033
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12740
13034
|
if (!toolPath.rules) continue;
|
|
12741
|
-
if (
|
|
13035
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
13036
|
+
if (!await isToolInstalledForConfig(tool, toolPath.rules, localConfig)) continue;
|
|
13037
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
12742
13038
|
const destDir = path32.join(baseDir, toolPath.rules);
|
|
12743
13039
|
if (!await pathExists(destDir)) continue;
|
|
12744
13040
|
const ext = ruleFileExtensionForTool(tool);
|
|
@@ -12746,7 +13042,7 @@ var init_rules = __esm({
|
|
|
12746
13042
|
for (const localFile of localFiles) {
|
|
12747
13043
|
const ruleName = ruleStemFromFilename(localFile);
|
|
12748
13044
|
if (ruleName === null) continue;
|
|
12749
|
-
if (tool === "joycode" && !tombstones.has(ruleName)) continue;
|
|
13045
|
+
if ((tool === "joycode" || usesCopilotInstructions(tool)) && !tombstones.has(ruleName)) continue;
|
|
12750
13046
|
if (isLegacyCursorRuleFile(tool, localFile)) {
|
|
12751
13047
|
await remove(path32.join(destDir, localFile));
|
|
12752
13048
|
log.debug(`Removed legacy .md rule ${localFile} from ${tool}`);
|
|
@@ -12762,8 +13058,9 @@ var init_rules = __esm({
|
|
|
12762
13058
|
}
|
|
12763
13059
|
await this.removeEmptyDirs(destDir);
|
|
12764
13060
|
}
|
|
12765
|
-
for (const [, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13061
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
12766
13062
|
if (!toolPath.claudemd) continue;
|
|
13063
|
+
const baseDir = resolveToolBaseDir(tool, localConfig);
|
|
12767
13064
|
const claudeMdPath = path32.join(baseDir, toolPath.claudemd);
|
|
12768
13065
|
try {
|
|
12769
13066
|
const content = await readFileSafe(claudeMdPath);
|
|
@@ -12941,9 +13238,9 @@ var init_env = __esm({
|
|
|
12941
13238
|
}
|
|
12942
13239
|
const envYamlPath = path34.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
12943
13240
|
if (!await pathExists(envYamlPath)) return [];
|
|
12944
|
-
const { execFile:
|
|
13241
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
12945
13242
|
const { promisify: promisify4 } = await import("util");
|
|
12946
|
-
const execFileAsync4 = promisify4(
|
|
13243
|
+
const execFileAsync4 = promisify4(execFile4);
|
|
12947
13244
|
try {
|
|
12948
13245
|
await execFileAsync4("git", ["diff", "--exit-code", "env/env.yaml"], {
|
|
12949
13246
|
cwd: localConfig.repo.localPath
|
|
@@ -13111,6 +13408,12 @@ import path35 from "path";
|
|
|
13111
13408
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
13112
13409
|
import matter3 from "gray-matter";
|
|
13113
13410
|
import { stringify as stringifyToml, parse as parseToml } from "smol-toml";
|
|
13411
|
+
function agentStemFromFilename(filename) {
|
|
13412
|
+
for (const ext of AGENT_FILE_EXTENSIONS) {
|
|
13413
|
+
if (filename.endsWith(ext)) return filename.slice(0, -ext.length);
|
|
13414
|
+
}
|
|
13415
|
+
return null;
|
|
13416
|
+
}
|
|
13114
13417
|
function agentFileExtensionForTool(tool) {
|
|
13115
13418
|
switch (tool) {
|
|
13116
13419
|
case "codex":
|
|
@@ -13528,7 +13831,7 @@ function renderForTool(spec, tool) {
|
|
|
13528
13831
|
return renderForOpencode(spec);
|
|
13529
13832
|
}
|
|
13530
13833
|
}
|
|
13531
|
-
var ALL_SUPPORTED_TOOLS, KIRO_SESSION_START_COMMAND, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_KIRO_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13834
|
+
var ALL_SUPPORTED_TOOLS, AGENT_FILE_EXTENSIONS, KIRO_SESSION_START_COMMAND, COMMON_CLAUDE_FIELDS, COMMON_CURSOR_FIELDS, COMMON_CODEX_FIELDS, COMMON_KIRO_FIELDS, COMMON_OPENCODE_FIELDS, MERGE_COMMON_FIELDS;
|
|
13532
13835
|
var init_agent_format = __esm({
|
|
13533
13836
|
"src/resources/agent-format.ts"() {
|
|
13534
13837
|
"use strict";
|
|
@@ -13548,6 +13851,7 @@ var init_agent_format = __esm({
|
|
|
13548
13851
|
"zcode",
|
|
13549
13852
|
"opencode"
|
|
13550
13853
|
];
|
|
13854
|
+
AGENT_FILE_EXTENSIONS = [".md", ".toml", ".json"];
|
|
13551
13855
|
KIRO_SESSION_START_COMMAND = getDispatchCommand("session-start", "kiro");
|
|
13552
13856
|
COMMON_CLAUDE_FIELDS = /* @__PURE__ */ new Set(["name", "description", "model", "tools"]);
|
|
13553
13857
|
COMMON_CURSOR_FIELDS = /* @__PURE__ */ new Set(["agent_id", "description", "model", "tools"]);
|
|
@@ -13585,8 +13889,7 @@ async function removeStaleAgentSiblings(targetAgentsDir, stem, targetExt) {
|
|
|
13585
13889
|
return;
|
|
13586
13890
|
}
|
|
13587
13891
|
for (const file of files) {
|
|
13588
|
-
|
|
13589
|
-
if (base !== stem) continue;
|
|
13892
|
+
if (agentStemFromFilename(file) !== stem) continue;
|
|
13590
13893
|
if (file === `${stem}${targetExt}`) continue;
|
|
13591
13894
|
try {
|
|
13592
13895
|
await remove(path36.join(targetAgentsDir, file));
|
|
@@ -13669,10 +13972,267 @@ var init_builtin_agents = __esm({
|
|
|
13669
13972
|
}
|
|
13670
13973
|
});
|
|
13671
13974
|
|
|
13672
|
-
// src/
|
|
13975
|
+
// src/projects.ts
|
|
13976
|
+
var projects_exports = {};
|
|
13977
|
+
__export(projects_exports, {
|
|
13978
|
+
describeProjects: () => describeProjects,
|
|
13979
|
+
findProject: () => findProject,
|
|
13980
|
+
isSafeNamespaceSegment: () => isSafeNamespaceSegment,
|
|
13981
|
+
listProjectIds: () => listProjectIds,
|
|
13982
|
+
loadProjectsManifest: () => loadProjectsManifest,
|
|
13983
|
+
mergeNamespaces: () => mergeNamespaces,
|
|
13984
|
+
resolveActiveLearningsNamespaces: () => resolveActiveLearningsNamespaces,
|
|
13985
|
+
resolveProjectResourceNamespaces: () => resolveProjectResourceNamespaces,
|
|
13986
|
+
saveProjectsManifest: () => saveProjectsManifest
|
|
13987
|
+
});
|
|
13673
13988
|
import path37 from "path";
|
|
13989
|
+
import YAML8 from "yaml";
|
|
13990
|
+
import { z as z6 } from "zod";
|
|
13991
|
+
function isSafeNamespaceSegment(seg) {
|
|
13992
|
+
return SAFE_ID.test(seg) && seg !== "." && seg !== "..";
|
|
13993
|
+
}
|
|
13994
|
+
function validateManifestShape2(raw) {
|
|
13995
|
+
if (!raw || typeof raw !== "object") {
|
|
13996
|
+
throw new Error("Invalid projects manifest: expected an object");
|
|
13997
|
+
}
|
|
13998
|
+
const candidate = raw;
|
|
13999
|
+
const projects = candidate.projects;
|
|
14000
|
+
if (projects !== void 0 && !Array.isArray(projects)) {
|
|
14001
|
+
throw new Error("Invalid projects manifest: projects must be an array");
|
|
14002
|
+
}
|
|
14003
|
+
for (const project of projects ?? []) {
|
|
14004
|
+
if (!project || typeof project !== "object") {
|
|
14005
|
+
throw new Error("Invalid projects manifest: every project must be an object");
|
|
14006
|
+
}
|
|
14007
|
+
const resources = project.resources;
|
|
14008
|
+
if (resources !== void 0 && (typeof resources !== "object" || Array.isArray(resources))) {
|
|
14009
|
+
throw new Error(`Invalid projects manifest: project ${project.id ?? "<unknown>"} has invalid resources`);
|
|
14010
|
+
}
|
|
14011
|
+
if (resources) {
|
|
14012
|
+
const ALLOWED_RESOURCE_KEYS = new Set(PROJECT_RESOURCE_TYPES);
|
|
14013
|
+
for (const key of Object.keys(resources)) {
|
|
14014
|
+
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
14015
|
+
throw new Error(`Invalid projects manifest: unknown resource type "${key}"`);
|
|
14016
|
+
}
|
|
14017
|
+
}
|
|
14018
|
+
}
|
|
14019
|
+
}
|
|
14020
|
+
const manifest = ProjectsManifestSchema.parse(raw);
|
|
14021
|
+
const ids = /* @__PURE__ */ new Set();
|
|
14022
|
+
for (const project of manifest.projects) {
|
|
14023
|
+
if (ids.has(project.id)) {
|
|
14024
|
+
throw new Error(`Invalid projects manifest: duplicate project id "${project.id}"`);
|
|
14025
|
+
}
|
|
14026
|
+
ids.add(project.id);
|
|
14027
|
+
}
|
|
14028
|
+
return manifest;
|
|
14029
|
+
}
|
|
14030
|
+
async function loadProjectsManifest(repoPath) {
|
|
14031
|
+
const manifestPath = path37.join(repoPath, "manifest", "projects.yaml");
|
|
14032
|
+
const content = await readFileSafe(manifestPath);
|
|
14033
|
+
if (!content) {
|
|
14034
|
+
return null;
|
|
14035
|
+
}
|
|
14036
|
+
let raw;
|
|
14037
|
+
try {
|
|
14038
|
+
raw = YAML8.parse(content);
|
|
14039
|
+
} catch (error) {
|
|
14040
|
+
throw new Error(`Invalid projects manifest YAML: ${error.message}`);
|
|
14041
|
+
}
|
|
14042
|
+
return validateManifestShape2(raw);
|
|
14043
|
+
}
|
|
14044
|
+
async function saveProjectsManifest(repoPath, manifest) {
|
|
14045
|
+
validateManifestShape2(manifest);
|
|
14046
|
+
const manifestDir = path37.join(repoPath, "manifest");
|
|
14047
|
+
const manifestPath = path37.join(manifestDir, "projects.yaml");
|
|
14048
|
+
await ensureDir(manifestDir);
|
|
14049
|
+
await writeFile(manifestPath, YAML8.stringify(manifest));
|
|
14050
|
+
}
|
|
14051
|
+
function findProject(manifest, projectId) {
|
|
14052
|
+
return manifest.projects.find((candidate) => candidate.id === projectId);
|
|
14053
|
+
}
|
|
14054
|
+
function listProjectIds(manifest) {
|
|
14055
|
+
return manifest.projects.map((project) => project.id);
|
|
14056
|
+
}
|
|
14057
|
+
function describeProjects(projects) {
|
|
14058
|
+
return projects.map((project) => {
|
|
14059
|
+
const label = project.name || project.id;
|
|
14060
|
+
return project.description ? `${label}: ${project.description}` : label;
|
|
14061
|
+
});
|
|
14062
|
+
}
|
|
14063
|
+
function getProjectOrThrow(manifest, projectId) {
|
|
14064
|
+
const project = findProject(manifest, projectId);
|
|
14065
|
+
if (!project) {
|
|
14066
|
+
throw new Error(`Unknown project "${projectId}". Valid projects: ${listProjectIds(manifest).join(", ")}`);
|
|
14067
|
+
}
|
|
14068
|
+
return project;
|
|
14069
|
+
}
|
|
14070
|
+
function resolveProjectResourceNamespaces(input) {
|
|
14071
|
+
const resolved = input.activeProjects.map((id) => getProjectOrThrow(input.manifest, id));
|
|
14072
|
+
const namespaces = {
|
|
14073
|
+
knowledge: [],
|
|
14074
|
+
skills: [],
|
|
14075
|
+
learnings: [],
|
|
14076
|
+
agents: []
|
|
14077
|
+
};
|
|
14078
|
+
for (const type of PROJECT_RESOURCE_TYPES) {
|
|
14079
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14080
|
+
for (const project of resolved) {
|
|
14081
|
+
for (const namespace of project.resources[type]) {
|
|
14082
|
+
if (seen.has(namespace)) continue;
|
|
14083
|
+
seen.add(namespace);
|
|
14084
|
+
namespaces[type].push(namespace);
|
|
14085
|
+
}
|
|
14086
|
+
}
|
|
14087
|
+
}
|
|
14088
|
+
return namespaces;
|
|
14089
|
+
}
|
|
14090
|
+
async function resolveActiveLearningsNamespaces(repoPath, activeProjects) {
|
|
14091
|
+
if (activeProjects.length === 0) return [];
|
|
14092
|
+
const manifest = await loadProjectsManifest(repoPath);
|
|
14093
|
+
if (!manifest) return [];
|
|
14094
|
+
try {
|
|
14095
|
+
return resolveProjectResourceNamespaces({ manifest, activeProjects }).learnings;
|
|
14096
|
+
} catch {
|
|
14097
|
+
return [];
|
|
14098
|
+
}
|
|
14099
|
+
}
|
|
14100
|
+
function mergeNamespaces(roleNamespaces, projectNamespaces) {
|
|
14101
|
+
const dedupe = (a, b) => {
|
|
14102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14103
|
+
const out = [];
|
|
14104
|
+
for (const ns of [...a, ...b]) {
|
|
14105
|
+
if (seen.has(ns)) continue;
|
|
14106
|
+
seen.add(ns);
|
|
14107
|
+
out.push(ns);
|
|
14108
|
+
}
|
|
14109
|
+
return out;
|
|
14110
|
+
};
|
|
14111
|
+
return {
|
|
14112
|
+
knowledge: dedupe(roleNamespaces.knowledge, projectNamespaces.knowledge),
|
|
14113
|
+
skills: dedupe(roleNamespaces.skills, projectNamespaces.skills),
|
|
14114
|
+
// Roles never contribute learnings; this is effectively the project set.
|
|
14115
|
+
learnings: dedupe(roleNamespaces.learnings, projectNamespaces.learnings),
|
|
14116
|
+
agents: dedupe(roleNamespaces.agents, projectNamespaces.agents)
|
|
14117
|
+
};
|
|
14118
|
+
}
|
|
14119
|
+
var PROJECT_RESOURCE_TYPES, ProjectResourceNamespacesSchema, SAFE_ID, ProjectSchema, ProjectsManifestSchema;
|
|
14120
|
+
var init_projects = __esm({
|
|
14121
|
+
"src/projects.ts"() {
|
|
14122
|
+
"use strict";
|
|
14123
|
+
init_fs();
|
|
14124
|
+
PROJECT_RESOURCE_TYPES = ["knowledge", "skills", "learnings", "agents"];
|
|
14125
|
+
ProjectResourceNamespacesSchema = z6.object({
|
|
14126
|
+
knowledge: z6.array(z6.string().min(1)).default([]),
|
|
14127
|
+
skills: z6.array(z6.string().min(1)).default([]),
|
|
14128
|
+
learnings: z6.array(z6.string().min(1)).default([]),
|
|
14129
|
+
agents: z6.array(z6.string().min(1)).default([])
|
|
14130
|
+
});
|
|
14131
|
+
SAFE_ID = /^[A-Za-z0-9._-]+$/;
|
|
14132
|
+
ProjectSchema = z6.object({
|
|
14133
|
+
id: z6.string().min(1).refine((v) => isSafeNamespaceSegment(v), {
|
|
14134
|
+
message: "project id must be a single path segment (letters, digits, '.', '_', '-'; no '/', '\\\\', or '..')"
|
|
14135
|
+
}),
|
|
14136
|
+
name: z6.string().default(""),
|
|
14137
|
+
description: z6.string().default(""),
|
|
14138
|
+
resources: ProjectResourceNamespacesSchema
|
|
14139
|
+
});
|
|
14140
|
+
ProjectsManifestSchema = z6.object({
|
|
14141
|
+
version: z6.number(),
|
|
14142
|
+
// Unlike roles (`.min(1)`), a repo may define zero projects — a team without
|
|
14143
|
+
// project partitioning simply has no projects.yaml, and an empty list is valid.
|
|
14144
|
+
projects: z6.array(ProjectSchema).default([])
|
|
14145
|
+
});
|
|
14146
|
+
}
|
|
14147
|
+
});
|
|
14148
|
+
|
|
14149
|
+
// src/resource-namespaces.ts
|
|
14150
|
+
async function resolveResourceNamespaces(localConfig) {
|
|
14151
|
+
const activeProjects = localConfig.projects ?? [];
|
|
14152
|
+
const primaryRole = localConfig.primaryRole;
|
|
14153
|
+
const hasRole = !!primaryRole;
|
|
14154
|
+
const hasProjects = activeProjects.length > 0;
|
|
14155
|
+
const projectsManifest = await loadProjectsManifest(localConfig.repo.localPath);
|
|
14156
|
+
const teamHasProjects = !!projectsManifest && projectsManifest.projects.length > 0;
|
|
14157
|
+
if (!hasRole && !hasProjects && !teamHasProjects) return null;
|
|
14158
|
+
let roleNamespaces = { knowledge: [], skills: [], learnings: [], agents: [] };
|
|
14159
|
+
let allRoleSkillNamespaces = /* @__PURE__ */ new Set();
|
|
14160
|
+
if (primaryRole) {
|
|
14161
|
+
let rolesManifest;
|
|
14162
|
+
try {
|
|
14163
|
+
rolesManifest = await loadRolesManifest(localConfig.repo.localPath);
|
|
14164
|
+
} catch {
|
|
14165
|
+
log.warn("Could not load roles manifest. Skipping role-based filtering.");
|
|
14166
|
+
rolesManifest = null;
|
|
14167
|
+
}
|
|
14168
|
+
if (rolesManifest) {
|
|
14169
|
+
try {
|
|
14170
|
+
roleNamespaces = resolveRoleResourceNamespaces({
|
|
14171
|
+
manifest: rolesManifest,
|
|
14172
|
+
primaryRole,
|
|
14173
|
+
additionalRoles: localConfig.additionalRoles ?? []
|
|
14174
|
+
});
|
|
14175
|
+
allRoleSkillNamespaces = new Set(rolesManifest.roles.flatMap((role) => role.resources.skills));
|
|
14176
|
+
} catch {
|
|
14177
|
+
log.warn(`Role "${localConfig.primaryRole}" not found in manifest. Falling back to unfiltered sync.`);
|
|
14178
|
+
log.warn("Run `teamai roles set <role>` to pick a valid role.");
|
|
14179
|
+
if (!hasProjects && !teamHasProjects) return null;
|
|
14180
|
+
}
|
|
14181
|
+
} else if (!hasProjects && !teamHasProjects) {
|
|
14182
|
+
return null;
|
|
14183
|
+
}
|
|
14184
|
+
}
|
|
14185
|
+
let projectNamespaces = { knowledge: [], skills: [], learnings: [], agents: [] };
|
|
14186
|
+
let allProjectSkillNamespaces = /* @__PURE__ */ new Set();
|
|
14187
|
+
if (projectsManifest) {
|
|
14188
|
+
allProjectSkillNamespaces = new Set(projectsManifest.projects.flatMap((p) => p.resources.skills));
|
|
14189
|
+
if (hasProjects) {
|
|
14190
|
+
try {
|
|
14191
|
+
projectNamespaces = resolveProjectResourceNamespaces({
|
|
14192
|
+
manifest: projectsManifest,
|
|
14193
|
+
activeProjects
|
|
14194
|
+
});
|
|
14195
|
+
} catch (e) {
|
|
14196
|
+
log.warn(`${e instanceof Error ? e.message : String(e)} Falling back to role-only filtering.`);
|
|
14197
|
+
}
|
|
14198
|
+
}
|
|
14199
|
+
} else if (hasProjects) {
|
|
14200
|
+
log.warn("Active projects configured but no projects manifest found. Skipping project-based filtering.");
|
|
14201
|
+
}
|
|
14202
|
+
const activeNamespaces = mergeNamespaces(roleNamespaces, projectNamespaces);
|
|
14203
|
+
const allSkillNamespaces = /* @__PURE__ */ new Set([...allRoleSkillNamespaces, ...allProjectSkillNamespaces]);
|
|
14204
|
+
return { activeNamespaces, allSkillNamespaces };
|
|
14205
|
+
}
|
|
14206
|
+
var init_resource_namespaces = __esm({
|
|
14207
|
+
"src/resource-namespaces.ts"() {
|
|
14208
|
+
"use strict";
|
|
14209
|
+
init_roles();
|
|
14210
|
+
init_projects();
|
|
14211
|
+
init_logger();
|
|
14212
|
+
}
|
|
14213
|
+
});
|
|
14214
|
+
|
|
14215
|
+
// src/resources/agents.ts
|
|
14216
|
+
import path38 from "path";
|
|
13674
14217
|
import { isDeepStrictEqual } from "util";
|
|
13675
14218
|
import { parse as parseYaml2 } from "yaml";
|
|
14219
|
+
async function listTeamAgentDirs(teamAgentsDir) {
|
|
14220
|
+
const dirs = [{ dir: teamAgentsDir }];
|
|
14221
|
+
for (const namespace of await listDirs(teamAgentsDir)) {
|
|
14222
|
+
if (isSafeNamespaceSegment(namespace)) dirs.push({ dir: path38.join(teamAgentsDir, namespace), namespace });
|
|
14223
|
+
}
|
|
14224
|
+
return dirs;
|
|
14225
|
+
}
|
|
14226
|
+
async function findTeamAgentFiles(teamAgentsDir, stem) {
|
|
14227
|
+
const found = [];
|
|
14228
|
+
for (const { dir, namespace } of await listTeamAgentDirs(teamAgentsDir)) {
|
|
14229
|
+
for (const ext of [".yaml", ".md"]) {
|
|
14230
|
+
const candidate = path38.join(dir, `${stem}${ext}`);
|
|
14231
|
+
if (await pathExists(candidate)) found.push({ path: candidate, ext, ...namespace ? { namespace } : {} });
|
|
14232
|
+
}
|
|
14233
|
+
}
|
|
14234
|
+
return found;
|
|
14235
|
+
}
|
|
13676
14236
|
function mergeCanonicalEdits(canonical, perTool) {
|
|
13677
14237
|
const merged = { ...canonical };
|
|
13678
14238
|
const extras = { ...canonical.tool_extras };
|
|
@@ -13714,16 +14274,10 @@ function mergeCanonicalEdits(canonical, perTool) {
|
|
|
13714
14274
|
else delete merged.tool_extras;
|
|
13715
14275
|
return { ok: true, spec: merged };
|
|
13716
14276
|
}
|
|
13717
|
-
function getAgentStem(filename) {
|
|
13718
|
-
if (filename.endsWith(".md")) return filename.slice(0, -3);
|
|
13719
|
-
if (filename.endsWith(".toml")) return filename.slice(0, -5);
|
|
13720
|
-
if (filename.endsWith(".json")) return filename.slice(0, -5);
|
|
13721
|
-
return null;
|
|
13722
|
-
}
|
|
13723
14277
|
async function removeStaleAgentSiblings2(agentsDir, stem, targetExt) {
|
|
13724
14278
|
for (const file of await listFiles(agentsDir)) {
|
|
13725
|
-
if (
|
|
13726
|
-
await remove(
|
|
14279
|
+
if (agentStemFromFilename(file) !== stem || file === `${stem}${targetExt}`) continue;
|
|
14280
|
+
await remove(path38.join(agentsDir, file));
|
|
13727
14281
|
log.debug(`Removed stale agent sibling ${file} for ${stem}`);
|
|
13728
14282
|
}
|
|
13729
14283
|
}
|
|
@@ -13737,7 +14291,7 @@ async function agentContentEqual(tool, localPath, teamPath) {
|
|
|
13737
14291
|
const canonicalContent = await readFileSafe(teamPath);
|
|
13738
14292
|
const localContent = await readFileSafe(localPath);
|
|
13739
14293
|
if (canonicalContent === null || localContent === null) return false;
|
|
13740
|
-
const parsed = parseAgentYaml(canonicalContent,
|
|
14294
|
+
const parsed = parseAgentYaml(canonicalContent, path38.basename(teamPath));
|
|
13741
14295
|
if (!parsed.ok) return false;
|
|
13742
14296
|
return localContent === renderForTool(parsed.spec, tool).content;
|
|
13743
14297
|
}
|
|
@@ -13767,7 +14321,7 @@ function reverseByTool(tool, filePath, content) {
|
|
|
13767
14321
|
return reverseFromOpencode(filePath, content);
|
|
13768
14322
|
}
|
|
13769
14323
|
}
|
|
13770
|
-
var AgentsHandler;
|
|
14324
|
+
var AgentsHandler, LEGACY_MD_TOOLS;
|
|
13771
14325
|
var init_agents = __esm({
|
|
13772
14326
|
"src/resources/agents.ts"() {
|
|
13773
14327
|
"use strict";
|
|
@@ -13776,6 +14330,9 @@ var init_agents = __esm({
|
|
|
13776
14330
|
init_logger();
|
|
13777
14331
|
init_types();
|
|
13778
14332
|
init_builtin_agents();
|
|
14333
|
+
init_resource_namespaces();
|
|
14334
|
+
init_projects();
|
|
14335
|
+
init_path_safety();
|
|
13779
14336
|
init_agent_format();
|
|
13780
14337
|
AgentsHandler = class extends ResourceHandler {
|
|
13781
14338
|
type = "agents";
|
|
@@ -13787,50 +14344,53 @@ var init_agents = __esm({
|
|
|
13787
14344
|
* Built-in CLI agents are excluded from push.
|
|
13788
14345
|
*/
|
|
13789
14346
|
async scanLocalForPush(teamConfig, localConfig) {
|
|
13790
|
-
const teamAgentsDir =
|
|
14347
|
+
const teamAgentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13791
14348
|
const tombstones = await this.readTombstones(localConfig);
|
|
13792
14349
|
const baseDir = resolveBaseDir(localConfig);
|
|
13793
14350
|
const directItems = [];
|
|
13794
14351
|
const directStems = /* @__PURE__ */ new Set();
|
|
13795
14352
|
if (isSelfMode(localConfig) && localConfig.projectRoot) {
|
|
13796
|
-
const activeAgentsDir =
|
|
14353
|
+
const activeAgentsDir = path38.join(localConfig.projectRoot, ".teamai", "agents");
|
|
13797
14354
|
if (await pathExists(activeAgentsDir)) {
|
|
13798
|
-
for (const
|
|
13799
|
-
const
|
|
13800
|
-
const
|
|
13801
|
-
|
|
13802
|
-
|
|
13803
|
-
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13807
|
-
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
14355
|
+
for (const { dir, namespace } of await listTeamAgentDirs(activeAgentsDir)) {
|
|
14356
|
+
const relDir = namespace ? `agents/${namespace}` : "agents";
|
|
14357
|
+
for (const file of await listFiles(dir)) {
|
|
14358
|
+
const isYaml = file.endsWith(".yaml");
|
|
14359
|
+
const isMd = file.endsWith(".md");
|
|
14360
|
+
if (!isYaml && !isMd) continue;
|
|
14361
|
+
const stem = file.replace(/\.(yaml|md)$/, "");
|
|
14362
|
+
if (tombstones.has(stem)) continue;
|
|
14363
|
+
if (BUILTIN_AGENT_NAMES.has(stem)) continue;
|
|
14364
|
+
const activePath = path38.join(dir, file);
|
|
14365
|
+
const basePath = path38.join(localConfig.repo.localPath, relDir, file);
|
|
14366
|
+
const baseExists = await pathExists(basePath);
|
|
14367
|
+
if (baseExists && await fileContentEqual(activePath, basePath)) continue;
|
|
14368
|
+
directItems.push({
|
|
14369
|
+
name: stem,
|
|
14370
|
+
type: "agents",
|
|
14371
|
+
sourcePath: activePath,
|
|
14372
|
+
relativePath: `${relDir}/${file}`,
|
|
14373
|
+
status: baseExists ? "modified" : "new",
|
|
14374
|
+
legacy: isMd
|
|
14375
|
+
});
|
|
14376
|
+
directStems.add(stem);
|
|
14377
|
+
}
|
|
13818
14378
|
}
|
|
13819
14379
|
}
|
|
13820
14380
|
}
|
|
13821
14381
|
const grouped = /* @__PURE__ */ new Map();
|
|
13822
14382
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
13823
14383
|
if (!toolPath.agents) continue;
|
|
13824
|
-
const agentsDir =
|
|
14384
|
+
const agentsDir = path38.join(baseDir, toolPath.agents);
|
|
13825
14385
|
if (!await pathExists(agentsDir)) continue;
|
|
13826
14386
|
const files = await listFiles(agentsDir);
|
|
13827
14387
|
for (const file of files) {
|
|
13828
|
-
const stem =
|
|
14388
|
+
const stem = agentStemFromFilename(file);
|
|
13829
14389
|
if (stem === null) continue;
|
|
13830
14390
|
if (tombstones.has(stem)) continue;
|
|
13831
14391
|
if (BUILTIN_AGENT_NAMES.has(stem)) continue;
|
|
13832
14392
|
if (directStems.has(stem)) continue;
|
|
13833
|
-
const filePath =
|
|
14393
|
+
const filePath = path38.join(agentsDir, file);
|
|
13834
14394
|
let toolGroup = grouped.get(stem);
|
|
13835
14395
|
if (!toolGroup) {
|
|
13836
14396
|
toolGroup = /* @__PURE__ */ new Map();
|
|
@@ -13842,11 +14402,41 @@ var init_agents = __esm({
|
|
|
13842
14402
|
}
|
|
13843
14403
|
}
|
|
13844
14404
|
const items = [...directItems];
|
|
14405
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
14406
|
+
const activeNamespaces = resolved?.activeNamespaces.agents ?? null;
|
|
13845
14407
|
for (const [stem, toolFiles] of grouped) {
|
|
13846
|
-
const
|
|
13847
|
-
const
|
|
13848
|
-
|
|
13849
|
-
|
|
14408
|
+
const sources = await findTeamAgentFiles(teamAgentsDir, stem);
|
|
14409
|
+
const candidates = sources.filter(
|
|
14410
|
+
(file) => activeNamespaces === null || !file.namespace || activeNamespaces.includes(file.namespace)
|
|
14411
|
+
);
|
|
14412
|
+
if (candidates.length > 1) {
|
|
14413
|
+
items.push({
|
|
14414
|
+
name: stem,
|
|
14415
|
+
type: "agents",
|
|
14416
|
+
sourcePath: teamAgentsDir,
|
|
14417
|
+
relativePath: `agents/${stem}.yaml`,
|
|
14418
|
+
status: "modified",
|
|
14419
|
+
skipReason: `Ambiguous agent "${stem}": multiple active sources (${candidates.map((file) => file.path).join(", ")}). Give active agents unique names before pushing.`
|
|
14420
|
+
});
|
|
14421
|
+
continue;
|
|
14422
|
+
}
|
|
14423
|
+
if (sources.length > 0 && candidates.length === 0) {
|
|
14424
|
+
items.push({
|
|
14425
|
+
name: stem,
|
|
14426
|
+
type: "agents",
|
|
14427
|
+
sourcePath: teamAgentsDir,
|
|
14428
|
+
relativePath: `agents/${stem}.yaml`,
|
|
14429
|
+
status: "modified",
|
|
14430
|
+
skipReason: `Agent "${stem}" has no active source. Activate its role or project before pushing local edits.`
|
|
14431
|
+
});
|
|
14432
|
+
continue;
|
|
14433
|
+
}
|
|
14434
|
+
const located = candidates[0];
|
|
14435
|
+
const teamYamlPath = located?.ext === ".yaml" ? located.path : path38.join(teamAgentsDir, `${stem}.yaml`);
|
|
14436
|
+
const teamMdPath = located?.ext === ".md" ? located.path : path38.join(teamAgentsDir, `${stem}.md`);
|
|
14437
|
+
const hasTeamYaml = located?.ext === ".yaml";
|
|
14438
|
+
const hasTeamMd = located?.ext === ".md";
|
|
14439
|
+
const teamDir = located?.namespace ? `agents/${located.namespace}` : "agents";
|
|
13850
14440
|
let canonicalSpec;
|
|
13851
14441
|
if (hasTeamYaml) {
|
|
13852
14442
|
const raw = await readFileSafe(teamYamlPath);
|
|
@@ -13856,7 +14446,7 @@ var init_agents = __esm({
|
|
|
13856
14446
|
name: stem,
|
|
13857
14447
|
type: "agents",
|
|
13858
14448
|
sourcePath: teamYamlPath,
|
|
13859
|
-
relativePath:
|
|
14449
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13860
14450
|
status: "modified",
|
|
13861
14451
|
skipReason: "cannot read or parse canonical agent YAML"
|
|
13862
14452
|
});
|
|
@@ -13934,7 +14524,7 @@ var init_agents = __esm({
|
|
|
13934
14524
|
name: stem,
|
|
13935
14525
|
type: "agents",
|
|
13936
14526
|
sourcePath: bestPath,
|
|
13937
|
-
relativePath:
|
|
14527
|
+
relativePath: `${teamDir}/${stem}.yaml`,
|
|
13938
14528
|
status: status2,
|
|
13939
14529
|
mergedSpec: mergeResult.spec
|
|
13940
14530
|
});
|
|
@@ -13945,7 +14535,7 @@ var init_agents = __esm({
|
|
|
13945
14535
|
name: stem,
|
|
13946
14536
|
type: "agents",
|
|
13947
14537
|
sourcePath: bestPath,
|
|
13948
|
-
relativePath:
|
|
14538
|
+
relativePath: `${teamDir}/${stem}.md`,
|
|
13949
14539
|
status: status2,
|
|
13950
14540
|
skipReason
|
|
13951
14541
|
});
|
|
@@ -13956,32 +14546,33 @@ var init_agents = __esm({
|
|
|
13956
14546
|
* Scan team repo `agents/` for files to pull.
|
|
13957
14547
|
* Recognizes both *.yaml (new) and *.md (legacy).
|
|
13958
14548
|
* Hidden files (tombstones) are filtered out by listFiles.
|
|
14549
|
+
*
|
|
14550
|
+
* Root-level files are shared with everyone. One level of subdirectories
|
|
14551
|
+
* (`agents/<namespace>/`) carries role/project-scoped agents, the same
|
|
14552
|
+
* convention `rules/<namespace>/` uses; pull filters them by the active
|
|
14553
|
+
* `agents` namespaces. Deeper nesting is not scanned.
|
|
13959
14554
|
*/
|
|
13960
14555
|
async scanTeamForPull(_teamConfig, localConfig) {
|
|
13961
|
-
const agentsDir =
|
|
14556
|
+
const agentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
13962
14557
|
if (!await pathExists(agentsDir)) return [];
|
|
13963
|
-
const files = await listFiles(agentsDir);
|
|
13964
14558
|
const items = [];
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14559
|
+
const scanDir = async (dir, namespace) => {
|
|
14560
|
+
const prefix = namespace ? `agents/${namespace}` : "agents";
|
|
14561
|
+
for (const file of await listFiles(dir)) {
|
|
14562
|
+
const legacy = file.endsWith(".md");
|
|
14563
|
+
if (!legacy && !file.endsWith(".yaml")) continue;
|
|
13968
14564
|
items.push({
|
|
13969
|
-
name:
|
|
13970
|
-
type: "agents",
|
|
13971
|
-
sourcePath: path37.join(agentsDir, file),
|
|
13972
|
-
relativePath: `agents/${file}`,
|
|
13973
|
-
legacy: false
|
|
13974
|
-
});
|
|
13975
|
-
} else if (file.endsWith(".md")) {
|
|
13976
|
-
const stem = file.replace(/\.md$/, "");
|
|
13977
|
-
items.push({
|
|
13978
|
-
name: stem,
|
|
14565
|
+
name: file.replace(/\.(yaml|md)$/, ""),
|
|
13979
14566
|
type: "agents",
|
|
13980
|
-
sourcePath:
|
|
13981
|
-
relativePath:
|
|
13982
|
-
legacy
|
|
14567
|
+
sourcePath: path38.join(dir, file),
|
|
14568
|
+
relativePath: `${prefix}/${file}`,
|
|
14569
|
+
legacy,
|
|
14570
|
+
...namespace ? { namespace } : {}
|
|
13983
14571
|
});
|
|
13984
14572
|
}
|
|
14573
|
+
};
|
|
14574
|
+
for (const { dir, namespace } of await listTeamAgentDirs(agentsDir)) {
|
|
14575
|
+
await scanDir(dir, namespace);
|
|
13985
14576
|
}
|
|
13986
14577
|
return items;
|
|
13987
14578
|
}
|
|
@@ -13994,22 +14585,27 @@ var init_agents = __esm({
|
|
|
13994
14585
|
async pushItem(item, _teamConfig, localConfig) {
|
|
13995
14586
|
const agentItem = item;
|
|
13996
14587
|
if (agentItem.skipReason) {
|
|
13997
|
-
log.warn(`[agents]
|
|
13998
|
-
log.warn(" \u5EFA\u8BAE\u4FEE\u6539\u540E\u91CD\u65B0 push \u8BE5 subagent");
|
|
14588
|
+
log.warn(`[agents] Skipped ${item.name}: ${agentItem.skipReason}`);
|
|
13999
14589
|
return;
|
|
14000
14590
|
}
|
|
14591
|
+
const teamDir = path38.resolve(localConfig.repo.localPath, path38.dirname(item.relativePath));
|
|
14592
|
+
assertWithinRoot(
|
|
14593
|
+
path38.join(localConfig.repo.localPath, "agents"),
|
|
14594
|
+
teamDir,
|
|
14595
|
+
`Invalid agent destination outside team repo agents directory: ${item.relativePath}`
|
|
14596
|
+
);
|
|
14001
14597
|
if (agentItem.mergedSpec) {
|
|
14002
|
-
const dest2 =
|
|
14003
|
-
await ensureDir(
|
|
14598
|
+
const dest2 = path38.join(teamDir, `${item.name}.yaml`);
|
|
14599
|
+
await ensureDir(path38.dirname(dest2));
|
|
14004
14600
|
const yamlContent = serializeAgentYaml(agentItem.mergedSpec);
|
|
14005
14601
|
await writeFile(dest2, yamlContent);
|
|
14006
14602
|
log.debug(`Wrote agent ${item.name} \u2192 team repo (YAML format)`);
|
|
14007
14603
|
return;
|
|
14008
14604
|
}
|
|
14009
14605
|
const ext = item.sourcePath.endsWith(".yaml") ? ".yaml" : ".md";
|
|
14010
|
-
const dest =
|
|
14606
|
+
const dest = path38.join(teamDir, `${item.name}${ext}`);
|
|
14011
14607
|
if (item.sourcePath !== dest) {
|
|
14012
|
-
await ensureDir(
|
|
14608
|
+
await ensureDir(path38.dirname(dest));
|
|
14013
14609
|
await copyFile(item.sourcePath, dest);
|
|
14014
14610
|
}
|
|
14015
14611
|
log.debug(`Copied agent ${item.name} \u2192 team repo (${ext} verbatim)`);
|
|
@@ -14053,12 +14649,12 @@ var init_agents = __esm({
|
|
|
14053
14649
|
continue;
|
|
14054
14650
|
}
|
|
14055
14651
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14056
|
-
const destDir =
|
|
14652
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14057
14653
|
try {
|
|
14058
14654
|
await ensureDir(destDir);
|
|
14059
14655
|
const { ext, content: rendered } = renderForTool(spec, tool);
|
|
14060
14656
|
await removeStaleAgentSiblings2(destDir, item.name, ext);
|
|
14061
|
-
const dest =
|
|
14657
|
+
const dest = path38.join(destDir, `${item.name}${ext}`);
|
|
14062
14658
|
await writeFile(dest, rendered);
|
|
14063
14659
|
log.debug(`Rendered agent ${item.name} \u2192 ${tool} (${ext})`);
|
|
14064
14660
|
} catch (e) {
|
|
@@ -14074,19 +14670,17 @@ var init_agents = __esm({
|
|
|
14074
14670
|
async removeItem(name, teamConfig, localConfig) {
|
|
14075
14671
|
const removed = [];
|
|
14076
14672
|
const baseDir = resolveBaseDir(localConfig);
|
|
14077
|
-
const teamAgentsDir =
|
|
14078
|
-
for (const
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
await remove(teamFile);
|
|
14082
|
-
removed.push(teamFile);
|
|
14083
|
-
}
|
|
14673
|
+
const teamAgentsDir = path38.join(localConfig.repo.localPath, "agents");
|
|
14674
|
+
for (const located of await findTeamAgentFiles(teamAgentsDir, name)) {
|
|
14675
|
+
await remove(located.path);
|
|
14676
|
+
removed.push(located.path);
|
|
14084
14677
|
}
|
|
14085
14678
|
await this.addTombstone(name, localConfig);
|
|
14086
14679
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14087
14680
|
if (!toolPath.agents) continue;
|
|
14088
|
-
|
|
14089
|
-
|
|
14681
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14682
|
+
for (const ext of AGENT_FILE_EXTENSIONS) {
|
|
14683
|
+
const filePath = path38.join(baseDir, toolPath.agents, `${name}${ext}`);
|
|
14090
14684
|
if (await pathExists(filePath)) {
|
|
14091
14685
|
await remove(filePath);
|
|
14092
14686
|
removed.push(filePath);
|
|
@@ -14096,14 +14690,69 @@ var init_agents = __esm({
|
|
|
14096
14690
|
}
|
|
14097
14691
|
return removed;
|
|
14098
14692
|
}
|
|
14693
|
+
/**
|
|
14694
|
+
* Revocation pass for role/project scoping. Removes the deployed copies of
|
|
14695
|
+
* every agent whose namespace is no longer active, on every installed tool.
|
|
14696
|
+
*
|
|
14697
|
+
* Data-safety gate, same as inactive skills: a file is deleted only when it
|
|
14698
|
+
* is byte-equal to what pull would render from the team source. A local edit
|
|
14699
|
+
* is kept and reported so nothing unpushed is lost. Root-level agents and
|
|
14700
|
+
* agents still deployed to the same tool destination never qualify.
|
|
14701
|
+
*/
|
|
14702
|
+
async cleanupInactiveNamespaces(teamConfig, localConfig, activeNamespaces) {
|
|
14703
|
+
const items = await this.scanTeamForPull(teamConfig, localConfig);
|
|
14704
|
+
const isActive = (item) => !item.namespace || activeNamespaces.includes(item.namespace);
|
|
14705
|
+
const active = items.filter(isActive);
|
|
14706
|
+
const inactive = items.filter((item) => !isActive(item) && !BUILTIN_AGENT_NAMES.has(item.name));
|
|
14707
|
+
if (inactive.length === 0) return;
|
|
14708
|
+
const baseDir = resolveBaseDir(localConfig);
|
|
14709
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14710
|
+
if (!toolPath.agents || !isKnownTool(tool) || isAgentExcluded(localConfig, tool)) continue;
|
|
14711
|
+
if (!await ResourceHandler.isToolInstalled(toolPath.agents, baseDir)) continue;
|
|
14712
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14713
|
+
const activeDestinations = /* @__PURE__ */ new Set();
|
|
14714
|
+
for (const item of active) {
|
|
14715
|
+
const rendered = await this.renderedForTool(item, tool);
|
|
14716
|
+
if (rendered) activeDestinations.add(`${item.name}${rendered.ext}`);
|
|
14717
|
+
}
|
|
14718
|
+
for (const item of inactive) {
|
|
14719
|
+
const expected = await this.renderedForTool(item, tool);
|
|
14720
|
+
if (!expected || activeDestinations.has(`${item.name}${expected.ext}`)) continue;
|
|
14721
|
+
const deployed = path38.join(destDir, `${item.name}${expected.ext}`);
|
|
14722
|
+
const current = await readFileSafe(deployed);
|
|
14723
|
+
if (current === null) continue;
|
|
14724
|
+
if (current !== expected.content) {
|
|
14725
|
+
log.warn(`[${localConfig.scope}] Kept agent "${item.name}" (${tool}): it differs from the team source ${item.relativePath}. Back it up, then delete it manually.`);
|
|
14726
|
+
continue;
|
|
14727
|
+
}
|
|
14728
|
+
await remove(deployed);
|
|
14729
|
+
log.debug(`[${localConfig.scope}] Removed inactive role-scoped agent ${item.name} from ${tool}`);
|
|
14730
|
+
}
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14733
|
+
/**
|
|
14734
|
+
* What `pullItem` writes for this agent on this tool, or null when the tool
|
|
14735
|
+
* is not a target (legacy `.md` only reaches LEGACY_MD_TOOLS, a YAML spec
|
|
14736
|
+
* honours `targets`, an unparsable spec is skipped like pull skips it).
|
|
14737
|
+
*/
|
|
14738
|
+
async renderedForTool(item, tool) {
|
|
14739
|
+
const content = await readFileSafe(item.sourcePath);
|
|
14740
|
+
if (content === null) return null;
|
|
14741
|
+
if (item.legacy) {
|
|
14742
|
+
return LEGACY_MD_TOOLS.has(tool) ? { ext: ".md", content } : null;
|
|
14743
|
+
}
|
|
14744
|
+
const parsed = parseAgentYaml(content, `${item.name}.yaml`);
|
|
14745
|
+
if (!parsed.ok) return null;
|
|
14746
|
+
if (parsed.spec.targets && !parsed.spec.targets.includes(tool)) return null;
|
|
14747
|
+
return renderForTool(parsed.spec, tool);
|
|
14748
|
+
}
|
|
14099
14749
|
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
14100
14750
|
/**
|
|
14101
14751
|
* Legacy pull: copies .md as-is to Claude-compatible tools, including JoyCode.
|
|
14102
14752
|
*/
|
|
14103
14753
|
async pullLegacyMd(item, teamConfig, baseDir, localConfig) {
|
|
14104
|
-
const legacyTools = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
14105
14754
|
for (const [tool, toolPath] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
14106
|
-
if (!
|
|
14755
|
+
if (!LEGACY_MD_TOOLS.has(tool)) continue;
|
|
14107
14756
|
if (!toolPath.agents) {
|
|
14108
14757
|
log.debug(`Skipping legacy agent sync for ${tool}: no agents path configured`);
|
|
14109
14758
|
continue;
|
|
@@ -14113,10 +14762,10 @@ var init_agents = __esm({
|
|
|
14113
14762
|
continue;
|
|
14114
14763
|
}
|
|
14115
14764
|
if (isAgentExcluded(localConfig, tool)) continue;
|
|
14116
|
-
const destDir =
|
|
14765
|
+
const destDir = path38.join(baseDir, toolPath.agents);
|
|
14117
14766
|
try {
|
|
14118
14767
|
await ensureDir(destDir);
|
|
14119
|
-
const dest =
|
|
14768
|
+
const dest = path38.join(destDir, `${item.name}.md`);
|
|
14120
14769
|
await copyFile(item.sourcePath, dest);
|
|
14121
14770
|
log.debug(`Synced legacy agent ${item.name} \u2192 ${tool}`);
|
|
14122
14771
|
} catch (e) {
|
|
@@ -14125,6 +14774,7 @@ var init_agents = __esm({
|
|
|
14125
14774
|
}
|
|
14126
14775
|
}
|
|
14127
14776
|
};
|
|
14777
|
+
LEGACY_MD_TOOLS = /* @__PURE__ */ new Set(["claude", "claude-internal", "tclaude", "codebuddy", "joycode"]);
|
|
14128
14778
|
}
|
|
14129
14779
|
});
|
|
14130
14780
|
|
|
@@ -14190,11 +14840,11 @@ __export(update_exports, {
|
|
|
14190
14840
|
resolveRegistryForPackage: () => resolveRegistryForPackage,
|
|
14191
14841
|
update: () => update
|
|
14192
14842
|
});
|
|
14193
|
-
import { execFile as
|
|
14843
|
+
import { execFile as execFile2 } from "child_process";
|
|
14194
14844
|
import { promisify as promisify2 } from "util";
|
|
14195
14845
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
14196
14846
|
import fs16 from "fs";
|
|
14197
|
-
import
|
|
14847
|
+
import path39 from "path";
|
|
14198
14848
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14199
14849
|
import fse7 from "fs-extra";
|
|
14200
14850
|
function resolveRegistryForPackage(pkgName) {
|
|
@@ -14204,15 +14854,15 @@ function resolveRegistryForPackage(pkgName) {
|
|
|
14204
14854
|
return PUBLIC_REGISTRY;
|
|
14205
14855
|
}
|
|
14206
14856
|
function resolveNpmCommand() {
|
|
14207
|
-
const nodeDir =
|
|
14857
|
+
const nodeDir = path39.dirname(process.execPath);
|
|
14208
14858
|
const candidates = [
|
|
14209
14859
|
// Bundled runtimes: npm installed flat next to node.exe.
|
|
14210
|
-
|
|
14860
|
+
path39.join(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14211
14861
|
// POSIX layout rooted at the node dir itself.
|
|
14212
|
-
|
|
14862
|
+
path39.join(nodeDir, "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
14213
14863
|
// Canonical POSIX install (official tarball, Homebrew, nvm): node lives in
|
|
14214
14864
|
// <prefix>/bin with npm at <prefix>/lib/node_modules — one level up.
|
|
14215
|
-
|
|
14865
|
+
path39.join(nodeDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
14216
14866
|
];
|
|
14217
14867
|
for (const c of candidates) {
|
|
14218
14868
|
if (fs16.existsSync(c)) return { cmd: process.execPath, args: [c] };
|
|
@@ -14221,16 +14871,16 @@ function resolveNpmCommand() {
|
|
|
14221
14871
|
return { cmd: "npm", args: [] };
|
|
14222
14872
|
}
|
|
14223
14873
|
function prefixFromEntryPath(entry, posix) {
|
|
14224
|
-
const marker = `${
|
|
14874
|
+
const marker = `${path39.sep}node_modules${path39.sep}`;
|
|
14225
14875
|
const idx = entry.lastIndexOf(marker);
|
|
14226
14876
|
if (idx <= 0) return null;
|
|
14227
14877
|
const root = entry.slice(0, idx);
|
|
14228
14878
|
const pkgDir = getCurrentPackageName();
|
|
14229
|
-
if (posix &&
|
|
14230
|
-
const prefix =
|
|
14231
|
-
return fs16.existsSync(
|
|
14879
|
+
if (posix && path39.basename(root) === "lib") {
|
|
14880
|
+
const prefix = path39.dirname(root);
|
|
14881
|
+
return fs16.existsSync(path39.join(prefix, "lib", "node_modules", pkgDir)) ? { prefix, global: true } : null;
|
|
14232
14882
|
}
|
|
14233
|
-
return fs16.existsSync(
|
|
14883
|
+
return fs16.existsSync(path39.join(root, "node_modules", pkgDir)) ? { prefix: root, global: !posix } : null;
|
|
14234
14884
|
}
|
|
14235
14885
|
function resolveInstallPrefix() {
|
|
14236
14886
|
return prefixFromEntryPath(fileURLToPath4(import.meta.url), process.platform !== "win32");
|
|
@@ -14355,7 +15005,7 @@ async function acquireLock(lockPath) {
|
|
|
14355
15005
|
owner
|
|
14356
15006
|
});
|
|
14357
15007
|
try {
|
|
14358
|
-
await ensureDir(
|
|
15008
|
+
await ensureDir(path39.dirname(resolved));
|
|
14359
15009
|
} catch {
|
|
14360
15010
|
return false;
|
|
14361
15011
|
}
|
|
@@ -14484,12 +15134,12 @@ async function doUpdate() {
|
|
|
14484
15134
|
if (entry) {
|
|
14485
15135
|
try {
|
|
14486
15136
|
const installed = JSON.parse(fs16.readFileSync(
|
|
14487
|
-
|
|
15137
|
+
path39.join(path39.dirname(path39.dirname(entry)), "package.json"),
|
|
14488
15138
|
"utf-8"
|
|
14489
15139
|
));
|
|
14490
15140
|
if (installed.version !== result.latest) {
|
|
14491
15141
|
log.warn(
|
|
14492
|
-
`The running install at ${
|
|
15142
|
+
`The running install at ${path39.dirname(path39.dirname(entry))} is still v${installed.version ?? "unknown"} (expected v${result.latest}) \u2014 it may need a manual update.`
|
|
14493
15143
|
);
|
|
14494
15144
|
}
|
|
14495
15145
|
} catch {
|
|
@@ -14543,7 +15193,7 @@ var init_update = __esm({
|
|
|
14543
15193
|
init_types();
|
|
14544
15194
|
init_prompt();
|
|
14545
15195
|
init_package_info();
|
|
14546
|
-
execFileAsync2 = promisify2(
|
|
15196
|
+
execFileAsync2 = promisify2(execFile2);
|
|
14547
15197
|
PUBLIC_REGISTRY = "https://registry.npmjs.org";
|
|
14548
15198
|
TNPM_REGISTRY = "http://r.tnpm.oa.com";
|
|
14549
15199
|
VERSION_CHECK_TIMEOUT = 5e3;
|
|
@@ -14558,9 +15208,9 @@ var marketplace_exports = {};
|
|
|
14558
15208
|
__export(marketplace_exports, {
|
|
14559
15209
|
refreshMarketplace: () => refreshMarketplace
|
|
14560
15210
|
});
|
|
14561
|
-
import
|
|
15211
|
+
import path40 from "path";
|
|
14562
15212
|
async function extractSkillDescription2(skillDir) {
|
|
14563
|
-
const skillMdPath =
|
|
15213
|
+
const skillMdPath = path40.join(skillDir, SKILL_MD2);
|
|
14564
15214
|
const content = await readFileSafe(skillMdPath);
|
|
14565
15215
|
if (!content) return "";
|
|
14566
15216
|
const { data } = parseFrontmatter(content);
|
|
@@ -14569,7 +15219,7 @@ async function extractSkillDescription2(skillDir) {
|
|
|
14569
15219
|
return String(desc).replace(/\s+/g, " ").trim();
|
|
14570
15220
|
}
|
|
14571
15221
|
async function refreshMarketplace(repoPath) {
|
|
14572
|
-
const marketplacePath =
|
|
15222
|
+
const marketplacePath = path40.join(repoPath, MARKETPLACE_PATH);
|
|
14573
15223
|
if (!await pathExists(marketplacePath)) {
|
|
14574
15224
|
return false;
|
|
14575
15225
|
}
|
|
@@ -14583,11 +15233,11 @@ async function refreshMarketplace(repoPath) {
|
|
|
14583
15233
|
return false;
|
|
14584
15234
|
}
|
|
14585
15235
|
if (!Array.isArray(marketplace.plugins)) return false;
|
|
14586
|
-
const skillsDir =
|
|
15236
|
+
const skillsDir = path40.join(repoPath, "skills");
|
|
14587
15237
|
const currentSkills = /* @__PURE__ */ new Set();
|
|
14588
15238
|
const dirs = await listDirs(skillsDir);
|
|
14589
15239
|
for (const dir of dirs) {
|
|
14590
|
-
const hasSkillMd = await pathExists(
|
|
15240
|
+
const hasSkillMd = await pathExists(path40.join(skillsDir, dir, SKILL_MD2));
|
|
14591
15241
|
if (hasSkillMd) {
|
|
14592
15242
|
currentSkills.add(dir);
|
|
14593
15243
|
}
|
|
@@ -14606,7 +15256,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14606
15256
|
for (const skillName of currentSkills) {
|
|
14607
15257
|
const existing = existingPlugins.get(skillName);
|
|
14608
15258
|
if (existing) {
|
|
14609
|
-
const desc = await extractSkillDescription2(
|
|
15259
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14610
15260
|
if (desc && desc !== existing.description) {
|
|
14611
15261
|
existing.description = desc;
|
|
14612
15262
|
changed = true;
|
|
@@ -14614,7 +15264,7 @@ async function refreshMarketplace(repoPath) {
|
|
|
14614
15264
|
updatedPlugins.push(existing);
|
|
14615
15265
|
existingPlugins.delete(skillName);
|
|
14616
15266
|
} else {
|
|
14617
|
-
const desc = await extractSkillDescription2(
|
|
15267
|
+
const desc = await extractSkillDescription2(path40.join(skillsDir, skillName));
|
|
14618
15268
|
updatedPlugins.push({
|
|
14619
15269
|
name: skillName,
|
|
14620
15270
|
source: `./skills/${skillName}`,
|
|
@@ -14651,177 +15301,6 @@ var init_marketplace = __esm({
|
|
|
14651
15301
|
}
|
|
14652
15302
|
});
|
|
14653
15303
|
|
|
14654
|
-
// src/projects.ts
|
|
14655
|
-
var projects_exports = {};
|
|
14656
|
-
__export(projects_exports, {
|
|
14657
|
-
describeProjects: () => describeProjects,
|
|
14658
|
-
findProject: () => findProject,
|
|
14659
|
-
isSafeNamespaceSegment: () => isSafeNamespaceSegment,
|
|
14660
|
-
listProjectIds: () => listProjectIds,
|
|
14661
|
-
loadProjectsManifest: () => loadProjectsManifest,
|
|
14662
|
-
mergeNamespaces: () => mergeNamespaces,
|
|
14663
|
-
resolveActiveLearningsNamespaces: () => resolveActiveLearningsNamespaces,
|
|
14664
|
-
resolveProjectResourceNamespaces: () => resolveProjectResourceNamespaces,
|
|
14665
|
-
saveProjectsManifest: () => saveProjectsManifest
|
|
14666
|
-
});
|
|
14667
|
-
import path40 from "path";
|
|
14668
|
-
import YAML8 from "yaml";
|
|
14669
|
-
import { z as z6 } from "zod";
|
|
14670
|
-
function isSafeNamespaceSegment(seg) {
|
|
14671
|
-
return SAFE_ID.test(seg) && seg !== "." && seg !== "..";
|
|
14672
|
-
}
|
|
14673
|
-
function validateManifestShape2(raw) {
|
|
14674
|
-
if (!raw || typeof raw !== "object") {
|
|
14675
|
-
throw new Error("Invalid projects manifest: expected an object");
|
|
14676
|
-
}
|
|
14677
|
-
const candidate = raw;
|
|
14678
|
-
const projects = candidate.projects;
|
|
14679
|
-
if (projects !== void 0 && !Array.isArray(projects)) {
|
|
14680
|
-
throw new Error("Invalid projects manifest: projects must be an array");
|
|
14681
|
-
}
|
|
14682
|
-
for (const project of projects ?? []) {
|
|
14683
|
-
if (!project || typeof project !== "object") {
|
|
14684
|
-
throw new Error("Invalid projects manifest: every project must be an object");
|
|
14685
|
-
}
|
|
14686
|
-
const resources = project.resources;
|
|
14687
|
-
if (resources !== void 0 && (typeof resources !== "object" || Array.isArray(resources))) {
|
|
14688
|
-
throw new Error(`Invalid projects manifest: project ${project.id ?? "<unknown>"} has invalid resources`);
|
|
14689
|
-
}
|
|
14690
|
-
if (resources) {
|
|
14691
|
-
const ALLOWED_RESOURCE_KEYS = new Set(PROJECT_RESOURCE_TYPES);
|
|
14692
|
-
for (const key of Object.keys(resources)) {
|
|
14693
|
-
if (!ALLOWED_RESOURCE_KEYS.has(key)) {
|
|
14694
|
-
throw new Error(`Invalid projects manifest: unknown resource type "${key}"`);
|
|
14695
|
-
}
|
|
14696
|
-
}
|
|
14697
|
-
}
|
|
14698
|
-
}
|
|
14699
|
-
const manifest = ProjectsManifestSchema.parse(raw);
|
|
14700
|
-
const ids = /* @__PURE__ */ new Set();
|
|
14701
|
-
for (const project of manifest.projects) {
|
|
14702
|
-
if (ids.has(project.id)) {
|
|
14703
|
-
throw new Error(`Invalid projects manifest: duplicate project id "${project.id}"`);
|
|
14704
|
-
}
|
|
14705
|
-
ids.add(project.id);
|
|
14706
|
-
}
|
|
14707
|
-
return manifest;
|
|
14708
|
-
}
|
|
14709
|
-
async function loadProjectsManifest(repoPath) {
|
|
14710
|
-
const manifestPath = path40.join(repoPath, "manifest", "projects.yaml");
|
|
14711
|
-
const content = await readFileSafe(manifestPath);
|
|
14712
|
-
if (!content) {
|
|
14713
|
-
return null;
|
|
14714
|
-
}
|
|
14715
|
-
let raw;
|
|
14716
|
-
try {
|
|
14717
|
-
raw = YAML8.parse(content);
|
|
14718
|
-
} catch (error) {
|
|
14719
|
-
throw new Error(`Invalid projects manifest YAML: ${error.message}`);
|
|
14720
|
-
}
|
|
14721
|
-
return validateManifestShape2(raw);
|
|
14722
|
-
}
|
|
14723
|
-
async function saveProjectsManifest(repoPath, manifest) {
|
|
14724
|
-
validateManifestShape2(manifest);
|
|
14725
|
-
const manifestDir = path40.join(repoPath, "manifest");
|
|
14726
|
-
const manifestPath = path40.join(manifestDir, "projects.yaml");
|
|
14727
|
-
await ensureDir(manifestDir);
|
|
14728
|
-
await writeFile(manifestPath, YAML8.stringify(manifest));
|
|
14729
|
-
}
|
|
14730
|
-
function findProject(manifest, projectId) {
|
|
14731
|
-
return manifest.projects.find((candidate) => candidate.id === projectId);
|
|
14732
|
-
}
|
|
14733
|
-
function listProjectIds(manifest) {
|
|
14734
|
-
return manifest.projects.map((project) => project.id);
|
|
14735
|
-
}
|
|
14736
|
-
function describeProjects(projects) {
|
|
14737
|
-
return projects.map((project) => {
|
|
14738
|
-
const label = project.name || project.id;
|
|
14739
|
-
return project.description ? `${label}: ${project.description}` : label;
|
|
14740
|
-
});
|
|
14741
|
-
}
|
|
14742
|
-
function getProjectOrThrow(manifest, projectId) {
|
|
14743
|
-
const project = findProject(manifest, projectId);
|
|
14744
|
-
if (!project) {
|
|
14745
|
-
throw new Error(`Unknown project "${projectId}". Valid projects: ${listProjectIds(manifest).join(", ")}`);
|
|
14746
|
-
}
|
|
14747
|
-
return project;
|
|
14748
|
-
}
|
|
14749
|
-
function resolveProjectResourceNamespaces(input) {
|
|
14750
|
-
const resolved = input.activeProjects.map((id) => getProjectOrThrow(input.manifest, id));
|
|
14751
|
-
const namespaces = {
|
|
14752
|
-
knowledge: [],
|
|
14753
|
-
skills: [],
|
|
14754
|
-
learnings: []
|
|
14755
|
-
};
|
|
14756
|
-
for (const type of PROJECT_RESOURCE_TYPES) {
|
|
14757
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14758
|
-
for (const project of resolved) {
|
|
14759
|
-
for (const namespace of project.resources[type]) {
|
|
14760
|
-
if (seen.has(namespace)) continue;
|
|
14761
|
-
seen.add(namespace);
|
|
14762
|
-
namespaces[type].push(namespace);
|
|
14763
|
-
}
|
|
14764
|
-
}
|
|
14765
|
-
}
|
|
14766
|
-
return namespaces;
|
|
14767
|
-
}
|
|
14768
|
-
async function resolveActiveLearningsNamespaces(repoPath, activeProjects) {
|
|
14769
|
-
if (activeProjects.length === 0) return [];
|
|
14770
|
-
const manifest = await loadProjectsManifest(repoPath);
|
|
14771
|
-
if (!manifest) return [];
|
|
14772
|
-
try {
|
|
14773
|
-
return resolveProjectResourceNamespaces({ manifest, activeProjects }).learnings;
|
|
14774
|
-
} catch {
|
|
14775
|
-
return [];
|
|
14776
|
-
}
|
|
14777
|
-
}
|
|
14778
|
-
function mergeNamespaces(roleNamespaces, projectNamespaces) {
|
|
14779
|
-
const dedupe = (a, b) => {
|
|
14780
|
-
const seen = /* @__PURE__ */ new Set();
|
|
14781
|
-
const out = [];
|
|
14782
|
-
for (const ns of [...a, ...b]) {
|
|
14783
|
-
if (seen.has(ns)) continue;
|
|
14784
|
-
seen.add(ns);
|
|
14785
|
-
out.push(ns);
|
|
14786
|
-
}
|
|
14787
|
-
return out;
|
|
14788
|
-
};
|
|
14789
|
-
return {
|
|
14790
|
-
knowledge: dedupe(roleNamespaces.knowledge, projectNamespaces.knowledge),
|
|
14791
|
-
skills: dedupe(roleNamespaces.skills, projectNamespaces.skills),
|
|
14792
|
-
// Roles never contribute learnings; this is effectively the project set.
|
|
14793
|
-
learnings: dedupe(roleNamespaces.learnings, projectNamespaces.learnings)
|
|
14794
|
-
};
|
|
14795
|
-
}
|
|
14796
|
-
var PROJECT_RESOURCE_TYPES, ProjectResourceNamespacesSchema, SAFE_ID, ProjectSchema, ProjectsManifestSchema;
|
|
14797
|
-
var init_projects = __esm({
|
|
14798
|
-
"src/projects.ts"() {
|
|
14799
|
-
"use strict";
|
|
14800
|
-
init_fs();
|
|
14801
|
-
PROJECT_RESOURCE_TYPES = ["knowledge", "skills", "learnings"];
|
|
14802
|
-
ProjectResourceNamespacesSchema = z6.object({
|
|
14803
|
-
knowledge: z6.array(z6.string().min(1)).default([]),
|
|
14804
|
-
skills: z6.array(z6.string().min(1)).default([]),
|
|
14805
|
-
learnings: z6.array(z6.string().min(1)).default([])
|
|
14806
|
-
});
|
|
14807
|
-
SAFE_ID = /^[A-Za-z0-9._-]+$/;
|
|
14808
|
-
ProjectSchema = z6.object({
|
|
14809
|
-
id: z6.string().min(1).refine((v) => isSafeNamespaceSegment(v), {
|
|
14810
|
-
message: "project id must be a single path segment (letters, digits, '.', '_', '-'; no '/', '\\\\', or '..')"
|
|
14811
|
-
}),
|
|
14812
|
-
name: z6.string().default(""),
|
|
14813
|
-
description: z6.string().default(""),
|
|
14814
|
-
resources: ProjectResourceNamespacesSchema
|
|
14815
|
-
});
|
|
14816
|
-
ProjectsManifestSchema = z6.object({
|
|
14817
|
-
version: z6.number(),
|
|
14818
|
-
// Unlike roles (`.min(1)`), a repo may define zero projects — a team without
|
|
14819
|
-
// project partitioning simply has no projects.yaml, and an empty list is valid.
|
|
14820
|
-
projects: z6.array(ProjectSchema).default([])
|
|
14821
|
-
});
|
|
14822
|
-
}
|
|
14823
|
-
});
|
|
14824
|
-
|
|
14825
15304
|
// src/pkg/types.ts
|
|
14826
15305
|
import { z as z7 } from "zod";
|
|
14827
15306
|
var PackageScopeSchema, NpmSpecSchema, ClaudeMarketplaceSchema, ClaudePluginSpecSchema, ClaudeEcosystemSchema, PackageSetSchema, PackageManifestSchema, NpmLockEntrySchema, ClaudeMarketplaceLockSchema, ClaudePluginLockSchema, PackageLockSchema;
|
|
@@ -15073,6 +15552,7 @@ __export(reports_branch_exports, {
|
|
|
15073
15552
|
ensureReportsDir: () => ensureReportsDir,
|
|
15074
15553
|
ensureReportsWorktree: () => ensureReportsWorktree,
|
|
15075
15554
|
refreshReportsWorktree: () => refreshReportsWorktree,
|
|
15555
|
+
updateReports: () => updateReports,
|
|
15076
15556
|
withKnowledgeWorktree: () => withKnowledgeWorktree
|
|
15077
15557
|
});
|
|
15078
15558
|
import path42 from "path";
|
|
@@ -15196,6 +15676,38 @@ async function writeWorktreeGitignore(wt) {
|
|
|
15196
15676
|
].join("\n");
|
|
15197
15677
|
await writeFile(path42.join(wt, ".gitignore"), content);
|
|
15198
15678
|
}
|
|
15679
|
+
async function commitAndPushReportsAt(wt, message, files, options = {}) {
|
|
15680
|
+
const git = createGit2(wt);
|
|
15681
|
+
await git.add(files);
|
|
15682
|
+
const status2 = await git.status();
|
|
15683
|
+
if (status2.staged.length === 0 && !options.pushIfUnchanged) {
|
|
15684
|
+
log.debug("[reports] nothing to commit");
|
|
15685
|
+
return false;
|
|
15686
|
+
}
|
|
15687
|
+
if (status2.staged.length > 0) await commitSkippingHooks(git, message);
|
|
15688
|
+
for (let attempt = 1; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
15689
|
+
try {
|
|
15690
|
+
await git.push(["origin", REPORTS_BRANCH]);
|
|
15691
|
+
return true;
|
|
15692
|
+
} catch (pushErr) {
|
|
15693
|
+
if (attempt === MAX_PUSH_RETRIES) {
|
|
15694
|
+
log.debug(`[reports] push failed after ${attempt} attempts: ${pushErr.message}`);
|
|
15695
|
+
return false;
|
|
15696
|
+
}
|
|
15697
|
+
try {
|
|
15698
|
+
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15699
|
+
await git.rebase([`origin/${REPORTS_BRANCH}`]);
|
|
15700
|
+
} catch (rebaseErr) {
|
|
15701
|
+
log.debug(`[reports] rebase failed, retrying: ${rebaseErr.message}`);
|
|
15702
|
+
try {
|
|
15703
|
+
await git.rebase(["--abort"]);
|
|
15704
|
+
} catch {
|
|
15705
|
+
}
|
|
15706
|
+
}
|
|
15707
|
+
}
|
|
15708
|
+
}
|
|
15709
|
+
return false;
|
|
15710
|
+
}
|
|
15199
15711
|
async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
15200
15712
|
const lockPath = reportsLockPath(localConfig);
|
|
15201
15713
|
const locked = await acquireLock(lockPath);
|
|
@@ -15205,38 +15717,37 @@ async function commitAndPushReports(localConfig, message, files, options = {}) {
|
|
|
15205
15717
|
}
|
|
15206
15718
|
try {
|
|
15207
15719
|
const wt = await ensureReportsWorktree(localConfig);
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15720
|
+
return await commitAndPushReportsAt(wt, message, files, options);
|
|
15721
|
+
} catch (e) {
|
|
15722
|
+
log.debug(`[reports] commitAndPushReports failed (non-blocking): ${e.message}`);
|
|
15723
|
+
return false;
|
|
15724
|
+
} finally {
|
|
15725
|
+
await releaseLock(lockPath);
|
|
15726
|
+
}
|
|
15727
|
+
}
|
|
15728
|
+
async function updateReports(localConfig, write, options = {}) {
|
|
15729
|
+
if (!usesReportsBranch(localConfig)) {
|
|
15730
|
+
throw new Error("updateReports requires a repo that stores reports on the teamai-reports branch");
|
|
15731
|
+
}
|
|
15732
|
+
const lockPath = reportsLockPath(localConfig);
|
|
15733
|
+
if (!await acquireLock(lockPath)) {
|
|
15734
|
+
log.debug("[reports] another reports write is in progress; skipping");
|
|
15735
|
+
return false;
|
|
15736
|
+
}
|
|
15737
|
+
try {
|
|
15738
|
+
const wt = await ensureReportsWorktree(localConfig);
|
|
15739
|
+
try {
|
|
15740
|
+
await syncReportsWorktree(wt);
|
|
15741
|
+
} catch (e) {
|
|
15742
|
+
log.debug(`[reports] sync before write failed, writing onto the local copy: ${e.message}`);
|
|
15214
15743
|
}
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
await git.push(["origin", REPORTS_BRANCH]);
|
|
15219
|
-
return true;
|
|
15220
|
-
} catch (pushErr) {
|
|
15221
|
-
if (attempt === MAX_PUSH_RETRIES) {
|
|
15222
|
-
log.debug(`[reports] push failed after ${attempt} attempts: ${pushErr.message}`);
|
|
15223
|
-
return false;
|
|
15224
|
-
}
|
|
15225
|
-
try {
|
|
15226
|
-
await git.fetch(["origin", REPORTS_BRANCH]);
|
|
15227
|
-
await git.rebase([`origin/${REPORTS_BRANCH}`]);
|
|
15228
|
-
} catch (rebaseErr) {
|
|
15229
|
-
log.debug(`[reports] rebase failed, retrying: ${rebaseErr.message}`);
|
|
15230
|
-
try {
|
|
15231
|
-
await git.rebase(["--abort"]);
|
|
15232
|
-
} catch {
|
|
15233
|
-
}
|
|
15234
|
-
}
|
|
15235
|
-
}
|
|
15744
|
+
const change = await write(wt);
|
|
15745
|
+
if (!change || change.files.length === 0) {
|
|
15746
|
+
return false;
|
|
15236
15747
|
}
|
|
15237
|
-
return
|
|
15748
|
+
return await commitAndPushReportsAt(wt, change.message, change.files, options);
|
|
15238
15749
|
} catch (e) {
|
|
15239
|
-
log.debug(`[reports]
|
|
15750
|
+
log.debug(`[reports] updateReports failed (non-blocking): ${e.message}`);
|
|
15240
15751
|
return false;
|
|
15241
15752
|
} finally {
|
|
15242
15753
|
await releaseLock(lockPath);
|
|
@@ -15593,6 +16104,10 @@ async function detectHomeInstalledAgents(candidateIds = SELF_MODE_AGENT_CHOICES)
|
|
|
15593
16104
|
const home = getUserHome();
|
|
15594
16105
|
const found = [];
|
|
15595
16106
|
for (const id of candidateIds) {
|
|
16107
|
+
if (id === COPILOT_TOOL_ID) {
|
|
16108
|
+
if (await pathExists(getCopilotHome())) found.push(id);
|
|
16109
|
+
continue;
|
|
16110
|
+
}
|
|
15596
16111
|
const skillsPath = KNOWN_AGENTS.find((a) => a.id === id)?.skillsPath;
|
|
15597
16112
|
if (!skillsPath) continue;
|
|
15598
16113
|
const rootSegment = skillsPath.split("/")[0];
|
|
@@ -15627,7 +16142,6 @@ function getEffectiveAgents(teamConfig, localConfig) {
|
|
|
15627
16142
|
return [...byId.values()];
|
|
15628
16143
|
}
|
|
15629
16144
|
async function detectInstalledAgents(localConfig, teamConfig) {
|
|
15630
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
15631
16145
|
const agents = getEffectiveAgents(teamConfig, localConfig);
|
|
15632
16146
|
const scoped = scopedToolPaths(teamConfig, localConfig);
|
|
15633
16147
|
const fromTeamConfig = new Set(
|
|
@@ -15635,12 +16149,11 @@ async function detectInstalledAgents(localConfig, teamConfig) {
|
|
|
15635
16149
|
);
|
|
15636
16150
|
const results = [];
|
|
15637
16151
|
for (const agent of agents) {
|
|
15638
|
-
const
|
|
15639
|
-
const
|
|
15640
|
-
const installed = rootSegment ? await pathExists(rootPath) : false;
|
|
16152
|
+
const baseDir = resolveToolBaseDir(agent.id, localConfig);
|
|
16153
|
+
const installed = await isToolInstalledForConfig(agent.id, agent.skillsPath, localConfig);
|
|
15641
16154
|
results.push({
|
|
15642
16155
|
...agent,
|
|
15643
|
-
absoluteSkillsPath:
|
|
16156
|
+
absoluteSkillsPath: path44.join(baseDir, agent.skillsPath),
|
|
15644
16157
|
installed,
|
|
15645
16158
|
fromTeamConfig: fromTeamConfig.has(agent.id)
|
|
15646
16159
|
});
|
|
@@ -15655,7 +16168,7 @@ var init_known_agents = __esm({
|
|
|
15655
16168
|
init_types();
|
|
15656
16169
|
init_base();
|
|
15657
16170
|
init_home();
|
|
15658
|
-
SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "joycode", "codebuddy", "workbuddy"];
|
|
16171
|
+
SELF_MODE_AGENT_CHOICES = ["claude", "codex", "cursor", "copilot", "joycode", "codebuddy", "workbuddy"];
|
|
15659
16172
|
KNOWN_AGENTS = [
|
|
15660
16173
|
// Coding agents already wired through teamConfig.toolPaths defaults
|
|
15661
16174
|
{ id: "claude", displayName: "Claude Code", category: "coding", skillsPath: ".claude/skills" },
|
|
@@ -15706,6 +16219,7 @@ var init_known_agents = __esm({
|
|
|
15706
16219
|
// src/init.ts
|
|
15707
16220
|
var init_exports = {};
|
|
15708
16221
|
__export(init_exports, {
|
|
16222
|
+
ALL_PROJECTS_SELECTOR: () => ALL_PROJECTS_SELECTOR,
|
|
15709
16223
|
buildSelfModeGitignore: () => buildSelfModeGitignore,
|
|
15710
16224
|
init: () => init,
|
|
15711
16225
|
initHttp: () => initHttp,
|
|
@@ -15713,6 +16227,7 @@ __export(init_exports, {
|
|
|
15713
16227
|
migrateSelfModeGitignore: () => migrateSelfModeGitignore,
|
|
15714
16228
|
migrateSelfModeGitignoreContent: () => migrateSelfModeGitignoreContent,
|
|
15715
16229
|
promptForSelfModeAgents: () => promptForSelfModeAgents,
|
|
16230
|
+
resolveActiveProjects: () => resolveActiveProjects,
|
|
15716
16231
|
resolveInheritUserScope: () => resolveInheritUserScope,
|
|
15717
16232
|
resolveInitRepo: () => resolveInitRepo,
|
|
15718
16233
|
resolveInitScope: () => resolveInitScope,
|
|
@@ -15783,6 +16298,10 @@ async function promptForRoleProfile(repoPath, roleFlag) {
|
|
|
15783
16298
|
resourceProfileVersion: manifest.version
|
|
15784
16299
|
};
|
|
15785
16300
|
}
|
|
16301
|
+
function dedupeIds(ids) {
|
|
16302
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16303
|
+
return ids.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
16304
|
+
}
|
|
15786
16305
|
async function resolveActiveProjects(repoPath, projectFlag) {
|
|
15787
16306
|
const requested = (projectFlag ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
15788
16307
|
if (requested.length === 0) {
|
|
@@ -15794,7 +16313,27 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15794
16313
|
`--project given but no projects manifest (manifest/projects.yaml) exists in the team repo.`
|
|
15795
16314
|
);
|
|
15796
16315
|
}
|
|
15797
|
-
const
|
|
16316
|
+
const declared = listProjectIds(manifest);
|
|
16317
|
+
if (requested.includes(ALL_PROJECTS_SELECTOR)) {
|
|
16318
|
+
if (requested.length > 1) {
|
|
16319
|
+
throw new Error(
|
|
16320
|
+
`--project "${ALL_PROJECTS_SELECTOR}" already covers every declared project; drop the other ids (got: ${requested.join(", ")}).`
|
|
16321
|
+
);
|
|
16322
|
+
}
|
|
16323
|
+
if (declared.length === 0) {
|
|
16324
|
+
log.warn(
|
|
16325
|
+
`--project "${ALL_PROJECTS_SELECTOR}" was given but manifest/projects.yaml declares no projects; nothing was activated.`
|
|
16326
|
+
);
|
|
16327
|
+
return { projects: [] };
|
|
16328
|
+
}
|
|
16329
|
+
if (declared.includes(ALL_PROJECTS_SELECTOR)) {
|
|
16330
|
+
log.warn(
|
|
16331
|
+
`manifest/projects.yaml declares a project with the id "${ALL_PROJECTS_SELECTOR}", which is the reserved --project selector: every project is activated (that one included). To activate only it, run \`teamai projects set ${ALL_PROJECTS_SELECTOR}\`.`
|
|
16332
|
+
);
|
|
16333
|
+
}
|
|
16334
|
+
return { projects: declared };
|
|
16335
|
+
}
|
|
16336
|
+
const validIds = new Set(declared);
|
|
15798
16337
|
for (const id of requested) {
|
|
15799
16338
|
if (!validIds.has(id)) {
|
|
15800
16339
|
throw new Error(
|
|
@@ -15802,9 +16341,7 @@ async function resolveActiveProjects(repoPath, projectFlag) {
|
|
|
15802
16341
|
);
|
|
15803
16342
|
}
|
|
15804
16343
|
}
|
|
15805
|
-
|
|
15806
|
-
const projects = requested.filter((id) => seen.has(id) ? false : (seen.add(id), true));
|
|
15807
|
-
return { projects };
|
|
16344
|
+
return { projects: dedupeIds(requested) };
|
|
15808
16345
|
}
|
|
15809
16346
|
function resolveInitScope(rawScope, cwd, homeDir) {
|
|
15810
16347
|
const cwdResolved = resolveRealPath(cwd);
|
|
@@ -16322,20 +16859,28 @@ async function initSelfRepo(options) {
|
|
|
16322
16859
|
}
|
|
16323
16860
|
if (!options.dryRun) {
|
|
16324
16861
|
try {
|
|
16325
|
-
const {
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
await
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
username
|
|
16334
|
-
|
|
16862
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
16863
|
+
let isNewSelfMember = false;
|
|
16864
|
+
let selfMemberChanged = false;
|
|
16865
|
+
const pushed = await updateReports2(localConfig, async (wt) => {
|
|
16866
|
+
const memberDir = path45.join(wt, "members");
|
|
16867
|
+
await ensureDir(memberDir);
|
|
16868
|
+
const memberPath = path45.join(memberDir, `${username}.yaml`);
|
|
16869
|
+
isNewSelfMember = !await pathExists(memberPath);
|
|
16870
|
+
const existingSelfMember = await getMemberConfig(wt, username);
|
|
16871
|
+
const merged = mergeMemberConfig(existingSelfMember, {
|
|
16872
|
+
username,
|
|
16873
|
+
projects: localConfig.projects
|
|
16874
|
+
});
|
|
16875
|
+
selfMemberChanged = merged.changed;
|
|
16876
|
+
if (!merged.changed) return null;
|
|
16877
|
+
await writeFile(memberPath, YAML11.stringify(merged.config));
|
|
16878
|
+
return {
|
|
16879
|
+
files: ["members/"],
|
|
16880
|
+
message: isNewSelfMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`
|
|
16881
|
+
};
|
|
16335
16882
|
});
|
|
16336
16883
|
if (selfMemberChanged) {
|
|
16337
|
-
await writeFile(memberPath, YAML11.stringify(selfMemberConfig));
|
|
16338
|
-
const pushed = await commitAndPushReports2(localConfig, isNewSelfMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
16339
16884
|
if (pushed) {
|
|
16340
16885
|
log.success(isNewSelfMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
16341
16886
|
} else {
|
|
@@ -16510,6 +17055,17 @@ async function init(options) {
|
|
|
16510
17055
|
}
|
|
16511
17056
|
} else {
|
|
16512
17057
|
log.info(`Repo already exists at ${localPath}, using existing clone`);
|
|
17058
|
+
try {
|
|
17059
|
+
const pullResult = await pullRepoFastForward(localPath);
|
|
17060
|
+
if (pullResult !== "already up to date") {
|
|
17061
|
+
log.info(`Refreshed existing clone (${pullResult})`);
|
|
17062
|
+
}
|
|
17063
|
+
} catch (e) {
|
|
17064
|
+
log.error(
|
|
17065
|
+
`Failed to refresh existing clone at ${localPath}: ${e.message}. The local clone was left unchanged. Fix network/auth or resolve divergence (commit/stash local edits), or remove ${localPath} manually to start fresh.`
|
|
17066
|
+
);
|
|
17067
|
+
process.exit(1);
|
|
17068
|
+
}
|
|
16513
17069
|
}
|
|
16514
17070
|
} else {
|
|
16515
17071
|
log.warn(`Existing ${localPath} is not a git repository, re-cloning`);
|
|
@@ -16656,29 +17212,37 @@ async function init(options) {
|
|
|
16656
17212
|
let isNewMember = true;
|
|
16657
17213
|
if (!options.dryRun) {
|
|
16658
17214
|
try {
|
|
16659
|
-
const {
|
|
16660
|
-
|
|
16661
|
-
|
|
16662
|
-
await
|
|
16663
|
-
|
|
16664
|
-
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
username
|
|
16668
|
-
|
|
17215
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
17216
|
+
let memberChanged = false;
|
|
17217
|
+
let memberProjects;
|
|
17218
|
+
const pushed = await updateReports2(reportsConfig, async (wt) => {
|
|
17219
|
+
const memberDir = path45.join(wt, "members");
|
|
17220
|
+
await ensureDir(memberDir);
|
|
17221
|
+
const memberPath = path45.join(memberDir, `${username}.yaml`);
|
|
17222
|
+
isNewMember = !await pathExists(memberPath);
|
|
17223
|
+
const existingMember = await getMemberConfig(wt, username);
|
|
17224
|
+
const merged = mergeMemberConfig(existingMember, {
|
|
17225
|
+
username,
|
|
17226
|
+
projects: resolvedProjects
|
|
17227
|
+
});
|
|
17228
|
+
memberChanged = merged.changed;
|
|
17229
|
+
memberProjects = merged.config.projects;
|
|
17230
|
+
if (!merged.changed) return null;
|
|
17231
|
+
await writeFile(memberPath, YAML11.stringify(merged.config));
|
|
17232
|
+
return {
|
|
17233
|
+
files: ["members/"],
|
|
17234
|
+
message: isNewMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`
|
|
17235
|
+
};
|
|
16669
17236
|
});
|
|
16670
17237
|
if (memberChanged) {
|
|
16671
|
-
|
|
16672
|
-
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberConfig.projects ? ` (projects: ${memberConfig.projects.join(", ")})` : ""}`);
|
|
16673
|
-
const pushed = await commitAndPushReports2(reportsConfig, isNewMember ? `[teamai] Register member: ${username}` : `[teamai] Update member roster: ${username}`, ["members/"]);
|
|
17238
|
+
log.success(isNewMember ? `Registered as team member: ${username}` : `Updated member roster: ${username}${memberProjects ? ` (projects: ${memberProjects.join(", ")})` : ""}`);
|
|
16674
17239
|
if (pushed) {
|
|
16675
17240
|
log.success(isNewMember ? "Member registered on the teamai-reports branch" : "Member roster updated on the teamai-reports branch");
|
|
16676
17241
|
} else {
|
|
16677
17242
|
log.warn("Member registration could not be pushed (no write access?). You are still set up locally.");
|
|
16678
17243
|
}
|
|
16679
|
-
} else {
|
|
17244
|
+
} else if (!isNewMember) {
|
|
16680
17245
|
log.info(`Member ${username} already registered`);
|
|
16681
|
-
isNewMember = false;
|
|
16682
17246
|
}
|
|
16683
17247
|
} catch (e) {
|
|
16684
17248
|
log.warn(`Member registration skipped (non-blocking): ${e.message}`);
|
|
@@ -16804,6 +17368,7 @@ async function init(options) {
|
|
|
16804
17368
|
log.info("Run `teamai status` to check current config.");
|
|
16805
17369
|
closePrompt();
|
|
16806
17370
|
}
|
|
17371
|
+
var ALL_PROJECTS_SELECTOR;
|
|
16807
17372
|
var init_init = __esm({
|
|
16808
17373
|
"src/init.ts"() {
|
|
16809
17374
|
"use strict";
|
|
@@ -16822,6 +17387,7 @@ var init_init = __esm({
|
|
|
16822
17387
|
init_members();
|
|
16823
17388
|
init_prompt();
|
|
16824
17389
|
init_known_agents();
|
|
17390
|
+
ALL_PROJECTS_SELECTOR = "all";
|
|
16825
17391
|
}
|
|
16826
17392
|
});
|
|
16827
17393
|
|
|
@@ -17144,13 +17710,19 @@ async function pushCore(localConfig, teamConfig, options, initialPendingTeamConf
|
|
|
17144
17710
|
}
|
|
17145
17711
|
} : teamConfig;
|
|
17146
17712
|
const pushableTypes = ["skills", "rules", "env", "agents"];
|
|
17147
|
-
const
|
|
17713
|
+
const fullScan = [];
|
|
17148
17714
|
for (const type of pushableTypes) {
|
|
17149
17715
|
const handler = getHandler(type);
|
|
17150
17716
|
const items = await handler.scanLocalForPush(scanTeamConfig, localConfig);
|
|
17151
|
-
|
|
17717
|
+
fullScan.push(...items);
|
|
17152
17718
|
}
|
|
17153
|
-
const
|
|
17719
|
+
const allItems = fullScan.filter((item) => {
|
|
17720
|
+
if (item.type === "agents" && "skipReason" in item && typeof item.skipReason === "string" && item.skipReason) {
|
|
17721
|
+
log.warn(`[agents] Skipped ${item.name}: ${item.skipReason}`);
|
|
17722
|
+
return false;
|
|
17723
|
+
}
|
|
17724
|
+
return true;
|
|
17725
|
+
});
|
|
17154
17726
|
spin.stop();
|
|
17155
17727
|
if (options.skill) {
|
|
17156
17728
|
const skillBasename = path46.basename(
|
|
@@ -17536,6 +18108,7 @@ __export(git_exports, {
|
|
|
17536
18108
|
listWorktrees: () => listWorktrees,
|
|
17537
18109
|
normalizeRepoUrlForCompare: () => normalizeRepoUrlForCompare,
|
|
17538
18110
|
pullRepo: () => pullRepo,
|
|
18111
|
+
pullRepoFastForward: () => pullRepoFastForward,
|
|
17539
18112
|
pushLearningToOrigin: () => pushLearningToOrigin,
|
|
17540
18113
|
pushRepoBranch: () => pushRepoBranch,
|
|
17541
18114
|
pushRepoDirectly: () => pushRepoDirectly,
|
|
@@ -17674,6 +18247,14 @@ async function pullRepo(localPath) {
|
|
|
17674
18247
|
return "reset to origin (diverged)";
|
|
17675
18248
|
}
|
|
17676
18249
|
}
|
|
18250
|
+
async function pullRepoFastForward(localPath) {
|
|
18251
|
+
const git = createGit2(localPath);
|
|
18252
|
+
const result = await git.pull(["--ff-only"]);
|
|
18253
|
+
if (result.summary.changes === 0 && result.summary.insertions === 0 && result.summary.deletions === 0) {
|
|
18254
|
+
return "already up to date";
|
|
18255
|
+
}
|
|
18256
|
+
return `${result.summary.changes} file(s) changed`;
|
|
18257
|
+
}
|
|
17677
18258
|
async function getDefaultBranch(localPath) {
|
|
17678
18259
|
const cached = defaultBranchCache.get(localPath);
|
|
17679
18260
|
if (cached) return cached;
|
|
@@ -18249,19 +18830,19 @@ async function bootstrapSelfRepo(dir, opts) {
|
|
|
18249
18830
|
log.debug(`[bootstrap] hook injection failed (non-blocking): ${e.message}`);
|
|
18250
18831
|
}
|
|
18251
18832
|
try {
|
|
18252
|
-
const {
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
|
|
18256
|
-
|
|
18257
|
-
|
|
18833
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
18834
|
+
await updateReports2(localConfig, async (wt) => {
|
|
18835
|
+
const memberDir = path49.join(wt, "members");
|
|
18836
|
+
await ensureDir(memberDir);
|
|
18837
|
+
const memberPath = path49.join(memberDir, `${username}.yaml`);
|
|
18838
|
+
if (await pathExists(memberPath)) return null;
|
|
18258
18839
|
await writeFile(memberPath, YAML14.stringify({
|
|
18259
18840
|
username,
|
|
18260
18841
|
displayName: username,
|
|
18261
18842
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18262
18843
|
}));
|
|
18263
|
-
|
|
18264
|
-
}
|
|
18844
|
+
return { files: ["members/"], message: `[teamai] Register member: ${username}` };
|
|
18845
|
+
});
|
|
18265
18846
|
} catch (e) {
|
|
18266
18847
|
log.debug(`[bootstrap] member registration skipped (non-blocking): ${e.message}`);
|
|
18267
18848
|
}
|
|
@@ -18529,68 +19110,6 @@ var init_config = __esm({
|
|
|
18529
19110
|
}
|
|
18530
19111
|
});
|
|
18531
19112
|
|
|
18532
|
-
// src/utils/exec.ts
|
|
18533
|
-
import spawn from "cross-spawn";
|
|
18534
|
-
async function probeBinary(binary, args = ["--version"], executor = execCommand) {
|
|
18535
|
-
try {
|
|
18536
|
-
const result = await executor(binary, args, { timeoutMs: 5e3 });
|
|
18537
|
-
if (result.code !== 0) return "";
|
|
18538
|
-
return (result.stdout || result.stderr).trim();
|
|
18539
|
-
} catch {
|
|
18540
|
-
return "";
|
|
18541
|
-
}
|
|
18542
|
-
}
|
|
18543
|
-
function formatCommand(command, args) {
|
|
18544
|
-
const quote = (value) => /^[a-zA-Z0-9_./:@=+-]+$/.test(value) ? value : JSON.stringify(value);
|
|
18545
|
-
return [command, ...args.map(quote)].join(" ");
|
|
18546
|
-
}
|
|
18547
|
-
var execCommand;
|
|
18548
|
-
var init_exec = __esm({
|
|
18549
|
-
"src/utils/exec.ts"() {
|
|
18550
|
-
"use strict";
|
|
18551
|
-
execCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
18552
|
-
const child = spawn(command, args, {
|
|
18553
|
-
cwd: options.cwd,
|
|
18554
|
-
env: options.env,
|
|
18555
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
18556
|
-
shell: false
|
|
18557
|
-
});
|
|
18558
|
-
let stdout = "";
|
|
18559
|
-
let stderr = "";
|
|
18560
|
-
let settled = false;
|
|
18561
|
-
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
18562
|
-
const finish = (callback) => {
|
|
18563
|
-
if (settled) return;
|
|
18564
|
-
settled = true;
|
|
18565
|
-
clearTimeout(timer);
|
|
18566
|
-
callback();
|
|
18567
|
-
};
|
|
18568
|
-
child.stdout?.on("data", (chunk) => {
|
|
18569
|
-
const text = chunk.toString();
|
|
18570
|
-
stdout += text;
|
|
18571
|
-
if (options.stream) process.stdout.write(text);
|
|
18572
|
-
});
|
|
18573
|
-
child.stderr?.on("data", (chunk) => {
|
|
18574
|
-
const text = chunk.toString();
|
|
18575
|
-
stderr += text;
|
|
18576
|
-
if (options.stream) process.stderr.write(text);
|
|
18577
|
-
});
|
|
18578
|
-
const timer = setTimeout(() => {
|
|
18579
|
-
child.kill();
|
|
18580
|
-
finish(() => reject(new Error(
|
|
18581
|
-
`Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`
|
|
18582
|
-
)));
|
|
18583
|
-
}, timeoutMs);
|
|
18584
|
-
child.on("error", (error) => finish(() => reject(error)));
|
|
18585
|
-
child.on("close", (code) => finish(() => resolve({
|
|
18586
|
-
stdout,
|
|
18587
|
-
stderr,
|
|
18588
|
-
code: code ?? 1
|
|
18589
|
-
})));
|
|
18590
|
-
});
|
|
18591
|
-
}
|
|
18592
|
-
});
|
|
18593
|
-
|
|
18594
19113
|
// src/pkg/adapters/base.ts
|
|
18595
19114
|
var PackageAdapter;
|
|
18596
19115
|
var init_base2 = __esm({
|
|
@@ -19596,9 +20115,9 @@ async function verifyStaging(legacyDir, staging) {
|
|
|
19596
20115
|
throw new Error("migration verify: team-repo/.git missing after copy (clone would be broken)");
|
|
19597
20116
|
}
|
|
19598
20117
|
try {
|
|
19599
|
-
const { execFile:
|
|
20118
|
+
const { execFile: execFile4 } = await import("child_process");
|
|
19600
20119
|
const { promisify: promisify4 } = await import("util");
|
|
19601
|
-
await promisify4(
|
|
20120
|
+
await promisify4(execFile4)("git", ["rev-parse", "HEAD"], { cwd: stagedRepo });
|
|
19602
20121
|
} catch (e) {
|
|
19603
20122
|
throw new Error(
|
|
19604
20123
|
`migration verify: the staged team-repo clone is not a usable git repository (${e.message})`
|
|
@@ -19983,6 +20502,7 @@ var init_tokenizer = __esm({
|
|
|
19983
20502
|
// src/votes.ts
|
|
19984
20503
|
var votes_exports = {};
|
|
19985
20504
|
__export(votes_exports, {
|
|
20505
|
+
hasPendingVoteDeltas: () => hasPendingVoteDeltas,
|
|
19986
20506
|
incrementRecalled: () => incrementRecalled,
|
|
19987
20507
|
incrementUpvoted: () => incrementUpvoted,
|
|
19988
20508
|
loadUserVotes: () => loadUserVotes,
|
|
@@ -20097,6 +20617,10 @@ function mergeDeltas(local, remote) {
|
|
|
20097
20617
|
}
|
|
20098
20618
|
return { version: 2, votes, deltas: {} };
|
|
20099
20619
|
}
|
|
20620
|
+
async function hasPendingVoteDeltas(localVotesDir, username) {
|
|
20621
|
+
const local = await loadUserVotes(path57.join(localVotesDir, `${username}.yaml`));
|
|
20622
|
+
return Object.keys(local.deltas).length > 0;
|
|
20623
|
+
}
|
|
20100
20624
|
async function syncVotesToTeam(repoPath, username, localVotesDir) {
|
|
20101
20625
|
const localVotePath = path57.join(localVotesDir, `${username}.yaml`);
|
|
20102
20626
|
const remoteVotePath = path57.join(repoPath, "votes", `${username}.yaml`);
|
|
@@ -22537,10 +23061,67 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22537
23061
|
const hasInterventions = hasInterventionDelta(interventionDelta);
|
|
22538
23062
|
const hasPromptTokens = hasPromptTokenDelta(promptTokenDelta);
|
|
22539
23063
|
const hasDaily = hasDailyDelta(dailyDelta);
|
|
22540
|
-
|
|
23064
|
+
const hasStats = hasUsage || hasInterventions || hasPromptTokens || hasDaily;
|
|
23065
|
+
const commitMsg = hasUsage ? `[teamai] Update usage stats for ${username}` : hasInterventions || hasPromptTokens || hasDaily ? `[teamai] Update session stats for ${username}` : `[teamai] Update votes for ${username}`;
|
|
23066
|
+
const writeReportFiles = async (writeRoot) => {
|
|
23067
|
+
if (hasStats) {
|
|
23068
|
+
const statsDir = path64.join(writeRoot, "stats");
|
|
23069
|
+
await ensureDir(statsDir);
|
|
23070
|
+
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
23071
|
+
const existing = await readExistingStats(statsPath);
|
|
23072
|
+
if (useReportsBranch) {
|
|
23073
|
+
const previousContent = await readFileSafe(statsPath);
|
|
23074
|
+
restoreStats = () => writeFile(statsPath, previousContent ?? "");
|
|
23075
|
+
}
|
|
23076
|
+
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
23077
|
+
const merged = mergeStats(existing, username, newStats);
|
|
23078
|
+
if (hasInterventions) {
|
|
23079
|
+
merged.interventions = mergeInterventionStats(existing?.interventions, interventionDelta);
|
|
23080
|
+
}
|
|
23081
|
+
if (hasPromptTokens) {
|
|
23082
|
+
const pt = mergePromptTokenStats(existing?.prompts, existing?.tokens, promptTokenDelta);
|
|
23083
|
+
merged.prompts = pt.prompts;
|
|
23084
|
+
merged.tokens = pt.tokens;
|
|
23085
|
+
}
|
|
23086
|
+
if (hasDaily) {
|
|
23087
|
+
merged.daily = mergeDailyStats(existing?.daily, dailyDelta);
|
|
23088
|
+
}
|
|
23089
|
+
await writeFile(statsPath, YAML21.stringify(merged));
|
|
23090
|
+
filesToPush.push(`stats/${username}.yaml`);
|
|
23091
|
+
}
|
|
23092
|
+
try {
|
|
23093
|
+
if (await pathExists(getUserVotesDir())) {
|
|
23094
|
+
const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
23095
|
+
const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
|
|
23096
|
+
if (synced) {
|
|
23097
|
+
filesToPush.push(`votes/${username}.yaml`);
|
|
23098
|
+
}
|
|
23099
|
+
}
|
|
23100
|
+
} catch (e) {
|
|
23101
|
+
log.error(`Vote staging skipped: ${e.message}`);
|
|
23102
|
+
}
|
|
23103
|
+
};
|
|
22541
23104
|
if (useReportsBranch && reportsConfig) {
|
|
22542
|
-
|
|
22543
|
-
|
|
23105
|
+
let hasVotes = false;
|
|
23106
|
+
if (!hasStats && await pathExists(getUserVotesDir())) {
|
|
23107
|
+
const { hasPendingVoteDeltas: hasPendingVoteDeltas2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
23108
|
+
hasVotes = await hasPendingVoteDeltas2(getUserVotesDir(), username);
|
|
23109
|
+
}
|
|
23110
|
+
if (!hasStats && !hasVotes) {
|
|
23111
|
+
log.debug("No usage events or votes to report");
|
|
23112
|
+
return true;
|
|
23113
|
+
}
|
|
23114
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
23115
|
+
const pushed = await updateReports2(reportsConfig, async (wt) => {
|
|
23116
|
+
filesToPush.length = 0;
|
|
23117
|
+
await writeReportFiles(wt);
|
|
23118
|
+
return filesToPush.length > 0 ? { files: [...filesToPush], message: commitMsg } : null;
|
|
23119
|
+
}, { pushIfUnchanged: true });
|
|
23120
|
+
if (!pushed) {
|
|
23121
|
+
log.debug("Auto-report push was not confirmed; keeping local report data");
|
|
23122
|
+
await restoreStats?.();
|
|
23123
|
+
return false;
|
|
23124
|
+
}
|
|
22544
23125
|
} else {
|
|
22545
23126
|
const git = createGit2(repoPath);
|
|
22546
23127
|
if (!await isDedicatedRepoRoot(repoPath)) {
|
|
@@ -22564,56 +23145,11 @@ async function reportUsageToTeam(repoPath, username, options) {
|
|
|
22564
23145
|
await writeFile(yamlPath, pendingTeamConfig);
|
|
22565
23146
|
}
|
|
22566
23147
|
}
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
const statsPath = path64.join(statsDir, `${username}.yaml`);
|
|
22572
|
-
const existing = await readExistingStats(statsPath);
|
|
22573
|
-
if (useReportsBranch) {
|
|
22574
|
-
const previousContent = await readFileSafe(statsPath);
|
|
22575
|
-
restoreStats = () => writeFile(statsPath, previousContent ?? "");
|
|
22576
|
-
}
|
|
22577
|
-
const newStats = hasUsage ? aggregateUsage(events) : [];
|
|
22578
|
-
const merged = mergeStats(existing, username, newStats);
|
|
22579
|
-
if (hasInterventions) {
|
|
22580
|
-
merged.interventions = mergeInterventionStats(existing?.interventions, interventionDelta);
|
|
22581
|
-
}
|
|
22582
|
-
if (hasPromptTokens) {
|
|
22583
|
-
const pt = mergePromptTokenStats(existing?.prompts, existing?.tokens, promptTokenDelta);
|
|
22584
|
-
merged.prompts = pt.prompts;
|
|
22585
|
-
merged.tokens = pt.tokens;
|
|
22586
|
-
}
|
|
22587
|
-
if (hasDaily) {
|
|
22588
|
-
merged.daily = mergeDailyStats(existing?.daily, dailyDelta);
|
|
22589
|
-
}
|
|
22590
|
-
await writeFile(statsPath, YAML21.stringify(merged));
|
|
22591
|
-
filesToPush.push(`stats/${username}.yaml`);
|
|
22592
|
-
}
|
|
22593
|
-
try {
|
|
22594
|
-
if (await pathExists(getUserVotesDir())) {
|
|
22595
|
-
const { syncVotesToTeam: syncVotesToTeam2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
22596
|
-
const synced = await syncVotesToTeam2(writeRoot, username, getUserVotesDir());
|
|
22597
|
-
if (synced) {
|
|
22598
|
-
filesToPush.push(`votes/${username}.yaml`);
|
|
22599
|
-
}
|
|
22600
|
-
}
|
|
22601
|
-
} catch (e) {
|
|
22602
|
-
log.error(`Vote staging skipped: ${e.message}`);
|
|
22603
|
-
}
|
|
22604
|
-
if (filesToPush.length === 0) {
|
|
22605
|
-
log.debug("No usage events or votes to report");
|
|
22606
|
-
return true;
|
|
22607
|
-
}
|
|
22608
|
-
const commitMsg = hasUsage ? `[teamai] Update usage stats for ${username}` : hasInterventions || hasPromptTokens || hasDaily ? `[teamai] Update session stats for ${username}` : `[teamai] Update votes for ${username}`;
|
|
22609
|
-
if (useReportsBranch && reportsConfig) {
|
|
22610
|
-
const { commitAndPushReports: commitAndPushReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
22611
|
-
if (!await commitAndPushReports2(reportsConfig, commitMsg, filesToPush, { pushIfUnchanged: true })) {
|
|
22612
|
-
log.debug("Auto-report push was not confirmed; keeping local report data");
|
|
22613
|
-
await restoreStats?.();
|
|
22614
|
-
return false;
|
|
23148
|
+
await writeReportFiles(repoPath);
|
|
23149
|
+
if (filesToPush.length === 0) {
|
|
23150
|
+
log.debug("No usage events or votes to report");
|
|
23151
|
+
return true;
|
|
22615
23152
|
}
|
|
22616
|
-
} else {
|
|
22617
23153
|
await pushRepoDirectly(repoPath, commitMsg, filesToPush);
|
|
22618
23154
|
}
|
|
22619
23155
|
restoreStats = void 0;
|
|
@@ -22836,6 +23372,7 @@ __export(pull_exports, {
|
|
|
22836
23372
|
compileClaudemd: () => compileClaudemd,
|
|
22837
23373
|
compileCulture: () => compileCulture,
|
|
22838
23374
|
compileRecallRulesBlock: () => compileRecallRulesBlock,
|
|
23375
|
+
filterAgentsByNamespaces: () => filterAgentsByNamespaces,
|
|
22839
23376
|
filterRulesByKnowledgeNamespaces: () => filterRulesByKnowledgeNamespaces,
|
|
22840
23377
|
injectRecallBlockIntoTools: () => injectRecallBlockIntoTools,
|
|
22841
23378
|
pull: () => pull,
|
|
@@ -22913,58 +23450,9 @@ async function usageReportDisabled(repoPath) {
|
|
|
22913
23450
|
return (await loadTeamConfig(repoPath))?.usageReport === false;
|
|
22914
23451
|
}
|
|
22915
23452
|
async function buildRolePullContext(localConfig) {
|
|
22916
|
-
const
|
|
22917
|
-
|
|
22918
|
-
const
|
|
22919
|
-
const projectsManifest = await loadProjectsManifest(localConfig.repo.localPath);
|
|
22920
|
-
const teamHasProjects = !!projectsManifest && projectsManifest.projects.length > 0;
|
|
22921
|
-
if (!hasRole && !hasProjects && !teamHasProjects) return null;
|
|
22922
|
-
let roleNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22923
|
-
let allRoleSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22924
|
-
if (hasRole) {
|
|
22925
|
-
let rolesManifest;
|
|
22926
|
-
try {
|
|
22927
|
-
rolesManifest = await loadRolesManifest(localConfig.repo.localPath);
|
|
22928
|
-
} catch {
|
|
22929
|
-
log.warn("Could not load roles manifest. Skipping role-based filtering.");
|
|
22930
|
-
rolesManifest = null;
|
|
22931
|
-
}
|
|
22932
|
-
if (rolesManifest) {
|
|
22933
|
-
try {
|
|
22934
|
-
roleNamespaces = resolveRoleResourceNamespaces({
|
|
22935
|
-
manifest: rolesManifest,
|
|
22936
|
-
primaryRole: localConfig.primaryRole,
|
|
22937
|
-
additionalRoles: localConfig.additionalRoles ?? []
|
|
22938
|
-
});
|
|
22939
|
-
allRoleSkillNamespaces = new Set(rolesManifest.roles.flatMap((role) => role.resources.skills));
|
|
22940
|
-
} catch {
|
|
22941
|
-
log.warn(`Role "${localConfig.primaryRole}" not found in manifest. Falling back to unfiltered sync.`);
|
|
22942
|
-
log.warn("Run `teamai roles set <role>` to pick a valid role.");
|
|
22943
|
-
if (!hasProjects && !teamHasProjects) return null;
|
|
22944
|
-
}
|
|
22945
|
-
} else if (!hasProjects && !teamHasProjects) {
|
|
22946
|
-
return null;
|
|
22947
|
-
}
|
|
22948
|
-
}
|
|
22949
|
-
let projectNamespaces = { knowledge: [], skills: [], learnings: [] };
|
|
22950
|
-
let allProjectSkillNamespaces = /* @__PURE__ */ new Set();
|
|
22951
|
-
if (projectsManifest) {
|
|
22952
|
-
allProjectSkillNamespaces = new Set(projectsManifest.projects.flatMap((p) => p.resources.skills));
|
|
22953
|
-
if (hasProjects) {
|
|
22954
|
-
try {
|
|
22955
|
-
projectNamespaces = resolveProjectResourceNamespaces({
|
|
22956
|
-
manifest: projectsManifest,
|
|
22957
|
-
activeProjects
|
|
22958
|
-
});
|
|
22959
|
-
} catch (e) {
|
|
22960
|
-
log.warn(`${e.message} Falling back to role-only filtering.`);
|
|
22961
|
-
}
|
|
22962
|
-
}
|
|
22963
|
-
} else if (hasProjects) {
|
|
22964
|
-
log.warn("Active projects configured but no projects manifest found. Skipping project-based filtering.");
|
|
22965
|
-
}
|
|
22966
|
-
const activeNamespaces = mergeNamespaces(roleNamespaces, projectNamespaces);
|
|
22967
|
-
const allSkillNamespaces = /* @__PURE__ */ new Set([...allRoleSkillNamespaces, ...allProjectSkillNamespaces]);
|
|
23453
|
+
const resolved = await resolveResourceNamespaces(localConfig);
|
|
23454
|
+
if (!resolved) return null;
|
|
23455
|
+
const { activeNamespaces, allSkillNamespaces } = resolved;
|
|
22968
23456
|
const inactiveSkillNamespaces = [...allSkillNamespaces].filter((namespace) => !activeNamespaces.skills.includes(namespace));
|
|
22969
23457
|
const activeSkillNames = /* @__PURE__ */ new Set();
|
|
22970
23458
|
const inactiveSkillNames = /* @__PURE__ */ new Set();
|
|
@@ -22997,6 +23485,20 @@ function filterRulesByKnowledgeNamespaces(rules, knowledgeNamespaces) {
|
|
|
22997
23485
|
return knowledgeNamespaces.includes(namespace);
|
|
22998
23486
|
});
|
|
22999
23487
|
}
|
|
23488
|
+
function filterAgentsByNamespaces(agents, agentNamespaces) {
|
|
23489
|
+
const kept = agentNamespaces ? agents.filter((agent) => !agent.namespace || agentNamespaces.includes(agent.namespace)) : agents;
|
|
23490
|
+
const seen = /* @__PURE__ */ new Map();
|
|
23491
|
+
for (const agent of kept) {
|
|
23492
|
+
const existing = seen.get(agent.name);
|
|
23493
|
+
if (existing) {
|
|
23494
|
+
throw new Error(
|
|
23495
|
+
`Duplicate agent "${agent.name}" found in active namespaces "${existing.namespace ?? "(root)"}" and "${agent.namespace ?? "(root)"}"`
|
|
23496
|
+
);
|
|
23497
|
+
}
|
|
23498
|
+
seen.set(agent.name, agent);
|
|
23499
|
+
}
|
|
23500
|
+
return kept;
|
|
23501
|
+
}
|
|
23000
23502
|
async function scanRoleAwareSkills(localConfig, namespaces) {
|
|
23001
23503
|
const items = /* @__PURE__ */ new Map();
|
|
23002
23504
|
for (const namespace of namespaces.skills) {
|
|
@@ -23098,6 +23600,42 @@ async function getInstalledResourceTargets(teamConfig, localConfig) {
|
|
|
23098
23600
|
}
|
|
23099
23601
|
return targets.sort();
|
|
23100
23602
|
}
|
|
23603
|
+
function tombstoneExtensions(type, tool) {
|
|
23604
|
+
if (type === "rules") return [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])];
|
|
23605
|
+
if (type === "agents") return AGENT_FILE_EXTENSIONS;
|
|
23606
|
+
return [""];
|
|
23607
|
+
}
|
|
23608
|
+
async function cleanupTombstonedResources(freshConfig, localConfig, scopeLabel) {
|
|
23609
|
+
const tombstoneTypes = [
|
|
23610
|
+
{ type: "rules", toolPathField: "rules" },
|
|
23611
|
+
{ type: "skills", toolPathField: "skills" },
|
|
23612
|
+
{ type: "agents", toolPathField: "agents" }
|
|
23613
|
+
];
|
|
23614
|
+
const baseDir = resolveBaseDir(localConfig);
|
|
23615
|
+
for (const { type, toolPathField } of tombstoneTypes) {
|
|
23616
|
+
const handler = getHandler(type);
|
|
23617
|
+
const tombstones = await handler.readTombstones(localConfig);
|
|
23618
|
+
if (tombstones.size === 0) continue;
|
|
23619
|
+
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
23620
|
+
const dir = toolPath[toolPathField];
|
|
23621
|
+
if (!dir) continue;
|
|
23622
|
+
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
23623
|
+
if (isAgentExcluded(localConfig, tool)) continue;
|
|
23624
|
+
for (const name of tombstones) {
|
|
23625
|
+
for (const extension of tombstoneExtensions(type, tool)) {
|
|
23626
|
+
const localPath = path66.join(baseDir, dir, `${name}${extension}`);
|
|
23627
|
+
if (!await pathExists(localPath)) continue;
|
|
23628
|
+
if (type === "skills" && await hasVcsMetadataRecursive(localPath)) {
|
|
23629
|
+
log.warn(`[${scopeLabel}] Kept tombstoned skill "${name}" (${tool}): it has local VCS metadata (.git) that may hold unpushed history. Back it up, then delete it manually.`);
|
|
23630
|
+
continue;
|
|
23631
|
+
}
|
|
23632
|
+
await remove(localPath);
|
|
23633
|
+
log.debug(`[${scopeLabel}] Cleaned up tombstoned ${type} ${name} from ${dir}`);
|
|
23634
|
+
}
|
|
23635
|
+
}
|
|
23636
|
+
}
|
|
23637
|
+
}
|
|
23638
|
+
}
|
|
23101
23639
|
async function pullForScope(localConfig, options, policy = {}) {
|
|
23102
23640
|
const scopeLabel = localConfig.scope;
|
|
23103
23641
|
const revisionField = policy.revisionField ?? "lastPullRev";
|
|
@@ -23151,6 +23689,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23151
23689
|
} catch {
|
|
23152
23690
|
}
|
|
23153
23691
|
await injectRecallBlockIntoTools(freshConfig, localConfig, scopeLabel);
|
|
23692
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
23154
23693
|
return;
|
|
23155
23694
|
}
|
|
23156
23695
|
log.debug(`[${scopeLabel}] Repo unchanged; resource target set changed, syncing`);
|
|
@@ -23223,6 +23762,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23223
23762
|
desiredSkillNames = new Set(items.map((i) => i.name));
|
|
23224
23763
|
knownRepoSkillNames = new Set(allTeamSkills.map((i) => i.name));
|
|
23225
23764
|
knownRepoSkillSources = new Map(allTeamSkills.map((i) => [i.name, i.sourcePath]));
|
|
23765
|
+
} else if (type === "agents") {
|
|
23766
|
+
items = filterAgentsByNamespaces(
|
|
23767
|
+
await handler.scanTeamForPull(freshConfig, localConfig),
|
|
23768
|
+
roleContext ? roleContext.activeNamespaces.agents : null
|
|
23769
|
+
);
|
|
23226
23770
|
} else {
|
|
23227
23771
|
items = await handler.scanTeamForPull(freshConfig, localConfig);
|
|
23228
23772
|
}
|
|
@@ -23281,37 +23825,7 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23281
23825
|
totalSynced += items.length;
|
|
23282
23826
|
}
|
|
23283
23827
|
if (!options.dryRun) {
|
|
23284
|
-
|
|
23285
|
-
{ type: "rules", ext: ".md", toolPathField: "rules" },
|
|
23286
|
-
{ type: "skills", toolPathField: "skills" },
|
|
23287
|
-
{ type: "agents", ext: ".md", toolPathField: "agents" }
|
|
23288
|
-
];
|
|
23289
|
-
const baseDir = resolveBaseDir(localConfig);
|
|
23290
|
-
for (const { type, ext, toolPathField } of tombstoneTypes) {
|
|
23291
|
-
const handler = getHandler(type);
|
|
23292
|
-
const tombstones = await handler.readTombstones(localConfig);
|
|
23293
|
-
if (tombstones.size === 0) continue;
|
|
23294
|
-
for (const [tool, toolPath] of Object.entries(scopedToolPaths(freshConfig, localConfig))) {
|
|
23295
|
-
const dir = toolPath[toolPathField];
|
|
23296
|
-
if (!dir) continue;
|
|
23297
|
-
if (!await ResourceHandler.isToolInstalled(dir, baseDir)) continue;
|
|
23298
|
-
if (isAgentExcluded(localConfig, tool)) continue;
|
|
23299
|
-
const extensions = type === "rules" ? [.../* @__PURE__ */ new Set([ruleFileExtensionForTool(tool), ".md"])] : [ext];
|
|
23300
|
-
for (const name of tombstones) {
|
|
23301
|
-
for (const extension of extensions) {
|
|
23302
|
-
const localPath = path66.join(baseDir, dir, extension ? `${name}${extension}` : name);
|
|
23303
|
-
if (await pathExists(localPath)) {
|
|
23304
|
-
if (type === "skills" && await hasVcsMetadataRecursive(localPath)) {
|
|
23305
|
-
log.warn(`[${scopeLabel}] Kept tombstoned skill "${name}" (${tool}): it has local VCS metadata (.git) that may hold unpushed history. Back it up, then delete it manually.`);
|
|
23306
|
-
continue;
|
|
23307
|
-
}
|
|
23308
|
-
await remove(localPath);
|
|
23309
|
-
log.debug(`[${scopeLabel}] Cleaned up tombstoned ${type} ${name} from ${dir}`);
|
|
23310
|
-
}
|
|
23311
|
-
}
|
|
23312
|
-
}
|
|
23313
|
-
}
|
|
23314
|
-
}
|
|
23828
|
+
await cleanupTombstonedResources(freshConfig, localConfig, scopeLabel);
|
|
23315
23829
|
if (roleContext) {
|
|
23316
23830
|
await cleanupInactiveNamespaceSkills(
|
|
23317
23831
|
freshConfig,
|
|
@@ -23320,6 +23834,11 @@ async function pullForScope(localConfig, options, policy = {}) {
|
|
|
23320
23834
|
roleContext.inactiveSkillNames,
|
|
23321
23835
|
roleContext.inactiveSkillSources
|
|
23322
23836
|
);
|
|
23837
|
+
await getHandler("agents").cleanupInactiveNamespaces(
|
|
23838
|
+
freshConfig,
|
|
23839
|
+
localConfig,
|
|
23840
|
+
roleContext.activeNamespaces.agents
|
|
23841
|
+
);
|
|
23323
23842
|
}
|
|
23324
23843
|
}
|
|
23325
23844
|
if (!options.dryRun && desiredSkillNames && knownRepoSkillNames) {
|
|
@@ -24034,11 +24553,11 @@ var init_pull = __esm({
|
|
|
24034
24553
|
init_resources();
|
|
24035
24554
|
init_base();
|
|
24036
24555
|
init_rule_format();
|
|
24556
|
+
init_agent_format();
|
|
24037
24557
|
init_tags();
|
|
24038
24558
|
init_builtin_skills();
|
|
24039
24559
|
init_types();
|
|
24040
|
-
|
|
24041
|
-
init_projects();
|
|
24560
|
+
init_resource_namespaces();
|
|
24042
24561
|
init_home();
|
|
24043
24562
|
init_update();
|
|
24044
24563
|
init_learnings_mirror();
|
|
@@ -24773,10 +25292,12 @@ async function removeCore(type, names, options, localConfig, teamConfig) {
|
|
|
24773
25292
|
log.info("Dry run \u2014 no changes made");
|
|
24774
25293
|
return;
|
|
24775
25294
|
}
|
|
24776
|
-
|
|
24777
|
-
|
|
24778
|
-
|
|
24779
|
-
|
|
25295
|
+
if (!options.force) {
|
|
25296
|
+
const confirmed = await askConfirmation("Are you sure? [y/N] ");
|
|
25297
|
+
if (!confirmed) {
|
|
25298
|
+
log.info("Cancelled");
|
|
25299
|
+
return;
|
|
25300
|
+
}
|
|
24780
25301
|
}
|
|
24781
25302
|
const spin = spinner(`Removing ${found.length} ${type}...`).start();
|
|
24782
25303
|
let totalRemoved = 0;
|
|
@@ -24858,16 +25379,20 @@ var init_remove = __esm({
|
|
|
24858
25379
|
// src/doctor.ts
|
|
24859
25380
|
var doctor_exports = {};
|
|
24860
25381
|
__export(doctor_exports, {
|
|
24861
|
-
|
|
25382
|
+
buildChecks: () => buildChecks,
|
|
25383
|
+
doctor: () => doctor,
|
|
25384
|
+
resolveDoctorContext: () => resolveDoctorContext
|
|
24862
25385
|
});
|
|
24863
25386
|
import path70 from "path";
|
|
24864
|
-
async function buildHookChecks(toolPaths, baseDir) {
|
|
25387
|
+
async function buildHookChecks(toolPaths, baseDir, localConfig) {
|
|
24865
25388
|
const checks = [];
|
|
24866
25389
|
for (const [tool, paths] of Object.entries(toolPaths)) {
|
|
24867
|
-
|
|
24868
|
-
|
|
25390
|
+
const hookPath = paths.hooks ? path70.join(resolveToolBaseDir(tool, localConfig), paths.hooks) : paths.settings ? path70.join(baseDir, paths.settings) : void 0;
|
|
25391
|
+
if (!hookPath) continue;
|
|
25392
|
+
const settingsPath = hookPath;
|
|
24869
25393
|
const parentDir = path70.dirname(settingsPath);
|
|
24870
|
-
|
|
25394
|
+
const installed = tool === COPILOT_TOOL_ID ? await isToolInstalledForConfig(tool, paths.hooks ?? paths.settings ?? "", localConfig) : await pathExists(parentDir);
|
|
25395
|
+
if (!installed) continue;
|
|
24871
25396
|
checks.push({
|
|
24872
25397
|
name: `teamai hooks in ${tool} settings`,
|
|
24873
25398
|
check: async () => {
|
|
@@ -24894,21 +25419,20 @@ async function hasInstalledCodexHooks(toolPaths, baseDir) {
|
|
|
24894
25419
|
}
|
|
24895
25420
|
return false;
|
|
24896
25421
|
}
|
|
24897
|
-
async function
|
|
24898
|
-
log.info("Running diagnostics...\n");
|
|
25422
|
+
async function resolveDoctorContext() {
|
|
24899
25423
|
const projectConfig = await detectProjectConfig();
|
|
24900
25424
|
const localConfig = projectConfig ?? await loadLocalConfig();
|
|
24901
|
-
|
|
24902
|
-
const
|
|
24903
|
-
|
|
24904
|
-
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
24908
|
-
|
|
24909
|
-
|
|
24910
|
-
const
|
|
24911
|
-
const
|
|
25425
|
+
if (!localConfig) return null;
|
|
25426
|
+
const teamConfig = await loadTeamConfig(localConfig.repo.localPath);
|
|
25427
|
+
const toolPaths = teamConfig ? Object.fromEntries(
|
|
25428
|
+
Object.entries(scopedToolPaths(teamConfig, localConfig)).filter(([tool]) => !isAgentExcluded(localConfig, tool))
|
|
25429
|
+
) : {};
|
|
25430
|
+
const baseDir = resolveHookScope(localConfig).baseDir;
|
|
25431
|
+
return { localConfig, teamConfig, toolPaths, baseDir };
|
|
25432
|
+
}
|
|
25433
|
+
async function buildChecks(ctx) {
|
|
25434
|
+
const { localConfig, teamConfig, toolPaths, baseDir } = ctx;
|
|
25435
|
+
const providerName = teamConfig?.provider;
|
|
24912
25436
|
const checks = [];
|
|
24913
25437
|
if (providerName === "tgit") {
|
|
24914
25438
|
const { isGfInstalled: isGfInstalled2, gfIsAuthenticated: gfIsAuthenticated2 } = await Promise.resolve().then(() => (init_tgit(), tgit_exports));
|
|
@@ -24954,34 +25478,24 @@ async function doctor(options) {
|
|
|
24954
25478
|
});
|
|
24955
25479
|
}
|
|
24956
25480
|
checks.push(
|
|
24957
|
-
{
|
|
24958
|
-
name: `Local config exists (${configPathLabel})`,
|
|
24959
|
-
check: async () => localConfig !== null,
|
|
24960
|
-
fix: "Run `teamai init` to initialize"
|
|
24961
|
-
},
|
|
24962
25481
|
{
|
|
24963
25482
|
name: "Team repo exists locally",
|
|
24964
|
-
check: async () =>
|
|
24965
|
-
if (!localConfig) return false;
|
|
24966
|
-
return pathExists(localConfig.repo.localPath);
|
|
24967
|
-
},
|
|
25483
|
+
check: async () => pathExists(localConfig.repo.localPath),
|
|
24968
25484
|
fix: "Run `teamai init` to clone the team repo"
|
|
24969
25485
|
},
|
|
24970
25486
|
{
|
|
24971
25487
|
name: "Team config (teamai.yaml) is valid",
|
|
24972
25488
|
check: async () => {
|
|
24973
|
-
if (!localConfig) return false;
|
|
24974
25489
|
const config = await loadTeamConfig(localConfig.repo.localPath);
|
|
24975
25490
|
return config !== null;
|
|
24976
25491
|
},
|
|
24977
25492
|
fix: "Check teamai.yaml in team repo for syntax errors"
|
|
24978
25493
|
},
|
|
24979
|
-
...await buildHookChecks(toolPaths, baseDir),
|
|
25494
|
+
...await buildHookChecks(toolPaths, baseDir, localConfig),
|
|
24980
25495
|
{
|
|
24981
25496
|
name: "Env variables injected in shell profile",
|
|
24982
25497
|
check: async () => {
|
|
24983
25498
|
if (teamConfig?.sharing?.env?.injectShellProfile === false) return true;
|
|
24984
|
-
if (!localConfig) return true;
|
|
24985
25499
|
const envYamlPath = path70.join(localConfig.repo.localPath, "env", "env.yaml");
|
|
24986
25500
|
if (!await pathExists(envYamlPath)) return true;
|
|
24987
25501
|
const home = getUserHome();
|
|
@@ -24999,28 +25513,80 @@ async function doctor(options) {
|
|
|
24999
25513
|
fix: "Run `teamai pull` to inject env variables into shell profile"
|
|
25000
25514
|
}
|
|
25001
25515
|
);
|
|
25002
|
-
|
|
25516
|
+
return checks;
|
|
25517
|
+
}
|
|
25518
|
+
async function runChecks(checks, onResult) {
|
|
25519
|
+
const results = [];
|
|
25003
25520
|
for (const { name, check, fix } of checks) {
|
|
25004
25521
|
const ok = await check();
|
|
25005
|
-
|
|
25006
|
-
|
|
25522
|
+
const result = ok ? { name, ok } : { name, ok, fix };
|
|
25523
|
+
results.push(result);
|
|
25524
|
+
onResult?.(result);
|
|
25525
|
+
}
|
|
25526
|
+
return results;
|
|
25527
|
+
}
|
|
25528
|
+
function emitReport(report) {
|
|
25529
|
+
console.log(JSON.stringify(report, null, 2));
|
|
25530
|
+
}
|
|
25531
|
+
function renderResult({ name, ok, fix }) {
|
|
25532
|
+
if (ok) {
|
|
25533
|
+
console.log(` \u2714 ${name}`);
|
|
25534
|
+
return;
|
|
25535
|
+
}
|
|
25536
|
+
console.log(` \u2716 ${name}`);
|
|
25537
|
+
if (fix) console.log(` \u2192 ${fix}`);
|
|
25538
|
+
}
|
|
25539
|
+
async function doctor(options) {
|
|
25540
|
+
const jsonMode = options.json === true;
|
|
25541
|
+
if (jsonMode) setStderrOnly(true);
|
|
25542
|
+
log.info("Running diagnostics...\n");
|
|
25543
|
+
const ctx = await resolveDoctorContext();
|
|
25544
|
+
if (!ctx) {
|
|
25545
|
+
const notInitialized = {
|
|
25546
|
+
name: "TeamAI is not initialized",
|
|
25547
|
+
ok: false,
|
|
25548
|
+
fix: "Run `teamai init <repo-url>` in a project, or add `--scope user` for all projects"
|
|
25549
|
+
};
|
|
25550
|
+
if (jsonMode) {
|
|
25551
|
+
emitReport({ ok: false, scope: null, checks: [notInitialized] });
|
|
25007
25552
|
} else {
|
|
25008
|
-
console.log(
|
|
25009
|
-
|
|
25010
|
-
|
|
25553
|
+
console.log(" Scope: not initialized\n");
|
|
25554
|
+
renderResult(notInitialized);
|
|
25555
|
+
console.log("");
|
|
25011
25556
|
}
|
|
25557
|
+
log.warn("Initialization is required before diagnostics can run.");
|
|
25558
|
+
return false;
|
|
25012
25559
|
}
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
25560
|
+
const { localConfig, toolPaths, baseDir } = ctx;
|
|
25561
|
+
const scope = localConfig.scope ?? "user";
|
|
25562
|
+
if (!jsonMode) {
|
|
25563
|
+
const scopeLabel = `${scope}${scope === "project" && localConfig.projectRoot ? ` (${localConfig.projectRoot})` : ""}`;
|
|
25564
|
+
console.log(` Scope: ${scopeLabel}
|
|
25565
|
+
`);
|
|
25566
|
+
}
|
|
25567
|
+
const results = await runChecks(await buildChecks(ctx), jsonMode ? void 0 : renderResult);
|
|
25568
|
+
let allPassed = results.every((r) => r.ok);
|
|
25569
|
+
const { pkgDoctorReport: pkgDoctorReport2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
|
|
25570
|
+
const packageReport = await pkgDoctorReport2(localConfig, process.cwd());
|
|
25571
|
+
if (packageReport && !packageReport.allPassed) allPassed = false;
|
|
25572
|
+
const codexNote = await hasInstalledCodexHooks(toolPaths, baseDir) ? codexTrustReminder() : null;
|
|
25573
|
+
if (jsonMode) {
|
|
25574
|
+
emitReport({
|
|
25575
|
+
ok: allPassed,
|
|
25576
|
+
scope,
|
|
25577
|
+
checks: results,
|
|
25578
|
+
// pkgDoctorReport renders its own lines; they are human text, not checks.
|
|
25579
|
+
...packageReport ? { packages: { ok: packageReport.allPassed, lines: packageReport.lines } } : {},
|
|
25580
|
+
...codexNote ? { notes: [codexNote] } : {}
|
|
25581
|
+
});
|
|
25582
|
+
return allPassed;
|
|
25020
25583
|
}
|
|
25021
|
-
if (
|
|
25584
|
+
if (packageReport) {
|
|
25585
|
+
for (const line of packageReport.lines) console.log(line);
|
|
25586
|
+
}
|
|
25587
|
+
if (codexNote) {
|
|
25022
25588
|
console.log("");
|
|
25023
|
-
log.info(
|
|
25589
|
+
log.info(codexNote);
|
|
25024
25590
|
}
|
|
25025
25591
|
console.log("");
|
|
25026
25592
|
if (allPassed) {
|
|
@@ -25028,6 +25594,7 @@ async function doctor(options) {
|
|
|
25028
25594
|
} else {
|
|
25029
25595
|
log.warn("Some checks failed. See suggestions above.");
|
|
25030
25596
|
}
|
|
25597
|
+
return allPassed;
|
|
25031
25598
|
}
|
|
25032
25599
|
var init_doctor = __esm({
|
|
25033
25600
|
"src/doctor.ts"() {
|
|
@@ -25036,6 +25603,7 @@ var init_doctor = __esm({
|
|
|
25036
25603
|
init_fs();
|
|
25037
25604
|
init_logger();
|
|
25038
25605
|
init_types();
|
|
25606
|
+
init_base();
|
|
25039
25607
|
init_hooks2();
|
|
25040
25608
|
init_home();
|
|
25041
25609
|
}
|
|
@@ -25152,7 +25720,8 @@ async function rolesInit(options) {
|
|
|
25152
25720
|
description,
|
|
25153
25721
|
resources: {
|
|
25154
25722
|
knowledge: namespaces,
|
|
25155
|
-
skills: namespaces
|
|
25723
|
+
skills: namespaces,
|
|
25724
|
+
agents: namespaces
|
|
25156
25725
|
}
|
|
25157
25726
|
});
|
|
25158
25727
|
log.success(`Added role: ${id} (namespaces: ${namespaces.join(", ")})`);
|
|
@@ -25296,7 +25865,8 @@ async function rolesAdd(roleId, options) {
|
|
|
25296
25865
|
description: options.description ?? "",
|
|
25297
25866
|
resources: {
|
|
25298
25867
|
knowledge: namespaces,
|
|
25299
|
-
skills: namespaces
|
|
25868
|
+
skills: namespaces,
|
|
25869
|
+
agents: namespaces
|
|
25300
25870
|
}
|
|
25301
25871
|
};
|
|
25302
25872
|
const updatedManifest = {
|
|
@@ -25410,7 +25980,8 @@ async function rolesUpdate(roleId, options) {
|
|
|
25410
25980
|
description: hasDesc ? options.description : existingRole.description,
|
|
25411
25981
|
resources: {
|
|
25412
25982
|
knowledge: updatedNamespaces,
|
|
25413
|
-
skills: updatedNamespaces
|
|
25983
|
+
skills: updatedNamespaces,
|
|
25984
|
+
agents: updatedNamespaces
|
|
25414
25985
|
}
|
|
25415
25986
|
};
|
|
25416
25987
|
const updatedManifest = {
|
|
@@ -25616,6 +26187,12 @@ async function saveTagsScopeConfig(localConfig) {
|
|
|
25616
26187
|
} else {
|
|
25617
26188
|
await saveLocalConfig(localConfig);
|
|
25618
26189
|
}
|
|
26190
|
+
try {
|
|
26191
|
+
const state = await loadStateForScope(localConfig);
|
|
26192
|
+
state.lastPullRev = null;
|
|
26193
|
+
await saveStateForScope(state, localConfig);
|
|
26194
|
+
} catch {
|
|
26195
|
+
}
|
|
25619
26196
|
}
|
|
25620
26197
|
async function tagsList(options) {
|
|
25621
26198
|
const localConfig = await resolveTagsScope();
|
|
@@ -25833,10 +26410,13 @@ async function collectTeamRuleNames(repoPath) {
|
|
|
25833
26410
|
async function collectTeamAgentNames(repoPath) {
|
|
25834
26411
|
const teamAgentsDir = path74.join(repoPath, "agents");
|
|
25835
26412
|
if (!await pathExists(teamAgentsDir)) return /* @__PURE__ */ new Set();
|
|
25836
|
-
const
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
|
|
26413
|
+
const names = /* @__PURE__ */ new Set();
|
|
26414
|
+
for (const { dir } of await listTeamAgentDirs(teamAgentsDir)) {
|
|
26415
|
+
for (const file of await listFiles(dir)) {
|
|
26416
|
+
if (file.endsWith(".yaml") || file.endsWith(".md")) names.add(file.replace(/\.(yaml|md)$/, ""));
|
|
26417
|
+
}
|
|
26418
|
+
}
|
|
26419
|
+
return names;
|
|
25840
26420
|
}
|
|
25841
26421
|
function isEmptyHooksResidue(parsed) {
|
|
25842
26422
|
if (parsed == null || !("hooks" in parsed) || typeof parsed.hooks !== "object" || parsed.hooks == null) return false;
|
|
@@ -25851,7 +26431,7 @@ function opencodePluginTargets(baseDir, scope) {
|
|
|
25851
26431
|
}
|
|
25852
26432
|
return targets;
|
|
25853
26433
|
}
|
|
25854
|
-
async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, teamAgentNames, hookTargets, scope) {
|
|
26434
|
+
async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, teamRuleNames, teamAgentNames, hookTargets, standaloneHookManifestPath, scope) {
|
|
25855
26435
|
const res = {
|
|
25856
26436
|
hookFiles: [],
|
|
25857
26437
|
openclawHookDirs: [],
|
|
@@ -25861,7 +26441,16 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25861
26441
|
ruleFiles: [],
|
|
25862
26442
|
agentFiles: []
|
|
25863
26443
|
};
|
|
25864
|
-
if (
|
|
26444
|
+
if (toolPath.hooks) {
|
|
26445
|
+
const hooksPath = path74.join(baseDir, toolPath.hooks);
|
|
26446
|
+
if (await pathExists(hooksPath) && (await hasTeamaiHooks(hooksPath, tool, standaloneHookManifestPath) || isEmptyHooksResidue(await readJson(hooksPath)))) {
|
|
26447
|
+
res.hookFiles.push({
|
|
26448
|
+
path: hooksPath,
|
|
26449
|
+
tool,
|
|
26450
|
+
manifestPath: standaloneHookManifestPath
|
|
26451
|
+
});
|
|
26452
|
+
}
|
|
26453
|
+
} else if (tool === "opencode") {
|
|
25865
26454
|
const { resolveOpencodePluginDir: resolveOpencodePluginDir2, OPENCODE_HOOK_FILE: OPENCODE_HOOK_FILE2 } = await Promise.resolve().then(() => (init_opencode_hooks(), opencode_hooks_exports));
|
|
25866
26455
|
for (const target of opencodePluginTargets(baseDir, scope)) {
|
|
25867
26456
|
const pluginDir = resolveOpencodePluginDir2(target.baseDir, target.scope);
|
|
@@ -25936,8 +26525,8 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25936
26525
|
const agentsDir = path74.join(baseDir, toolPath.agents);
|
|
25937
26526
|
if (await pathExists(agentsDir)) {
|
|
25938
26527
|
for (const file of await listFiles(agentsDir)) {
|
|
25939
|
-
|
|
25940
|
-
|
|
26528
|
+
const name = agentStemFromFilename(path74.basename(file));
|
|
26529
|
+
if (name === null) continue;
|
|
25941
26530
|
if (!teamAgentNames.has(name) && !BUILTIN_AGENT_NAMES.has(name)) continue;
|
|
25942
26531
|
res.agentFiles.push(path74.join(agentsDir, file));
|
|
25943
26532
|
}
|
|
@@ -25948,6 +26537,10 @@ async function discoverToolResources(tool, toolPath, baseDir, teamSkillNames, te
|
|
|
25948
26537
|
async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
|
|
25949
26538
|
const baseDir = resolveBaseDir(localConfig);
|
|
25950
26539
|
const teamaiHome = getDataHome(localConfig);
|
|
26540
|
+
const standaloneHookManifestPath = getManagedHooksPath(
|
|
26541
|
+
localConfig.scope,
|
|
26542
|
+
localConfig.projectRoot
|
|
26543
|
+
);
|
|
25951
26544
|
const repoPath = localConfig.repo.localPath;
|
|
25952
26545
|
const teamSkillNames = await collectTeamSkillNames(repoPath);
|
|
25953
26546
|
for (const name of BUILTIN_SKILL_NAMES) teamSkillNames.add(name);
|
|
@@ -25984,11 +26577,12 @@ async function buildRemovalPlan(localConfig, teamConfig, agentFilter) {
|
|
|
25984
26577
|
await discoverToolResources(
|
|
25985
26578
|
tool,
|
|
25986
26579
|
toolPath,
|
|
25987
|
-
|
|
26580
|
+
resolveToolBaseDir(tool, localConfig),
|
|
25988
26581
|
teamSkillNames,
|
|
25989
26582
|
teamRuleNames,
|
|
25990
26583
|
teamAgentNames,
|
|
25991
26584
|
hookTargets,
|
|
26585
|
+
standaloneHookManifestPath,
|
|
25992
26586
|
localConfig.scope
|
|
25993
26587
|
)
|
|
25994
26588
|
);
|
|
@@ -26415,6 +27009,8 @@ var init_uninstall = __esm({
|
|
|
26415
27009
|
init_types();
|
|
26416
27010
|
init_builtin_rules();
|
|
26417
27011
|
init_rule_format();
|
|
27012
|
+
init_agent_format();
|
|
27013
|
+
init_agents();
|
|
26418
27014
|
init_builtin_agents();
|
|
26419
27015
|
init_builtin_skills();
|
|
26420
27016
|
init_fs();
|
|
@@ -26604,16 +27200,16 @@ async function hooksList(_options) {
|
|
|
26604
27200
|
const { localConfig, teamConfig } = await autoDetectInit();
|
|
26605
27201
|
const { baseDir } = resolveHookScope(localConfig);
|
|
26606
27202
|
const rows = [];
|
|
26607
|
-
for (const [tool, paths] of Object.entries(teamConfig
|
|
26608
|
-
|
|
27203
|
+
for (const [tool, paths] of Object.entries(scopedToolPaths(teamConfig, localConfig))) {
|
|
27204
|
+
const hookPath = paths.hooks ? path76.join(resolveToolBaseDir(tool, localConfig), paths.hooks) : paths.settings ? path76.join(baseDir, paths.settings) : void 0;
|
|
27205
|
+
if (!hookPath) {
|
|
26609
27206
|
rows.push({ tool, status: "not configured", settingsPath: "no settings configured" });
|
|
26610
27207
|
continue;
|
|
26611
27208
|
}
|
|
26612
|
-
const settingsPath = path76.join(baseDir, paths.settings);
|
|
26613
27209
|
rows.push({
|
|
26614
27210
|
tool,
|
|
26615
|
-
status: await getHookStatus(
|
|
26616
|
-
settingsPath: formatDisplayPath(
|
|
27211
|
+
status: await getHookStatus(hookPath, tool),
|
|
27212
|
+
settingsPath: formatDisplayPath(hookPath)
|
|
26617
27213
|
});
|
|
26618
27214
|
}
|
|
26619
27215
|
console.log(formatHooksList(rows));
|
|
@@ -26642,6 +27238,18 @@ async function hooksRemove(_options) {
|
|
|
26642
27238
|
const { localConfig, teamConfig } = await autoDetectInit();
|
|
26643
27239
|
const { baseDir, manifestPath } = resolveHookScope(localConfig);
|
|
26644
27240
|
await reconcileHooksToAllTools(teamConfig.toolPaths, baseDir, [], manifestPath, { removeAll: true });
|
|
27241
|
+
const copilotPaths = scopedToolPaths(teamConfig, localConfig)[COPILOT_TOOL_ID];
|
|
27242
|
+
if (copilotPaths?.hooks) {
|
|
27243
|
+
await reconcileHooks(
|
|
27244
|
+
path76.join(resolveToolBaseDir(COPILOT_TOOL_ID, localConfig), copilotPaths.hooks),
|
|
27245
|
+
COPILOT_TOOL_ID,
|
|
27246
|
+
[],
|
|
27247
|
+
{
|
|
27248
|
+
manifestPath: getManagedHooksPath(localConfig.scope, localConfig.projectRoot),
|
|
27249
|
+
removeAll: true
|
|
27250
|
+
}
|
|
27251
|
+
);
|
|
27252
|
+
}
|
|
26645
27253
|
await sweepLegacyProjectHooks(teamConfig.toolPaths, localConfig);
|
|
26646
27254
|
log.success("Hooks removed from all AI tool settings");
|
|
26647
27255
|
}
|
|
@@ -26967,20 +27575,25 @@ async function saveSession(options) {
|
|
|
26967
27575
|
if (usesReportsBranch(localConfig)) {
|
|
26968
27576
|
const spin2 = spinner("Pushing session summary to team...").start();
|
|
26969
27577
|
try {
|
|
26970
|
-
const {
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
const written = await appendMonthlyLog(teamDir2, summary, { includePrompt: options.includePrompt });
|
|
26974
|
-
if (!written) {
|
|
26975
|
-
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
26976
|
-
return;
|
|
26977
|
-
}
|
|
26978
|
-
const rel = path79.relative(wt, written);
|
|
27578
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
27579
|
+
let rel;
|
|
27580
|
+
let ran = false;
|
|
26979
27581
|
const pushed = await withTimeout(
|
|
26980
|
-
|
|
27582
|
+
updateReports2(localConfig, async (wt) => {
|
|
27583
|
+
ran = true;
|
|
27584
|
+
const teamDir2 = path79.join(wt, "sessions", username);
|
|
27585
|
+
const written = await appendMonthlyLog(teamDir2, summary, { includePrompt: options.includePrompt });
|
|
27586
|
+
if (!written) return null;
|
|
27587
|
+
rel = path79.relative(wt, written);
|
|
27588
|
+
return { files: [rel], message: commitMsg };
|
|
27589
|
+
}),
|
|
26981
27590
|
1e4,
|
|
26982
27591
|
"Push timeout (10s)"
|
|
26983
27592
|
);
|
|
27593
|
+
if (ran && !rel) {
|
|
27594
|
+
spin2.info("Session already present in the team log \u2014 nothing to push.");
|
|
27595
|
+
return;
|
|
27596
|
+
}
|
|
26984
27597
|
if (pushed) spin2.succeed(`Pushed: ${rel}`);
|
|
26985
27598
|
else spin2.info("Nothing new to push.");
|
|
26986
27599
|
} catch (e) {
|
|
@@ -31104,11 +31717,13 @@ var init_hook_handlers = __esm({
|
|
|
31104
31717
|
const { usesReportsBranch: usesReportsBranch2 } = await Promise.resolve().then(() => (init_types(), types_exports));
|
|
31105
31718
|
if (usesReportsBranch2(localConfig)) {
|
|
31106
31719
|
try {
|
|
31107
|
-
const {
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
|
|
31720
|
+
const { hasPendingVoteDeltas: hasPendingVoteDeltas2 } = await Promise.resolve().then(() => (init_votes(), votes_exports));
|
|
31721
|
+
if (await hasPendingVoteDeltas2(votesDir, localConfig.username)) {
|
|
31722
|
+
const { updateReports: updateReports2 } = await Promise.resolve().then(() => (init_reports_branch(), reports_branch_exports));
|
|
31723
|
+
await updateReports2(localConfig, async (wt) => await syncVotesToTeam2(wt, localConfig.username, votesDir) ? {
|
|
31724
|
+
files: [`votes/${localConfig.username}.yaml`],
|
|
31725
|
+
message: `[teamai] Update votes for ${localConfig.username}`
|
|
31726
|
+
} : null);
|
|
31112
31727
|
}
|
|
31113
31728
|
} catch {
|
|
31114
31729
|
}
|
|
@@ -34828,7 +35443,7 @@ var init_codebase = __esm({
|
|
|
34828
35443
|
|
|
34829
35444
|
// src/wiki-engine/code-knowledge/code-collector.ts
|
|
34830
35445
|
import { createHash as createHash5 } from "crypto";
|
|
34831
|
-
import { execFile as
|
|
35446
|
+
import { execFile as execFile3 } from "child_process";
|
|
34832
35447
|
import { readFile as readFile6, readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
34833
35448
|
import path105 from "path";
|
|
34834
35449
|
import { promisify as promisify3 } from "util";
|
|
@@ -34984,7 +35599,7 @@ var init_code_collector = __esm({
|
|
|
34984
35599
|
"src/wiki-engine/code-knowledge/code-collector.ts"() {
|
|
34985
35600
|
"use strict";
|
|
34986
35601
|
init_wiki_protocol();
|
|
34987
|
-
execFileAsync3 = promisify3(
|
|
35602
|
+
execFileAsync3 = promisify3(execFile3);
|
|
34988
35603
|
KEY_FILE_PATTERNS = {
|
|
34989
35604
|
go: [/main\.go$/, /cmd\/.*\.go$/, /handler.*\.go$/, /server\.go$/, /router\.go$/],
|
|
34990
35605
|
python: [/main\.py$/, /app\.py$/, /server\.py$/, /routes?\.py$/, /models?\.py$/],
|
|
@@ -42953,7 +43568,7 @@ program.name("teamai").description("TeamAI \u2014 Make Every Team AI Native").ve
|
|
|
42953
43568
|
process.exit(1);
|
|
42954
43569
|
}
|
|
42955
43570
|
});
|
|
42956
|
-
program.command("init").description("Initialize teamai (configure Git provider, clone repo, register member)").argument("[repo]", 'Team repo (owner/repo or full URL). Pass "." for single-repo mode (the current git repo is the team repo).').option("--repo <repo>", "Team repo (alias of the positional argument)").option("--http <url>", "Git-free HTTP team repo (read-only consumer; only needs an API key)").option("--self", "Single-repo mode: the current git repo is the team repo (equivalent to `teamai init .`). Knowledge lives on main under .teamai/; reports go to the teamai-reports orphan branch.").option("--token <key>", "API key for HTTP team repo / status reporting (stored 0600, never committed). Also reads TEAMAI_API_TOKEN.").option("--scope <scope>", "Install scope: project (default, <cwd>/.teamai + <cwd>/.claude) or user (~/.teamai + ~/.claude)").option("--inherit-user-scope", "In project scope, also sync safe user-scope resources and search its knowledge").option("--no-inherit-user-scope", "Disable user-scope inheritance for this project").option("--role <id>", "Primary role ID (e.g. hai_dev) for non-interactive setup").option("--project <ids>",
|
|
43571
|
+
program.command("init").description("Initialize teamai (configure Git provider, clone repo, register member)").argument("[repo]", 'Team repo (owner/repo or full URL). Pass "." for single-repo mode (the current git repo is the team repo).').option("--repo <repo>", "Team repo (alias of the positional argument)").option("--http <url>", "Git-free HTTP team repo (read-only consumer; only needs an API key)").option("--self", "Single-repo mode: the current git repo is the team repo (equivalent to `teamai init .`). Knowledge lives on main under .teamai/; reports go to the teamai-reports orphan branch.").option("--token <key>", "API key for HTTP team repo / status reporting (stored 0600, never committed). Also reads TEAMAI_API_TOKEN.").option("--scope <scope>", "Install scope: project (default, <cwd>/.teamai + <cwd>/.claude) or user (~/.teamai + ~/.claude)").option("--inherit-user-scope", "In project scope, also sync safe user-scope resources and search its knowledge").option("--no-inherit-user-scope", "Disable user-scope inheritance for this project").option("--role <id>", "Primary role ID (e.g. hai_dev) for non-interactive setup").option("--project <ids>", 'Active logical project(s) from manifest/projects.yaml (comma-separated); scopes which project resources and learnings this directory syncs. Pass "all" to activate every project the manifest declares (a snapshot taken now)').option("--agent <name>", "AI tools to set up (e.g. claude, codex, cursor, codebuddy, workbuddy, dsh). Repeatable or comma-separated. In single-repo mode, selects which tool dirs to create; omit for an interactive picker. Additive on repeated runs.", (val, acc) => acc.concat(val), []).option("--force", "Overwrite existing config without confirmation").action(async (repoArg, cmdOpts) => {
|
|
42957
43572
|
const globalOpts = program.opts();
|
|
42958
43573
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42959
43574
|
await init2({ ...globalOpts, ...cmdOpts, repoPositional: repoArg });
|
|
@@ -43024,16 +43639,17 @@ membersCmd.command("list").description("List team members").action(async () => {
|
|
|
43024
43639
|
const { listMembers: listMembers2 } = await Promise.resolve().then(() => (init_members(), members_exports));
|
|
43025
43640
|
await listMembers2(globalOpts);
|
|
43026
43641
|
});
|
|
43027
|
-
program.command("remove <type> <names...>").description("Remove resource(s) from team repo and all local AI tools (type: skills|rules|agents|mcp)").action(async (type, names) => {
|
|
43642
|
+
program.command("remove <type> <names...>").description("Remove resource(s) from team repo and all local AI tools (type: skills|rules|agents|mcp)").option("--force", "Skip confirmation prompt").action(async (type, names, cmdOpts) => {
|
|
43028
43643
|
const globalOpts = program.opts();
|
|
43029
43644
|
const { remove: remove3 } = await Promise.resolve().then(() => (init_remove(), remove_exports));
|
|
43030
|
-
await remove3(type, names, globalOpts);
|
|
43645
|
+
await remove3(type, names, { ...globalOpts, ...cmdOpts });
|
|
43031
43646
|
});
|
|
43032
43647
|
registerPackagesCommand(program);
|
|
43033
|
-
program.command("doctor").description("Diagnose configuration issues").action(async () => {
|
|
43648
|
+
program.command("doctor").description("Diagnose configuration issues").option("--json", "Output the report as JSON (suitable for CI)").action(async (cmdOpts) => {
|
|
43034
43649
|
const globalOpts = program.opts();
|
|
43035
43650
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
43036
|
-
await doctor2(globalOpts);
|
|
43651
|
+
const allPassed = await doctor2({ ...globalOpts, ...cmdOpts });
|
|
43652
|
+
if (!allPassed) process.exitCode = 1;
|
|
43037
43653
|
});
|
|
43038
43654
|
var rolesCmd = program.command("roles").description("Manage team roles and resource namespaces").action(async () => {
|
|
43039
43655
|
const globalOpts = program.opts();
|
|
@@ -43334,7 +43950,7 @@ program.command("todowrite-hint", { hidden: true }).description("Remind the agen
|
|
|
43334
43950
|
await todoWriteHint2();
|
|
43335
43951
|
}
|
|
43336
43952
|
});
|
|
43337
|
-
program.command("import").description("Import knowledge from local directories, remote repos, organizations, MRs, or iWiki").option("--dir <path>", "Extract code knowledge from a local directory (same as --from-repo but no clone)").addOption(new Option("--from-claude", "Scan Claude/Cursor rule directories (~/.claude/rules, ~/.cursor/rules)").hideHelp()).option("--from-mr <url>", "Extract learning from merged MR/PR and trigger incremental teamwiki update").option("--from-iwiki <space-id-or-url>", "Import documents from iWiki Space ID or page URL (requires TAI_PAT_TOKEN)").addOption(new Option("--resume", "Resume an interrupted import session").hideHelp()).option("--all", "Accept all suggestions without interactive confirmation").addOption(new Option("--output <path>", "Write drafts to this directory instead of pushing to team repo").hideHelp()).option("--from-repo <url>", "Clone a remote repo and generate per-repo codebase summary").addOption(new Option("--ssh", "Force SSH clone even if HTTPS token is available").hideHelp()).addOption(new Option("--domain <name>", "Skip AI recommendation and assign repo to this domain explicitly").hideHelp()).option("--from-repo-list <path>", "Batch import repos from a YAML whitelist").addOption(new Option("--concurrency <n>", "Concurrent repos for --from-repo-list (default 3)").default("3").hideHelp()).option("--incremental", "Use cached clone with fetch+reset (with --from-repo or --from-repo-list)").option("--skip-enrich", "Skip AI enrichment (only clone + extract + graph, no LLM calls)").option("--from-org <org>", "List repos under an org and generate a repo whitelist").addOption(new Option("--max-repos <n>", "Cap on repos pulled from --from-org (default 200)").default("200").hideHelp()).addOption(new Option("--exclude-archived", "Exclude archived repos from --from-org (default true)").hideHelp()).addOption(new Option("--include-pattern <re>", "Regex to include repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--exclude-pattern <re>", "Regex to exclude repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--skip-import", "Only write drafts; skip the actual --from-repo-list run").hideHelp()).addOption(new Option("--iwiki-dual", "Enable dual-output mode for --from-iwiki (write codebase sections in addition to learning)").hideHelp()).addOption(new Option("--require-review", "Defer codebase section writes to .teamai/pending-review.jsonl for human review").hideHelp()).option("--cache-status", "Show import cache status (repos cached, disk usage)").option("--cache-gc", "Garbage-collect stale import cache entries").addOption(new Option("--max-bytes <n>", "Override capacity cap for --cache-gc").hideHelp()).addOption(new Option("--stale-days <n>", "Threshold for stale-eviction in days (default 30)").default("30").hideHelp()).action(async (cmdOpts) => {
|
|
43953
|
+
program.command("import").description("Import knowledge from local directories, remote repos, organizations, MRs, or iWiki").option("--dir <path>", "Extract code knowledge from a local directory (same as --from-repo but no clone)").addOption(new Option("--from-claude", "Scan Claude/Cursor rule directories (~/.claude/rules, ~/.cursor/rules)").hideHelp()).option("--from-mr <url>", "Extract learning from merged MR/PR and trigger incremental teamwiki update").option("--from-iwiki <space-id-or-url>", "Import documents from iWiki Space ID or page URL (requires TAI_PAT_TOKEN)").addOption(new Option("--resume", "Resume an interrupted import session").hideHelp()).option("--all", "Accept all suggestions without interactive confirmation").addOption(new Option("--output <path>", "Write drafts to this directory instead of pushing to team repo").hideHelp()).option("--from-repo <url>", "Clone a remote repo and generate per-repo codebase summary").addOption(new Option("--ssh", "Force SSH clone even if HTTPS token is available").hideHelp()).addOption(new Option("--domain <name>", "Skip AI recommendation and assign repo to this domain explicitly").hideHelp()).option("--from-repo-list <path>", "Batch import repos from a YAML whitelist").addOption(new Option("--concurrency <n>", "Concurrent repos for --from-repo-list (default 3)").default("3").hideHelp()).option("--incremental", "Use cached clone with fetch+reset (with --from-repo or --from-repo-list)").option("--skip-enrich", "Skip AI enrichment (only clone + extract + graph, no LLM calls)").option("--from-org <org>", "List repos under an org and generate a repo whitelist").addOption(new Option("--max-repos <n>", "Cap on repos pulled from --from-org (default 200)").default("200").hideHelp()).addOption(new Option("--exclude-archived", "Exclude archived repos from --from-org (default true)").hideHelp()).addOption(new Option("--include-pattern <re>", "Regex to include repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--exclude-pattern <re>", "Regex to exclude repos by full name (used with --from-org)").hideHelp()).addOption(new Option("--skip-import", "Only write drafts; skip the actual --from-repo-list run").hideHelp()).addOption(new Option("--iwiki-dual", "Enable dual-output mode for --from-iwiki (write codebase sections in addition to learning)").hideHelp()).addOption(new Option("--require-review", "Defer codebase section writes to .teamai/pending-review.jsonl for human review").hideHelp()).option("--cache-status", "Show import cache status (repos cached, disk usage)").option("--cache-gc", "Garbage-collect stale import cache entries").option("--json", "Output cache status or GC result as JSON").addOption(new Option("--max-bytes <n>", "Override capacity cap for --cache-gc").hideHelp()).addOption(new Option("--stale-days <n>", "Threshold for stale-eviction in days (default 30)").default("30").hideHelp()).action(async (cmdOpts) => {
|
|
43338
43954
|
const globalOpts = program.opts();
|
|
43339
43955
|
if (cmdOpts.cacheStatus || cmdOpts.cacheGc) {
|
|
43340
43956
|
const { cacheCmd: cacheCmd2 } = await Promise.resolve().then(() => (init_cache_cmd(), cache_cmd_exports));
|