Skip to content

Commit a58ff1a

Browse files
committed
refactor(commands): split CLI commands
1 parent 2b60ee9 commit a58ff1a

74 files changed

Lines changed: 7996 additions & 7807 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/commands/agents.bindings.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
2+
import { getChannelPlugin } from "../channels/plugins/index.js";
3+
import type { ChatChannelId } from "../channels/registry.js";
4+
import { normalizeChatChannelId } from "../channels/registry.js";
5+
import type { ClawdbotConfig } from "../config/config.js";
6+
import type { AgentBinding } from "../config/types.js";
7+
import {
8+
DEFAULT_ACCOUNT_ID,
9+
normalizeAgentId,
10+
} from "../routing/session-key.js";
11+
import type { ChannelChoice } from "./onboard-types.js";
12+
13+
function bindingMatchKey(match: AgentBinding["match"]) {
14+
const accountId = match.accountId?.trim() || DEFAULT_ACCOUNT_ID;
15+
return [
16+
match.channel,
17+
accountId,
18+
match.peer?.kind ?? "",
19+
match.peer?.id ?? "",
20+
match.guildId ?? "",
21+
match.teamId ?? "",
22+
].join("|");
23+
}
24+
25+
export function describeBinding(binding: AgentBinding) {
26+
const match = binding.match;
27+
const parts = [match.channel];
28+
if (match.accountId) parts.push(`accountId=${match.accountId}`);
29+
if (match.peer) parts.push(`peer=${match.peer.kind}:${match.peer.id}`);
30+
if (match.guildId) parts.push(`guild=${match.guildId}`);
31+
if (match.teamId) parts.push(`team=${match.teamId}`);
32+
return parts.join(" ");
33+
}
34+
35+
export function applyAgentBindings(
36+
cfg: ClawdbotConfig,
37+
bindings: AgentBinding[],
38+
): {
39+
config: ClawdbotConfig;
40+
added: AgentBinding[];
41+
skipped: AgentBinding[];
42+
conflicts: Array<{ binding: AgentBinding; existingAgentId: string }>;
43+
} {
44+
const existing = cfg.bindings ?? [];
45+
const existingMatchMap = new Map<string, string>();
46+
for (const binding of existing) {
47+
const key = bindingMatchKey(binding.match);
48+
if (!existingMatchMap.has(key)) {
49+
existingMatchMap.set(key, normalizeAgentId(binding.agentId));
50+
}
51+
}
52+
53+
const added: AgentBinding[] = [];
54+
const skipped: AgentBinding[] = [];
55+
const conflicts: Array<{ binding: AgentBinding; existingAgentId: string }> =
56+
[];
57+
58+
for (const binding of bindings) {
59+
const agentId = normalizeAgentId(binding.agentId);
60+
const key = bindingMatchKey(binding.match);
61+
const existingAgentId = existingMatchMap.get(key);
62+
if (existingAgentId) {
63+
if (existingAgentId === agentId) {
64+
skipped.push(binding);
65+
} else {
66+
conflicts.push({ binding, existingAgentId });
67+
}
68+
continue;
69+
}
70+
existingMatchMap.set(key, agentId);
71+
added.push({ ...binding, agentId });
72+
}
73+
74+
if (added.length === 0) {
75+
return { config: cfg, added, skipped, conflicts };
76+
}
77+
78+
return {
79+
config: {
80+
...cfg,
81+
bindings: [...existing, ...added],
82+
},
83+
added,
84+
skipped,
85+
conflicts,
86+
};
87+
}
88+
89+
function resolveDefaultAccountId(
90+
cfg: ClawdbotConfig,
91+
provider: ChatChannelId,
92+
): string {
93+
const plugin = getChannelPlugin(provider);
94+
if (!plugin) return DEFAULT_ACCOUNT_ID;
95+
return resolveChannelDefaultAccountId({ plugin, cfg });
96+
}
97+
98+
export function buildChannelBindings(params: {
99+
agentId: string;
100+
selection: ChannelChoice[];
101+
config: ClawdbotConfig;
102+
accountIds?: Partial<Record<ChannelChoice, string>>;
103+
}): AgentBinding[] {
104+
const bindings: AgentBinding[] = [];
105+
const agentId = normalizeAgentId(params.agentId);
106+
for (const channel of params.selection) {
107+
const match: AgentBinding["match"] = { channel };
108+
const accountId = params.accountIds?.[channel]?.trim();
109+
if (accountId) {
110+
match.accountId = accountId;
111+
} else {
112+
const plugin = getChannelPlugin(channel);
113+
if (plugin?.meta.forceAccountBinding) {
114+
match.accountId = resolveDefaultAccountId(params.config, channel);
115+
}
116+
}
117+
bindings.push({ agentId, match });
118+
}
119+
return bindings;
120+
}
121+
122+
export function parseBindingSpecs(params: {
123+
agentId: string;
124+
specs?: string[];
125+
config: ClawdbotConfig;
126+
}): { bindings: AgentBinding[]; errors: string[] } {
127+
const bindings: AgentBinding[] = [];
128+
const errors: string[] = [];
129+
const specs = params.specs ?? [];
130+
const agentId = normalizeAgentId(params.agentId);
131+
for (const raw of specs) {
132+
const trimmed = raw?.trim();
133+
if (!trimmed) continue;
134+
const [channelRaw, accountRaw] = trimmed.split(":", 2);
135+
const channel = normalizeChatChannelId(channelRaw);
136+
if (!channel) {
137+
errors.push(`Unknown channel "${channelRaw}".`);
138+
continue;
139+
}
140+
let accountId = accountRaw?.trim();
141+
if (accountRaw !== undefined && !accountId) {
142+
errors.push(`Invalid binding "${trimmed}" (empty account id).`);
143+
continue;
144+
}
145+
if (!accountId) {
146+
const plugin = getChannelPlugin(channel);
147+
if (plugin?.meta.forceAccountBinding) {
148+
accountId = resolveDefaultAccountId(params.config, channel);
149+
}
150+
}
151+
const match: AgentBinding["match"] = { channel };
152+
if (accountId) match.accountId = accountId;
153+
bindings.push({ agentId, match });
154+
}
155+
return { bindings, errors };
156+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { ClawdbotConfig } from "../config/config.js";
2+
import { readConfigFileSnapshot } from "../config/config.js";
3+
import type { RuntimeEnv } from "../runtime.js";
4+
5+
export function createQuietRuntime(runtime: RuntimeEnv): RuntimeEnv {
6+
return { ...runtime, log: () => {} };
7+
}
8+
9+
export async function requireValidConfig(
10+
runtime: RuntimeEnv,
11+
): Promise<ClawdbotConfig | null> {
12+
const snapshot = await readConfigFileSnapshot();
13+
if (snapshot.exists && !snapshot.valid) {
14+
const issues =
15+
snapshot.issues.length > 0
16+
? snapshot.issues
17+
.map((issue) => `- ${issue.path}: ${issue.message}`)
18+
.join("\n")
19+
: "Unknown validation issue.";
20+
runtime.error(`Config invalid:\n${issues}`);
21+
runtime.error("Fix the config or run clawdbot doctor.");
22+
runtime.exit(1);
23+
return null;
24+
}
25+
return snapshot.config;
26+
}

0 commit comments

Comments
 (0)