@quiel/cli 0.2.0 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +1132 -441
  2. package/package.json +5 -2
package/dist/cli.js CHANGED
@@ -3,237 +3,85 @@
3
3
  // src/cli/program.ts
4
4
  import { Command } from "commander";
5
5
 
6
- // src/config/claude-md.ts
7
- import { existsSync, readFileSync, writeFileSync } from "fs";
8
- import { join } from "path";
9
- var QUIEL_CLAUDE_MD_HEADING = "## Quiel \u2014 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430";
10
- var SECTION_BODY = `\u041A\u043E\u0433\u0434\u0430 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043F\u0440\u043E\u0441\u0438\u0442 \xAB\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430\xBB, \u0432\u044B\u0437\u043E\u0432\u0438 \`set_mode(auto)\`, \u0437\u0430\u0442\u0435\u043C \`next_task\`.
11
- \u0414\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438:
12
- 1. \u0412\u044B\u0437\u043E\u0432\u0438 \`get_context\` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438/\u0432\u043E\u043F\u0440\u043E\u0441\u044B. \u0415\u0441\u043B\u0438 \u0443 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043F\u0443\u0441\u0442\u043E\u0439 \`contentMd\`, \u043D\u043E \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D \`file\` \u2014 \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u0430 \u0444\u0430\u0439\u043B\u043E\u043C: \u0441\u043A\u0430\u0447\u0430\u0439 \`file.downloadUrl\` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E.
13
- 2. \`next_task\` \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0432\u043E\u0439\u0434\u0438 \u0432 \u043D\u0435\u0433\u043E \u0447\u0435\u0440\u0435\u0437 \`EnterWorktree\` \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0430\u043C. \u0421\u0432\u043E\u0439 git worktree \u041D\u0415 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439: \u043E\u043D \u0443\u0436\u0435 \u0441\u043E\u0437\u0434\u0430\u043D \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 \`project.branchName\`, \u0430 \u0432\u0442\u043E\u0440\u043E\u0439 \u0440\u0430\u0437\u043E\u0448\u0451\u043B\u0441\u044F \u0431\u044B \u0441 \u0442\u0435\u043C, \u043E\u0442\u043A\u0443\u0434\u0430 \`submit_work\` \u0431\u0435\u0440\u0451\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F.
14
- 3. \u0414\u0435\u043B\u0435\u0433\u0438\u0440\u0443\u0439 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0441\u0443\u0431\u0430\u0433\u0435\u043D\u0442\u0443, \u043F\u0435\u0440\u0435\u0434\u0430\u0432 \u043F\u043E\u043B\u043D\u044B\u0439 TaskPayload. \u0421\u0430\u043C \u043D\u0435 \u043F\u0438\u0448\u0438 \u043A\u043E\u0434 \u0432 \u0433\u043B\u0430\u0432\u043D\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438.
15
- 4. \u0415\u0441\u043B\u0438 \u0447\u0435\u0433\u043E-\u0442\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043E\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \`ask_human(blocking=true)\`. \u0415\u0441\u043B\u0438 \u043D\u0443\u0436\u043D\u0430 \u0440\u0430\u0431\u043E\u0442\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0440\u043E\u043B\u0438 \u2014 \`create_task\`.
16
- 5. \u041A\u043E\u043C\u0430\u043D\u0434\u044B \u0438\u0437 \u0441\u0442\u043E\u043F-\u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0437\u043E\u0432\u0443\u0442 \u043E\u0442\u043A\u0430\u0437 \u0445\u0443\u043A\u0430 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \`request_approval\`, \u043D\u0435 \u043F\u044B\u0442\u0430\u0439\u0441\u044F \u043E\u0431\u043E\u0439\u0442\u0438.
17
- 6. \u041F\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u0438: \u043A\u043E\u043C\u043C\u0438\u0442 \u0441 \u043F\u0440\u0435\u0444\u0438\u043A\u0441\u043E\u043C \`project.commitPrefix\`, push (\u0438\u043B\u0438 PR \u2014 \u043F\u043E \`gitMode\`), \u0437\u0430\u0442\u0435\u043C \`report\` \u0441 \u0441\u0441\u044B\u043B\u043A\u0430\u043C\u0438 \u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C \u0442\u0435\u0441\u0442\u043E\u0432.
18
- 7. \u041F\u043E\u0441\u043B\u0435 \`report\` \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E \u0432\u044B\u0437\u043E\u0432\u0438 \`next_task\`. \u041D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0439 \u0445\u043E\u0434 \u0441\u0430\u043C, \u043F\u043E\u043A\u0430 \u0440\u0435\u0436\u0438\u043C auto.
19
- \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044B\u0435 \u0447\u0435\u0440\u0435\u0437 \`next_task\`, \u0438 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0440\u043E\u043B\u0435\u0439.`;
20
- var QUIEL_CLAUDE_MD_SECTION = `${QUIEL_CLAUDE_MD_HEADING}
21
-
22
- ${SECTION_BODY}
23
- `;
24
- function getClaudeMdPath(dir) {
25
- return join(dir, "CLAUDE.md");
26
- }
27
- function readClaudeMd(dir) {
28
- const path = getClaudeMdPath(dir);
29
- if (!existsSync(path)) return null;
30
- return readFileSync(path, "utf8");
31
- }
32
- function writeClaudeMd(dir, content) {
33
- const path = getClaudeMdPath(dir);
34
- writeFileSync(path, content, "utf8");
35
- return path;
36
- }
37
- function findSectionRange(content, heading) {
38
- const lines = content.split("\n");
39
- const headingLineIndex = lines.findIndex((line) => line.trim() === heading);
40
- if (headingLineIndex === -1) return null;
41
- let endLineIndex = lines.length;
42
- for (let i = headingLineIndex + 1; i < lines.length; i++) {
43
- const line = lines[i] ?? "";
44
- if (/^#{1,2}\s/.test(line)) {
45
- endLineIndex = i;
46
- break;
47
- }
48
- }
49
- const start = lines.slice(0, headingLineIndex).join("\n").length + (headingLineIndex > 0 ? 1 : 0);
50
- const end = lines.slice(0, endLineIndex).join("\n").length + (endLineIndex < lines.length ? 1 : 0);
51
- return { start, end };
52
- }
53
- function upsertClaudeMdSection(existingContent) {
54
- if (existingContent === null || existingContent.trim().length === 0) {
55
- return QUIEL_CLAUDE_MD_SECTION;
56
- }
57
- const range = findSectionRange(existingContent, QUIEL_CLAUDE_MD_HEADING);
58
- if (!range) {
59
- const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
60
- return `${existingContent}${separator}${QUIEL_CLAUDE_MD_SECTION}`;
61
- }
62
- const before = existingContent.slice(0, range.start);
63
- const after = existingContent.slice(range.end).replace(/^\n+/, "");
64
- const afterSeparator = after.length > 0 ? "\n" : "";
65
- return `${before}${QUIEL_CLAUDE_MD_SECTION}${afterSeparator}${after}`;
66
- }
67
-
68
- // src/config/claude-settings.ts
69
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
70
- import { join as join2 } from "path";
6
+ // ../shared/src/actor.ts
71
7
  import { z } from "zod";
