@quiel/cli 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/README.ru.md +137 -0
- package/dist/cli.js +1112 -439
- package/package.json +15 -3
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/
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
var
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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
|
|
220
|
-
|
|
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
|
-
|
|
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 их не
|
|
@@ -2953,35 +2850,210 @@ function evaluateStructuralRules(parsed, rules) {
|
|
|
2953
2850
|
} else if (!push.force && !rules.git.allowPushDefault) {
|
|
2954
2851
|
reasons.push("git push \u0437\u0430\u043F\u0440\u0435\u0449\u0451\u043D \u043F\u0440\u0430\u0432\u0438\u043B\u043E\u043C git.allowPushDefault = false");
|
|
2955
2852
|
}
|
|
2956
|
-
}
|
|
2957
|
-
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
|
+
};
|
|
2958
3042
|
}
|
|
2959
|
-
function
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
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)."
|
|
2964
|
-
};
|
|
2965
|
-
}
|
|
2966
|
-
const check = checkProtectedPath(relativePath, rules.files.protectedPaths);
|
|
2967
|
-
if (check.matched) {
|
|
2968
|
-
return {
|
|
2969
|
-
decision: "approve",
|
|
2970
|
-
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.`,
|
|
2971
|
-
matchedProtectedPath: check.matchedPattern
|
|
2972
|
-
};
|
|
2973
|
-
}
|
|
2974
|
-
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 };
|
|
2975
3047
|
}
|
|
2976
3048
|
|
|
2977
3049
|
// src/state/schema.ts
|
|
2978
|
-
import { z as
|
|
3050
|
+
import { z as z28 } from "zod";
|
|
2979
3051
|
var STATE_SCHEMA_VERSION = 1;
|
|
2980
|
-
var CurrentTaskSchema =
|
|
2981
|
-
taskId:
|
|
3052
|
+
var CurrentTaskSchema = z28.object({
|
|
3053
|
+
taskId: z28.string().min(1),
|
|
2982
3054
|
/** Человекочитаемый ключ вроде `REL-42` — для текста в хуках. Необязателен: на первых порах может быть неизвестен. */
|
|
2983
|
-
taskKey:
|
|
2984
|
-
resumeToken:
|
|
3055
|
+
taskKey: z28.string().min(1).optional(),
|
|
3056
|
+
resumeToken: z28.string().min(1),
|
|
2985
3057
|
/**
|
|
2986
3058
|
* Каталог, в котором агент работает над этой задачей (`.claude/worktrees/…`).
|
|
2987
3059
|
*
|
|
@@ -2995,7 +3067,7 @@ var CurrentTaskSchema = z27.object({
|
|
|
2995
3067
|
* в чекауте), а у состояний, записанных прежними версиями клиента,
|
|
2996
3068
|
* поля просто нет.
|
|
2997
3069
|
*/
|
|
2998
|
-
worktreeDir:
|
|
3070
|
+
worktreeDir: z28.string().min(1).optional(),
|
|
2999
3071
|
/**
|
|
3000
3072
|
* Git-режим проекта (§13.1 ТЗ) — `DIRECT` или `PR`.
|
|
3001
3073
|
*
|
|
@@ -3009,58 +3081,58 @@ var CurrentTaskSchema = z27.object({
|
|
|
3009
3081
|
* Необязательно: состояния, записанные прежними версиями клиента, поля
|
|
3010
3082
|
* не имеют — тогда `submit_work` работает как раньше, в режиме DIRECT.
|
|
3011
3083
|
*/
|
|
3012
|
-
gitMode:
|
|
3013
|
-
});
|
|
3014
|
-
var TrustCacheSchema =
|
|
3015
|
-
profileKey:
|
|
3016
|
-
updatedAt:
|
|
3017
|
-
rules:
|
|
3018
|
-
bash:
|
|
3019
|
-
mode:
|
|
3020
|
-
stopPatterns:
|
|
3021
|
-
allowPatterns:
|
|
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()
|
|
3022
3094
|
}),
|
|
3023
|
-
files:
|
|
3024
|
-
mode:
|
|
3025
|
-
protectedPaths:
|
|
3095
|
+
files: z28.object({
|
|
3096
|
+
mode: z28.enum(["allow", "approve"]),
|
|
3097
|
+
protectedPaths: z28.array(z28.string()).default([])
|
|
3026
3098
|
}).optional(),
|
|
3027
|
-
git:
|
|
3028
|
-
network:
|
|
3029
|
-
approvalTtlMin:
|
|
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()
|
|
3030
3102
|
}).passthrough()
|
|
3031
3103
|
}).nullable();
|
|
3032
|
-
var ApprovedCommandSchema =
|
|
3033
|
-
token:
|
|
3034
|
-
command:
|
|
3035
|
-
cwd:
|
|
3036
|
-
expiresAt:
|
|
3037
|
-
createdAt:
|
|
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()
|
|
3038
3110
|
});
|
|
3039
|
-
var PendingApprovalSchema =
|
|
3040
|
-
command:
|
|
3041
|
-
cwd:
|
|
3042
|
-
createdAt:
|
|
3111
|
+
var PendingApprovalSchema = z28.object({
|
|
3112
|
+
command: z28.string().min(1),
|
|
3113
|
+
cwd: z28.string().min(1),
|
|
3114
|
+
createdAt: z28.string()
|
|
3043
3115
|
});
|
|
3044
|
-
var AgentModeSchema =
|
|
3116
|
+
var AgentModeSchema = z28.enum([AgentMode.AUTONOMOUS, AgentMode.MANUAL]);
|
|
3045
3117
|
var DEFAULT_CONTEXT_RESET_AFTER_TASKS = ProjectSettingsSchema.parse({}).contextResetAfterTasks;
|
|
3046
|
-
var ClientStateSchema =
|
|
3047
|
-
version:
|
|
3118
|
+
var ClientStateSchema = z28.object({
|
|
3119
|
+
version: z28.literal(STATE_SCHEMA_VERSION).default(STATE_SCHEMA_VERSION),
|
|
3048
3120
|
mode: AgentModeSchema.default(AgentMode.MANUAL),
|
|
3049
3121
|
currentTask: CurrentTaskSchema.nullable().default(null),
|
|
3050
3122
|
/** Счётчик сдач задач в текущей «сессии» (§10.4 ТЗ). Сбрасывается `SessionStart` при `source ∈ {startup, clear}` — см. src/hooks/session-start.ts. */
|
|
3051
|
-
tasksCompletedInSession:
|
|
3123
|
+
tasksCompletedInSession: z28.number().int().nonnegative().default(0),
|
|
3052
3124
|
/** Кэш `ProjectSettings.contextResetAfterTasks` (§7.2 ТЗ) — сколько задач подряд можно сдать, прежде чем `Stop`-хук попросит `/clear`. */
|
|
3053
|
-
contextResetAfterTasks:
|
|
3125
|
+
contextResetAfterTasks: z28.number().int().positive().default(DEFAULT_CONTEXT_RESET_AFTER_TASKS),
|
|
3054
3126
|
/** Кэш стоп-паттернов/правил доверия для `PreToolUse` фазы 4. */
|
|
3055
3127
|
trust: TrustCacheSchema.default(null),
|
|
3056
3128
|
/** Снимок задачи на момент последнего `PreCompact` — пишет src/hooks/pre-compact.ts, читает SessionStart(source="compact"), чтобы напомнить модели, чем она занималась. */
|
|
3057
|
-
lastPreCompact: CurrentTaskSchema.extend({ savedAt:
|
|
3129
|
+
lastPreCompact: CurrentTaskSchema.extend({ savedAt: z28.string() }).nullable().default(null),
|
|
3058
3130
|
/** Кэш одноразовых токенов подтверждения (§14.4 ТЗ), ключ — `hashCommandKey(command, cwd)`. Читает/жжёт `PreToolUse` (`hooks/pre-tool-use.ts`), пишут `request_approval`/`wait_approval` и пуш `approval:decided` (`socket/client.ts`). */
|
|
3059
|
-
approvedCommands:
|
|
3131
|
+
approvedCommands: z28.record(z28.string(), ApprovedCommandSchema).default({}),
|
|
3060
3132
|
/** Незавершённые запросы подтверждения, ключ — `approvalId`. См. `PendingApprovalSchema`. */
|
|
3061
|
-
pendingApprovals:
|
|
3133
|
+
pendingApprovals: z28.record(z28.string(), PendingApprovalSchema).default({}),
|
|
3062
3134
|
/** Штамп последней записи файла — выставляется в state/store.ts при каждом `writeState`. */
|
|
3063
|
-
updatedAt:
|
|
3135
|
+
updatedAt: z28.string().default(() => (/* @__PURE__ */ new Date()).toISOString())
|
|
3064
3136
|
});
|
|
3065
3137
|
function createDefaultState() {
|
|
3066
3138
|
return ClientStateSchema.parse({});
|
|
@@ -3112,20 +3184,33 @@ function updateState(patch, filePath = getStateFilePath()) {
|
|
|
3112
3184
|
}
|
|
3113
3185
|
|
|
3114
3186
|
// src/config/schema.ts
|
|
3115
|
-
import { z as
|
|
3187
|
+
import { z as z29 } from "zod";
|
|
3116
3188
|
var PROJECT_CONFIG_SCHEMA_VERSION = 1;
|
|
3117
|
-
var ProjectConfigSchema =
|
|
3118
|
-
version:
|
|
3189
|
+
var ProjectConfigSchema = z29.object({
|
|
3190
|
+
version: z29.literal(PROJECT_CONFIG_SCHEMA_VERSION).default(PROJECT_CONFIG_SCHEMA_VERSION),
|
|
3119
3191
|
/** URL платформы (§10.1 ТЗ), например `https://quiel.example.com`. */
|
|
3120
|
-
url:
|
|
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"),
|
|
3121
3193
|
/** Ключ проекта на платформе (OQ-11), например `REL`. */
|
|
3122
|
-
projectKey:
|
|
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"),
|
|
3123
3195
|
/** Идентификатор агента — см. комментарий выше. */
|
|
3124
|
-
agentId:
|
|
3196
|
+
agentId: z29.string().trim().min(1, "ID \u0430\u0433\u0435\u043D\u0442\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D"),
|
|
3125
3197
|
/** Человекочитаемое имя агента (§7.4 ТЗ: «odya-macbook») — необязательно, только для локального отображения. */
|
|
3126
|
-
agentName:
|
|
3198
|
+
agentName: z29.string().trim().min(1).optional(),
|
|
3127
3199
|
/** Роли, с которыми агент забирает задачи — подмножество ролей участника (§7.4 ТЗ). */
|
|
3128
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),
|
|
3129
3214
|
/**
|
|
3130
3215
|
* Изолировать ли работу агента в отдельном каталоге.
|
|
3131
3216
|
*
|
|
@@ -3143,19 +3228,19 @@ var ProjectConfigSchema = z28.object({
|
|
|
3143
3228
|
* абсолютный путь вроде `/Users/odya/...` у коллеги на другой машине
|
|
3144
3229
|
* оказался бы битым. Режим переносим — это соглашение проекта.
|
|
3145
3230
|
*/
|
|
3146
|
-
worktrees:
|
|
3231
|
+
worktrees: z29.enum(["isolated", "checkout"]).default("isolated")
|
|
3147
3232
|
});
|
|
3148
|
-
var CredentialsEntrySchema =
|
|
3149
|
-
url:
|
|
3150
|
-
token:
|
|
3151
|
-
projectKey:
|
|
3233
|
+
var CredentialsEntrySchema = z29.object({
|
|
3234
|
+
url: z29.string().trim().url(),
|
|
3235
|
+
token: z29.string().min(1),
|
|
3236
|
+
projectKey: z29.string().min(1),
|
|
3152
3237
|
/** Когда записали — для диагностики (`quiel status`), не для логики. */
|
|
3153
|
-
savedAt:
|
|
3238
|
+
savedAt: z29.string()
|
|
3154
3239
|
});
|
|
3155
3240
|
var CREDENTIALS_SCHEMA_VERSION = 1;
|
|
3156
|
-
var CredentialsFileSchema =
|
|
3157
|
-
version:
|
|
3158
|
-
agents:
|
|
3241
|
+
var CredentialsFileSchema = z29.object({
|
|
3242
|
+
version: z29.literal(CREDENTIALS_SCHEMA_VERSION).default(CREDENTIALS_SCHEMA_VERSION),
|
|
3243
|
+
agents: z29.record(z29.string(), CredentialsEntrySchema).default({})
|
|
3159
3244
|
});
|
|
3160
3245
|
function createEmptyCredentialsFile() {
|
|
3161
3246
|
return CredentialsFileSchema.parse({});
|
|
@@ -3236,39 +3321,15 @@ function ensureWorktreesIgnored(dir) {
|
|
|
3236
3321
|
// src/config/mcp-json.ts
|
|
3237
3322
|
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
3238
3323
|
import { join as join6 } from "path";
|
|
3239
|
-
import { z as
|
|
3240
|
-
var McpServerEntrySchema2 =
|
|
3241
|
-
command:
|
|
3242
|
-
args:
|
|
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()
|
|
3243
3328
|
}).passthrough();
|
|
3244
|
-
var McpJsonSchema =
|
|
3245
|
-
mcpServers:
|
|
3329
|
+
var McpJsonSchema = z30.object({
|
|
3330
|
+
mcpServers: z30.record(z30.string(), McpServerEntrySchema2).optional()
|
|
3246
3331
|
}).passthrough();
|
|
3247
3332
|
var MCP_JSON_RELATIVE_PATH = ".mcp.json";
|
|
3248
|
-
function getMcpJsonPath(dir) {
|
|
3249
|
-
return join6(dir, MCP_JSON_RELATIVE_PATH);
|
|
3250
|
-
}
|
|
3251
|
-
function readMcpJson(dir) {
|
|
3252
|
-
const path = getMcpJsonPath(dir);
|
|
3253
|
-
if (!existsSync6(path)) return { ok: true, config: {} };
|
|
3254
|
-
let raw;
|
|
3255
|
-
try {
|
|
3256
|
-
raw = readFileSync6(path, "utf8");
|
|
3257
|
-
} catch (error) {
|
|
3258
|
-
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)}` };
|
|
3259
|
-
}
|
|
3260
|
-
let parsedJson;
|
|
3261
|
-
try {
|
|
3262
|
-
parsedJson = raw.trim().length === 0 ? {} : JSON.parse(raw);
|
|
3263
|
-
} catch {
|
|
3264
|
-
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` };
|
|
3265
|
-
}
|
|
3266
|
-
const parsed = McpJsonSchema.safeParse(parsedJson);
|
|
3267
|
-
if (!parsed.success) {
|
|
3268
|
-
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}` };
|
|
3269
|
-
}
|
|
3270
|
-
return { ok: true, config: parsed.data };
|
|
3271
|
-
}
|
|
3272
3333
|
function mergeQuielMcpJson(existing) {
|
|
3273
3334
|
return {
|
|
3274
3335
|
...existing,
|
|
@@ -3278,12 +3339,6 @@ function mergeQuielMcpJson(existing) {
|
|
|
3278
3339
|
}
|
|
3279
3340
|
};
|
|
3280
3341
|
}
|
|
3281
|
-
function writeMcpJson(dir, config) {
|
|
3282
|
-
const path = getMcpJsonPath(dir);
|
|
3283
|
-
writeFileSync6(path, `${JSON.stringify(config, null, 2)}
|
|
3284
|
-
`, "utf8");
|
|
3285
|
-
return path;
|
|
3286
|
-
}
|
|
3287
3342
|
|
|
3288
3343
|
// src/config/project-config.ts
|
|
3289
3344
|
import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
@@ -3365,15 +3420,353 @@ disable-model-invocation: true
|
|
|
3365
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\`.
|
|
3366
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.
|
|
3367
3422
|
`;
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
function
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
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
|
+
);
|
|
3377
3770
|
}
|
|
3378
3771
|
|
|
3379
3772
|
// src/cli/context.ts
|
|
@@ -3461,6 +3854,156 @@ function trimTrailingSlash(url) {
|
|
|
3461
3854
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
3462
3855
|
}
|
|
3463
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
|
+
|
|
3464
4007
|
// src/socket/auth-reject.ts
|
|
3465
4008
|
var AUTH_REJECT_REASONS = /* @__PURE__ */ new Set(["missing", "invalid"]);
|
|
3466
4009
|
function isAuthRejection(error) {
|
|
@@ -3533,7 +4076,8 @@ function resolveOptions(options) {
|
|
|
3533
4076
|
roleKeys: options.roleKeys,
|
|
3534
4077
|
cwd: options.cwd,
|
|
3535
4078
|
clientVersion: options.clientVersion,
|
|
3536
|
-
|
|
4079
|
+
agentVersion: options.agentVersion ?? "unknown",
|
|
4080
|
+
agentKind: options.agentKind ?? DEFAULT_AGENT_KIND,
|
|
3537
4081
|
os: options.os ?? process.platform,
|
|
3538
4082
|
// По умолчанию — файл ИМЕННО этого агента (state/store.ts::getStateFilePath(agentId)),
|
|
3539
4083
|
// а не общий `state.json`: не трогаем src/state/**, но обязаны использовать
|
|
@@ -3727,7 +4271,12 @@ var AgentSocketClient = class extends EventEmitter {
|
|
|
3727
4271
|
clientVersion: this.opts.clientVersion,
|
|
3728
4272
|
cwd: this.opts.cwd,
|
|
3729
4273
|
os: this.opts.os,
|
|
3730
|
-
|
|
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 } : {},
|
|
3731
4280
|
roleKeys: [...this.opts.roleKeys],
|
|
3732
4281
|
...state.currentTask?.resumeToken ? { resumeToken: state.currentTask.resumeToken } : {}
|
|
3733
4282
|
});
|
|
@@ -4121,12 +4670,22 @@ ${question.answerMd ?? ""}`;
|
|
|
4121
4670
|
// package.json
|
|
4122
4671
|
var package_default = {
|
|
4123
4672
|
name: "@quiel/cli",
|
|
4124
|
-
version: "0.
|
|
4125
|
-
description: "CLI \u0438 MCP-\u0441\u0435\u0440\u0432\u0435\u0440 Quiel: \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0430\u0435\u0442
|
|
4673
|
+
version: "0.4.1",
|
|
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",
|
|
4126
4675
|
homepage: "https://quiel.ai",
|
|
4676
|
+
repository: {
|
|
4677
|
+
type: "git",
|
|
4678
|
+
url: "git+https://github.com/Quiel-AI/quiel-cli.git"
|
|
4679
|
+
},
|
|
4680
|
+
bugs: {
|
|
4681
|
+
url: "https://github.com/Quiel-AI/quiel-cli/issues"
|
|
4682
|
+
},
|
|
4127
4683
|
keywords: [
|
|
4128
4684
|
"quiel",
|
|
4129
4685
|
"claude-code",
|
|
4686
|
+
"codex",
|
|
4687
|
+
"cursor",
|
|
4688
|
+
"opencode",
|
|
4130
4689
|
"mcp",
|
|
4131
4690
|
"mcp-server",
|
|
4132
4691
|
"ai-agents",
|
|
@@ -4138,7 +4697,9 @@ var package_default = {
|
|
|
4138
4697
|
quiel: "dist/cli.js"
|
|
4139
4698
|
},
|
|
4140
4699
|
files: [
|
|
4141
|
-
"dist"
|
|
4700
|
+
"dist",
|
|
4701
|
+
"README.md",
|
|
4702
|
+
"README.ru.md"
|
|
4142
4703
|
],
|
|
4143
4704
|
engines: {
|
|
4144
4705
|
node: ">=22"
|
|
@@ -5079,7 +5640,7 @@ async function settleApproval(ack, commandCwd, runtime, waitSecUsed) {
|
|
|
5079
5640
|
}
|
|
5080
5641
|
|
|
5081
5642
|
// src/mcp/set-mode.ts
|
|
5082
|
-
import { z as
|
|
5643
|
+
import { z as z35 } from "zod";
|
|
5083
5644
|
|
|
5084
5645
|
// src/cli/mode.ts
|
|
5085
5646
|
function parseModeArg(raw) {
|
|
@@ -5120,7 +5681,7 @@ async function requestModeChange(modeArg, options = {}) {
|
|
|
5120
5681
|
|
|
5121
5682
|
// src/mcp/set-mode.ts
|
|
5122
5683
|
var setModeInputShape = {
|
|
5123
|
-
mode:
|
|
5684
|
+
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)")
|
|
5124
5685
|
};
|
|
5125
5686
|
async function setModeHandler(args, runtime) {
|
|
5126
5687
|
const result = await requestModeChange(args.mode, {
|
|
@@ -5135,20 +5696,20 @@ async function setModeHandler(args, runtime) {
|
|
|
5135
5696
|
}
|
|
5136
5697
|
|
|
5137
5698
|
// src/mcp/submit-work.ts
|
|
5138
|
-
import { z as
|
|
5699
|
+
import { z as z36 } from "zod";
|
|
5139
5700
|
var submitWorkInputShape = {
|
|
5140
|
-
branchName:
|
|
5701
|
+
branchName: z36.string().min(1).describe(
|
|
5141
5702
|
"\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."
|
|
5142
5703
|
),
|
|
5143
|
-
defaultBranch:
|
|
5144
|
-
commitPrefix:
|
|
5145
|
-
taskKey:
|
|
5146
|
-
taskTitle:
|
|
5147
|
-
repoRoot:
|
|
5148
|
-
testsPassed:
|
|
5704
|
+
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."),
|
|
5705
|
+
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:".'),
|
|
5706
|
+
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."),
|
|
5707
|
+
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."),
|
|
5708
|
+
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."),
|
|
5709
|
+
testsPassed: z36.boolean().optional().describe(
|
|
5149
5710
|
"\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."
|
|
5150
5711
|
),
|
|
5151
|
-
requireTestsPassed:
|
|
5712
|
+
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.")
|
|
5152
5713
|
};
|
|
5153
5714
|
async function submitWorkHandler(args, runtime) {
|
|
5154
5715
|
const state = readState(runtime.stateFilePath);
|
|
@@ -5537,9 +6098,10 @@ function resolveMcpRuntime(options = {}) {
|
|
|
5537
6098
|
roleKeys: context.config.roleKeys,
|
|
5538
6099
|
cwd,
|
|
5539
6100
|
clientVersion: MCP_SERVER_VERSION,
|
|
5540
|
-
|
|
5541
|
-
//
|
|
5542
|
-
|
|
6101
|
+
agentKind: context.config.agentKind,
|
|
6102
|
+
// Надёжного источника версии агентской программы нет ни у кого,
|
|
6103
|
+
// кроме Claude Code — см. agents/version.ts.
|
|
6104
|
+
agentVersion: options.agentVersion ?? readAgentVersion(context.config.agentKind),
|
|
5543
6105
|
stateFilePath,
|
|
5544
6106
|
createSocket: options.createSocket
|
|
5545
6107
|
});
|
|
@@ -5583,9 +6145,10 @@ async function runConnect(options = {}, deps = {}) {
|
|
|
5583
6145
|
roleKeys: context.config.roleKeys,
|
|
5584
6146
|
cwd,
|
|
5585
6147
|
clientVersion: MCP_SERVER_VERSION,
|
|
5586
|
-
|
|
5587
|
-
//
|
|
5588
|
-
|
|
6148
|
+
agentKind: context.config.agentKind,
|
|
6149
|
+
// Надёжного источника версии агентской программы нет ни у кого,
|
|
6150
|
+
// кроме Claude Code — см. agents/version.ts.
|
|
6151
|
+
agentVersion: deps.agentVersion ?? readAgentVersion(context.config.agentKind),
|
|
5589
6152
|
stateFilePath: deps.stateFilePath,
|
|
5590
6153
|
createSocket: deps.createSocket
|
|
5591
6154
|
});
|
|
@@ -5619,13 +6182,13 @@ function formatConnectResult(result) {
|
|
|
5619
6182
|
}
|
|
5620
6183
|
|
|
5621
6184
|
// src/hooks/pre-compact.ts
|
|
5622
|
-
import { z as
|
|
5623
|
-
var PreCompactInputSchema =
|
|
5624
|
-
hook_event_name:
|
|
5625
|
-
session_id:
|
|
5626
|
-
cwd:
|
|
5627
|
-
trigger:
|
|
5628
|
-
custom_instructions:
|
|
6185
|
+
import { z as z37 } from "zod";
|
|
6186
|
+
var PreCompactInputSchema = z37.object({
|
|
6187
|
+
hook_event_name: z37.literal("PreCompact"),
|
|
6188
|
+
session_id: z37.string(),
|
|
6189
|
+
cwd: z37.string(),
|
|
6190
|
+
trigger: z37.enum(["manual", "auto"]),
|
|
6191
|
+
custom_instructions: z37.string().nullable()
|
|
5629
6192
|
}).passthrough();
|
|
5630
6193
|
function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5631
6194
|
const lastPreCompact = state.currentTask ? { ...state.currentTask, savedAt: now() } : null;
|
|
@@ -5634,14 +6197,14 @@ function preCompactHook(_input, state, now = () => (/* @__PURE__ */ new Date()).
|
|
|
5634
6197
|
|
|
5635
6198
|
// src/hooks/pre-tool-use.ts
|
|
5636
6199
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
|
|
5637
|
-
import { z as
|
|
5638
|
-
var PreToolUseInputSchema =
|
|
5639
|
-
hook_event_name:
|
|
5640
|
-
session_id:
|
|
5641
|
-
cwd:
|
|
5642
|
-
tool_name:
|
|
5643
|
-
tool_input:
|
|
5644
|
-
tool_use_id:
|
|
6200
|
+
import { z as z38 } from "zod";
|
|
6201
|
+
var PreToolUseInputSchema = z38.object({
|
|
6202
|
+
hook_event_name: z38.literal("PreToolUse"),
|
|
6203
|
+
session_id: z38.string(),
|
|
6204
|
+
cwd: z38.string(),
|
|
6205
|
+
tool_name: z38.string(),
|
|
6206
|
+
tool_input: z38.record(z38.string(), z38.unknown()),
|
|
6207
|
+
tool_use_id: z38.string()
|
|
5645
6208
|
}).passthrough();
|
|
5646
6209
|
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.";
|
|
5647
6210
|
var BOOTSTRAP_COMMAND_RE = /^quiel\s+(connect|status)\b/;
|
|
@@ -5786,16 +6349,16 @@ function handleFileWrite(input, state, now) {
|
|
|
5786
6349
|
}
|
|
5787
6350
|
|
|
5788
6351
|
// src/hooks/run.ts
|
|
5789
|
-
import { z as
|
|
6352
|
+
import { z as z42 } from "zod";
|
|
5790
6353
|
|
|
5791
6354
|
// src/hooks/session-start.ts
|
|
5792
|
-
import { z as
|
|
5793
|
-
var SessionStartInputSchema =
|
|
5794
|
-
hook_event_name:
|
|
5795
|
-
session_id:
|
|
5796
|
-
cwd:
|
|
5797
|
-
source:
|
|
5798
|
-
model:
|
|
6355
|
+
import { z as z39 } from "zod";
|
|
6356
|
+
var SessionStartInputSchema = z39.object({
|
|
6357
|
+
hook_event_name: z39.literal("SessionStart"),
|
|
6358
|
+
session_id: z39.string(),
|
|
6359
|
+
cwd: z39.string(),
|
|
6360
|
+
source: z39.enum(["startup", "resume", "clear", "compact", "fork"]),
|
|
6361
|
+
model: z39.string().optional()
|
|
5799
6362
|
}).passthrough();
|
|
5800
6363
|
var SESSION_RESET_SOURCES = /* @__PURE__ */ new Set([
|
|
5801
6364
|
"startup",
|
|
@@ -5834,13 +6397,13 @@ function sessionStartHook(input, state) {
|
|
|
5834
6397
|
}
|
|
5835
6398
|
|
|
5836
6399
|
// src/hooks/stop.ts
|
|
5837
|
-
import { z as
|
|
5838
|
-
var StopInputSchema =
|
|
5839
|
-
hook_event_name:
|
|
5840
|
-
session_id:
|
|
5841
|
-
cwd:
|
|
5842
|
-
stop_hook_active:
|
|
5843
|
-
last_assistant_message:
|
|
6400
|
+
import { z as z40 } from "zod";
|
|
6401
|
+
var StopInputSchema = z40.object({
|
|
6402
|
+
hook_event_name: z40.literal("Stop"),
|
|
6403
|
+
session_id: z40.string(),
|
|
6404
|
+
cwd: z40.string(),
|
|
6405
|
+
stop_hook_active: z40.boolean(),
|
|
6406
|
+
last_assistant_message: z40.string().optional()
|
|
5844
6407
|
}).passthrough();
|
|
5845
6408
|
function taskLabel(state) {
|
|
5846
6409
|
if (!state.currentTask) return "";
|
|
@@ -5884,8 +6447,8 @@ function stopHook(input, state) {
|
|
|
5884
6447
|
}
|
|
5885
6448
|
|
|
5886
6449
|
// src/hooks/stop-failure.ts
|
|
5887
|
-
import { z as
|
|
5888
|
-
var StopFailureErrorTypeSchema =
|
|
6450
|
+
import { z as z41 } from "zod";
|
|
6451
|
+
var StopFailureErrorTypeSchema = z41.enum([
|
|
5889
6452
|
"rate_limit",
|
|
5890
6453
|
"overloaded",
|
|
5891
6454
|
"authentication_failed",
|
|
@@ -5898,13 +6461,13 @@ var StopFailureErrorTypeSchema = z36.enum([
|
|
|
5898
6461
|
"max_output_tokens",
|
|
5899
6462
|
"unknown"
|
|
5900
6463
|
]);
|
|
5901
|
-
var StopFailureInputSchema =
|
|
5902
|
-
hook_event_name:
|
|
5903
|
-
session_id:
|
|
5904
|
-
cwd:
|
|
6464
|
+
var StopFailureInputSchema = z41.object({
|
|
6465
|
+
hook_event_name: z41.literal("StopFailure"),
|
|
6466
|
+
session_id: z41.string(),
|
|
6467
|
+
cwd: z41.string(),
|
|
5905
6468
|
error: StopFailureErrorTypeSchema,
|
|
5906
|
-
error_details:
|
|
5907
|
-
last_assistant_message:
|
|
6469
|
+
error_details: z41.unknown().optional(),
|
|
6470
|
+
last_assistant_message: z41.string().optional()
|
|
5908
6471
|
}).passthrough();
|
|
5909
6472
|
function extractRetryAfterSec(errorDetails) {
|
|
5910
6473
|
if (typeof errorDetails === "number" && Number.isFinite(errorDetails) && errorDetails >= 0) {
|
|
@@ -5933,7 +6496,7 @@ function stopFailureHook(input, _state) {
|
|
|
5933
6496
|
}
|
|
5934
6497
|
|
|
5935
6498
|
// src/hooks/run.ts
|
|
5936
|
-
var HookInputSchema =
|
|
6499
|
+
var HookInputSchema = z42.discriminatedUnion("hook_event_name", [
|
|
5937
6500
|
SessionStartInputSchema,
|
|
5938
6501
|
StopInputSchema,
|
|
5939
6502
|
StopFailureInputSchema,
|
|
@@ -5999,6 +6562,28 @@ function runCliHook(rawStdin, stateFilePath, cwd = process.cwd()) {
|
|
|
5999
6562
|
}
|
|
6000
6563
|
return runHook(parsed, stateFilePath ?? resolveAgentStateFilePath(cwd));
|
|
6001
6564
|
}
|
|
6565
|
+
function runCliHookFormatted(format, hook, rawStdin, stateFilePath, cwd = process.cwd()) {
|
|
6566
|
+
let parsed;
|
|
6567
|
+
try {
|
|
6568
|
+
parsed = rawStdin.trim().length > 0 ? JSON.parse(rawStdin) : void 0;
|
|
6569
|
+
} catch {
|
|
6570
|
+
parsed = void 0;
|
|
6571
|
+
}
|
|
6572
|
+
const translated = translateHookInput(format, hook, parsed, cwd);
|
|
6573
|
+
const forced = forcedDenyReason(translated);
|
|
6574
|
+
if (forced !== null) {
|
|
6575
|
+
const rendered2 = renderHookOutput(format, hook, {
|
|
6576
|
+
hookSpecificOutput: { permissionDecision: "deny", permissionDecisionReason: forced }
|
|
6577
|
+
});
|
|
6578
|
+
return { ...rendered2, execution: null };
|
|
6579
|
+
}
|
|
6580
|
+
if (translated === null) {
|
|
6581
|
+
return { ...renderHookOutput(format, hook, {}), execution: null };
|
|
6582
|
+
}
|
|
6583
|
+
const execution = runHook(translated, stateFilePath ?? resolveAgentStateFilePath(cwd));
|
|
6584
|
+
const rendered = renderHookOutput(format, hook, JSON.parse(execution.stdout));
|
|
6585
|
+
return { ...rendered, execution };
|
|
6586
|
+
}
|
|
6002
6587
|
function resolveAgentStateFilePath(cwd) {
|
|
6003
6588
|
const dir = findProjectRoot(cwd);
|
|
6004
6589
|
if (!dir) return void 0;
|
|
@@ -6007,6 +6592,10 @@ function resolveAgentStateFilePath(cwd) {
|
|
|
6007
6592
|
return getStateFilePath(config.config.agentId);
|
|
6008
6593
|
}
|
|
6009
6594
|
|
|
6595
|
+
// src/cli/init.ts
|
|
6596
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
6597
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
6598
|
+
|
|
6010
6599
|
// src/cli/prompt.ts
|
|
6011
6600
|
import { createInterface } from "readline/promises";
|
|
6012
6601
|
function createReadlinePrompter() {
|
|
@@ -6038,6 +6627,46 @@ var FIELD_PROMPTS = {
|
|
|
6038
6627
|
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): ",
|
|
6039
6628
|
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): "
|
|
6040
6629
|
};
|
|
6630
|
+
async function askAgentKind(options, prompter, interactive, cwd) {
|
|
6631
|
+
const fromFlag = options.agent?.trim();
|
|
6632
|
+
if (fromFlag && fromFlag.length > 0) {
|
|
6633
|
+
const kind = agentKindFromCliKey(fromFlag);
|
|
6634
|
+
if (!kind) {
|
|
6635
|
+
throw new Error(
|
|
6636
|
+
`\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(", ")}.`
|
|
6637
|
+
);
|
|
6638
|
+
}
|
|
6639
|
+
return kind;
|
|
6640
|
+
}
|
|
6641
|
+
if (!interactive) return DEFAULT_AGENT_KIND;
|
|
6642
|
+
const detected = detectAgentKinds((relative3) => existsSync10(join10(cwd, relative3)));
|
|
6643
|
+
const ordered = [
|
|
6644
|
+
...AGENT_KINDS.filter((info) => detected.includes(info.kind)),
|
|
6645
|
+
...AGENT_KINDS.filter((info) => !detected.includes(info.kind))
|
|
6646
|
+
];
|
|
6647
|
+
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?"];
|
|
6648
|
+
ordered.forEach((info, index) => {
|
|
6649
|
+
const marks = [
|
|
6650
|
+
detected.includes(info.kind) ? "\u043D\u0430\u0439\u0434\u0435\u043D \u0432 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0435" : "",
|
|
6651
|
+
info.support === "documented" ? "\u0431\u0435\u0442\u0430: \u0436\u0438\u0432\u044C\u0451\u043C \u043D\u0435 \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D\u043E" : ""
|
|
6652
|
+
].filter((mark) => mark.length > 0);
|
|
6653
|
+
lines.push(
|
|
6654
|
+
` ${index + 1}) ${info.label} \u2014 ${info.vendor}${marks.length > 0 ? ` [${marks.join(", ")}]` : ""}`
|
|
6655
|
+
);
|
|
6656
|
+
});
|
|
6657
|
+
lines.push("\u0412\u044B\u0431\u043E\u0440 [1]: ");
|
|
6658
|
+
const answer = (await prompter.question(lines.join("\n"))).trim().toLowerCase();
|
|
6659
|
+
if (answer.length === 0) return ordered[0]?.kind ?? DEFAULT_AGENT_KIND;
|
|
6660
|
+
const byNumber = Number.parseInt(answer, 10);
|
|
6661
|
+
if (Number.isInteger(byNumber) && byNumber >= 1 && byNumber <= ordered.length) {
|
|
6662
|
+
return ordered[byNumber - 1]?.kind ?? DEFAULT_AGENT_KIND;
|
|
6663
|
+
}
|
|
6664
|
+
const byKey = agentKindFromCliKey(answer);
|
|
6665
|
+
if (byKey) return byKey;
|
|
6666
|
+
throw new Error(
|
|
6667
|
+
`\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(", ")}.`
|
|
6668
|
+
);
|
|
6669
|
+
}
|
|
6041
6670
|
async function askWorktreeMode(options, prompter, interactive) {
|
|
6042
6671
|
const fromFlag = options.worktrees?.trim().toLowerCase();
|
|
6043
6672
|
if (fromFlag === "isolated" || fromFlag === "checkout") return fromFlag;
|
|
@@ -6105,28 +6734,24 @@ async function runInit(options, deps = {}) {
|
|
|
6105
6734
|
const agentName = agentNameFromFlag && agentNameFromFlag.length > 0 ? agentNameFromFlag : wantsPrompts ? await prompter.question(
|
|
6106
6735
|
"\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): "
|
|
6107
6736
|
) : void 0;
|
|
6737
|
+
const agentKind = await askAgentKind(options, prompter, wantsPrompts, cwd);
|
|
6108
6738
|
const worktrees = await askWorktreeMode(options, prompter, wantsPrompts);
|
|
6109
6739
|
const config = ProjectConfigSchema.parse({
|
|
6110
6740
|
url: answers.url,
|
|
6111
6741
|
projectKey: answers.projectKey,
|
|
6112
6742
|
agentId: answers.agentId,
|
|
6113
6743
|
roleKeys,
|
|
6744
|
+
agentKind,
|
|
6114
6745
|
worktrees,
|
|
6115
6746
|
...agentName && agentName.length > 0 ? { agentName } : {}
|
|
6116
6747
|
});
|
|
6117
|
-
const
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
throw new Error(existingMcpJsonResult.message);
|
|
6125
|
-
}
|
|
6126
|
-
const existingMcpJson = existingMcpJsonResult.config;
|
|
6127
|
-
const existingClaudeMd = readClaudeMd(cwd);
|
|
6128
|
-
const nextClaudeMd = upsertClaudeMdSection(existingClaudeMd);
|
|
6129
|
-
const claudeMdWasUpdated = existingClaudeMd !== nextClaudeMd;
|
|
6748
|
+
const plan = installerFor(agentKind).plan({
|
|
6749
|
+
cwd,
|
|
6750
|
+
readFile: (relativePath) => {
|
|
6751
|
+
const path = join10(cwd, relativePath);
|
|
6752
|
+
return existsSync10(path) ? readFileSync8(path, "utf8") : null;
|
|
6753
|
+
}
|
|
6754
|
+
});
|
|
6130
6755
|
const projectConfigPath = writeProjectConfig(cwd, config);
|
|
6131
6756
|
const credentialsPath = deps.credentialsFilePath ?? getCredentialsFilePath();
|
|
6132
6757
|
saveAgentCredentials(
|
|
@@ -6134,21 +6759,26 @@ async function runInit(options, deps = {}) {
|
|
|
6134
6759
|
{ url: config.url, token: answers.token, projectKey: config.projectKey, savedAt: now() },
|
|
6135
6760
|
deps.credentialsFilePath
|
|
6136
6761
|
);
|
|
6137
|
-
const
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6762
|
+
const files = plan.files.map((file) => {
|
|
6763
|
+
const path = join10(cwd, file.path);
|
|
6764
|
+
const before = existsSync10(path) ? readFileSync8(path, "utf8") : null;
|
|
6765
|
+
if (before !== file.content) {
|
|
6766
|
+
mkdirSync7(dirname5(path), { recursive: true });
|
|
6767
|
+
writeFileSync9(path, file.content, "utf8");
|
|
6768
|
+
}
|
|
6769
|
+
return { path, changed: before !== file.content };
|
|
6770
|
+
});
|
|
6141
6771
|
const gitignore = ensureWorktreesIgnored(cwd);
|
|
6142
6772
|
return {
|
|
6143
6773
|
projectConfigPath,
|
|
6144
6774
|
credentialsPath,
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
claudeMdWasUpdated,
|
|
6775
|
+
agentKind,
|
|
6776
|
+
files,
|
|
6777
|
+
notes: plan.notes,
|
|
6778
|
+
restartHint: plan.restartHint,
|
|
6150
6779
|
gitignorePath: gitignore.path,
|
|
6151
|
-
gitignoreWasUpdated: gitignore.changed
|
|
6780
|
+
gitignoreWasUpdated: gitignore.changed,
|
|
6781
|
+
mcpJsonPath: join10(cwd, ".mcp.json")
|
|
6152
6782
|
};
|
|
6153
6783
|
} finally {
|
|
6154
6784
|
if (ownPrompter) prompter.close();
|
|
@@ -6199,6 +6829,7 @@ async function getStatus(options = {}, deps = {}) {
|
|
|
6199
6829
|
projectKey: context.config.projectKey,
|
|
6200
6830
|
agentId: context.config.agentId,
|
|
6201
6831
|
agentName: context.config.agentName,
|
|
6832
|
+
agentKind: context.config.agentKind,
|
|
6202
6833
|
roleKeys: context.config.roleKeys,
|
|
6203
6834
|
mode: state.mode,
|
|
6204
6835
|
currentTask: state.currentTask ? { taskId: state.currentTask.taskId, taskKey: state.currentTask.taskKey } : null,
|
|
@@ -6207,6 +6838,12 @@ async function getStatus(options = {}, deps = {}) {
|
|
|
6207
6838
|
reachability
|
|
6208
6839
|
};
|
|
6209
6840
|
}
|
|
6841
|
+
function describeAgentKindLine(kind) {
|
|
6842
|
+
const info = describeAgentKind(kind);
|
|
6843
|
+
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?)`;
|
|
6844
|
+
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" : "";
|
|
6845
|
+
return `${info.label} \u2014 ${info.vendor}${beta}`;
|
|
6846
|
+
}
|
|
6210
6847
|
function describeMode2(mode) {
|
|
6211
6848
|
return mode === AgentMode.AUTONOMOUS ? "auto" : "manual";
|
|
6212
6849
|
}
|
|
@@ -6217,6 +6854,10 @@ function formatStatus(report) {
|
|
|
6217
6854
|
`\u041F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0430: ${report.url}`,
|
|
6218
6855
|
`\u041A\u043B\u044E\u0447 \u043F\u0440\u043E\u0435\u043A\u0442\u0430: ${report.projectKey}`,
|
|
6219
6856
|
`\u0410\u0433\u0435\u043D\u0442: ${report.agentId}${report.agentName ? ` (${report.agentName})` : ""}`,
|
|
6857
|
+
// Вид агента показывается ВСЕГДА, даже когда он Claude Code: человек
|
|
6858
|
+
// должен видеть, что именно настроено в этом каталоге, — особенно
|
|
6859
|
+
// если на машине стоят два инструмента.
|
|
6860
|
+
`\u041F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u0430: ${describeAgentKindLine(report.agentKind)}`,
|
|
6220
6861
|
`\u0420\u043E\u043B\u0438: ${report.roleKeys.join(", ")}`,
|
|
6221
6862
|
`\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)}`,
|
|
6222
6863
|
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",
|
|
@@ -6239,11 +6880,14 @@ function createProgram() {
|
|
|
6239
6880
|
const program = new Command();
|
|
6240
6881
|
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);
|
|
6241
6882
|
program.command("init").description(
|
|
6242
|
-
"\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,
|
|
6883
|
+
"\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)"
|
|
6243
6884
|
).option("--url <url>", "URL \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u044B").option(
|
|
6244
6885
|
"--token <token>",
|
|
6245
6886
|
"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)"
|
|
6246
6887
|
).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(
|
|
6888
|
+
"--agent <kind>",
|
|
6889
|
+
`\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)`
|
|
6890
|
+
).option(
|
|
6247
6891
|
"--cwd <dir>",
|
|
6248
6892
|
"\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)"
|
|
6249
6893
|
).action(async (opts) => {
|
|
@@ -6254,22 +6898,30 @@ function createProgram() {
|
|
|
6254
6898
|
agentId: opts.agentId,
|
|
6255
6899
|
agentName: opts.agentName,
|
|
6256
6900
|
roles: opts.roles,
|
|
6901
|
+
agent: opts.agent,
|
|
6257
6902
|
cwd: opts.cwd
|
|
6258
6903
|
};
|
|
6259
6904
|
try {
|
|
6260
6905
|
const result = await runInit(options);
|
|
6261
|
-
|
|
6262
|
-
console.log(`
|
|
6906
|
+
const info = describeAgentKind(result.agentKind);
|
|
6907
|
+
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}.`);
|
|
6908
|
+
console.log(` .quiel.json: ${result.projectConfigPath}`);
|
|
6263
6909
|
console.log(` \u0422\u043E\u043A\u0435\u043D (\u0432\u043D\u0435 git, 0600): ${result.credentialsPath}`);
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6910
|
+
for (const file of result.files) {
|
|
6911
|
+
console.log(` ${file.path}${file.changed ? "" : " (\u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439)"}`);
|
|
6912
|
+
}
|
|
6913
|
+
if (info?.support === "documented") {
|
|
6914
|
+
console.log("");
|
|
6915
|
+
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`);
|
|
6916
|
+
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");
|
|
6917
|
+
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.");
|
|
6918
|
+
}
|
|
6919
|
+
for (const note of result.notes) {
|
|
6920
|
+
console.log("");
|
|
6921
|
+
console.log(` \u2022 ${note}`);
|
|
6922
|
+
}
|
|
6270
6923
|
console.log("");
|
|
6271
|
-
console.log(
|
|
6272
|
-
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.");
|
|
6924
|
+
console.log(` \u0414\u0430\u043B\u044C\u0448\u0435: ${result.restartHint}`);
|
|
6273
6925
|
} catch (error) {
|
|
6274
6926
|
printError(error);
|
|
6275
6927
|
process.exitCode = 1;
|
|
@@ -6302,14 +6954,35 @@ function createProgram() {
|
|
|
6302
6954
|
process.exitCode = 1;
|
|
6303
6955
|
});
|
|
6304
6956
|
program.command("hook").description(
|
|
6305
|
-
"\u0412\u044B\u0437\u0432\u0430\u0442\u044C \u0445\u0443\u043A
|
|
6306
|
-
).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).
|
|
6957
|
+
"\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`"
|
|
6958
|
+
).argument("<name>", `\u0438\u043C\u044F \u0445\u0443\u043A\u0430: ${KNOWN_HOOK_NAMES.join(", ")}`).option(
|
|
6959
|
+
"--format <format>",
|
|
6960
|
+
`\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)`
|
|
6961
|
+
).action(async (name, options) => {
|
|
6307
6962
|
if (!KNOWN_HOOK_NAMES.includes(name)) {
|
|
6308
6963
|
printError(
|
|
6309
6964
|
`\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.`
|
|
6310
6965
|
);
|
|
6311
6966
|
}
|
|
6967
|
+
const format = asHookFormat(options.format ?? "claude");
|
|
6968
|
+
if (!format) {
|
|
6969
|
+
printError(
|
|
6970
|
+
`\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(", ")}.`
|
|
6971
|
+
);
|
|
6972
|
+
process.exitCode = 1;
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6312
6975
|
const raw = await readStdin();
|
|
6976
|
+
if (format !== "claude") {
|
|
6977
|
+
const result = runCliHookFormatted(format, name, raw);
|
|
6978
|
+
process.stdout.write(`${result.stdout}
|
|
6979
|
+
`);
|
|
6980
|
+
if (result.stderr.length > 0) process.stderr.write(`${result.stderr}
|
|
6981
|
+
`);
|
|
6982
|
+
process.exitCode = result.exitCode;
|
|
6983
|
+
if (result.execution) sendAuditActions(result.execution.serverActions);
|
|
6984
|
+
return;
|
|
6985
|
+
}
|
|
6313
6986
|
const execution = runCliHook(raw);
|
|
6314
6987
|
process.stdout.write(`${execution.stdout}
|
|
6315
6988
|
`);
|