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