72
- var HookCommandEntrySchema = z.object({
73
- type: z.literal("command"),
74
- command: z.string().min(1),
75
- timeout: z.number().int().positive().optional()
76
- }).passthrough();
77
- var HookMatcherGroupSchema = z.object({
78
- matcher: z.string().optional(),
79
- hooks: z.array(HookCommandEntrySchema)
80
- }).passthrough();
81
- var McpServerEntrySchema = z.object({
82
- command: z.string().min(1),
83
- args: z.array(z.string()).optional()
84
- }).passthrough();
85
- var ClaudeSettingsSchema = z.object({
86
- mcpServers: z.record(z.string(), McpServerEntrySchema).optional(),
87
- hooks: z.record(z.string(), z.array(HookMatcherGroupSchema)).optional(),
88
- env: z.record(z.string(), z.string()).optional()
89
- }).passthrough();
90
- var CLAUDE_SETTINGS_RELATIVE_PATH = join2(".claude", "settings.json");
91
- function getClaudeSettingsPath(dir) {
92
- return join2(dir, ".claude", "settings.json");
93
- }
94
- function readClaudeSettings(dir) {
95
- const path = getClaudeSettingsPath(dir);
96
- if (!existsSync2(path)) return { ok: true, settings: {} };
97
- let raw;
98
- try {
99
- raw = readFileSync2(path, "utf8");
100
- } catch (error) {
101
- return { ok: false, message: `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${path}: ${String(error)}` };
102
- }
103
- let parsedJson;
104
- try {
105
- parsedJson = raw.trim().length === 0 ? {} : JSON.parse(raw);
106
- } catch {
107
- return {
108
- ok: false,
109
- message: `${path} \u2014 \u043D\u0435 \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON. \u041F\u043E\u043F\u0440\u0430\u0432\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E (\u044D\u0442\u043E \u0444\u0430\u0439\u043B Claude Code, \u043D\u0435 \u043D\u0430\u0448) \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 "quiel init".`
110
- };
111
- }
112
- const parsed = ClaudeSettingsSchema.safeParse(parsedJson);
113
- if (!parsed.success) {
114
- return {
115
- ok: false,
116
- message: `${path} \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u0443\u044E \u0444\u043E\u0440\u043C\u0443: ${parsed.error.issues.map((i) => i.message).join("; ")}`
117
- };
118
- }
119
- return { ok: true, settings: parsed.data };
120
- }
121
- function writeClaudeSettings(dir, settings) {
122
- const path = getClaudeSettingsPath(dir);
123
- const claudeDir = join2(dir, ".claude");
124
- if (!existsSync2(claudeDir)) mkdirSync(claudeDir, { recursive: true });
125
- writeFileSync2(path, `${JSON.stringify(settings, null, 2)}
126
- `, "utf8");
127
- return path;
128
- }
129
- var CLI_BIN = "quiel";
130
- var HOOK_SPECS = [
131
- { event: "SessionStart", command: `${CLI_BIN} hook session-start` },
132
- { event: "Stop", command: `${CLI_BIN} hook stop`, timeout: 10 },
8
+ var ActorKind = {
9
+ HUMAN: "HUMAN",
10
+ AGENT: "AGENT",
11
+ SYSTEM: "SYSTEM"
12
+ };
13
+ var ActorSchema = z.object({
14
+ kind: z.enum([ActorKind.HUMAN, ActorKind.AGENT, ActorKind.SYSTEM]),
15
+ id: z.string().min(1)
16
+ });
17
+
18
+ // ../shared/src/agents/kinds.ts
19
+ import { z as z2 } from "zod";
20
+ var AgentKind = {
21
+ CLAUDE_CODE: "CLAUDE_CODE",
22
+ CODEX: "CODEX",
23
+ CURSOR: "CURSOR",
24
+ OPENCODE: "OPENCODE"
25
+ };
26
+ var AGENT_KINDS = [
133
27
  {
134
- event: "StopFailure",
135
- matcher: "rate_limit|overloaded",
136
- command: `${CLI_BIN} hook stop-failure`
28
+ kind: AgentKind.CLAUDE_CODE,
29
+ label: "Claude Code",
30
+ vendor: "Anthropic",
31
+ cliKey: "claude-code",
32
+ support: "verified",
33
+ docsUrl: "https://code.claude.com/docs/en/hooks",
34
+ configFiles: [
35
+ ".claude/settings.json",
36
+ ".mcp.json",
37
+ "CLAUDE.md",
38
+ ".claude/commands/quiel-auto.md"
39
+ ]
137
40
  },
138
41
  {
139
- event: "PreToolUse",
140
- matcher: "Bash|Edit|Write",
141
- command: `${CLI_BIN} hook pre-tool-use`,
142
- timeout: 5
42
+ kind: AgentKind.CODEX,
43
+ label: "Codex",
44
+ vendor: "OpenAI",
45
+ cliKey: "codex",
46
+ support: "documented",
47
+ docsUrl: "https://learn.chatgpt.com/docs/hooks",
48
+ configFiles: [".codex/hooks.json", ".codex/config.toml", "AGENTS.md"]
143
49
  },
144
- { event: "PreCompact", command: `${CLI_BIN} hook pre-compact` }
145
- ];
146
- var MCP_TOOL_TIMEOUT_MS = "600000";
147
- function sameMatcher(a, b) {
148
- return (a ?? "") === (b ?? "");
149
- }
150
- function upsertHookSpec(groups, spec) {
151
- const commandEntry = {
152
- type: "command",
153
- command: spec.command,
154
- ...spec.timeout !== void 0 ? { timeout: spec.timeout } : {}
155
- };
156
- const groupIndex = groups.findIndex((g) => sameMatcher(g.matcher, spec.matcher));
157
- if (groupIndex === -1) {
158
- const newGroup = {
159
- ...spec.matcher !== void 0 ? { matcher: spec.matcher } : {},
160
- hooks: [commandEntry]
161
- };
162
- return [...groups, newGroup];
163
- }
164
- const group = groups[groupIndex];
165
- const entryIndex = group.hooks.findIndex((h) => h.command === spec.command);
166
- const nextHooks = entryIndex === -1 ? [...group.hooks, commandEntry] : group.hooks.map((h, i) => i === entryIndex ? { ...h, ...commandEntry } : h);
167
- const nextGroup = { ...group, hooks: nextHooks };
168
- return groups.map((g, i) => i === groupIndex ? nextGroup : g);
169
- }
170
- function mergeQuielSettings(existing) {
171
- let hooks = { ...existing.hooks };
172
- for (const spec of HOOK_SPECS) {
173
- hooks = { ...hooks, [spec.event]: upsertHookSpec(hooks[spec.event] ?? [], spec) };
50
+ {
51
+ kind: AgentKind.CURSOR,
52
+ label: "Cursor",
53
+ vendor: "Anysphere",
54
+ cliKey: "cursor",
55
+ support: "documented",
56
+ docsUrl: "https://cursor.com/docs/hooks",
57
+ configFiles: [".cursor/hooks.json", ".cursor/mcp.json", "AGENTS.md"]
58
+ },
59
+ {
60
+ kind: AgentKind.OPENCODE,
61
+ label: "OpenCode",
62
+ vendor: "SST",
63
+ cliKey: "opencode",
64
+ support: "documented",
65
+ docsUrl: "https://opencode.ai/docs/plugins/",
66
+ configFiles: ["opencode.json", ".opencode/plugins/quiel.js", "AGENTS.md"]
174
67
  }
175
- const env = { ...existing.env, MCP_TOOL_TIMEOUT: MCP_TOOL_TIMEOUT_MS };
176
- return { ...existing, hooks, env };
177
- }
178
-
179
- // src/config/credentials.ts
180
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
181
- import { dirname as dirname2, join as join4 } from "path";
182
-
183
- // src/state/approvals.ts
184
- import { createHash } from "crypto";
185
- function hashCommandKey(command, cwd) {
186
- return createHash("sha256").update(`${cwd}\0${command}`).digest("hex");
187
- }
188
- function findApprovedCommand(state, command, cwd, now = () => /* @__PURE__ */ new Date()) {
189
- const key = hashCommandKey(command, cwd);
190
- const entry = state.approvedCommands[key];
191
- if (!entry) return void 0;
192
- if (Date.parse(entry.expiresAt) <= now().getTime()) return void 0;
193
- return { key, entry };
194
- }
195
- function burnApprovedCommand(state, key) {
196
- const next = { ...state.approvedCommands };
197
- delete next[key];
198
- return { approvedCommands: next };
199
- }
200
- function storeApprovedCommand(state, args, now = () => /* @__PURE__ */ new Date()) {
201
- const key = hashCommandKey(args.command, args.cwd);
202
- const entry = {
203
- token: args.token,
204
- command: args.command,
205
- cwd: args.cwd,
206
- expiresAt: args.expiresAt,
207
- createdAt: now().toISOString()
208
- };
209
- return { approvedCommands: { ...state.approvedCommands, [key]: entry } };
68
+ ];
69
+ var DEFAULT_AGENT_KIND = AgentKind.CLAUDE_CODE;
70
+ var BY_KIND = new Map(AGENT_KINDS.map((info) => [info.kind, info]));
71
+ var BY_CLI_KEY = new Map(AGENT_KINDS.map((info) => [info.cliKey, info]));
72
+ var agentKindSchema = z2.enum(
73
+ AGENT_KINDS.map((info) => info.kind)
74
+ );
75
+ function describeAgentKind(raw) {
76
+ return typeof raw === "string" ? BY_KIND.get(raw) ?? null : null;
210
77
  }
211
- function storePendingApproval(state, approvalId, args, now = () => /* @__PURE__ */ new Date()) {
212
- return {
213
- pendingApprovals: {
214
- ...state.pendingApprovals,
215
- [approvalId]: { command: args.command, cwd: args.cwd, createdAt: now().toISOString() }
216
- }
217
- };
78
+ function agentKindFromCliKey(raw) {
79
+ return BY_CLI_KEY.get(raw.trim().toLowerCase())?.kind ?? null;
218
80
  }
219
- function clearPendingApproval(state, approvalId) {
220
- const next = { ...state.pendingApprovals };
221
- delete next[approvalId];
222
- return { pendingApprovals: next };
81
+ function agentKindCliKeys() {
82
+ return AGENT_KINDS.map((info) => info.cliKey);
223
83
  }
224
84
 
225
- // ../shared/src/actor.ts
226
- import { z as z2 } from "zod";
227
- var ActorKind = {
228
- HUMAN: "HUMAN",
229
- AGENT: "AGENT",
230
- SYSTEM: "SYSTEM"
231
- };
232
- var ActorSchema = z2.object({
233
- kind: z2.enum([ActorKind.HUMAN, ActorKind.AGENT, ActorKind.SYSTEM]),
234
- id: z2.string().min(1)
235
- });
236
-
237
85
  // ../shared/src/api/error.ts
238
86
  import { z as z3 } from "zod";
239
87
  var ApiErrorSchema = z3.object({
@@ -1208,6 +1056,24 @@ var agentSummarySchema = z22.object({
1208
1056
  mode: z22.enum(agentModeValues),
1209
1057
  lastSeenAt: z22.string().datetime().nullable(),
1210
1058
  clientVersion: z22.string().nullable(),
1059
+ /**
1060
+ * Вид агентской программы — СТРОКА, а не `agentKindSchema`.
1061
+ *
1062
+ * Это поле идёт с сервера К КЛИЕНТУ, и сервер может знать вид, которого
1063
+ * в реестре старого клиента ещё нет. Строгий enum здесь означал бы, что
1064
+ * такой клиент не смог бы разобрать ответ на `agent:hello` — то есть
1065
+ * не подключился бы вовсе из-за поля, которое ему нужно только
1066
+ * показать. Разбирает значение `describeAgentKind`.
1067
+ *
1068
+ * И С УМОЛЧАНИЕМ — по той же причине, но в другую сторону. Новый
1069
+ * клиент может оказаться у человека РАНЬШЕ, чем обновится стенд: он
1070
+ * поставил `@quiel/cli`, а сервер ещё старый и поля не присылает. Без
1071
+ * умолчания разбор ack упал бы целиком, и агент не подключился бы
1072
+ * вовсе — молча, потому что `agent:hello` просто не завершался бы.
1073
+ * Поймано тестом `cli/connect.test.ts`, который как раз отвечает
1074
+ * ack-ом без этого поля.
1075
+ */
1076
+ kind: z22.string().nullable().default(null),
1211
1077
  currentTaskId: z22.string().nullable(),
1212
1078
  tasksCompletedInSession: z22.number().int().nonnegative()
1213
1079
  });
@@ -1221,7 +1087,38 @@ var agentHelloPayloadSchema = z22.object({
1221
1087
  clientVersion: z22.string().min(1),
1222
1088
  cwd: z22.string().min(1),
1223
1089
  os: z22.string().min(1),
1224
- claudeVersion: z22.string().min(1),
1090
+ /**
1091
+ * Какую агентскую программу человек подключил (реестр `AGENT_KINDS`).
1092
+ *
1093
+ * НЕОБЯЗАТЕЛЬНОЕ И ОСТАНЕТСЯ ТАКИМ. Клиенты 0.3.0 и старше поля не
1094
+ * присылают вовсе — до этой версии других видов не было. Сервер
1095
+ * подставляет `DEFAULT_AGENT_KIND`, а не пишет «неизвестный агент»
1096
+ * каждому, кто не обновился.
1097
+ *
1098
+ * СТРОКА, А НЕ `agentKindSchema`, И ЭТО НАМЕРЕННО. Клиент бывает новее
1099
+ * сервера: человек обновил `@quiel/cli`, где появился новый вид, а
1100
+ * стенд ещё не выкачен. Со строгим enum такой `agent:hello` не прошёл
1101
+ * бы валидацию ЦЕЛИКОМ — то есть агент не подключился бы вовсе из-за
1102
+ * поля, которое нужно только чтобы показать значок в дашборде. Цена
1103
+ * терпимости — опечатка вида доедет до БД; там она видна как
1104
+ * «неизвестный агент», и это несопоставимо дешевле.
1105
+ */
1106
+ agentKind: z22.string().min(1).max(64).optional(),
1107
+ /**
1108
+ * Версия самой агентской программы: Claude Code, Codex, Cursor…
1109
+ * `clientVersion` выше — версия НАШЕГО пакета `@quiel/cli`, это разные
1110
+ * числа, и путать их дорого при разборе инцидента.
1111
+ */
1112
+ agentVersion: z22.string().min(1).optional(),
1113
+ /**
1114
+ * Прежнее имя `agentVersion`, когда вид агента был ровно один.
1115
+ *
1116
+ * СТАЛО НЕОБЯЗАТЕЛЬНЫМ, А НЕ УДАЛЕНО: клиент 0.3.0 присылает только
1117
+ * его, и обязательность здесь означала бы «после выкладки сервера все
1118
+ * неподнявшиеся клиенты отваливаются». Новый клиент шлёт его только
1119
+ * для Claude Code — для Codex поле с таким именем было бы неправдой.
1120
+ */
1121
+ claudeVersion: z22.string().min(1).optional(),
1225
1122
  // Роли, которыми клиент СЕБЯ считает — информационно (лог/диагностика
1226
1123
  // рассинхрона), не авторизация: авторизующие `Agent.roleKeys` идут из
1227
1124
  // БД (заданы при создании агента, REST §12 ТЗ), этот payload их не
@@ -1372,7 +1269,7 @@ var reportAckSchema = z22.object({
1372
1269
  /** true, если этот ack — повтор уже принятой сдачи (идемпотентность). */
1373
1270
  duplicate: z22.boolean().optional()
1374
1271
  });
1375
- var contextIncludeSchema = z22.enum(["spec", "decisions", "related", "repo"]);
1272
+ var contextIncludeSchema = z22.enum(["spec", "decisions", "documents", "related", "repo"]);
1376
1273
  var contextGetPayloadSchema = z22.object({
1377
1274
  taskId: z22.string().min(1).optional(),
1378
1275
  include: z22.array(contextIncludeSchema).min(1)
@@ -1401,8 +1298,25 @@ var contextDocumentSchema = z22.object({
1401
1298
  file: contextDocumentFileSchema.nullable().optional()
1402
1299
  });
1403
1300
  var contextPayloadSchema = z22.object({
1301
+ /**
1302
+ * Замечания платформы к этому контексту — для агента, словами.
1303
+ *
1304
+ * ЗАЧЕМ. `spec: null` означает «документа с типом „Спецификация“ в
1305
+ * проекте нет» — и неотличим от трёх разных причин: спецификацию не
1306
+ * приложили вовсе; приложили, но выбрали другой тип документа; агент
1307
+ * подключён к другому проекту. Агент видит одно и то же `null` и во
1308
+ * всех трёх случаях делает одно и то же — работает без спецификации.
1309
+ *
1310
+ * 14 сентября 2026 это и случилось: владелец приложил PDF, агент
1311
+ * отработал так, будто ничего нет, и разбирательство свелось к
1312
+ * гаданию. Пустой ответ, который не объясняет свою пустоту, — та же
1313
+ * молчаливая ошибка, что и пустая строка вместо файла.
1314
+ */
1315
+ notes: z22.array(z22.string()).optional(),
1404
1316
  spec: contextDocumentSchema.nullable().optional(),
1405
1317
  decisions: z22.array(contextDocumentSchema).optional(),
1318
+ /** Все документы проекта — любого типа, включая спецификацию и решения. */
1319
+ documents: z22.array(contextDocumentSchema).optional(),
1406
1320
  related: z22.array(taskPayloadDependencySchema).optional(),
1407
1321
  repo: z22.object({
1408
1322
  repos: z22.array(taskPayloadRepoSchema),
@@ -2936,35 +2850,210 @@ function evaluateStructuralRules(parsed, rules) {
2936
2850
  } else if (!push.force && !rules.git.allowPushDefault) {
2937
2851
  reasons.push("git push \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C git.allowPushDefault = false");
2938
2852
  }
2939
- }
2940
- return { escalate: reasons.length > 0, reasons, warnings: [] };
2853
+ }
2854
+ return { escalate: reasons.length > 0, reasons, warnings: [] };
2855
+ }
2856
+ function evaluateFileAccess(relativePath, rules) {
2857
+ if (rules.files.mode === "approve") {
2858
+ return {
2859
+ decision: "approve",
2860
+ reason: "\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0439 \u0437\u0430\u043F\u0438\u0441\u0438 \u0444\u0430\u0439\u043B\u0430 (files.mode = approve)."
2861
+ };
2862
+ }
2863
+ const check = checkProtectedPath(relativePath, rules.files.protectedPaths);
2864
+ if (check.matched) {
2865
+ return {
2866
+ decision: "approve",
2867
+ reason: check.brokenPatterns.length > 0 ? `\u041F\u0443\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F \u043D\u0435\u0440\u0430\u0431\u043E\u0447\u0438\u043C \u0448\u0430\u0431\u043B\u043E\u043D\u043E\u043C \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0445 \u043F\u0443\u0442\u0435\u0439 (${check.brokenPatterns.join(", ")}) \u2014 \u043F\u0440\u0438 \u0441\u043E\u043C\u043D\u0435\u043D\u0438\u0438 \u0441\u0447\u0438\u0442\u0430\u0435\u043C \u043F\u0443\u0442\u044C \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u043C.` : `\u041F\u0443\u0442\u044C \u043F\u043E\u043F\u0430\u0434\u0430\u0435\u0442 \u043F\u043E\u0434 \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0439 \u0448\u0430\u0431\u043B\u043E\u043D \xAB${check.matchedPattern}\xBB \u2014 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435.`,
2868
+ matchedProtectedPath: check.matchedPattern
2869
+ };
2870
+ }
2871
+ return { decision: "allow", reason: "\u041F\u0443\u0442\u044C \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0445." };
2872
+ }
2873
+
2874
+ // src/config/claude-md.ts
2875
+ import { existsSync, readFileSync, writeFileSync } from "fs";
2876
+ import { join } from "path";
2877
+ var QUIEL_CLAUDE_MD_HEADING = "## Quiel \u2014 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430";
2878
+ var SECTION_BODY = `\u041A\u043E\u0433\u0434\u0430 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C \u043F\u0440\u043E\u0441\u0438\u0442 \xAB\u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430\xBB, \u0432\u044B\u0437\u043E\u0432\u0438 \`set_mode(auto)\`, \u0437\u0430\u0442\u0435\u043C \`next_task\`.
2879
+ \u0414\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0439 \u0437\u0430\u0434\u0430\u0447\u0438:
2880
+ 1. \u0412\u044B\u0437\u043E\u0432\u0438 \`get_context\` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438/\u0432\u043E\u043F\u0440\u043E\u0441\u044B. \u0415\u0441\u043B\u0438 \u0443 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043F\u0443\u0441\u0442\u043E\u0439 \`contentMd\`, \u043D\u043E \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D \`file\` \u2014 \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u0430 \u0444\u0430\u0439\u043B\u043E\u043C: \u0441\u043A\u0430\u0447\u0430\u0439 \`file.downloadUrl\` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0435\u0433\u043E.
2881
+ 2. \`next_task\` \u043D\u0430\u0437\u044B\u0432\u0430\u0435\u0442 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0432\u043E\u0439\u0434\u0438 \u0432 \u043D\u0435\u0433\u043E \u0447\u0435\u0440\u0435\u0437 \`EnterWorktree\` \u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0430\u043C. \u0421\u0432\u043E\u0439 git worktree \u041D\u0415 \u0441\u043E\u0437\u0434\u0430\u0432\u0430\u0439: \u043E\u043D \u0443\u0436\u0435 \u0441\u043E\u0437\u0434\u0430\u043D \u043D\u0430 \u0432\u0435\u0442\u043A\u0435 \`project.branchName\`, \u0430 \u0432\u0442\u043E\u0440\u043E\u0439 \u0440\u0430\u0437\u043E\u0448\u0451\u043B\u0441\u044F \u0431\u044B \u0441 \u0442\u0435\u043C, \u043E\u0442\u043A\u0443\u0434\u0430 \`submit_work\` \u0431\u0435\u0440\u0451\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F.
2882
+ 3. \u0414\u0435\u043B\u0435\u0433\u0438\u0440\u0443\u0439 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435 \u0441\u0443\u0431\u0430\u0433\u0435\u043D\u0442\u0443, \u043F\u0435\u0440\u0435\u0434\u0430\u0432 \u043F\u043E\u043B\u043D\u044B\u0439 TaskPayload. \u0421\u0430\u043C \u043D\u0435 \u043F\u0438\u0448\u0438 \u043A\u043E\u0434 \u0432 \u0433\u043B\u0430\u0432\u043D\u043E\u0439 \u0441\u0435\u0441\u0441\u0438\u0438.
2883
+ 4. \u0415\u0441\u043B\u0438 \u0447\u0435\u0433\u043E-\u0442\u043E \u043D\u0435 \u0445\u0432\u0430\u0442\u0430\u0435\u0442 \u043E\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \`ask_human(blocking=true)\`. \u0415\u0441\u043B\u0438 \u043D\u0443\u0436\u043D\u0430 \u0440\u0430\u0431\u043E\u0442\u0430 \u0434\u0440\u0443\u0433\u043E\u0439 \u0440\u043E\u043B\u0438 \u2014 \`create_task\`.
2884
+ 5. \u041A\u043E\u043C\u0430\u043D\u0434\u044B \u0438\u0437 \u0441\u0442\u043E\u043F-\u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0437\u043E\u0432\u0443\u0442 \u043E\u0442\u043A\u0430\u0437 \u0445\u0443\u043A\u0430 \u2014 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0439 \`request_approval\`, \u043D\u0435 \u043F\u044B\u0442\u0430\u0439\u0441\u044F \u043E\u0431\u043E\u0439\u0442\u0438.
2885
+ 6. \u041F\u043E \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u0438: \u043A\u043E\u043C\u043C\u0438\u0442 \u0441 \u043F\u0440\u0435\u0444\u0438\u043A\u0441\u043E\u043C \`project.commitPrefix\`, push (\u0438\u043B\u0438 PR \u2014 \u043F\u043E \`gitMode\`), \u0437\u0430\u0442\u0435\u043C \`report\` \u0441 \u0441\u0441\u044B\u043B\u043A\u0430\u043C\u0438 \u0438 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u043C \u0442\u0435\u0441\u0442\u043E\u0432.
2886
+ 7. \u041F\u043E\u0441\u043B\u0435 \`report\` \u043D\u0435\u043C\u0435\u0434\u043B\u0435\u043D\u043D\u043E \u0432\u044B\u0437\u043E\u0432\u0438 \`next_task\`. \u041D\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0439 \u0445\u043E\u0434 \u0441\u0430\u043C, \u043F\u043E\u043A\u0430 \u0440\u0435\u0436\u0438\u043C auto.
2887
+ \u041D\u0438\u043A\u043E\u0433\u0434\u0430 \u043D\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0439 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043D\u044B\u0435 \u0447\u0435\u0440\u0435\u0437 \`next_task\`, \u0438 \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0440\u043E\u043B\u0435\u0439.`;
2888
+ var QUIEL_CLAUDE_MD_SECTION = `${QUIEL_CLAUDE_MD_HEADING}
2889
+
2890
+ ${SECTION_BODY}
2891
+ `;
2892
+ var INSTRUCTIONS_FILE_CLAUDE = "CLAUDE.md";
2893
+ var INSTRUCTIONS_FILE_AGENTS = "AGENTS.md";
2894
+ function findSectionRange(content, heading) {
2895
+ const lines = content.split("\n");
2896
+ const headingLineIndex = lines.findIndex((line) => line.trim() === heading);
2897
+ if (headingLineIndex === -1) return null;
2898
+ let endLineIndex = lines.length;
2899
+ for (let i = headingLineIndex + 1; i < lines.length; i++) {
2900
+ const line = lines[i] ?? "";
2901
+ if (/^#{1,2}\s/.test(line)) {
2902
+ endLineIndex = i;
2903
+ break;
2904
+ }
2905
+ }
2906
+ const start = lines.slice(0, headingLineIndex).join("\n").length + (headingLineIndex > 0 ? 1 : 0);
2907
+ const end = lines.slice(0, endLineIndex).join("\n").length + (endLineIndex < lines.length ? 1 : 0);
2908
+ return { start, end };
2909
+ }
2910
+ function upsertClaudeMdSection(existingContent) {
2911
+ if (existingContent === null || existingContent.trim().length === 0) {
2912
+ return QUIEL_CLAUDE_MD_SECTION;
2913
+ }
2914
+ const range = findSectionRange(existingContent, QUIEL_CLAUDE_MD_HEADING);
2915
+ if (!range) {
2916
+ const separator = existingContent.endsWith("\n") ? "\n" : "\n\n";
2917
+ return `${existingContent}${separator}${QUIEL_CLAUDE_MD_SECTION}`;
2918
+ }
2919
+ const before = existingContent.slice(0, range.start);
2920
+ const after = existingContent.slice(range.end).replace(/^\n+/, "");
2921
+ const afterSeparator = after.length > 0 ? "\n" : "";
2922
+ return `${before}${QUIEL_CLAUDE_MD_SECTION}${afterSeparator}${after}`;
2923
+ }
2924
+
2925
+ // src/config/claude-settings.ts
2926
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
2927
+ import { join as join2 } from "path";
2928
+ import { z as z27 } from "zod";
2929
+ var HookCommandEntrySchema = z27.object({
2930
+ type: z27.literal("command"),
2931
+ command: z27.string().min(1),
2932
+ timeout: z27.number().int().positive().optional()
2933
+ }).passthrough();
2934
+ var HookMatcherGroupSchema = z27.object({
2935
+ matcher: z27.string().optional(),
2936
+ hooks: z27.array(HookCommandEntrySchema)
2937
+ }).passthrough();
2938
+ var McpServerEntrySchema = z27.object({
2939
+ command: z27.string().min(1),
2940
+ args: z27.array(z27.string()).optional()
2941
+ }).passthrough();
2942
+ var ClaudeSettingsSchema = z27.object({
2943
+ mcpServers: z27.record(z27.string(), McpServerEntrySchema).optional(),
2944
+ hooks: z27.record(z27.string(), z27.array(HookMatcherGroupSchema)).optional(),
2945
+ env: z27.record(z27.string(), z27.string()).optional()
2946
+ }).passthrough();
2947
+ var CLAUDE_SETTINGS_RELATIVE_PATH = join2(".claude", "settings.json");
2948
+ var CLI_BIN = "quiel";
2949
+ var HOOK_SPECS = [
2950
+ { event: "SessionStart", command: `${CLI_BIN} hook session-start` },
2951
+ { event: "Stop", command: `${CLI_BIN} hook stop`, timeout: 10 },
2952
+ {
2953
+ event: "StopFailure",
2954
+ matcher: "rate_limit|overloaded",
2955
+ command: `${CLI_BIN} hook stop-failure`
2956
+ },
2957
+ {
2958
+ event: "PreToolUse",
2959
+ matcher: "Bash|Edit|Write",
2960
+ command: `${CLI_BIN} hook pre-tool-use`,
2961
+ timeout: 5
2962
+ },
2963
+ { event: "PreCompact", command: `${CLI_BIN} hook pre-compact` }
2964
+ ];
2965
+ var MCP_TOOL_TIMEOUT_MS = "600000";
2966
+ var MCP_TOOL_TIMEOUT_SEC = 600;
2967
+ function sameMatcher(a, b) {
2968
+ return (a ?? "") === (b ?? "");
2969
+ }
2970
+ function upsertHookSpec(groups, spec) {
2971
+ const commandEntry = {
2972
+ type: "command",
2973
+ command: spec.command,
2974
+ ...spec.timeout !== void 0 ? { timeout: spec.timeout } : {}
2975
+ };
2976
+ const groupIndex = groups.findIndex((g) => sameMatcher(g.matcher, spec.matcher));
2977
+ if (groupIndex === -1) {
2978
+ const newGroup = {
2979
+ ...spec.matcher !== void 0 ? { matcher: spec.matcher } : {},
2980
+ hooks: [commandEntry]
2981
+ };
2982
+ return [...groups, newGroup];
2983
+ }
2984
+ const group = groups[groupIndex];
2985
+ const entryIndex = group.hooks.findIndex((h) => h.command === spec.command);
2986
+ const nextHooks = entryIndex === -1 ? [...group.hooks, commandEntry] : group.hooks.map((h, i) => i === entryIndex ? { ...h, ...commandEntry } : h);
2987
+ const nextGroup = { ...group, hooks: nextHooks };
2988
+ return groups.map((g, i) => i === groupIndex ? nextGroup : g);
2989
+ }
2990
+ function mergeHookSpecs(existing, specs = HOOK_SPECS) {
2991
+ let hooks = { ...existing };
2992
+ for (const spec of specs) {
2993
+ hooks = { ...hooks, [spec.event]: upsertHookSpec(hooks[spec.event] ?? [], spec) };
2994
+ }
2995
+ return hooks;
2996
+ }
2997
+ function mergeQuielSettings(existing) {
2998
+ const hooks = mergeHookSpecs(existing.hooks, HOOK_SPECS);
2999
+ const env = { ...existing.env, MCP_TOOL_TIMEOUT: MCP_TOOL_TIMEOUT_MS };
3000
+ return { ...existing, hooks, env };
3001
+ }
3002
+
3003
+ // src/config/credentials.ts
3004
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
3005
+ import { dirname as dirname2, join as join4 } from "path";
3006
+
3007
+ // src/state/approvals.ts
3008
+ import { createHash } from "crypto";
3009
+ function hashCommandKey(command, cwd) {
3010
+ return createHash("sha256").update(`${cwd}\0${command}`).digest("hex");
3011
+ }
3012
+ function findApprovedCommand(state, command, cwd, now = () => /* @__PURE__ */ new Date()) {
3013
+ const key = hashCommandKey(command, cwd);
3014
+ const entry = state.approvedCommands[key];
3015
+ if (!entry) return void 0;
3016
+ if (Date.parse(entry.expiresAt) <= now().getTime()) return void 0;
3017
+ return { key, entry };
3018
+ }
3019
+ function burnApprovedCommand(state, key) {
3020
+ const next = { ...state.approvedCommands };
3021
+ delete next[key];
3022
+ return { approvedCommands: next };
3023
+ }
3024
+ function storeApprovedCommand(state, args, now = () => /* @__PURE__ */ new Date()) {
3025
+ const key = hashCommandKey(args.command, args.cwd);
3026
+ const entry = {
3027
+ token: args.token,
3028
+ command: args.command,
3029
+ cwd: args.cwd,
3030
+ expiresAt: args.expiresAt,
3031
+ createdAt: now().toISOString()
3032
+ };
3033
+ return { approvedCommands: { ...state.approvedCommands, [key]: entry } };
3034
+ }
3035
+ function storePendingApproval(state, approvalId, args, now = () => /* @__PURE__ */ new Date()) {
3036
+ return {
3037
+ pendingApprovals: {
3038
+ ...state.pendingApprovals,
3039
+ [approvalId]: { command: args.command, cwd: args.cwd, createdAt: now().toISOString() }
3040
+ }
3041
+ };
2941
3042
  }
2942
- function evaluateFileAccess(relativePath, rules) {
2943
- if (rules.files.mode === "approve") {
2944
- return {
2945
- decision: "approve",
2946
- reason: "\u041F\u0440\u043E\u0444\u0438\u043B\u044C \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u043A\u0430\u0436\u0434\u043E\u0439 \u0437\u0430\u043F\u0438\u0441\u0438 \u0444\u0430\u0439\u043B\u0430 (files.mode = approve)."
2947
- };
2948
- }
2949
- const check = checkProtectedPath(relativePath, rules.files.protectedPaths);
2950
- if (check.matched) {
2951
- return {
2952
- decision: "approve",
2953
- reason: check.brokenPatterns.length > 0 ? `\u041F\u0443\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u044F\u0435\u0442\u0441\u044F \u043D\u0435\u0440\u0430\u0431\u043E\u0447\u0438\u043C \u0448\u0430\u0431\u043B\u043E\u043D\u043E\u043C \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0445 \u043F\u0443\u0442\u0435\u0439 (${check.brokenPatterns.join(", ")}) \u2014 \u043F\u0440\u0438 \u0441\u043E\u043C\u043D\u0435\u043D\u0438\u0438 \u0441\u0447\u0438\u0442\u0430\u0435\u043C \u043F\u0443\u0442\u044C \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u043C.` : `\u041F\u0443\u0442\u044C \u043F\u043E\u043F\u0430\u0434\u0430\u0435\u0442 \u043F\u043E\u0434 \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0439 \u0448\u0430\u0431\u043B\u043E\u043D \xAB${check.matchedPattern}\xBB \u2014 \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044F \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435.`,
2954
- matchedProtectedPath: check.matchedPattern
2955
- };
2956
- }
2957
- return { decision: "allow", reason: "\u041F\u0443\u0442\u044C \u043D\u0435 \u0432\u0445\u043E\u0434\u0438\u0442 \u0432 \u0441\u043F\u0438\u0441\u043E\u043A \u0437\u0430\u0449\u0438\u0449\u0451\u043D\u043D\u044B\u0445." };
3043
+ function clearPendingApproval(state, approvalId) {
3044
+ const next = { ...state.pendingApprovals };
3045
+ delete next[approvalId];
3046
+ return { pendingApprovals: next };
2958
3047
  }
2959
3048
 
2960
3049
  // src/state/schema.ts
2961
- import { z as z27 } from "zod";
3050
+ import { z as z28 } from "zod";
2962
3051
  var STATE_SCHEMA_VERSION = 1;
2963
- var CurrentTaskSchema = z27.object({
2964
- taskId: z27.string().min(1),
3052
+ var CurrentTaskSchema = z28.object({
3053
+ taskId: z28.string().min(1),
2965
3054
  /** Человекочитаемый ключ вроде `REL-42` — для текста в хуках. Необязателен: на первых порах может быть неизвестен. */
2966
- taskKey: z27.string().min(1).optional(),
2967
- resumeToken: z27.string().min(1),
3055
+ taskKey: z28.string().min(1).optional(),
3056
+ resumeToken: z28.string().min(1),
2968
3057
  /**
2969
3058
  * Каталог, в котором агент работает над этой задачей (`.claude/worktrees/…`).
2970
3059
  *
@@ -2978,7 +3067,7 @@ var CurrentTaskSchema = z27.object({
2978
3067
  * в чекауте), а у состояний, записанных прежними версиями клиента,
2979
3068
  * поля просто нет.
2980
3069
  */
2981
- worktreeDir: z27.string().min(1).optional(),
3070
+ worktreeDir: z28.string().min(1).optional(),
2982
3071
  /**
2983
3072
  * Git-режим проекта (§13.1 ТЗ) — `DIRECT` или `PR`.
2984
3073
  *
@@ -2992,58 +3081,58 @@ var CurrentTaskSchema = z27.object({
2992
3081
  * Необязательно: состояния, записанные прежними версиями клиента, поля
2993
3082
  * не имеют — тогда `submit_work` работает как раньше, в режиме DIRECT.
2994
3083
  */
2995
- gitMode: z27.enum([GitMode.DIRECT, GitMode.PR]).optional()
2996
- });
2997
- var TrustCacheSchema = z27.object({
2998
- profileKey: z27.string().min(1),
2999
- updatedAt: z27.string(),
3000
- rules: z27.object({
3001
- bash: z27.object({
3002
- mode: z27.enum(["allow", "approve", "deny"]),
3003
- stopPatterns: z27.array(z27.string()).default([]),
3004
- allowPatterns: z27.array(z27.string()).optional()
3084
+ gitMode: z28.enum([GitMode.DIRECT, GitMode.PR]).optional()
3085
+ });
3086
+ var TrustCacheSchema = z28.object({
3087
+ profileKey: z28.string().min(1),
3088
+ updatedAt: z28.string(),
3089
+ rules: z28.object({
3090
+ bash: z28.object({
3091
+ mode: z28.enum(["allow", "approve", "deny"]),
3092
+ stopPatterns: z28.array(z28.string()).default([]),
3093
+ allowPatterns: z28.array(z28.string()).optional()
3005
3094
  }),
3006
- files: z27.object({
3007
- mode: z27.enum(["allow", "approve"]),
3008
- protectedPaths: z27.array(z27.string()).default([])
3095
+ files: z28.object({
3096
+ mode: z28.enum(["allow", "approve"]),
3097
+ protectedPaths: z28.array(z28.string()).default([])
3009
3098
  }).optional(),
3010
- git: z27.object({ allowPushDefault: z27.boolean(), allowForcePush: z27.boolean() }).optional(),
3011
- network: z27.object({ allowSsh: z27.boolean(), sshHostsAllow: z27.array(z27.string()).optional() }).optional(),
3012
- approvalTtlMin: z27.number().int().positive().optional()
3099
+ git: z28.object({ allowPushDefault: z28.boolean(), allowForcePush: z28.boolean() }).optional(),
3100
+ network: z28.object({ allowSsh: z28.boolean(), sshHostsAllow: z28.array(z28.string()).optional() }).optional(),
3101
+ approvalTtlMin: z28.number().int().positive().optional()
3013
3102
  }).passthrough()
3014
3103
  }).nullable();
3015
- var ApprovedCommandSchema = z27.object({
3016
- token: z27.string().min(1),
3017
- command: z27.string().min(1),
3018
- cwd: z27.string().min(1),
3019
- expiresAt: z27.string(),
3020
- createdAt: z27.string()
3104
+ var ApprovedCommandSchema = z28.object({
3105
+ token: z28.string().min(1),
3106
+ command: z28.string().min(1),
3107
+ cwd: z28.string().min(1),
3108
+ expiresAt: z28.string(),
3109
+ createdAt: z28.string()
3021
3110
  });
3022
- var PendingApprovalSchema = z27.object({
3023
- command: z27.string().min(1),
3024
- cwd: z27.string().min(1),
3025
- createdAt: z27.string()
3111
+ var PendingApprovalSchema = z28.object({
3112
+ command: z28.string().min(1),
3113
+ cwd: z28.string().min(1),
3114
+ createdAt: z28.string()
3026
3115
  });
3027
- var AgentModeSchema = z27.enum([AgentMode.AUTONOMOUS, AgentMode.MANUAL]);
3116
+ var AgentModeSchema = z28.enum([AgentMode.AUTONOMOUS, AgentMode.MANUAL]);
3028
3117
  var DEFAULT_CONTEXT_RESET_AFTER_TASKS = ProjectSettingsSchema.parse({}).contextResetAfterTasks;
3029
- var ClientStateSchema = z27.object({
3030
- version: z27.literal(STATE_SCHEMA_VERSION).default(STATE_SCHEMA_VERSION),
3118
+ var ClientStateSchema = z28.object({
3119
+ version: z28.literal(STATE_SCHEMA_VERSION).default(STATE_SCHEMA_VERSION),
3031
3120
  mode: AgentModeSchema.default(AgentMode.MANUAL),
3032
3121
  currentTask: CurrentTaskSchema.nullable().default(null),
3033
3122
  /** Счётчик сдач задач в текущей «сессии» (§10.4 ТЗ). Сбрасывается `SessionStart` при `source ∈ {startup, clear}` — см. src/hooks/session-start.ts. */
3034
- tasksCompletedInSession: z27.number().int().nonnegative().default(0),
3123
+ tasksCompletedInSession: z28.number().int().nonnegative().default(0),
3035
3124
  /** Кэш `ProjectSettings.contextResetAfterTasks` (§7.2 ТЗ) — сколько задач подряд можно сдать, прежде чем `Stop`-хук попросит `/clear`. */
3036
- contextResetAfterTasks: z27.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
3125
+ contextResetAfterTasks: z28.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
3037
3126
  /** Кэш стоп-паттернов/правил доверия для `PreToolUse` фазы 4. */
3038
3127
  trust: TrustCacheSchema.default(null),
3039
3128
  /** Снимок задачи на момент последнего `PreCompact` — пишет src/hooks/pre-compact.ts, читает SessionStart(source="compact"), чтобы напомнить модели, чем она занималась. */
3040
- lastPreCompact: CurrentTaskSchema.extend({ savedAt: z27.string() }).nullable().default(null),
3129
+ lastPreCompact: CurrentTaskSchema.extend({ savedAt: z28.string() }).nullable().default(null),
3041
3130
  /** Кэш одноразовых токенов подтверждения (§14.4 ТЗ), ключ — `hashCommandKey(command, cwd)`. Читает/жжёт `PreToolUse` (`hooks/pre-tool-use.ts`), пишут `request_approval`/`wait_approval` и пуш `approval:decided` (`socket/client.ts`). */
3042
- approvedCommands: z27.record(z27.string(), ApprovedCommandSchema).default({}),
3131
+ approvedCommands: z28.record(z28.string(), ApprovedCommandSchema).default({}),
3043
3132
  /** Незавершённые запросы подтверждения, ключ — `approvalId`. См. `PendingApprovalSchema`. */
3044
- pendingApprovals: z27.record(z27.string(), PendingApprovalSchema).default({}),
3133
+ pendingApprovals: z28.record(z28.string(), PendingApprovalSchema).default({}),
3045
3134
  /** Штамп последней записи файла — выставляется в state/store.ts при каждом `writeState`. */
3046
- updatedAt: z27.string().default(() => (/* @__PURE__ */ new Date()).toISOString())
3135
+ updatedAt: z28.string().default(() => (/* @__PURE__ */ new Date()).toISOString())
3047
3136
  });
3048
3137
  function createDefaultState() {
3049
3138
  return ClientStateSchema.parse({});
@@ -3095,20 +3184,33 @@ function updateState(patch, filePath = getStateFilePath()) {
3095
3184
  }
3096
3185
 
3097
3186
  // src/config/schema.ts
3098
- import { z as z28 } from "zod";
3187
+ import { z as z29 } from "zod";
3099
3188
  var PROJECT_CONFIG_SCHEMA_VERSION = 1;
3100
- var ProjectConfigSchema = z28.object({
3101
- version: z28.literal(PROJECT_CONFIG_SCHEMA_VERSION).default(PROJECT_CONFIG_SCHEMA_VERSION),
3189
+ var ProjectConfigSchema = z29.object({
3190
+ version: z29.literal(PROJECT_CONFIG_SCHEMA_VERSION).default(PROJECT_CONFIG_SCHEMA_VERSION),
3102
3191
  /** URL платформы (§10.1 ТЗ), например `https://quiel.example.com`. */
3103
- url: z28.string().trim().url("\u041D\u0443\u0436\u0435\u043D \u043F\u043E\u043B\u043D\u044B\u0439 URL, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 https://quiel.example.com"),
3192
+ url: z29.string().trim().url("\u041D\u0443\u0436\u0435\u043D \u043F\u043E\u043B\u043D\u044B\u0439 URL, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 https://quiel.example.com"),
3104
3193
  /** Ключ проекта на платформе (OQ-11), например `REL`. */
3105
- projectKey: z28.string().trim().min(1, "\u041A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D"),
3194
+ projectKey: z29.string().trim().min(1, "\u041A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D"),
3106
3195
  /** Идентификатор агента — см. комментарий выше. */
3107
- agentId: z28.string().trim().min(1, "ID \u0430\u0433\u0435\u043D\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D"),
3196
+ agentId: z29.string().trim().min(1, "ID \u0430\u0433\u0435\u043D\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D"),
3108
3197
  /** Человекочитаемое имя агента (§7.4 ТЗ: «odya-macbook») — необязательно, только для локального отображения. */
3109
- agentName: z28.string().trim().min(1).optional(),
3198
+ agentName: z29.string().trim().min(1).optional(),
3110
3199
  /** Роли, с которыми агент забирает задачи — подмножество ролей участника (§7.4 ТЗ). */
3111
3200
  roleKeys: AgentRoleKeysSchema,
3201
+ /**
3202
+ * Какую агентскую программу человек подключил в этом каталоге:
3203
+ * Claude Code, Codex, Cursor, OpenCode (реестр `AGENT_KINDS`).
3204
+ *
3205
+ * ЗДЕСЬ, А НЕ В `~/.quiel/`: у одного человека разные проекты могут
3206
+ * вестись разными инструментами, а вот внутри одного каталога лежит
3207
+ * конфигурация ровно одной программы — ту, что записал `init`.
3208
+ *
3209
+ * УМОЛЧАНИЕ, А НЕ ОБЯЗАТЕЛЬНОЕ ПОЛЕ: `.quiel.json`, написанный
3210
+ * клиентом 0.3.0, этого поля не содержит, и он остаётся валидным —
3211
+ * других видов тогда не существовало.
3212
+ */
3213
+ agentKind: agentKindSchema.default(DEFAULT_AGENT_KIND),
3112
3214
  /**
3113
3215
  * Изолировать ли работу агента в отдельном каталоге.
3114
3216
  *
@@ -3126,19 +3228,19 @@ var ProjectConfigSchema = z28.object({
3126
3228
  * абсолютный путь вроде `/Users/odya/...` у коллеги на другой машине
3127
3229
  * оказался бы битым. Режим переносим — это соглашение проекта.
3128
3230
  */
3129
- worktrees: z28.enum(["isolated", "checkout"]).default("isolated")
3231
+ worktrees: z29.enum(["isolated", "checkout"]).default("isolated")
3130
3232
  });
3131
- var CredentialsEntrySchema = z28.object({
3132
- url: z28.string().trim().url(),
3133
- token: z28.string().min(1),
3134
- projectKey: z28.string().min(1),
3233
+ var CredentialsEntrySchema = z29.object({
3234
+ url: z29.string().trim().url(),
3235
+ token: z29.string().min(1),
3236
+ projectKey: z29.string().min(1),
3135
3237
  /** Когда записали — для диагностики (`quiel status`), не для логики. */
3136
- savedAt: z28.string()
3238
+ savedAt: z29.string()
3137
3239
  });
3138
3240
  var CREDENTIALS_SCHEMA_VERSION = 1;
3139
- var CredentialsFileSchema = z28.object({
3140
- version: z28.literal(CREDENTIALS_SCHEMA_VERSION).default(CREDENTIALS_SCHEMA_VERSION),
3141
- agents: z28.record(z28.string(), CredentialsEntrySchema).default({})
3241
+ var CredentialsFileSchema = z29.object({
3242
+ version: z29.literal(CREDENTIALS_SCHEMA_VERSION).default(CREDENTIALS_SCHEMA_VERSION),
3243
+ agents: z29.record(z29.string(), CredentialsEntrySchema).default({})
3142
3244
  });
3143
3245
  function createEmptyCredentialsFile() {
3144
3246
  return CredentialsFileSchema.parse({});
@@ -3219,39 +3321,15 @@ function ensureWorktreesIgnored(dir) {
3219
3321
  // src/config/mcp-json.ts
3220
3322
  import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
3221
3323
  import { join as join6 } from "path";
3222
- import { z as z29 } from "zod";
3223
- var McpServerEntrySchema2 = z29.object({
3224
- command: z29.string().min(1).optional(),
3225
- args: z29.array(z29.string()).optional()
3324
+ import { z as z30 } from "zod";
3325
+ var McpServerEntrySchema2 = z30.object({
3326
+ command: z30.string().min(1).optional(),
3327
+ args: z30.array(z30.string()).optional()
3226
3328
  }).passthrough();
3227
- var McpJsonSchema = z29.object({
3228
- mcpServers: z29.record(z29.string(), McpServerEntrySchema2).optional()
3329
+ var McpJsonSchema = z30.object({
3330
+ mcpServers: z30.record(z30.string(), McpServerEntrySchema2).optional()
3229
3331
  }).passthrough();
3230
3332
  var MCP_JSON_RELATIVE_PATH = ".mcp.json";
3231
- function getMcpJsonPath(dir) {
3232
- return join6(dir, MCP_JSON_RELATIVE_PATH);
3233
- }
3234
- function readMcpJson(dir) {
3235
- const path = getMcpJsonPath(dir);
3236
- if (!existsSync6(path)) return { ok: true, config: {} };
3237
- let raw;
3238
- try {
3239
- raw = readFileSync6(path, "utf8");
3240
- } catch (error) {
3241
- return { ok: false, message: `\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${path}: ${String(error)}` };
3242
- }
3243
- let parsedJson;
3244
- try {
3245
- parsedJson = raw.trim().length === 0 ? {} : JSON.parse(raw);
3246
- } catch {
3247
- return { ok: false, message: `${path} \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u0435\u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON \u2014 \u043F\u043E\u0447\u0438\u043D\u0438 \u0435\u0433\u043E \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 init` };
3248
- }
3249
- const parsed = McpJsonSchema.safeParse(parsedJson);
3250
- if (!parsed.success) {
3251
- return { ok: false, message: `${path} \u043D\u0435 \u043F\u043E\u0445\u043E\u0436 \u043D\u0430 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044E MCP: ${parsed.error.message}` };
3252
- }
3253
- return { ok: true, config: parsed.data };
3254
- }
3255
3333
  function mergeQuielMcpJson(existing) {
3256
3334
  return {
3257
3335
  ...existing,
@@ -3261,12 +3339,6 @@ function mergeQuielMcpJson(existing) {
3261
3339
  }
3262
3340
  };
3263
3341
  }
3264
- function writeMcpJson(dir, config) {
3265
- const path = getMcpJsonPath(dir);
3266
- writeFileSync6(path, `${JSON.stringify(config, null, 2)}
3267
- `, "utf8");
3268
- return path;
3269
- }
3270
3342
 
3271
3343
  // src/config/project-config.ts
3272
3344
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
@@ -3348,15 +3420,353 @@ disable-model-invocation: true
3348
3420
  \u0412\u043A\u043B\u044E\u0447\u0438 \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C Quiel: \u0432\u044B\u0437\u043E\u0432\u0438 \`set_mode(auto)\`, \u0437\u0430\u0442\u0435\u043C \`next_task\`.
3349
3421
  \u0414\u0430\u043B\u044C\u0448\u0435 \u0441\u043B\u0435\u0434\u0443\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u0438 \u0438\u0437 \u0441\u0435\u043A\u0446\u0438\u0438 \xABQuiel \u2014 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430\xBB \u0432 CLAUDE.md \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0435\u043A\u0442\u0430.
3350
3422
  `;
3351
- function getSlashCommandPath(dir) {
3352
- return join8(dir, ".claude", "commands", SLASH_COMMAND_FILENAME);
3353
- }
3354
- function writeSlashCommand(dir) {
3355
- const path = getSlashCommandPath(dir);
3356
- const commandsDir = join8(dir, ".claude", "commands");
3357
- if (!existsSync8(commandsDir)) mkdirSync5(commandsDir, { recursive: true });
3358
- writeFileSync8(path, SLASH_COMMAND_CONTENT, "utf8");
3359
- return path;
3423
+
3424
+ // src/config/toml.ts
3425
+ var TABLE_HEADER = /^\s*\[\[?[^\]]+\]\]?\s*(#.*)?$/;
3426
+ function isHeaderOf(line, table) {
3427
+ return line.trim().replace(/\s*#.*$/, "") === `[${table}]`;
3428
+ }
3429
+ function upsertTomlTable(existing, table, body) {
3430
+ const block = `[${table}]
3431
+ ${body.trimEnd()}
3432
+ `;
3433
+ if (existing === null || existing.trim().length === 0) return block;
3434
+ const lines = existing.split("\n");
3435
+ const start = lines.findIndex((line) => isHeaderOf(line, table));
3436
+ if (start === -1) {
3437
+ const separator = existing.endsWith("\n") ? "\n" : "\n\n";
3438
+ return `${existing}${separator}${block}`;
3439
+ }
3440
+ let end = lines.length;
3441
+ for (let i = start + 1; i < lines.length; i++) {
3442
+ if (TABLE_HEADER.test(lines[i] ?? "")) {
3443
+ end = i;
3444
+ break;
3445
+ }
3446
+ }
3447
+ const before = lines.slice(0, start).join("\n");
3448
+ const after = lines.slice(end).join("\n");
3449
+ const head = before.length > 0 ? `${before}
3450
+ ` : "";
3451
+ const tail = after.trim().length > 0 ? after.replace(/^\n+/, "") : "";
3452
+ return `${head}${block}${tail.length > 0 ? `
3453
+ ${tail}` : ""}`;
3454
+ }
3455
+ function tomlString(value) {
3456
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
3457
+ }
3458
+ function tomlStringArray(values) {
3459
+ return `[${values.map(tomlString).join(", ")}]`;
3460
+ }
3461
+
3462
+ // src/agents/json.ts
3463
+ function parseForeignJson(raw, schema, path) {
3464
+ if (raw === null || raw.trim().length === 0) return null;
3465
+ let parsed;
3466
+ try {
3467
+ parsed = JSON.parse(raw);
3468
+ } catch {
3469
+ throw new Error(
3470
+ `${path} \u2014 \u043D\u0435 \u0432\u0430\u043B\u0438\u0434\u043D\u044B\u0439 JSON. \u042D\u0442\u043E \u043D\u0435 \u043D\u0430\u0448 \u0444\u0430\u0439\u043B: \u043F\u043E\u0447\u0438\u043D\u0438 \u0435\u0433\u043E \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438 "quiel init".`
3471
+ );
3472
+ }
3473
+ const result = schema.safeParse(parsed);
3474
+ if (!result.success) {
3475
+ throw new Error(
3476
+ `${path} \u043D\u0435 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u0443\u044E \u0444\u043E\u0440\u043C\u0443: ${result.error.issues.map((i) => i.message).join("; ")}`
3477
+ );
3478
+ }
3479
+ return result.data;
3480
+ }
3481
+ function formatJson(value) {
3482
+ return `${JSON.stringify(value, null, 2)}
3483
+ `;
3484
+ }
3485
+
3486
+ // src/agents/claude-code.ts
3487
+ var claudeCodeInstaller = {
3488
+ kind: AgentKind.CLAUDE_CODE,
3489
+ markers: [".claude", "CLAUDE.md"],
3490
+ plan(ctx) {
3491
+ const settings = parseForeignJson(
3492
+ ctx.readFile(CLAUDE_SETTINGS_RELATIVE_PATH),
3493
+ ClaudeSettingsSchema,
3494
+ CLAUDE_SETTINGS_RELATIVE_PATH
3495
+ ) ?? {};
3496
+ const mcpJson = parseForeignJson(
3497
+ ctx.readFile(MCP_JSON_RELATIVE_PATH),
3498
+ McpJsonSchema,
3499
+ MCP_JSON_RELATIVE_PATH
3500
+ ) ?? {};
3501
+ return {
3502
+ kind: AgentKind.CLAUDE_CODE,
3503
+ files: [
3504
+ { path: CLAUDE_SETTINGS_RELATIVE_PATH, content: formatJson(mergeQuielSettings(settings)) },
3505
+ { path: MCP_JSON_RELATIVE_PATH, content: formatJson(mergeQuielMcpJson(mcpJson)) },
3506
+ {
3507
+ path: INSTRUCTIONS_FILE_CLAUDE,
3508
+ content: upsertClaudeMdSection(ctx.readFile(INSTRUCTIONS_FILE_CLAUDE))
3509
+ },
3510
+ { path: `.claude/commands/${SLASH_COMMAND_FILENAME}`, content: SLASH_COMMAND_CONTENT }
3511
+ ],
3512
+ notes: [],
3513
+ restartHint: "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u044E Claude Code: MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u0438."
3514
+ };
3515
+ }
3516
+ };
3517
+
3518
+ // src/agents/codex.ts
3519
+ import { z as z31 } from "zod";
3520
+ var CODEX_HOOKS_PATH = ".codex/hooks.json";
3521
+ var CODEX_CONFIG_PATH = ".codex/config.toml";
3522
+ var CodexHooksFileSchema = z31.object({
3523
+ hooks: z31.record(z31.string(), z31.array(z31.object({}).passthrough())).optional()
3524
+ }).passthrough();
3525
+ function mcpServerTable() {
3526
+ return [
3527
+ `command = ${tomlString(CLI_BIN)}`,
3528
+ `args = ${tomlStringArray(["mcp"])}`,
3529
+ `tool_timeout_sec = ${MCP_TOOL_TIMEOUT_SEC}`
3530
+ ].join("\n");
3531
+ }
3532
+ var codexInstaller = {
3533
+ kind: AgentKind.CODEX,
3534
+ markers: [".codex", "AGENTS.md"],
3535
+ plan(ctx) {
3536
+ const existingHooks = parseForeignJson(
3537
+ ctx.readFile(CODEX_HOOKS_PATH),
3538
+ CodexHooksFileSchema,
3539
+ CODEX_HOOKS_PATH
3540
+ );
3541
+ const specs = HOOK_SPECS.filter((spec) => spec.event !== "StopFailure");
3542
+ const hooks = mergeHookSpecs(
3543
+ existingHooks?.hooks,
3544
+ specs
3545
+ );
3546
+ return {
3547
+ kind: AgentKind.CODEX,
3548
+ files: [
3549
+ { path: CODEX_HOOKS_PATH, content: formatJson({ ...existingHooks, hooks }) },
3550
+ {
3551
+ path: CODEX_CONFIG_PATH,
3552
+ content: upsertTomlTable(
3553
+ ctx.readFile(CODEX_CONFIG_PATH),
3554
+ "mcp_servers.quiel",
3555
+ mcpServerTable()
3556
+ )
3557
+ },
3558
+ {
3559
+ path: INSTRUCTIONS_FILE_AGENTS,
3560
+ content: upsertClaudeMdSection(ctx.readFile(INSTRUCTIONS_FILE_AGENTS))
3561
+ }
3562
+ ],
3563
+ notes: [
3564
+ "\u0423 Codex \u043D\u0435\u0442 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \xAB\u043C\u043E\u0434\u0435\u043B\u044C \u0443\u043F\u0451\u0440\u043B\u0430\u0441\u044C \u0432 \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438\xBB (\u0430\u043D\u0430\u043B\u043E\u0433\u0430 StopFailure). \u0410\u0433\u0435\u043D\u0442 \u0432 \u044D\u0442\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u043D\u0435 \u043F\u0435\u0440\u0435\u0439\u0434\u0451\u0442 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441 RATE_LIMITED \u0441\u0430\u043C \u2014 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430 \u0443\u0432\u0438\u0434\u0438\u0442 \u0435\u0433\u043E \u043A\u0430\u043A \u0437\u0430\u0432\u0438\u0441\u0448\u0435\u0433\u043E \u043F\u043E \u0438\u0441\u0442\u0435\u0447\u0435\u043D\u0438\u0438 \u0430\u0440\u0435\u043D\u0434\u044B.",
3565
+ "\u041F\u0440\u043E\u0435\u043A\u0442\u043D\u044B\u0439 .codex/config.toml Codex \u0447\u0438\u0442\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0443 \u0434\u043E\u0432\u0435\u0440\u0435\u043D\u043D\u044B\u0445 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432. \u0415\u0441\u043B\u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u043F\u043E\u044F\u0432\u0438\u043B\u0441\u044F \u2014 \u0434\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u043F\u0440\u043E\u0435\u043A\u0442 \u0432 Codex \u043B\u0438\u0431\u043E \u043F\u0435\u0440\u0435\u043D\u0435\u0441\u0438\u0442\u0435 \u0431\u043B\u043E\u043A [mcp_servers.quiel] \u0432 ~/.codex/config.toml."
3566
+ ],
3567
+ restartHint: "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u044E Codex: MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u0438."
3568
+ };
3569
+ }
3570
+ };
3571
+
3572
+ // src/agents/cursor.ts
3573
+ import { z as z32 } from "zod";
3574
+ var CURSOR_HOOKS_PATH = ".cursor/hooks.json";
3575
+ var CURSOR_MCP_PATH = ".cursor/mcp.json";
3576
+ var CursorHookEntrySchema = z32.object({ command: z32.string().min(1) }).passthrough();
3577
+ var CursorHooksFileSchema = z32.object({
3578
+ version: z32.number().optional(),
3579
+ hooks: z32.record(z32.string(), z32.array(CursorHookEntrySchema)).optional()
3580
+ }).passthrough();
3581
+ var CursorMcpFileSchema = z32.object({
3582
+ mcpServers: z32.record(z32.string(), z32.object({}).passthrough()).optional()
3583
+ }).passthrough();
3584
+ var CURSOR_HOOKS = {
3585
+ sessionStart: `${CLI_BIN} hook session-start --format cursor`,
3586
+ beforeShellExecution: `${CLI_BIN} hook pre-tool-use --format cursor`,
3587
+ stop: `${CLI_BIN} hook stop --format cursor`,
3588
+ preCompact: `${CLI_BIN} hook pre-compact --format cursor`
3589
+ };
3590
+ function upsertCursorHook(existing, command) {
3591
+ const entries = existing ?? [];
3592
+ return entries.some((entry) => entry.command === command) ? entries : [...entries, { command }];
3593
+ }
3594
+ var cursorInstaller = {
3595
+ kind: AgentKind.CURSOR,
3596
+ markers: [".cursor", "AGENTS.md"],
3597
+ plan(ctx) {
3598
+ const existingHooks = parseForeignJson(
3599
+ ctx.readFile(CURSOR_HOOKS_PATH),
3600
+ CursorHooksFileSchema,
3601
+ CURSOR_HOOKS_PATH
3602
+ );
3603
+ const existingMcp = parseForeignJson(
3604
+ ctx.readFile(CURSOR_MCP_PATH),
3605
+ CursorMcpFileSchema,
3606
+ CURSOR_MCP_PATH
3607
+ );
3608
+ let hooks = { ...existingHooks?.hooks };
3609
+ for (const [event, command] of Object.entries(CURSOR_HOOKS)) {
3610
+ hooks = { ...hooks, [event]: upsertCursorHook(hooks[event], command) };
3611
+ }
3612
+ return {
3613
+ kind: AgentKind.CURSOR,
3614
+ files: [
3615
+ {
3616
+ path: CURSOR_HOOKS_PATH,
3617
+ // `version: 1` — обязательное поле формата Cursor; чужое
3618
+ // значение (если файл уже был) не трогаем.
3619
+ content: formatJson({ version: 1, ...existingHooks, hooks })
3620
+ },
3621
+ {
3622
+ path: CURSOR_MCP_PATH,
3623
+ content: formatJson({
3624
+ ...existingMcp,
3625
+ mcpServers: {
3626
+ ...existingMcp?.mcpServers,
3627
+ [CLI_BIN]: { command: CLI_BIN, args: ["mcp"] }
3628
+ }
3629
+ })
3630
+ },
3631
+ {
3632
+ path: INSTRUCTIONS_FILE_AGENTS,
3633
+ content: upsertClaudeMdSection(ctx.readFile(INSTRUCTIONS_FILE_AGENTS))
3634
+ }
3635
+ ],
3636
+ notes: [
3637
+ "\u0417\u0430\u0449\u0438\u0442\u0430 \u0444\u0430\u0439\u043B\u043E\u0432 (protectedPaths \u043F\u0440\u043E\u0444\u0438\u043B\u044F \u0434\u043E\u0432\u0435\u0440\u0438\u044F) \u043D\u0430 Cursor \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0430: \u043F\u0435\u0440\u0435\u0445\u0432\u0430\u0442 \u0448\u0435\u043B\u043B\u0430 \u0443 Cursor \u0437\u0430\u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0438\u0440\u043E\u0432\u0430\u043D, \u0430 \u0444\u043E\u0440\u043C\u0430\u0442 \u043E\u0431\u0449\u0435\u0433\u043E preToolUse \u2014 \u043D\u0435\u0442, \u0438 \u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0445\u0443\u043A \u043D\u0430 \u0441\u043E\u0431\u044B\u0442\u0438\u0435 \u0441 \u043D\u0435\u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043D\u044B\u043C \u0444\u043E\u0440\u043C\u0430\u0442\u043E\u043C \u043E\u043F\u0430\u0441\u043D\u043E \u2014 \u0443 Cursor \u043E\u0442\u0432\u0435\u0442 \u043D\u0435 \u043F\u043E \u0441\u0445\u0435\u043C\u0435 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u0435\u0442 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0446\u0435\u043B\u0438\u043A\u043E\u043C. \u0421\u0442\u043E\u043F-\u043F\u0430\u0442\u0442\u0435\u0440\u043D\u044B \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0434\u043B\u044F \u043A\u043E\u043C\u0430\u043D\u0434 \u0440\u0430\u0431\u043E\u0442\u0430\u044E\u0442.",
3638
+ "\u0423 Cursor \u043D\u0435\u0442 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \xAB\u043C\u043E\u0434\u0435\u043B\u044C \u0443\u043F\u0451\u0440\u043B\u0430\u0441\u044C \u0432 \u043B\u0438\u043C\u0438\u0442 \u043F\u043E\u0434\u043F\u0438\u0441\u043A\u0438\xBB (\u0430\u043D\u0430\u043B\u043E\u0433\u0430 StopFailure): \u0430\u0433\u0435\u043D\u0442 \u043D\u0435 \u043F\u0435\u0440\u0435\u0439\u0434\u0451\u0442 \u0432 RATE_LIMITED \u0441\u0430\u043C."
3639
+ ],
3640
+ restartHint: "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u044E Cursor: MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u0438."
3641
+ };
3642
+ }
3643
+ };
3644
+
3645
+ // src/agents/opencode.ts
3646
+ import { z as z33 } from "zod";
3647
+
3648
+ // src/agents/opencode-plugin.ts
3649
+ var OPENCODE_PLUGIN_FILENAME = "quiel.js";
3650
+ var OPENCODE_PLUGIN_PATH = `.opencode/plugins/${OPENCODE_PLUGIN_FILENAME}`;
3651
+ var OPENCODE_PLUGIN_SOURCE = `// \u042D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u0441\u043E\u0437\u0434\u0430\u043D "quiel init". \u041F\u0440\u0430\u0432\u043A\u0438 \u0437\u0430\u0442\u0440\u0443\u0442\u0441\u044F \u043F\u0440\u0438 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435.
3652
+ //
3653
+ // \u041F\u043B\u0430\u0433\u0438\u043D \u043D\u0430\u043C\u0435\u0440\u0435\u043D\u043D\u043E \u0442\u043E\u043D\u043A\u0438\u0439: \u043E\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0435\u0440\u0435\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0435\u0442 \u0432\u0445\u043E\u0434 \u0432 "${CLI_BIN} hook"
3654
+ // \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043E\u0442\u0432\u0435\u0442 \u0432 \u0442\u043E, \u0447\u0442\u043E \u043F\u043E\u043D\u0438\u043C\u0430\u0435\u0442 OpenCode. \u0412\u0441\u044F \u043B\u043E\u0433\u0438\u043A\u0430 \u2014 \u0441\u0442\u043E\u043F-
3655
+ // \u043F\u0430\u0442\u0442\u0435\u0440\u043D\u044B, \u0430\u0440\u0435\u043D\u0434\u0430, \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u2014 \u0436\u0438\u0432\u0451\u0442 \u0432 \u043A\u043B\u0438\u0435\u043D\u0442\u0435 Quiel \u0438 \u043F\u043E\u043A\u0440\u044B\u0442\u0430 \u0442\u0430\u043C
3656
+ // \u0442\u0435\u0441\u0442\u0430\u043C\u0438.
3657
+ import { spawnSync } from 'node:child_process'
3658
+
3659
+ /** \u0421\u0442\u043E\u043B\u044C\u043A\u043E \u0436\u0435, \u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0442\u0430\u0439\u043C\u0430\u0443\u0442 PreToolUse \u0443 Claude Code (\xA710.4 \u0422\u0417). */
3660
+ const HOOK_TIMEOUT_MS = 5000
3661
+
3662
+ function callQuiel(hook, payload) {
3663
+ try {
3664
+ return spawnSync(${JSON.stringify(CLI_BIN)}, ['hook', hook, '--format', 'opencode'], {
3665
+ input: JSON.stringify(payload),
3666
+ encoding: 'utf8',
3667
+ timeout: HOOK_TIMEOUT_MS,
3668
+ })
3669
+ } catch {
3670
+ // \u041A\u043B\u0438\u0435\u043D\u0442 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0438\u043B\u0438 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u0442\u0438\u043B\u0441\u044F \u2014 \u044D\u0442\u043E \u043D\u0435 \u043F\u043E\u0432\u043E\u0434 \u0437\u0430\u043F\u0438\u0440\u0430\u0442\u044C
3671
+ // \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0443 \u0435\u0433\u043E \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442. \u0417\u0430\u043F\u0440\u0435\u0449\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u043A\u043E\u0434 2, \u0441\u043C. \u043D\u0438\u0436\u0435.
3672
+ return { status: 0, stderr: '' }
3673
+ }
3674
+ }
3675
+
3676
+ export const QuielPlugin = async ({ directory }) => {
3677
+ return {
3678
+ 'tool.execute.before': async (input, output) => {
3679
+ const result = callQuiel('pre-tool-use', { directory, input, output })
3680
+ if (result.status === 2) {
3681
+ throw new Error((result.stderr || '').trim() || 'Quiel: \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0437\u0430\u043F\u0440\u0435\u0449\u0435\u043D\u043E \u043F\u0440\u043E\u0444\u0438\u043B\u0435\u043C \u0434\u043E\u0432\u0435\u0440\u0438\u044F')
3682
+ }
3683
+ },
3684
+ event: async ({ event }) => {
3685
+ if (event.type === 'session.created') callQuiel('session-start', { directory, event })
3686
+ else if (event.type === 'session.idle') callQuiel('stop', { directory, event })
3687
+ else if (event.type === 'session.compacted') callQuiel('pre-compact', { directory, event })
3688
+ },
3689
+ }
3690
+ }
3691
+ `;
3692
+
3693
+ // src/agents/opencode.ts
3694
+ var OPENCODE_CONFIG_PATH = "opencode.json";
3695
+ var OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
3696
+ var OpencodeConfigSchema = z33.object({
3697
+ $schema: z33.string().optional(),
3698
+ mcp: z33.record(z33.string(), z33.object({}).passthrough()).optional()
3699
+ }).passthrough();
3700
+ var opencodeInstaller = {
3701
+ kind: AgentKind.OPENCODE,
3702
+ markers: [".opencode", "opencode.json", "AGENTS.md"],
3703
+ plan(ctx) {
3704
+ const existing = parseForeignJson(
3705
+ ctx.readFile(OPENCODE_CONFIG_PATH),
3706
+ OpencodeConfigSchema,
3707
+ OPENCODE_CONFIG_PATH
3708
+ );
3709
+ return {
3710
+ kind: AgentKind.OPENCODE,
3711
+ files: [
3712
+ {
3713
+ path: OPENCODE_CONFIG_PATH,
3714
+ content: formatJson({
3715
+ $schema: OPENCODE_SCHEMA_URL,
3716
+ ...existing,
3717
+ mcp: {
3718
+ ...existing?.mcp,
3719
+ [CLI_BIN]: { type: "local", command: [CLI_BIN, "mcp"], enabled: true }
3720
+ }
3721
+ })
3722
+ },
3723
+ { path: OPENCODE_PLUGIN_PATH, content: OPENCODE_PLUGIN_SOURCE },
3724
+ {
3725
+ path: INSTRUCTIONS_FILE_AGENTS,
3726
+ content: upsertClaudeMdSection(ctx.readFile(INSTRUCTIONS_FILE_AGENTS))
3727
+ }
3728
+ ],
3729
+ notes: [
3730
+ "\u0418\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u0434\u044B\u0440\u0430 OpenCode: \u0432\u044B\u0437\u043E\u0432\u044B \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u0418\u0417 \u0421\u0423\u0411\u0410\u0413\u0415\u041D\u0422\u0410 \u043C\u043E\u0433\u043B\u0438 \u043D\u0435 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0442\u044C \u043F\u043B\u0430\u0433\u0438\u043D \u0432\u043E\u0432\u0441\u0435 (issue #5894 \u2014 \u0437\u0430\u043A\u0440\u044B\u0442, \u043D\u043E \u0432\u0435\u0440\u0441\u0438\u044F \u0441 \u0438\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0432 \u043D\u0451\u043C \u043D\u0435 \u043D\u0430\u0437\u0432\u0430\u043D\u0430). \u041D\u0430\u0448\u0430 \u0441\u0435\u043A\u0446\u0438\u044F \u0432 AGENTS.md \u0432\u0435\u043B\u0438\u0442 \u0430\u0433\u0435\u043D\u0442\u0443 \u0434\u0435\u043B\u0435\u0433\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0441\u0443\u0431\u0430\u0433\u0435\u043D\u0442\u0443, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u044D\u0442\u043E \u043F\u0435\u0440\u0432\u044B\u043C \u0434\u0435\u043B\u043E\u043C: \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442\u0435 \u0430\u0433\u0435\u043D\u0442\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0437\u0430\u0432\u0435\u0434\u043E\u043C\u043E \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u0443\u044E \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0447\u0435\u0440\u0435\u0437 \u0441\u0443\u0431\u0430\u0433\u0435\u043D\u0442\u0430 \u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044C, \u0447\u0442\u043E \u043E\u043D\u0430 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u0430.",
3731
+ `\u0422\u0430\u0439\u043C\u0430\u0443\u0442 \u0432\u044B\u0437\u043E\u0432\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 \u0434\u043B\u044F MCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043D\u0435 \u0432\u044B\u0441\u0442\u0430\u0432\u043B\u0435\u043D: \u0432 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438 OpenCode \u0443 \u043F\u043E\u043B\u044F timeout \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430 \u0435\u0434\u0438\u043D\u0438\u0446\u0430 \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044F, \u0430 \u043E\u0448\u0438\u0431\u043A\u0430 \u0437\u0434\u0435\u0441\u044C \u0441\u043B\u043E\u043C\u0430\u043B\u0430 \u0431\u044B \u0432\u044B\u0437\u043E\u0432\u044B \u0432\u043E\u0432\u0441\u0435. \u0415\u0441\u043B\u0438 \u0431\u043B\u043E\u043A\u0438\u0440\u0443\u044E\u0449\u0438\u0439 next_task \u043E\u0431\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u2014 \u0432\u044B\u0441\u0442\u0430\u0432\u044C\u0442\u0435 timeout \u0432\u0440\u0443\u0447\u043D\u0443\u044E \u0432 ${OPENCODE_CONFIG_PATH}.`
3732
+ ],
3733
+ restartHint: "\u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u0435 OpenCode: \u043F\u043B\u0430\u0433\u0438\u043D \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u044E\u0442\u0441\u044F \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435."
3734
+ };
3735
+ }
3736
+ };
3737
+
3738
+ // src/agents/version.ts
3739
+ var VERSION_ENV = {
3740
+ [AgentKind.CLAUDE_CODE]: "CLAUDE_CODE_VERSION"
3741
+ };
3742
+ var UNKNOWN_AGENT_VERSION = "unknown";
3743
+ function readAgentVersion(kind, env = process.env) {
3744
+ const name = VERSION_ENV[kind];
3745
+ const value = name ? env[name] : void 0;
3746
+ return value && value.trim().length > 0 ? value.trim() : UNKNOWN_AGENT_VERSION;
3747
+ }
3748
+
3749
+ // src/agents/index.ts
3750
+ var INSTALLERS = [
3751
+ claudeCodeInstaller,
3752
+ codexInstaller,
3753
+ cursorInstaller,
3754
+ opencodeInstaller
3755
+ ];
3756
+ var BY_KIND2 = new Map(INSTALLERS.map((i) => [i.kind, i]));
3757
+ function installerFor(kind) {
3758
+ const installer = BY_KIND2.get(kind);
3759
+ if (!installer) {
3760
+ throw new Error(
3761
+ `\u0414\u043B\u044F \u0432\u0438\u0434\u0430 \u0430\u0433\u0435\u043D\u0442\u0430 ${kind} \u043D\u0435\u0442 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0449\u0438\u043A\u0430. \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u0435 @quiel/cli: \u0432\u0438\u0434\u0438\u043C\u043E, \u0440\u0435\u0435\u0441\u0442\u0440 \u043D\u043E\u0432\u0435\u0435 \u044D\u0442\u043E\u0433\u043E \u043F\u0430\u043A\u0435\u0442\u0430.`
3762
+ );
3763
+ }
3764
+ return installer;
3765
+ }
3766
+ function detectAgentKinds(exists) {
3767
+ return INSTALLERS.filter((installer) => installer.markers.some((marker) => exists(marker))).map(
3768
+ (installer) => installer.kind
3769
+ );
3360
3770
  }
3361
3771
 
3362
3772
  // src/cli/context.ts
@@ -3444,6 +3854,156 @@ function trimTrailingSlash(url) {
3444
3854
  return url.endsWith("/") ? url.slice(0, -1) : url;
3445
3855
  }
3446
3856
 
3857
+ // src/hooks/format.ts
3858
+ import { z as z34 } from "zod";
3859
+ var HOOK_FORMATS = ["claude", "cursor", "opencode"];
3860
+ function asHookFormat(raw) {
3861
+ const value = raw.trim().toLowerCase();
3862
+ return HOOK_FORMATS.includes(value) ? value : null;
3863
+ }
3864
+ var CLAUDE_EVENT = {
3865
+ "session-start": "SessionStart",
3866
+ stop: "Stop",
3867
+ "stop-failure": "StopFailure",
3868
+ "pre-tool-use": "PreToolUse",
3869
+ "pre-compact": "PreCompact"
3870
+ };
3871
+ var CursorShellInputSchema = z34.object({ command: z34.string(), cwd: z34.string().optional() }).passthrough();
3872
+ var CursorStopInputSchema = z34.object({ status: z34.string().optional(), loop_count: z34.number().optional() }).passthrough();
3873
+ function cursorPermission(decision) {
3874
+ if (decision === "deny") return "deny";
3875
+ if (decision === "allow") return "allow";
3876
+ return "ask";
3877
+ }
3878
+ var OpencodePayloadSchema = z34.object({
3879
+ directory: z34.string().optional(),
3880
+ input: z34.object({ tool: z34.string().optional() }).passthrough().optional(),
3881
+ output: z34.object({ args: z34.record(z34.string(), z34.unknown()).optional() }).passthrough().optional(),
3882
+ event: z34.object({ type: z34.string().optional() }).passthrough().optional()
3883
+ }).passthrough();
3884
+ var OPENCODE_TOOLS = {
3885
+ bash: { name: "Bash", arg: "command", field: "command" },
3886
+ write: { name: "Write", arg: "filePath", field: "file_path" },
3887
+ edit: { name: "Edit", arg: "filePath", field: "file_path" }
3888
+ };
3889
+ var OPENCODE_UNPARSED_BASH = "Quiel \u043D\u0435 \u0441\u043C\u043E\u0433 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u0442\u044C \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u044B shell-\u043A\u043E\u043C\u0430\u043D\u0434\u044B OpenCode \u0438 \u0437\u0430\u043F\u0440\u0435\u0442\u0438\u043B \u0435\u0451 \u0438\u0437 \u043E\u0441\u0442\u043E\u0440\u043E\u0436\u043D\u043E\u0441\u0442\u0438: \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u043C\u0438\u043C\u043E \u0441\u0442\u043E\u043F-\u043F\u0430\u0442\u0442\u0435\u0440\u043D\u043E\u0432 \u043E\u043F\u0430\u0441\u043D\u0435\u0435, \u0447\u0435\u043C \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C\u0441\u044F. \u0421\u043E\u043E\u0431\u0449\u0438\u0442\u0435 \u043E\u0431 \u044D\u0442\u043E\u043C \u2014 \u043D\u0443\u0436\u0435\u043D \u0440\u0430\u0437\u0431\u043E\u0440 \u0444\u043E\u0440\u043C\u0430\u0442\u0430 \u0432\u0430\u0448\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 OpenCode.";
3890
+ function translateHookInput(format, hook, raw, cwd) {
3891
+ if (format === "claude") return raw;
3892
+ const event = CLAUDE_EVENT[hook];
3893
+ if (format === "cursor") {
3894
+ if (hook === "pre-tool-use") {
3895
+ const parsed2 = CursorShellInputSchema.safeParse(raw);
3896
+ if (!parsed2.success) return null;
3897
+ return {
3898
+ hook_event_name: event,
3899
+ session_id: "cursor",
3900
+ cwd: parsed2.data.cwd ?? cwd,
3901
+ tool_name: "Bash",
3902
+ tool_input: { command: parsed2.data.command },
3903
+ tool_use_id: "cursor"
3904
+ };
3905
+ }
3906
+ if (hook === "stop") {
3907
+ const parsed2 = CursorStopInputSchema.safeParse(raw);
3908
+ return {
3909
+ hook_event_name: event,
3910
+ session_id: "cursor",
3911
+ cwd,
3912
+ stop_hook_active: (parsed2.success ? parsed2.data.loop_count ?? 0 : 0) > 0
3913
+ };
3914
+ }
3915
+ if (hook === "session-start") {
3916
+ return { hook_event_name: event, session_id: "cursor", cwd, source: "startup" };
3917
+ }
3918
+ if (hook === "pre-compact") {
3919
+ return { hook_event_name: event, session_id: "cursor", cwd, trigger: "auto" };
3920
+ }
3921
+ return null;
3922
+ }
3923
+ const parsed = OpencodePayloadSchema.safeParse(raw);
3924
+ if (!parsed.success) return null;
3925
+ const directory = parsed.data.directory ?? cwd;
3926
+ if (hook === "pre-tool-use") {
3927
+ const tool = OPENCODE_TOOLS[parsed.data.input?.tool ?? ""];
3928
+ if (!tool) return null;
3929
+ const value = parsed.data.output?.args?.[tool.arg];
3930
+ if (typeof value !== "string") {
3931
+ return tool.name === "Bash" ? { __quielDenyReason: OPENCODE_UNPARSED_BASH } : null;
3932
+ }
3933
+ return {
3934
+ hook_event_name: event,
3935
+ session_id: "opencode",
3936
+ cwd: directory,
3937
+ tool_name: tool.name,
3938
+ tool_input: { [tool.field]: value },
3939
+ tool_use_id: "opencode"
3940
+ };
3941
+ }
3942
+ if (hook === "session-start") {
3943
+ return { hook_event_name: event, session_id: "opencode", cwd: directory, source: "startup" };
3944
+ }
3945
+ if (hook === "stop") {
3946
+ return {
3947
+ hook_event_name: event,
3948
+ session_id: "opencode",
3949
+ cwd: directory,
3950
+ stop_hook_active: false
3951
+ };
3952
+ }
3953
+ if (hook === "pre-compact") {
3954
+ return { hook_event_name: event, session_id: "opencode", cwd: directory, trigger: "auto" };
3955
+ }
3956
+ return null;
3957
+ }
3958
+ function forcedDenyReason(translated) {
3959
+ if (translated && typeof translated === "object" && "__quielDenyReason" in translated) {
3960
+ return String(translated.__quielDenyReason);
3961
+ }
3962
+ return null;
3963
+ }
3964
+ function renderHookOutput(format, hook, output) {
3965
+ const value = output ?? {};
3966
+ if (format === "claude") {
3967
+ return { stdout: JSON.stringify(value), stderr: "", exitCode: 0 };
3968
+ }
3969
+ const decision = value.hookSpecificOutput?.permissionDecision;
3970
+ const reason = value.hookSpecificOutput?.permissionDecisionReason ?? value.reason ?? "";
3971
+ if (format === "opencode") {
3972
+ if (hook === "pre-tool-use" && decision === "deny") {
3973
+ return { stdout: "{}", stderr: reason, exitCode: 2 };
3974
+ }
3975
+ return { stdout: "{}", stderr: "", exitCode: 0 };
3976
+ }
3977
+ if (hook === "pre-tool-use") {
3978
+ const permission = cursorPermission(decision);
3979
+ return {
3980
+ stdout: JSON.stringify({
3981
+ permission,
3982
+ ...permission === "deny" ? { user_message: reason, agent_message: reason } : {}
3983
+ }),
3984
+ stderr: "",
3985
+ exitCode: 0
3986
+ };
3987
+ }
3988
+ if (hook === "stop") {
3989
+ const followup = value.decision === "block" ? value.reason ?? "" : "";
3990
+ return {
3991
+ stdout: JSON.stringify(followup.length > 0 ? { followup_message: followup } : {}),
3992
+ stderr: "",
3993
+ exitCode: 0
3994
+ };
3995
+ }
3996
+ if (hook === "session-start") {
3997
+ const context = value.hookSpecificOutput?.additionalContext ?? "";
3998
+ return {
3999
+ stdout: JSON.stringify(context.length > 0 ? { additional_context: context } : {}),
4000
+ stderr: "",
4001
+ exitCode: 0
4002
+ };
4003
+ }
4004
+ return { stdout: "{}", stderr: "", exitCode: 0 };
4005
+ }
4006
+
3447
4007
  // src/socket/auth-reject.ts
3448
4008
  var AUTH_REJECT_REASONS = /* @__PURE__ */ new Set(["missing", "invalid"]);
3449
4009
  function isAuthRejection(error) {
@@ -3516,7 +4076,8 @@ function resolveOptions(options) {
3516
4076
  roleKeys: options.roleKeys,
3517
4077
  cwd: options.cwd,
3518
4078
  clientVersion: options.clientVersion,
3519
- claudeVersion: options.claudeVersion ?? "unknown",
4079
+ agentVersion: options.agentVersion ?? "unknown",
4080
+ agentKind: options.agentKind ?? DEFAULT_AGENT_KIND,
3520
4081
  os: options.os ?? process.platform,
3521
4082
  // По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
3522
4083
  // а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
@@ -3710,7 +4271,12 @@ var AgentSocketClient = class extends EventEmitter {
3710
4271
  clientVersion: this.opts.clientVersion,
3711
4272
  cwd: this.opts.cwd,
3712
4273
  os: this.opts.os,
3713
- claudeVersion: this.opts.claudeVersion,
4274
+ agentKind: this.opts.agentKind,
4275
+ agentVersion: this.opts.agentVersion,
4276
+ // `claudeVersion` шлётся ТОЛЬКО для Claude Code — и только чтобы
4277
+ // клиент работал против ещё не обновлённого сервера, который знает
4278
+ // одно это поле. Для Codex поле с таким именем было бы неправдой.
4279
+ ...this.opts.agentKind === AgentKind.CLAUDE_CODE ? { claudeVersion: this.opts.agentVersion } : {},
3714
4280
  roleKeys: [...this.opts.roleKeys],
3715
4281
  ...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
3716
4282
  });
@@ -4104,12 +4670,15 @@ ${question.answerMd ?? ""}`;
4104
4670
  // package.json
4105
4671
  var package_default = {
4106
4672
  name: "@quiel/cli",
4107
- version: "0.2.0",
4108
- description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 Claude Code \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
4673
+ version: "0.4.0",
4674
+ description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442 \u0430\u0433\u0435\u043D\u0442\u0430 \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u0430 (Claude Code, Codex, Cursor, OpenCode) \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435",
4109
4675
  homepage: "https://quiel.ai",
4110
4676
  keywords: [
4111
4677
  "quiel",
4112
4678
  "claude-code",
4679
+ "codex",
4680
+ "cursor",
4681
+ "opencode",
4113
4682
  "mcp",
4114
4683
  "mcp-server",
4115
4684
  "ai-agents",
@@ -4160,7 +4729,17 @@ var MCP_SERVER_NAME = "quiel";
4160
4729
  var MCP_SERVER_VERSION = VERSION;
4161
4730
 
4162
4731
  // src/mcp/get-context.ts
4163
- var ALL_CONTEXT_INCLUDES = ["spec", "decisions", "related", "repo"];
4732
+ var ALL_CONTEXT_INCLUDES = [
4733
+ "spec",
4734
+ "decisions",
4735
+ // ВСЕ документы проекта — по умолчанию, а не по особому запросу.
4736
+ // `spec`/`decisions` покрывают два типа из пяти, и человек, положивший
4737
+ // контекст под типом `DATA` или `PLAN`, вправе ожидать, что агент его
4738
+ // увидит. 14 сентября 2026 ровно этого и не случилось.
4739
+ "documents",
4740
+ "related",
4741
+ "repo"
4742
+ ];
4164
4743
  var getContextInputShape = {
4165
4744
  taskId: contextGetPayloadSchema.shape.taskId.describe(
4166
4745
  "ID \u0437\u0430\u0434\u0430\u0447\u0438, \u0434\u043B\u044F \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u043D\u0443\u0436\u0435\u043D \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442. \u0415\u0441\u043B\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0434\u0430\u043D \u2014 \u0431\u0435\u0440\u0451\u0442\u0441\u044F \u0442\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0430\u0433\u0435\u043D\u0442\u0430 (\u0435\u0441\u043B\u0438 \u0435\u0441\u0442\u044C)."
@@ -5052,7 +5631,7 @@ async function settleApproval(ack, commandCwd, runtime, waitSecUsed) {
5052
5631
  }
5053
5632
 
5054
5633
  // src/mcp/set-mode.ts
5055
- import { z as z30 } from "zod";
5634
+ import { z as z35 } from "zod";
5056
5635
 
5057
5636
  // src/cli/mode.ts
5058
5637
  function parseModeArg(raw) {
@@ -5093,7 +5672,7 @@ async function requestModeChange(modeArg, options = {}) {
5093
5672
 
5094
5673
  // src/mcp/set-mode.ts
5095
5674
  var setModeInputShape = {
5096
- mode: z30.enum(["auto", "manual"]).describe("auto \u2014 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430, manual \u2014 \u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C (\xA710.2 \u0422\u0417)")
5675
+ mode: z35.enum(["auto", "manual"]).describe("auto \u2014 \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0430\u0432\u0442\u043E\u043D\u043E\u043C\u043D\u044B\u0439 \u0440\u0435\u0436\u0438\u043C \u0430\u0433\u0435\u043D\u0442\u0430, manual \u2014 \u0432\u044B\u043A\u043B\u044E\u0447\u0438\u0442\u044C (\xA710.2 \u0422\u0417)")
5097
5676
  };
5098
5677
  async function setModeHandler(args, runtime) {
5099
5678
  const result = await requestModeChange(args.mode, {
@@ -5108,20 +5687,20 @@ async function setModeHandler(args, runtime) {
5108
5687
  }
5109
5688
 
5110
5689
  // src/mcp/submit-work.ts
5111
- import { z as z31 } from "zod";
5690
+ import { z as z36 } from "zod";
5112
5691
  var submitWorkInputShape = {
5113
- branchName: z31.string().min(1).describe(
5692
+ branchName: z36.string().min(1).describe(
5114
5693
  "\u0418\u043C\u044F \u0432\u0435\u0442\u043A\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0431\u0435\u0440\u0438 \u0440\u043E\u0432\u043D\u043E \u0438\u0437 project.branchName \u0432 TaskPayload, \u043A\u043E\u0442\u043E\u0440\u044B\u0439 \u0432\u0435\u0440\u043D\u0443\u043B next_task. \u041D\u0435 \u043F\u0440\u0438\u0434\u0443\u043C\u044B\u0432\u0430\u0439 \u0441\u0432\u043E\u0451."
5115
5694
  ),
5116
- defaultBranch: z31.string().min(1).describe("\u041E\u0441\u043D\u043E\u0432\u043D\u0430\u044F \u0432\u0435\u0442\u043A\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u2014 \u0438\u0437 project.repos[].defaultBranch."),
5117
- commitPrefix: z31.string().min(1).describe('\u041F\u0440\u0435\u0444\u0438\u043A\u0441 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u0438\u0437 project.commitPrefix, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 "ATL-42:".'),
5118
- taskKey: z31.string().min(1).describe("\u041A\u043B\u044E\u0447 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 ATL-42 \u2014 \u0438\u0437 task.key."),
5119
- taskTitle: z31.string().min(1).describe("\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0438\u0437 task.title."),
5120
- repoRoot: z31.string().min(1).optional().describe("\u041A\u043E\u0440\u0435\u043D\u044C git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u2014 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430."),
5121
- testsPassed: z31.boolean().optional().describe(
5695
+ defaultBranch: z36.string().min(1).describe("\u041E\u0441\u043D\u043E\u0432\u043D\u0430\u044F \u0432\u0435\u0442\u043A\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F \u2014 \u0438\u0437 project.repos[].defaultBranch."),
5696
+ commitPrefix: z36.string().min(1).describe('\u041F\u0440\u0435\u0444\u0438\u043A\u0441 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043A\u043E\u043C\u043C\u0438\u0442\u0430 \u2014 \u0438\u0437 project.commitPrefix, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 "ATL-42:".'),
5697
+ taskKey: z36.string().min(1).describe("\u041A\u043B\u044E\u0447 \u0437\u0430\u0434\u0430\u0447\u0438, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 ATL-42 \u2014 \u0438\u0437 task.key."),
5698
+ taskTitle: z36.string().min(1).describe("\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A \u0437\u0430\u0434\u0430\u0447\u0438 \u2014 \u0438\u0437 task.title."),
5699
+ repoRoot: z36.string().min(1).optional().describe("\u041A\u043E\u0440\u0435\u043D\u044C git-\u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u2014 \u0440\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430."),
5700
+ testsPassed: z36.boolean().optional().describe(
5122
5701
  "\u041F\u0440\u043E\u0448\u043B\u0438 \u043B\u0438 \u0442\u0435\u0441\u0442\u044B. \u0415\u0441\u043B\u0438 \u043F\u0440\u043E\u0435\u043A\u0442 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0437\u0435\u043B\u0451\u043D\u044B\u0445 \u0442\u0435\u0441\u0442\u043E\u0432, \u0430 \u0437\u0434\u0435\u0441\u044C false \u2014 \u0441\u0434\u0430\u0447\u0430 \u043D\u0435 \u0441\u043E\u0441\u0442\u043E\u0438\u0442\u0441\u044F, \u0438 \u044D\u0442\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u043E."
5123
5702
  ),
5124
- requireTestsPassed: z31.boolean().optional().describe("ProjectSettings.requireTests \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0438\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0438.")
5703
+ requireTestsPassed: z36.boolean().optional().describe("ProjectSettings.requireTests \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u2014 \u0438\u0437 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0430 \u0437\u0430\u0434\u0430\u0447\u0438.")
5125
5704
  };
5126
5705
  async function submitWorkHandler(args, runtime) {
5127
5706
  const state = readState(runtime.stateFilePath);
@@ -5388,7 +5967,7 @@ function registerTools(server, runtime) {
5388
5967
  "get_context",
5389
5968
  {
5390
5969
  title: "\u041F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u0437\u0430\u0434\u0430\u0447\u0438",
5391
- description: "\u0421\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430, \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B-\u0440\u0435\u0448\u0435\u043D\u0438\u044F, \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435/\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430\u043C\u0438, \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u0432\u0435\u0442\u043A\u0443 (\xA710.3 \u0422\u0417). \u0412\u0410\u0416\u041D\u041E: \u0443 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C \u0442\u0435\u043A\u0441\u0442\u0430 \u2014 \u0442\u043E\u0433\u0434\u0430 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u043F\u043E\u043B\u0435 `file` (\u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u0430 \u0444\u0430\u0439\u043B\u043E\u043C, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 PDF). \u0412 \u044D\u0442\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0441\u043A\u0430\u0447\u0430\u0439 `file.downloadUrl` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0444\u0430\u0439\u043B: \u043F\u0443\u0441\u0442\u043E\u0439 `contentMd` \u0440\u044F\u0434\u043E\u043C \u0441 \u043D\u0435\u043F\u0443\u0441\u0442\u044B\u043C `file` \u041D\u0415 \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043D\u0435\u0442. \u0421\u0441\u044B\u043B\u043A\u0430 \u0436\u0438\u0432\u0451\u0442 `file.expiresInSeconds` \u0441\u0435\u043A\u0443\u043D\u0434 \u2014 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u0439 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043D\u0435 \u043E\u0442\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0439. \u041F\u0443\u0441\u0442\u0430\u044F `downloadUrl` \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E: \u0442\u043E\u0433\u0434\u0430 \u0441\u043F\u0440\u043E\u0441\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 `ask_human`, \u0430 \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0431\u0435\u0437 \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438.",
5970
+ description: "\u0412\u0421\u0415 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (\u043F\u043E\u043B\u0435 `documents`, \u043B\u044E\u0431\u043E\u0433\u043E \u0442\u0438\u043F\u0430), \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0438 \u0440\u0435\u0448\u0435\u043D\u0438\u044F \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E, \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435/\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u044B\u0435 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430\u043C\u0438, \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u0438 \u0432\u0435\u0442\u043A\u0443 (\xA710.3 \u0422\u0417). \u0412\u0410\u0416\u041D\u041E: \u0443 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043C\u043E\u0436\u0435\u0442 \u043D\u0435 \u0431\u044B\u0442\u044C \u0442\u0435\u043A\u0441\u0442\u0430 \u2014 \u0442\u043E\u0433\u0434\u0430 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D\u043E \u043F\u043E\u043B\u0435 `file` (\u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u044F \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u0430 \u0444\u0430\u0439\u043B\u043E\u043C, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 PDF). \u0412 \u044D\u0442\u043E\u043C \u0441\u043B\u0443\u0447\u0430\u0435 \u0441\u043A\u0430\u0447\u0430\u0439 `file.downloadUrl` \u0438 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0439 \u0444\u0430\u0439\u043B: \u043F\u0443\u0441\u0442\u043E\u0439 `contentMd` \u0440\u044F\u0434\u043E\u043C \u0441 \u043D\u0435\u043F\u0443\u0441\u0442\u044B\u043C `file` \u041D\u0415 \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u043D\u0435\u0442. \u0421\u0441\u044B\u043B\u043A\u0430 \u0436\u0438\u0432\u0451\u0442 `file.expiresInSeconds` \u0441\u0435\u043A\u0443\u043D\u0434 \u2014 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u0439 \u0441\u0440\u0430\u0437\u0443, \u0430 \u043D\u0435 \u043E\u0442\u043A\u043B\u0430\u0434\u044B\u0432\u0430\u0439. \u041F\u0443\u0441\u0442\u0430\u044F `downloadUrl` \u043E\u0437\u043D\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E: \u0442\u043E\u0433\u0434\u0430 \u0441\u043F\u0440\u043E\u0441\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0447\u0435\u0440\u0435\u0437 `ask_human`, \u0430 \u043D\u0435 \u0440\u0430\u0431\u043E\u0442\u0430\u0439 \u0431\u0435\u0437 \u0441\u043F\u0435\u0446\u0438\u0444\u0438\u043A\u0430\u0446\u0438\u0438.",
5392
5971
  inputSchema: getContextInputShape
5393
5972
  },
5394
5973
  (args) => getContextHandler(args, runtime)
@@ -5510,9 +6089,10 @@ function resolveMcpRuntime(options = {}) {
5510
6089
  roleKeys: context.config.roleKeys,
5511
6090
  cwd,
5512
6091
  clientVersion: MCP_SERVER_VERSION,
5513
- // Источника «версии Claude Code» вне MCP-рукопожатия нет — то же
5514
- // ограничение, что уже отмечено в `cli/connect.ts` (P2-11).
5515
- claudeVersion: options.claudeVersion ?? process.env.CLAUDE_CODE_VERSION ?? "unknown",
6092
+ agentKind: context.config.agentKind,
6093
+ // Надёжного источника версии агентской программы нет ни у кого,
6094
+ // кроме Claude Code — см. agents/version.ts.
6095
+ agentVersion: options.agentVersion ?? readAgentVersion(context.config.agentKind),
5516
6096
  stateFilePath,
5517
6097
  createSocket: options.createSocket
5518
6098
  });
@@ -5556,9 +6136,10 @@ async function runConnect(options = {}, deps = {}) {
5556
6136
  roleKeys: context.config.roleKeys,
5557
6137
  cwd,
5558
6138
  clientVersion: MCP_SERVER_VERSION,
5559
- // Источника «версии Claude Code» вне MCP-рукопожатия нет — `connect` не
5560
- // MCP-сервер, он его не проходит (см. блок «ВОПРОСЫ» отчёта P2-11).
5561
- claudeVersion: deps.claudeVersion ?? process.env.CLAUDE_CODE_VERSION ?? "unknown",
6139
+ agentKind: context.config.agentKind,
6140
+ // Надёжного источника версии агентской программы нет ни у кого,
6141
+ // кроме Claude Code — см. agents/version.ts.
6142
+ agentVersion: deps.agentVersion ?? readAgentVersion(context.config.agentKind),
5562
6143
  stateFilePath: deps.stateFilePath,
5563
6144
  createSocket: deps.createSocket
5564
6145
  });
@@ -5592,13 +6173,13 @@ function formatConnectResult(result) {
5592
6173
  }
5593
6174
 
5594
6175
  // src/hooks/pre-compact.ts
5595
- import { z as z32 } from "zod";
5596
- var PreCompactInputSchema = z32.object({
5597
- hook_event_name: z32.literal("PreCompact"),
5598
- session_id: z32.string(),
5599
- cwd: z32.string(),
5600
- trigger: z32.enum(["manual", "auto"]),
5601
- custom_instructions: z32.string().nullable()
6176
+ import { z as z37 } from "zod";
6177
+ var PreCompactInputSchema = z37.object({
6178
+ hook_event_name: z37.literal("PreCompact"),
6179
+ session_id: z37.string(),
6180
+ cwd: z37.string(),
6181
+ trigger: z37.enum(["manual", "auto"]),
6182
+ custom_instructions: z37.string().nullable()
5602
6183
  }).passthrough();
5603
6184
  function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
5604
6185
  const lastPreCompact = state.currentTask ? { ...state.currentTask, savedAt: now() } : null;
@@ -5607,14 +6188,14 @@ function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).
5607
6188
 
5608
6189
  // src/hooks/pre-tool-use.ts
5609
6190
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
5610
- import { z as z33 } from "zod";
5611
- var PreToolUseInputSchema = z33.object({
5612
- hook_event_name: z33.literal("PreToolUse"),
5613
- session_id: z33.string(),
5614
- cwd: z33.string(),
5615
- tool_name: z33.string(),
5616
- tool_input: z33.record(z33.string(), z33.unknown()),
5617
- tool_use_id: z33.string()
6191
+ import { z as z38 } from "zod";
6192
+ var PreToolUseInputSchema = z38.object({
6193
+ hook_event_name: z38.literal("PreToolUse"),
6194
+ session_id: z38.string(),
6195
+ cwd: z38.string(),
6196
+ tool_name: z38.string(),
6197
+ tool_input: z38.record(z38.string(), z38.unknown()),
6198
+ tool_use_id: z38.string()
5618
6199
  }).passthrough();
5619
6200
  var NO_TRUST_CACHE_REASON = "\u041A\u044D\u0448 \u043F\u0440\u0430\u0432\u0438\u043B \u0434\u043E\u0432\u0435\u0440\u0438\u044F (TrustRules) \u043F\u0443\u0441\u0442 \u2014 \u043A\u043B\u0438\u0435\u043D\u0442 \u043D\u0438 \u0440\u0430\u0437\u0443 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u043B \u0438\u0445 \u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 (\u043D\u0435\u0442 agent:hello/trust:updated). \u041E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0439 \u043F\u0440\u043E\u0444\u0438\u043B\u044C (full/guarded/approve-all) \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u043D\u0435\u043B\u044C\u0437\u044F, \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u043F\u043E \u043F\u0440\u0430\u0432\u0438\u043B\u0443 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (fail-closed \u043F\u0440\u0438 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E\u0441\u0442\u0438 \u0441\u0435\u0440\u0432\u0435\u0440\u0430, \xA710.4 \u0422\u0417) \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F. \u041F\u043E\u0434\u043D\u0438\u043C\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435: `quiel connect` (\u044D\u0442\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043D\u0430 \u0438\u043C\u0435\u043D\u043D\u043E \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u043B\u0443\u0447\u0430\u044F, \u0441\u043C. isBootstrapCommand) \u2014 \u043F\u043E\u0441\u043B\u0435 \u0440\u0443\u043A\u043E\u043F\u043E\u0436\u0430\u0442\u0438\u044F \u043F\u0440\u0430\u0432\u0438\u043B\u0430 \u043F\u0440\u0438\u0434\u0443\u0442, \u0438 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043D\u0430\u0447\u043D\u0443\u0442 \u043F\u0440\u043E\u0445\u043E\u0434\u0438\u0442\u044C. \u0415\u0441\u043B\u0438 \u0441\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C, \u0432\u044B\u0437\u043E\u0432\u0438 request_approval.";
5620
6201
  var BOOTSTRAP_COMMAND_RE = /^quiel\s+(connect|status)\b/;
@@ -5759,16 +6340,16 @@ function handleFileWrite(input, state, now) {
5759
6340
  }
5760
6341
 
5761
6342
  // src/hooks/run.ts
5762
- import { z as z37 } from "zod";
6343
+ import { z as z42 } from "zod";
5763
6344
 
5764
6345
  // src/hooks/session-start.ts
5765
- import { z as z34 } from "zod";
5766
- var SessionStartInputSchema = z34.object({
5767
- hook_event_name: z34.literal("SessionStart"),
5768
- session_id: z34.string(),
5769
- cwd: z34.string(),
5770
- source: z34.enum(["startup", "resume", "clear", "compact", "fork"]),
5771
- model: z34.string().optional()
6346
+ import { z as z39 } from "zod";
6347
+ var SessionStartInputSchema = z39.object({
6348
+ hook_event_name: z39.literal("SessionStart"),
6349
+ session_id: z39.string(),
6350
+ cwd: z39.string(),
6351
+ source: z39.enum(["startup", "resume", "clear", "compact", "fork"]),
6352
+ model: z39.string().optional()
5772
6353
  }).passthrough();
5773
6354
  var SESSION_RESET_SOURCES = /* @__PURE__ */ new Set([
5774
6355
  "startup",
@@ -5807,13 +6388,13 @@ function sessionStartHook(input, state) {
5807
6388
  }
5808
6389
 
5809
6390
  // src/hooks/stop.ts
5810
- import { z as z35 } from "zod";
5811
- var StopInputSchema = z35.object({
5812
- hook_event_name: z35.literal("Stop"),
5813
- session_id: z35.string(),
5814
- cwd: z35.string(),
5815
- stop_hook_active: z35.boolean(),
5816
- last_assistant_message: z35.string().optional()
6391
+ import { z as z40 } from "zod";
6392
+ var StopInputSchema = z40.object({
6393
+ hook_event_name: z40.literal("Stop"),
6394
+ session_id: z40.string(),
6395
+ cwd: z40.string(),
6396
+ stop_hook_active: z40.boolean(),
6397
+ last_assistant_message: z40.string().optional()
5817
6398
  }).passthrough();
5818
6399
  function taskLabel(state) {
5819
6400
  if (!state.currentTask) return "";
@@ -5857,8 +6438,8 @@ function stopHook(input, state) {
5857
6438
  }
5858
6439
 
5859
6440
  // src/hooks/stop-failure.ts
5860
- import { z as z36 } from "zod";
5861
- var StopFailureErrorTypeSchema = z36.enum([
6441
+ import { z as z41 } from "zod";
6442
+ var StopFailureErrorTypeSchema = z41.enum([
5862
6443
  "rate_limit",
5863
6444
  "overloaded",
5864
6445
  "authentication_failed",
@@ -5871,13 +6452,13 @@ var StopFailureErrorTypeSchema = z36.enum([
5871
6452
  "max_output_tokens",
5872
6453
  "unknown"
5873
6454
  ]);
5874
- var StopFailureInputSchema = z36.object({
5875
- hook_event_name: z36.literal("StopFailure"),
5876
- session_id: z36.string(),
5877
- cwd: z36.string(),
6455
+ var StopFailureInputSchema = z41.object({
6456
+ hook_event_name: z41.literal("StopFailure"),
6457
+ session_id: z41.string(),
6458
+ cwd: z41.string(),
5878
6459
  error: StopFailureErrorTypeSchema,
5879
- error_details: z36.unknown().optional(),
5880
- last_assistant_message: z36.string().optional()
6460
+ error_details: z41.unknown().optional(),
6461
+ last_assistant_message: z41.string().optional()
5881
6462
  }).passthrough();
5882
6463
  function extractRetryAfterSec(errorDetails) {
5883
6464
  if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
@@ -5906,7 +6487,7 @@ function stopFailureHook(input, _state) {
5906
6487
  }
5907
6488
 
5908
6489
  // src/hooks/run.ts
5909
- var HookInputSchema = z37.discriminatedUnion("hook_event_name", [
6490
+ var HookInputSchema = z42.discriminatedUnion("hook_event_name", [
5910
6491
  SessionStartInputSchema,
5911
6492
  StopInputSchema,
5912
6493
  StopFailureInputSchema,
@@ -5972,6 +6553,28 @@ function runCliHook(rawStdin, stateFilePath, cwd = process.cwd()) {
5972
6553
  }
5973
6554
  return runHook(parsed, stateFilePath ?? resolveAgentStateFilePath(cwd));
5974
6555
  }
6556
+ function runCliHookFormatted(format, hook, rawStdin, stateFilePath, cwd = process.cwd()) {
6557
+ let parsed;
6558
+ try {
6559
+ parsed = rawStdin.trim().length > 0 ? JSON.parse(rawStdin) : void 0;
6560
+ } catch {
6561
+ parsed = void 0;
6562
+ }
6563
+ const translated = translateHookInput(format, hook, parsed, cwd);
6564
+ const forced = forcedDenyReason(translated);
6565
+ if (forced !== null) {
6566
+ const rendered2 = renderHookOutput(format, hook, {
6567
+ hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason: forced }
6568
+ });
6569
+ return { ...rendered2, execution: null };
6570
+ }
6571
+ if (translated === null) {
6572
+ return { ...renderHookOutput(format, hook, {}), execution: null };
6573
+ }
6574
+ const execution = runHook(translated, stateFilePath ?? resolveAgentStateFilePath(cwd));
6575
+ const rendered = renderHookOutput(format, hook, JSON.parse(execution.stdout));
6576
+ return { ...rendered, execution };
6577
+ }
5975
6578
  function resolveAgentStateFilePath(cwd) {
5976
6579
  const dir = findProjectRoot(cwd);
5977
6580
  if (!dir) return void 0;
@@ -5980,6 +6583,10 @@ function resolveAgentStateFilePath(cwd) {
5980
6583
  return getStateFilePath(config.config.agentId);
5981
6584
  }
5982
6585
 
6586
+ // src/cli/init.ts
6587
+ import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
6588
+ import { dirname as dirname5, join as join10 } from "path";
6589
+
5983
6590
  // src/cli/prompt.ts
5984
6591
  import { createInterface } from "readline/promises";
5985
6592
  function createReadlinePrompter() {
@@ -6011,6 +6618,46 @@ var FIELD_PROMPTS = {
6011
6618
  agentId: "ID \u0430\u0433\u0435\u043D\u0442\u0430 (\u0441\u043E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0430\u0433\u0435\u043D\u0442\u0430 \u0432 UI): ",
6012
6619
  roles: "\u0420\u043E\u043B\u0438 \u0430\u0433\u0435\u043D\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440, backend,frontend): "
6013
6620
  };
6621
+ async function askAgentKind(options, prompter, interactive, cwd) {
6622
+ const fromFlag = options.agent?.trim();
6623
+ if (fromFlag && fromFlag.length > 0) {
6624
+ const kind = agentKindFromCliKey(fromFlag);
6625
+ if (!kind) {
6626
+ throw new Error(
6627
+ `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 --agent: ${fromFlag}. \u0414\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E: ${agentKindCliKeys().join(", ")}.`
6628
+ );
6629
+ }
6630
+ return kind;
6631
+ }
6632
+ if (!interactive) return DEFAULT_AGENT_KIND;
6633
+ const detected = detectAgentKinds((relative3) => existsSync10(join10(cwd, relative3)));
6634
+ const ordered = [
6635
+ ...AGENT_KINDS.filter((info) => detected.includes(info.kind)),
6636
+ ...AGENT_KINDS.filter((info) => !detected.includes(info.kind))
6637
+ ];
6638
+ const lines = ["", "\u041A\u0430\u043A\u043E\u0439 \u0430\u0433\u0435\u043D\u0442 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0432 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435?"];
6639
+ ordered.forEach((info, index) => {
6640
+ const marks = [
6641
+ detected.includes(info.kind) ? "\u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435" : "",
6642
+ info.support === "documented" ? "\u0431\u0435\u0442\u0430: \u0436\u0438\u0432\u044C\u0451\u043C \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E" : ""
6643
+ ].filter((mark) => mark.length > 0);
6644
+ lines.push(
6645
+ ` ${index + 1}) ${info.label} \u2014 ${info.vendor}${marks.length > 0 ? ` [${marks.join(", ")}]` : ""}`
6646
+ );
6647
+ });
6648
+ lines.push("\u0412\u044B\u0431\u043E\u0440 [1]: ");
6649
+ const answer = (await prompter.question(lines.join("\n"))).trim().toLowerCase();
6650
+ if (answer.length === 0) return ordered[0]?.kind ?? DEFAULT_AGENT_KIND;
6651
+ const byNumber = Number.parseInt(answer, 10);
6652
+ if (Number.isInteger(byNumber) && byNumber >= 1 && byNumber <= ordered.length) {
6653
+ return ordered[byNumber - 1]?.kind ?? DEFAULT_AGENT_KIND;
6654
+ }
6655
+ const byKey = agentKindFromCliKey(answer);
6656
+ if (byKey) return byKey;
6657
+ throw new Error(
6658
+ `\u041D\u0435 \u043F\u043E\u043D\u044F\u043B \u043E\u0442\u0432\u0435\u0442 "${answer}". \u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043D\u043E\u043C\u0435\u0440 \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0438\u043B\u0438 \u043A\u043B\u044E\u0447: ${agentKindCliKeys().join(", ")}.`
6659
+ );
6660
+ }
6014
6661
  async function askWorktreeMode(options, prompter, interactive) {
6015
6662
  const fromFlag = options.worktrees?.trim().toLowerCase();
6016
6663
  if (fromFlag === "isolated" || fromFlag === "checkout") return fromFlag;
@@ -6078,28 +6725,24 @@ async function runInit(options, deps = {}) {
6078
6725
  const agentName = agentNameFromFlag && agentNameFromFlag.length > 0 ? agentNameFromFlag : wantsPrompts ? await prompter.question(
6079
6726
  "\u0418\u043C\u044F \u0430\u0433\u0435\u043D\u0442\u0430 \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F (\u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E, Enter \u2014 \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C): "
6080
6727
  ) : void 0;
6728
+ const agentKind = await askAgentKind(options, prompter, wantsPrompts, cwd);
6081
6729
  const worktrees = await askWorktreeMode(options, prompter, wantsPrompts);
6082
6730
  const config = ProjectConfigSchema.parse({
6083
6731
  url: answers.url,
6084
6732
  projectKey: answers.projectKey,
6085
6733
  agentId: answers.agentId,
6086
6734
  roleKeys,
6735
+ agentKind,
6087
6736
  worktrees,
6088
6737
  ...agentName && agentName.length > 0 ? { agentName } : {}
6089
6738
  });
6090
- const existingSettingsResult = readClaudeSettings(cwd);
6091
- if (!existingSettingsResult.ok) {
6092
- throw new Error(existingSettingsResult.message);
6093
- }
6094
- const mergedSettings = mergeQuielSettings(existingSettingsResult.settings);
6095
- const existingMcpJsonResult = readMcpJson(cwd);
6096
- if (!existingMcpJsonResult.ok) {
6097
- throw new Error(existingMcpJsonResult.message);
6098
- }
6099
- const existingMcpJson = existingMcpJsonResult.config;
6100
- const existingClaudeMd = readClaudeMd(cwd);
6101
- const nextClaudeMd = upsertClaudeMdSection(existingClaudeMd);
6102
- const claudeMdWasUpdated = existingClaudeMd !== nextClaudeMd;
6739
+ const plan = installerFor(agentKind).plan({
6740
+ cwd,
6741
+ readFile: (relativePath) => {
6742
+ const path = join10(cwd, relativePath);
6743
+ return existsSync10(path) ? readFileSync8(path, "utf8") : null;
6744
+ }
6745
+ });
6103
6746
  const projectConfigPath = writeProjectConfig(cwd, config);
6104
6747
  const credentialsPath = deps.credentialsFilePath ?? getCredentialsFilePath();
6105
6748
  saveAgentCredentials(
@@ -6107,21 +6750,26 @@ async function runInit(options, deps = {}) {
6107
6750
  { url: config.url, token: answers.token, projectKey: config.projectKey, savedAt: now() },
6108
6751
  deps.credentialsFilePath
6109
6752
  );
6110
- const claudeSettingsPath = writeClaudeSettings(cwd, mergedSettings);
6111
- const mcpJsonPath = writeMcpJson(cwd, mergeQuielMcpJson(existingMcpJson));
6112
- const claudeMdPath = writeClaudeMd(cwd, nextClaudeMd);
6113
- const slashCommandPath = writeSlashCommand(cwd);
6753
+ const files = plan.files.map((file) => {
6754
+ const path = join10(cwd, file.path);
6755
+ const before = existsSync10(path) ? readFileSync8(path, "utf8") : null;
6756
+ if (before !== file.content) {
6757
+ mkdirSync7(dirname5(path), { recursive: true });
6758
+ writeFileSync9(path, file.content, "utf8");
6759
+ }
6760
+ return { path, changed: before !== file.content };
6761
+ });
6114
6762
  const gitignore = ensureWorktreesIgnored(cwd);
6115
6763
  return {
6116
6764
  projectConfigPath,
6117
6765
  credentialsPath,
6118
- claudeSettingsPath,
6119
- claudeMdPath,
6120
- slashCommandPath,
6121
- mcpJsonPath,
6122
- claudeMdWasUpdated,
6766
+ agentKind,
6767
+ files,
6768
+ notes: plan.notes,
6769
+ restartHint: plan.restartHint,
6123
6770
  gitignorePath: gitignore.path,
6124
- gitignoreWasUpdated: gitignore.changed
6771
+ gitignoreWasUpdated: gitignore.changed,
6772
+ mcpJsonPath: join10(cwd, ".mcp.json")
6125
6773
  };
6126
6774
  } finally {
6127
6775
  if (ownPrompter) prompter.close();
@@ -6172,6 +6820,7 @@ async function getStatus(options = {}, deps = {}) {
6172
6820
  projectKey: context.config.projectKey,
6173
6821
  agentId: context.config.agentId,
6174
6822
  agentName: context.config.agentName,
6823
+ agentKind: context.config.agentKind,
6175
6824
  roleKeys: context.config.roleKeys,
6176
6825
  mode: state.mode,
6177
6826
  currentTask: state.currentTask ? { taskId: state.currentTask.taskId, taskKey: state.currentTask.taskKey } : null,
@@ -6180,6 +6829,12 @@ async function getStatus(options = {}, deps = {}) {
6180
6829
  reachability
6181
6830
  };
6182
6831
  }
6832
+ function describeAgentKindLine(kind) {
6833
+ const info = describeAgentKind(kind);
6834
+ if (!info) return `${kind} (\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0430\u0433\u0435\u043D\u0442 \u2014 \u043A\u043B\u0438\u0435\u043D\u0442 \u043D\u043E\u0432\u0435\u0435 \u0440\u0435\u0435\u0441\u0442\u0440\u0430?)`;
6835
+ const beta = info.support === "documented" ? ", \u0431\u0435\u0442\u0430: \u0436\u0438\u0432\u044C\u0451\u043C \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E" : "";
6836
+ return `${info.label} \u2014 ${info.vendor}${beta}`;
6837
+ }
6183
6838
  function describeMode2(mode) {
6184
6839
  return mode === AgentMode.AUTONOMOUS ? "auto" : "manual";
6185
6840
  }
@@ -6190,6 +6845,10 @@ function formatStatus(report) {
6190
6845
  `\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430: ${report.url}`,
6191
6846
  `\u041A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: ${report.projectKey}`,
6192
6847
  `\u0410\u0433\u0435\u043D\u0442: ${report.agentId}${report.agentName ? ` (${report.agentName})` : ""}`,
6848
+ // Вид агента показывается ВСЕГДА, даже когда он Claude Code: человек
6849
+ // должен видеть, что именно настроено в этом каталоге, — особенно
6850
+ // если на машине стоят два инструмента.
6851
+ `\u041F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430: ${describeAgentKindLine(report.agentKind)}`,
6193
6852
  `\u0420\u043E\u043B\u0438: ${report.roleKeys.join(", ")}`,
6194
6853
  `\u0420\u0435\u0436\u0438\u043C (\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448, \u0441\u0435\u0440\u0432\u0435\u0440 \u2014 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u043F\u0440\u0430\u0432\u0434\u044B, \u0441\u043C. OQ-06): ${describeMode2(report.mode)}`,
6195
6854
  report.currentTask ? `\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430: ${report.currentTask.taskKey ?? report.currentTask.taskId}` : "\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430: \u043D\u0435\u0442",
@@ -6212,11 +6871,14 @@ function createProgram() {
6212
6871
  const program = new Command();
6213
6872
  program.name("quiel").description("CLI \u043A\u043B\u0438\u0435\u043D\u0442\u0430 Quiel \u0434\u043B\u044F Claude Code \u2014 init/connect/status/mode/hook/mcp (\xA710.1 \u0422\u0417)").version(CLI_VERSION);
6214
6873
  program.command("init").description(
6215
- "\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435: .quiel.json, .claude/settings.json, CLAUDE.md, slash-\u043A\u043E\u043C\u0430\u043D\u0434\u0430 (\xA710.1 \u0422\u0417)"
6874
+ "\u041F\u043E\u0434\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u043A \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435: .quiel.json, \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044F \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u043E\u0439 \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B, \u0441\u0435\u043A\u0446\u0438\u044F \u0432 \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044F\u0445 (\xA710.1 \u0422\u0417)"
6216
6875
  ).option("--url <url>", "URL \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B").option(
6217
6876
  "--token <token>",
6218
6877
  "Agent-\u0442\u043E\u043A\u0435\u043D (\u043D\u0435 \u043F\u043E\u043F\u0430\u0434\u0430\u0435\u0442 \u0432 .quiel.json \u2014 \u0445\u0440\u0430\u043D\u0438\u0442\u0441\u044F \u0432 ~/.quiel/credentials.json)"
6219
6878
  ).option("--project-key <key>", "\u041A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 \u043D\u0430 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435").option("--agent-id <id>", "ID \u0430\u0433\u0435\u043D\u0442\u0430 (\u0441\u043E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B \u0430\u0433\u0435\u043D\u0442\u0430 \u0432 UI)").option("--agent-name <name>", "\u0418\u043C\u044F \u0430\u0433\u0435\u043D\u0442\u0430 \u0434\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u043E\u0442\u043E\u0431\u0440\u0430\u0436\u0435\u043D\u0438\u044F (\u043D\u0435\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E)").option("--roles <roles>", "\u0420\u043E\u043B\u0438 \u0430\u0433\u0435\u043D\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 backend,frontend").option(
6879
+ "--agent <kind>",
6880
+ `\u0410\u0433\u0435\u043D\u0442\u0441\u043A\u0430\u044F \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430: ${agentKindCliKeys().join(", ")} (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442\u0441\u044F)`
6881
+ ).option(
6220
6882
  "--cwd <dir>",
6221
6883
  "\u0420\u0430\u0431\u043E\u0447\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u2014 \u0442\u0435\u043A\u0443\u0449\u0438\u0439; \u0434\u043B\u044F \u0442\u0435\u0441\u0442\u043E\u0432/\u0430\u0432\u0442\u043E\u043C\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438)"
6222
6884
  ).action(async (opts) => {
@@ -6227,22 +6889,30 @@ function createProgram() {
6227
6889
  agentId: opts.agentId,
6228
6890
  agentName: opts.agentName,
6229
6891
  roles: opts.roles,
6892
+ agent: opts.agent,
6230
6893
  cwd: opts.cwd
6231
6894
  };
6232
6895
  try {
6233
6896
  const result = await runInit(options);
6234
- console.log("Quiel \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D \u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0443.");
6235
- console.log(` .quiel.json: ${result.projectConfigPath}`);
6897
+ const info = describeAgentKind(result.agentKind);
6898
+ console.log(`Quiel \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0451\u043D \u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0443. \u0410\u0433\u0435\u043D\u0442: ${info?.label ?? result.agentKind}.`);
6899
+ console.log(` .quiel.json: ${result.projectConfigPath}`);
6236
6900
  console.log(` \u0422\u043E\u043A\u0435\u043D (\u0432\u043D\u0435 git, 0600): ${result.credentialsPath}`);
6237
- console.log(` .claude/settings.json: ${result.claudeSettingsPath} (\u0445\u0443\u043A\u0438)`);
6238
- console.log(` .mcp.json: ${result.mcpJsonPath} (MCP-\u0441\u0435\u0440\u0432\u0435\u0440)`);
6239
- console.log(
6240
- ` CLAUDE.md: ${result.claudeMdPath}${result.claudeMdWasUpdated ? " (\u0441\u0435\u043A\u0446\u0438\u044F \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430)" : " (\u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439)"}`
6241
- );
6242
- console.log(` Slash-\u043A\u043E\u043C\u0430\u043D\u0434\u0430: ${result.slashCommandPath}`);
6901
+ for (const file of result.files) {
6902
+ console.log(` ${file.path}${file.changed ? "" : " (\u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439)"}`);
6903
+ }
6904
+ if (info?.support === "documented") {
6905
+ console.log("");
6906
+ console.log(` \u26A0 \u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430 ${info.label} \u0441\u0434\u0435\u043B\u0430\u043D\u0430 \u043F\u043E \u043E\u0444\u0438\u0446\u0438\u0430\u043B\u044C\u043D\u043E\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438, \u043D\u043E \u0436\u0438\u0432\u044B\u043C`);
6907
+ console.log(" \u043F\u0440\u043E\u0433\u043E\u043D\u043E\u043C \u043F\u043E\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u0430. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0433\u043B\u0430\u0432\u043D\u043E\u0435 \u0441\u0430\u043C\u0438: \u043F\u043E\u043F\u0440\u043E\u0441\u0438\u0442\u0435 \u0430\u0433\u0435\u043D\u0442\u0430");
6908
+ console.log(" \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u0437\u0430\u0432\u0435\u0434\u043E\u043C\u043E \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D\u043D\u0443\u044E \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0438 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044C, \u0447\u0442\u043E \u043E\u043D\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430.");
6909
+ }
6910
+ for (const note of result.notes) {
6911
+ console.log("");
6912
+ console.log(` \u2022 ${note}`);
6913
+ }
6243
6914
  console.log("");
6244
- console.log(" \u0414\u0430\u043B\u044C\u0448\u0435: \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0441\u0435\u0441\u0441\u0438\u044E Claude Code \u0432 \u044D\u0442\u043E\u043C \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435 \u2014 MCP-\u0441\u0435\u0440\u0432\u0435\u0440");
6245
- console.log(" \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0441\u0441\u0438\u0438.");
6915
+ console.log(` \u0414\u0430\u043B\u044C\u0448\u0435: ${result.restartHint}`);
6246
6916
  } catch (error) {
6247
6917
  printError(error);
6248
6918
  process.exitCode = 1;
@@ -6275,14 +6945,35 @@ function createProgram() {
6275
6945
  process.exitCode = 1;
6276
6946
  });
6277
6947
  program.command("hook").description(
6278
- "\u0412\u044B\u0437\u0432\u0430\u0442\u044C \u0445\u0443\u043A Claude Code (JSON \u043D\u0430 stdin) \u2014 \u043F\u0440\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u0432 .claude/settings.json init-\u043E\u043C"
6279
- ).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).action(async (name) => {
6948
+ "\u0412\u044B\u0437\u0432\u0430\u0442\u044C \u0445\u0443\u043A \u0430\u0433\u0435\u043D\u0442\u0441\u043A\u043E\u0439 \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B (JSON \u043D\u0430 stdin) \u2014 \u043F\u0440\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043A\u043E\u043D\u0444\u0438\u0433\u043E\u043C \u043F\u0440\u0438 `quiel init`"
6949
+ ).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).option(
6950
+ "--format <format>",
6951
+ `\u0444\u043E\u0440\u043C\u0430\u0442 \u0432\u0445\u043E\u0434\u0430 \u0438 \u0432\u044B\u0445\u043E\u0434\u0430: ${HOOK_FORMATS.join(", ")} (\u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u0435 claude \u2014 \u043E\u043D \u0436\u0435 \u0443 Codex)`
6952
+ ).action(async (name, options) => {
6280
6953
  if (!KNOWN_HOOK_NAMES.includes(name)) {
6281
6954
  printError(
6282
6955
  `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E\u0435 \u0438\u043C\u044F \u0445\u0443\u043A\u0430 "${name}". \u041E\u0436\u0438\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0434\u043D\u043E \u0438\u0437: ${KNOWN_HOOK_NAMES.join(", ")}. \u0414\u0430\u043B\u044C\u0448\u0435 \u0432\u0441\u0451 \u0440\u0430\u0432\u043D\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u044E \u0432\u0432\u043E\u0434 \u0432 runHook \u2014 \u0440\u0430\u0441\u043F\u043E\u0437\u043D\u0430\u0432\u0430\u043D\u0438\u0435 \u0438\u0434\u0451\u0442 \u043F\u043E hook_event_name \u043D\u0430 stdin.`
6283
6956
  );
6284
6957
  }
6958
+ const format = asHookFormat(options.format ?? "claude");
6959
+ if (!format) {
6960
+ printError(
6961
+ `\u041D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442 "${options.format}". \u041E\u0436\u0438\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0434\u043D\u043E \u0438\u0437: ${HOOK_FORMATS.join(", ")}.`
6962
+ );
6963
+ process.exitCode = 1;
6964
+ return;
6965
+ }
6285
6966
  const raw = await readStdin();
6967
+ if (format !== "claude") {
6968
+ const result = runCliHookFormatted(format, name, raw);
6969
+ process.stdout.write(`${result.stdout}
6970
+ `);
6971
+ if (result.stderr.length > 0) process.stderr.write(`${result.stderr}
6972
+ `);
6973
+ process.exitCode = result.exitCode;
6974
+ if (result.execution) sendAuditActions(result.execution.serverActions);
6975
+ return;
6976
+ }
6286
6977
  const execution = runCliHook(raw);
6287
6978
  process.stdout.write(`${execution.stdout}
6288
6979
  `);