kimi-plugin-cc 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +16 -0
  3. package/.claude-plugin/plugin.json +32 -0
  4. package/CHANGELOG.md +1096 -0
  5. package/LICENSE +201 -0
  6. package/README.md +772 -0
  7. package/agents/kimi-ask.md +82 -0
  8. package/agents/kimi-challenge.md +82 -0
  9. package/agents/kimi-pursue.md +91 -0
  10. package/agents/kimi-rescue.md +85 -0
  11. package/agents/kimi-review.md +82 -0
  12. package/agents/kimi-swarm-write.md +92 -0
  13. package/agents/kimi-swarm.md +83 -0
  14. package/commands/README.md +16 -0
  15. package/commands/ask.md +32 -0
  16. package/commands/cancel.md +17 -0
  17. package/commands/challenge.md +29 -0
  18. package/commands/pursue.md +37 -0
  19. package/commands/replay.md +13 -0
  20. package/commands/rescue.md +32 -0
  21. package/commands/result.md +13 -0
  22. package/commands/review.md +29 -0
  23. package/commands/setup.md +23 -0
  24. package/commands/status.md +13 -0
  25. package/commands/swarm.md +38 -0
  26. package/dist/background-spawn.js +224 -0
  27. package/dist/cli-cancellation.js +43 -0
  28. package/dist/cli-client.js +845 -0
  29. package/dist/commands/ask.js +327 -0
  30. package/dist/commands/cancel.js +178 -0
  31. package/dist/commands/cli-helpers.js +112 -0
  32. package/dist/commands/models.js +140 -0
  33. package/dist/commands/not-implemented.js +4 -0
  34. package/dist/commands/pursue.js +311 -0
  35. package/dist/commands/registry.js +87 -0
  36. package/dist/commands/repair-sessions.js +159 -0
  37. package/dist/commands/replay.js +123 -0
  38. package/dist/commands/rescue.js +333 -0
  39. package/dist/commands/result.js +58 -0
  40. package/dist/commands/review-gate.js +393 -0
  41. package/dist/commands/review.js +218 -0
  42. package/dist/commands/setup.js +1132 -0
  43. package/dist/commands/status.js +27 -0
  44. package/dist/commands/swarm.js +576 -0
  45. package/dist/commands/worker.js +31 -0
  46. package/dist/companion.js +114 -0
  47. package/dist/config.js +22 -0
  48. package/dist/errors.js +40 -0
  49. package/dist/git.js +215 -0
  50. package/dist/hooks/approval-hook.js +112 -0
  51. package/dist/hooks/approval-policy.js +254 -0
  52. package/dist/hooks/config-safety.js +637 -0
  53. package/dist/hooks/install-paths.js +504 -0
  54. package/dist/hooks/install.js +281 -0
  55. package/dist/hooks/managed-block.js +745 -0
  56. package/dist/hooks/review-gate-stop.js +19 -0
  57. package/dist/job-store.js +559 -0
  58. package/dist/jobs.js +202 -0
  59. package/dist/kimi-command.js +72 -0
  60. package/dist/kimi-engine.js +624 -0
  61. package/dist/kimi-errors.js +220 -0
  62. package/dist/kimi-home.js +9 -0
  63. package/dist/kimi-timeouts.js +68 -0
  64. package/dist/kimi-version-probe.js +1410 -0
  65. package/dist/logging.js +22 -0
  66. package/dist/native-v2-preflight.js +417 -0
  67. package/dist/package.json +1 -0
  68. package/dist/parsing.js +553 -0
  69. package/dist/paths.js +27 -0
  70. package/dist/plugin-data.js +98 -0
  71. package/dist/process-tree.js +448 -0
  72. package/dist/render.js +159 -0
  73. package/dist/rescue-approval.js +951 -0
  74. package/dist/schemas/review-gate-output.js +43 -0
  75. package/dist/session-title.js +293 -0
  76. package/dist/stream-json.js +498 -0
  77. package/dist/types.js +1 -0
  78. package/dist/vendor/shell-quote/LICENSE +24 -0
  79. package/dist/vendor/shell-quote/index.js +1 -0
  80. package/dist/vendor/shell-quote/parse.js +229 -0
  81. package/dist/vendor/smol-toml/LICENSE +24 -0
  82. package/dist/vendor/smol-toml/date.js +127 -0
  83. package/dist/vendor/smol-toml/error.js +63 -0
  84. package/dist/vendor/smol-toml/extract.js +100 -0
  85. package/dist/vendor/smol-toml/parse.js +148 -0
  86. package/dist/vendor/smol-toml/primitive.js +179 -0
  87. package/dist/vendor/smol-toml/struct.js +184 -0
  88. package/dist/vendor/smol-toml/util.js +113 -0
  89. package/dist/version.js +7 -0
  90. package/docs/migration.md +215 -0
  91. package/docs/models.md +82 -0
  92. package/docs/registry-releases.md +70 -0
  93. package/docs/safety.md +318 -0
  94. package/docs/session-visibility.md +32 -0
  95. package/hooks/README.md +9 -0
  96. package/hooks/hooks.json +16 -0
  97. package/package.json +67 -0
  98. package/plugins/kimi-codex/.codex-plugin/plugin.json +39 -0
  99. package/plugins/kimi-codex/dist/background-spawn.js +224 -0
  100. package/plugins/kimi-codex/dist/cli-cancellation.js +43 -0
  101. package/plugins/kimi-codex/dist/cli-client.js +845 -0
  102. package/plugins/kimi-codex/dist/commands/ask.js +327 -0
  103. package/plugins/kimi-codex/dist/commands/cancel.js +178 -0
  104. package/plugins/kimi-codex/dist/commands/cli-helpers.js +112 -0
  105. package/plugins/kimi-codex/dist/commands/models.js +140 -0
  106. package/plugins/kimi-codex/dist/commands/not-implemented.js +4 -0
  107. package/plugins/kimi-codex/dist/commands/pursue.js +311 -0
  108. package/plugins/kimi-codex/dist/commands/registry.js +87 -0
  109. package/plugins/kimi-codex/dist/commands/repair-sessions.js +159 -0
  110. package/plugins/kimi-codex/dist/commands/replay.js +123 -0
  111. package/plugins/kimi-codex/dist/commands/rescue.js +333 -0
  112. package/plugins/kimi-codex/dist/commands/result.js +58 -0
  113. package/plugins/kimi-codex/dist/commands/review-gate.js +393 -0
  114. package/plugins/kimi-codex/dist/commands/review.js +218 -0
  115. package/plugins/kimi-codex/dist/commands/setup.js +1132 -0
  116. package/plugins/kimi-codex/dist/commands/status.js +27 -0
  117. package/plugins/kimi-codex/dist/commands/swarm.js +576 -0
  118. package/plugins/kimi-codex/dist/commands/worker.js +31 -0
  119. package/plugins/kimi-codex/dist/companion.js +114 -0
  120. package/plugins/kimi-codex/dist/config.js +22 -0
  121. package/plugins/kimi-codex/dist/errors.js +40 -0
  122. package/plugins/kimi-codex/dist/git.js +215 -0
  123. package/plugins/kimi-codex/dist/hooks/approval-hook.js +112 -0
  124. package/plugins/kimi-codex/dist/hooks/approval-policy.js +254 -0
  125. package/plugins/kimi-codex/dist/hooks/config-safety.js +637 -0
  126. package/plugins/kimi-codex/dist/hooks/install-paths.js +504 -0
  127. package/plugins/kimi-codex/dist/hooks/install.js +281 -0
  128. package/plugins/kimi-codex/dist/hooks/managed-block.js +745 -0
  129. package/plugins/kimi-codex/dist/hooks/review-gate-stop.js +19 -0
  130. package/plugins/kimi-codex/dist/job-store.js +559 -0
  131. package/plugins/kimi-codex/dist/jobs.js +202 -0
  132. package/plugins/kimi-codex/dist/kimi-command.js +72 -0
  133. package/plugins/kimi-codex/dist/kimi-engine.js +624 -0
  134. package/plugins/kimi-codex/dist/kimi-errors.js +220 -0
  135. package/plugins/kimi-codex/dist/kimi-home.js +9 -0
  136. package/plugins/kimi-codex/dist/kimi-timeouts.js +68 -0
  137. package/plugins/kimi-codex/dist/kimi-version-probe.js +1410 -0
  138. package/plugins/kimi-codex/dist/logging.js +22 -0
  139. package/plugins/kimi-codex/dist/native-v2-preflight.js +417 -0
  140. package/plugins/kimi-codex/dist/package.json +1 -0
  141. package/plugins/kimi-codex/dist/parsing.js +553 -0
  142. package/plugins/kimi-codex/dist/paths.js +27 -0
  143. package/plugins/kimi-codex/dist/plugin-data.js +98 -0
  144. package/plugins/kimi-codex/dist/process-tree.js +448 -0
  145. package/plugins/kimi-codex/dist/render.js +159 -0
  146. package/plugins/kimi-codex/dist/rescue-approval.js +951 -0
  147. package/plugins/kimi-codex/dist/schemas/review-gate-output.js +43 -0
  148. package/plugins/kimi-codex/dist/session-title.js +293 -0
  149. package/plugins/kimi-codex/dist/stream-json.js +498 -0
  150. package/plugins/kimi-codex/dist/types.js +1 -0
  151. package/plugins/kimi-codex/dist/vendor/shell-quote/LICENSE +24 -0
  152. package/plugins/kimi-codex/dist/vendor/shell-quote/index.js +1 -0
  153. package/plugins/kimi-codex/dist/vendor/shell-quote/parse.js +229 -0
  154. package/plugins/kimi-codex/dist/vendor/smol-toml/LICENSE +24 -0
  155. package/plugins/kimi-codex/dist/vendor/smol-toml/date.js +127 -0
  156. package/plugins/kimi-codex/dist/vendor/smol-toml/error.js +63 -0
  157. package/plugins/kimi-codex/dist/vendor/smol-toml/extract.js +100 -0
  158. package/plugins/kimi-codex/dist/vendor/smol-toml/parse.js +148 -0
  159. package/plugins/kimi-codex/dist/vendor/smol-toml/primitive.js +179 -0
  160. package/plugins/kimi-codex/dist/vendor/smol-toml/struct.js +184 -0
  161. package/plugins/kimi-codex/dist/vendor/smol-toml/util.js +113 -0
  162. package/plugins/kimi-codex/dist/version.js +7 -0
  163. package/plugins/kimi-codex/scripts/companion.sh +37 -0
  164. package/plugins/kimi-codex/scripts/review-gate-hook.sh +32 -0
  165. package/plugins/kimi-codex/skills/kimi-ask/SKILL.md +31 -0
  166. package/plugins/kimi-codex/skills/kimi-ask/agents/openai.yaml +6 -0
  167. package/plugins/kimi-codex/skills/kimi-cancel/SKILL.md +25 -0
  168. package/plugins/kimi-codex/skills/kimi-cancel/agents/openai.yaml +6 -0
  169. package/plugins/kimi-codex/skills/kimi-challenge/SKILL.md +31 -0
  170. package/plugins/kimi-codex/skills/kimi-challenge/agents/openai.yaml +6 -0
  171. package/plugins/kimi-codex/skills/kimi-pursue/SKILL.md +30 -0
  172. package/plugins/kimi-codex/skills/kimi-pursue/agents/openai.yaml +6 -0
  173. package/plugins/kimi-codex/skills/kimi-replay/SKILL.md +25 -0
  174. package/plugins/kimi-codex/skills/kimi-replay/agents/openai.yaml +6 -0
  175. package/plugins/kimi-codex/skills/kimi-rescue/SKILL.md +30 -0
  176. package/plugins/kimi-codex/skills/kimi-rescue/agents/openai.yaml +6 -0
  177. package/plugins/kimi-codex/skills/kimi-result/SKILL.md +25 -0
  178. package/plugins/kimi-codex/skills/kimi-result/agents/openai.yaml +6 -0
  179. package/plugins/kimi-codex/skills/kimi-review/SKILL.md +31 -0
  180. package/plugins/kimi-codex/skills/kimi-review/agents/openai.yaml +6 -0
  181. package/plugins/kimi-codex/skills/kimi-setup/SKILL.md +32 -0
  182. package/plugins/kimi-codex/skills/kimi-setup/agents/openai.yaml +6 -0
  183. package/plugins/kimi-codex/skills/kimi-status/SKILL.md +25 -0
  184. package/plugins/kimi-codex/skills/kimi-status/agents/openai.yaml +6 -0
  185. package/plugins/kimi-codex/skills/kimi-swarm/SKILL.md +31 -0
  186. package/plugins/kimi-codex/skills/kimi-swarm/agents/openai.yaml +6 -0
  187. package/plugins/kimi-codex/skills/kimi-swarm-write/SKILL.md +32 -0
  188. package/plugins/kimi-codex/skills/kimi-swarm-write/agents/openai.yaml +6 -0
  189. package/scripts/companion.sh +37 -0
  190. package/scripts/review-gate-hook.sh +32 -0
@@ -0,0 +1,553 @@
1
+ import { RuntimeError } from "./errors.js";
2
+ /**
3
+ * Tokens like `--foo` or `-x` that aren't in a command's known-flags set are
4
+ * almost certainly typos (e.g. `--from HEAD~2` instead of `--base HEAD~2`)
5
+ * or — for agent-mediated callers — hallucinated flag names (e.g. wrappers
6
+ * inventing `--file` / `--context` because that shape is common in other
7
+ * CLIs). The historical behavior — silently treating them as free-form
8
+ * trailing prompt/focus text with an advisory stderr warning — produced
9
+ * misleading runs (Kimi chewing on a bloated blob, or hitting the prompt
10
+ * timeout). All command parsers now hard-fail on unknown flag-shaped tokens
11
+ * while still preserving prose mode after the first non-flag token and the
12
+ * `--` escape.
13
+ */
14
+ function looksLikeFlag(token) {
15
+ if (!token.startsWith("-")) {
16
+ return false;
17
+ }
18
+ if (token.startsWith("--") && token.length > 2) {
19
+ return true;
20
+ }
21
+ // Short flag: a single dash followed by exactly one letter (`-r`, `-m`).
22
+ return /^-[a-zA-Z]$/.test(token);
23
+ }
24
+ const ASK_SUPPORTED_FLAGS = "-m/--model <name>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
25
+ const RESCUE_SUPPORTED_FLAGS = "-m/--model <name>, --background, --wait, --fresh, -r, --resume <job-id|session-id>";
26
+ // Thinking-on is always-on for user-facing commands in v1.0. review-gate
27
+ // pins thinking=false via the cli-client options bag instead of an argv
28
+ // flag (see runtime/cli-client.ts::CliClientOptions). The parser rejects
29
+ // --thinking and --no-thinking with a hard error so a documented-removed
30
+ // flag doesn't get silently accepted as a no-op. Both bare (`--thinking`)
31
+ // and assignment (`--thinking=true`) forms are rejected — otherwise the
32
+ // assignment form would fall through to the generic "Unknown flag" path
33
+ // and a wrapper agent reading the error would think the cure is to drop
34
+ // the `=value` rather than the flag itself.
35
+ const THINKING_FLAG_REMOVED_MESSAGE = "--thinking / --no-thinking were removed in v1.0. Thinking is always on for user-facing commands.";
36
+ function isRemovedThinkingFlag(token) {
37
+ return (token === "--thinking" ||
38
+ token === "--no-thinking" ||
39
+ token.startsWith("--thinking=") ||
40
+ token.startsWith("--no-thinking="));
41
+ }
42
+ const PURSUE_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --turns <N>";
43
+ const SWARM_SUPPORTED_FLAGS = "-m/--model <name>, --budget <30m|1h|90s>, --cap <N>, --max-concurrency <N>, --write";
44
+ /**
45
+ * Parser for /kimi:swarm (read-only parallel fan-out). Foreground-only:
46
+ * no --background/--fresh/--resume. Trailing tokens are the objective.
47
+ */
48
+ export function parseSwarmArgs(argv) {
49
+ let model;
50
+ const thinking = true;
51
+ let budgetMs;
52
+ let cap;
53
+ let maxConcurrency;
54
+ let write = false;
55
+ let trailingTokens;
56
+ for (let index = 0; index < argv.length; index += 1) {
57
+ const token = argv[index];
58
+ if (token === "--") {
59
+ trailingTokens = argv.slice(index + 1);
60
+ break;
61
+ }
62
+ if (!token.startsWith("-")) {
63
+ trailingTokens = argv.slice(index);
64
+ break;
65
+ }
66
+ switch (token) {
67
+ case "-m":
68
+ case "--model": {
69
+ const value = argv[index + 1];
70
+ if (!value) {
71
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "swarm.parse");
72
+ }
73
+ if (value.startsWith("-")) {
74
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "swarm.parse");
75
+ }
76
+ model = value;
77
+ index += 1;
78
+ break;
79
+ }
80
+ case "--write":
81
+ write = true;
82
+ break;
83
+ case "--budget": {
84
+ const value = argv[index + 1];
85
+ if (!value || value.startsWith("-")) {
86
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "swarm.parse");
87
+ }
88
+ budgetMs = parseDurationMs(value, "--budget");
89
+ index += 1;
90
+ break;
91
+ }
92
+ case "--cap": {
93
+ cap = parsePositiveIntFlag("--cap", argv[index + 1]);
94
+ index += 1;
95
+ break;
96
+ }
97
+ case "--max-concurrency": {
98
+ maxConcurrency = parsePositiveIntFlag("--max-concurrency", argv[index + 1]);
99
+ index += 1;
100
+ break;
101
+ }
102
+ default:
103
+ if (isRemovedThinkingFlag(token)) {
104
+ throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "swarm.parse");
105
+ }
106
+ if (looksLikeFlag(token)) {
107
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for swarm. Supported flags: ${SWARM_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "swarm.parse");
108
+ }
109
+ trailingTokens = argv.slice(index);
110
+ index = argv.length;
111
+ break;
112
+ }
113
+ }
114
+ return {
115
+ budgetMs,
116
+ cap,
117
+ maxConcurrency,
118
+ write,
119
+ model,
120
+ thinking,
121
+ objective: trailingTokens?.join(" "),
122
+ };
123
+ }
124
+ /**
125
+ * Shared positive-integer flag parser for swarm's `--cap` / `--max-concurrency`.
126
+ * Both must be a strict positive integer; `--max-concurrency` in particular maps
127
+ * to kimi-code's `KIMI_CODE_AGENT_SWARM_MAX_CONCURRENCY`, whose upstream resolver
128
+ * THROWS on a non-positive-integer — so the same `Number.isInteger && > 0`
129
+ * predicate guarantees every accepted value round-trips through `String()`.
130
+ */
131
+ function parsePositiveIntFlag(flag, value) {
132
+ if (!value || value.startsWith("-")) {
133
+ throw new RuntimeError("INVALID_ARGS", `${flag} requires a positive integer.`, "swarm.parse");
134
+ }
135
+ const parsed = Number(value);
136
+ if (!Number.isInteger(parsed) || parsed <= 0) {
137
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive integer.`, "swarm.parse");
138
+ }
139
+ return parsed;
140
+ }
141
+ /**
142
+ * Upper bound for any `--budget` duration. The budget is the SOLE hard
143
+ * wall-clock bound on the plugin's autonomous/fan-out surfaces (pursue, swarm),
144
+ * so a typo'd or absurd value (`999999h`, a pasted nanosecond count) would
145
+ * effectively disable that bound — defeating the one guarantee these modes rest
146
+ * on. 24h is far above any legitimate budget (pursue defaults to 45m, swarm to
147
+ * 30m) yet still finite, so the AbortController always fires. Above it we
148
+ * hard-fail INVALID_ARGS rather than silently clamp — a wrapper LLM passing a
149
+ * runaway budget should see the rejection, not a quietly-different value.
150
+ */
151
+ export const MAX_DURATION_MS = 24 * 60 * 60 * 1_000;
152
+ /**
153
+ * Parse a duration token (`30m`, `1h`, `90s`, or a bare integer = minutes)
154
+ * into milliseconds. Throws INVALID_ARGS on a malformed, non-positive, or
155
+ * absurdly large value (> {@link MAX_DURATION_MS}).
156
+ */
157
+ export function parseDurationMs(token, flag) {
158
+ const match = /^(\d+)(s|m|h)?$/.exec(token.trim());
159
+ if (!match) {
160
+ throw new RuntimeError("INVALID_ARGS", `${flag} expects a duration like 30m, 1h, or 90s (bare number = minutes); got "${token}".`, "pursue.parse");
161
+ }
162
+ const value = Number(match[1]);
163
+ const unit = match[2] ?? "m";
164
+ if (!Number.isFinite(value) || value <= 0) {
165
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be a positive duration.`, "pursue.parse");
166
+ }
167
+ const unitMs = unit === "s" ? 1_000 : unit === "h" ? 3_600_000 : 60_000;
168
+ const ms = value * unitMs;
169
+ if (ms > MAX_DURATION_MS) {
170
+ throw new RuntimeError("INVALID_ARGS", `${flag} must be at most 24h (got "${token}"); the budget is a safety ceiling, not a scheduler.`, "pursue.parse");
171
+ }
172
+ return ms;
173
+ }
174
+ /**
175
+ * Parser for /kimi:pursue (autonomous goal mode). Foreground-only in the
176
+ * prototype: no --background/--fresh/--resume (the goalId/sessionId split makes
177
+ * session resume unreliable — see pursue.ts). Trailing tokens are the objective.
178
+ */
179
+ export function parsePursueArgs(argv) {
180
+ let model;
181
+ const thinking = true;
182
+ let budgetMs;
183
+ let turns;
184
+ let trailingTokens;
185
+ for (let index = 0; index < argv.length; index += 1) {
186
+ const token = argv[index];
187
+ if (token === "--") {
188
+ trailingTokens = argv.slice(index + 1);
189
+ break;
190
+ }
191
+ if (!token.startsWith("-")) {
192
+ trailingTokens = argv.slice(index);
193
+ break;
194
+ }
195
+ switch (token) {
196
+ case "-m":
197
+ case "--model": {
198
+ const value = argv[index + 1];
199
+ if (!value) {
200
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "pursue.parse");
201
+ }
202
+ if (value.startsWith("-")) {
203
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "pursue.parse");
204
+ }
205
+ model = value;
206
+ index += 1;
207
+ break;
208
+ }
209
+ case "--budget": {
210
+ const value = argv[index + 1];
211
+ if (!value || value.startsWith("-")) {
212
+ throw new RuntimeError("INVALID_ARGS", "--budget requires a duration (e.g. 30m, 1h, 90s).", "pursue.parse");
213
+ }
214
+ budgetMs = parseDurationMs(value, "--budget");
215
+ index += 1;
216
+ break;
217
+ }
218
+ case "--turns": {
219
+ const value = argv[index + 1];
220
+ if (!value || value.startsWith("-")) {
221
+ throw new RuntimeError("INVALID_ARGS", "--turns requires a positive integer.", "pursue.parse");
222
+ }
223
+ const parsedTurns = Number(value);
224
+ if (!Number.isInteger(parsedTurns) || parsedTurns <= 0) {
225
+ throw new RuntimeError("INVALID_ARGS", "--turns must be a positive integer.", "pursue.parse");
226
+ }
227
+ turns = parsedTurns;
228
+ index += 1;
229
+ break;
230
+ }
231
+ default:
232
+ if (isRemovedThinkingFlag(token)) {
233
+ throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "pursue.parse");
234
+ }
235
+ if (looksLikeFlag(token)) {
236
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for pursue. Supported flags: ${PURSUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped objective text to pass it through.`, "pursue.parse");
237
+ }
238
+ trailingTokens = argv.slice(index);
239
+ index = argv.length;
240
+ break;
241
+ }
242
+ }
243
+ return {
244
+ budgetMs,
245
+ turns,
246
+ model,
247
+ thinking,
248
+ objective: trailingTokens?.join(" "),
249
+ };
250
+ }
251
+ export function parseAskArgs(argv) {
252
+ let model;
253
+ // Thinking is on by default across ask/review/challenge/rescue. The
254
+ // parser rejects --thinking / --no-thinking; review-gate's runtime
255
+ // caller pins thinking=false via CliClientOptions instead.
256
+ let thinking = true;
257
+ let background = false;
258
+ let wait = false;
259
+ let fresh = false;
260
+ let resume = false;
261
+ let resumeTarget;
262
+ let explicitResumeTarget = false;
263
+ let trailingTokens;
264
+ for (let index = 0; index < argv.length; index += 1) {
265
+ const token = argv[index];
266
+ if (token === "--") {
267
+ trailingTokens = argv.slice(index + 1);
268
+ break;
269
+ }
270
+ if (!token.startsWith("-")) {
271
+ trailingTokens = argv.slice(index);
272
+ break;
273
+ }
274
+ switch (token) {
275
+ case "-m":
276
+ case "--model": {
277
+ const value = argv[index + 1];
278
+ if (!value) {
279
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
280
+ }
281
+ if (value.startsWith("-")) {
282
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
283
+ }
284
+ model = value;
285
+ index += 1;
286
+ break;
287
+ }
288
+ case "--background":
289
+ background = true;
290
+ break;
291
+ case "--wait":
292
+ wait = true;
293
+ break;
294
+ case "--fresh":
295
+ fresh = true;
296
+ break;
297
+ case "-r":
298
+ resume = true;
299
+ break;
300
+ case "--resume": {
301
+ const value = argv[index + 1];
302
+ if (!value || value.startsWith("-")) {
303
+ throw new RuntimeError("INVALID_ARGS", "--resume requires a job-id or session-id. Use -r to resume the latest ask session for this repo.", "ask.parse");
304
+ }
305
+ resume = true;
306
+ resumeTarget = value;
307
+ explicitResumeTarget = true;
308
+ index += 1;
309
+ break;
310
+ }
311
+ default:
312
+ if (isRemovedThinkingFlag(token)) {
313
+ throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
314
+ }
315
+ if (looksLikeFlag(token)) {
316
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ask. Supported flags: ${ASK_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
317
+ }
318
+ trailingTokens = argv.slice(index);
319
+ index = argv.length;
320
+ break;
321
+ }
322
+ }
323
+ if (fresh && resume) {
324
+ throw new RuntimeError("INVALID_ARGS", "ask does not allow --fresh and --resume together.", "ask.parse");
325
+ }
326
+ if (explicitResumeTarget && trailingTokens?.length) {
327
+ throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest ask session with a prompt.", "ask.parse");
328
+ }
329
+ return {
330
+ background,
331
+ wait,
332
+ fresh,
333
+ resume,
334
+ resumeTarget,
335
+ model,
336
+ thinking,
337
+ prompt: trailingTokens?.join(" "),
338
+ };
339
+ }
340
+ export function parseReviewArgs(argv, commandName = "review") {
341
+ const parsed = parseKnownFlags(argv, new Set(["--base", "--background", "--wait", "-m", "--model"]), commandName);
342
+ return {
343
+ base: parsed.base,
344
+ background: parsed.background,
345
+ wait: parsed.wait,
346
+ model: parsed.model,
347
+ thinking: parsed.thinking,
348
+ focus: parsed.trailingText,
349
+ };
350
+ }
351
+ export function parseRescueArgs(argv) {
352
+ let model;
353
+ let thinking = true;
354
+ let background = false;
355
+ let wait = false;
356
+ let fresh = false;
357
+ let resume = false;
358
+ let resumeTarget;
359
+ let explicitResumeTarget = false;
360
+ let trailingTokens;
361
+ for (let index = 0; index < argv.length; index += 1) {
362
+ const token = argv[index];
363
+ if (token === "--") {
364
+ trailingTokens = argv.slice(index + 1);
365
+ break;
366
+ }
367
+ if (!token.startsWith("-")) {
368
+ trailingTokens = argv.slice(index);
369
+ break;
370
+ }
371
+ switch (token) {
372
+ case "-m":
373
+ case "--model": {
374
+ const value = argv[index + 1];
375
+ if (!value) {
376
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
377
+ }
378
+ if (value.startsWith("-")) {
379
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
380
+ }
381
+ model = value;
382
+ index += 1;
383
+ break;
384
+ }
385
+ case "--background":
386
+ background = true;
387
+ break;
388
+ case "--wait":
389
+ wait = true;
390
+ break;
391
+ case "--fresh":
392
+ fresh = true;
393
+ break;
394
+ case "-r":
395
+ resume = true;
396
+ break;
397
+ case "--resume": {
398
+ const value = argv[index + 1];
399
+ if (!value || value.startsWith("-")) {
400
+ throw new RuntimeError("INVALID_ARGS", "--resume requires a job-id or session-id. Use -r to resume the latest rescue session for this repo.", "rescue.parse");
401
+ }
402
+ resume = true;
403
+ resumeTarget = value;
404
+ explicitResumeTarget = true;
405
+ index += 1;
406
+ break;
407
+ }
408
+ default:
409
+ if (isRemovedThinkingFlag(token)) {
410
+ throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
411
+ }
412
+ if (looksLikeFlag(token)) {
413
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for rescue. Supported flags: ${RESCUE_SUPPORTED_FLAGS}. Use \`--\` before flag-shaped prompt text to pass it through.`, "args.parse");
414
+ }
415
+ trailingTokens = argv.slice(index);
416
+ index = argv.length;
417
+ break;
418
+ }
419
+ }
420
+ if (fresh && resume) {
421
+ throw new RuntimeError("INVALID_ARGS", "rescue does not allow --fresh and --resume together.", "rescue.parse");
422
+ }
423
+ if (explicitResumeTarget && trailingTokens?.length) {
424
+ throw new RuntimeError("INVALID_ARGS", "--resume only accepts a job-id or session-id. Use -r to resume the latest rescue session with a prompt.", "rescue.parse");
425
+ }
426
+ return {
427
+ background,
428
+ wait,
429
+ fresh,
430
+ resume,
431
+ resumeTarget,
432
+ model,
433
+ thinking,
434
+ prompt: trailingTokens?.join(" "),
435
+ };
436
+ }
437
+ export function parseJobLookupArgs(argv) {
438
+ let type;
439
+ let jobId;
440
+ let json = false;
441
+ for (let index = 0; index < argv.length; index += 1) {
442
+ const token = argv[index];
443
+ if (token === "--type") {
444
+ const value = argv[index + 1];
445
+ if (!value || !isManagedCommandType(value)) {
446
+ throw new RuntimeError("INVALID_ARGS", "--type requires one of review, challenge, rescue, review_gate, ask.", "args.parse");
447
+ }
448
+ type = value;
449
+ index += 1;
450
+ continue;
451
+ }
452
+ if (token === "--json") {
453
+ json = true;
454
+ continue;
455
+ }
456
+ if (token.startsWith("-")) {
457
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token}. Supported flags: --type <review|challenge|rescue|review_gate|ask>, --json.`, "args.parse");
458
+ }
459
+ if (jobId) {
460
+ throw new RuntimeError("INVALID_ARGS", "Only one optional job id may be supplied.", "args.parse");
461
+ }
462
+ jobId = token;
463
+ }
464
+ return {
465
+ jobId,
466
+ type,
467
+ json,
468
+ };
469
+ }
470
+ function isManagedCommandType(value) {
471
+ return (value === "review" ||
472
+ value === "challenge" ||
473
+ value === "rescue" ||
474
+ value === "review_gate" ||
475
+ value === "ask");
476
+ }
477
+ function parseKnownFlags(argv, knownFlags, commandName) {
478
+ let model;
479
+ let thinking = true;
480
+ let base;
481
+ let background = false;
482
+ let wait = false;
483
+ let trailingTokens;
484
+ for (let index = 0; index < argv.length; index += 1) {
485
+ const token = argv[index];
486
+ if (token === "--") {
487
+ trailingTokens = argv.slice(index + 1);
488
+ break;
489
+ }
490
+ if (!knownFlags.has(token)) {
491
+ if (isRemovedThinkingFlag(token)) {
492
+ throw new RuntimeError("INVALID_ARGS", THINKING_FLAG_REMOVED_MESSAGE, "args.parse");
493
+ }
494
+ if (looksLikeFlag(token)) {
495
+ // Wrapper agents routinely hallucinate flags like `--file` /
496
+ // `--context` because that shape is common in other CLIs. Silently
497
+ // slurping them into focus text produced a bloated prompt that
498
+ // looked like a hang. Hard-fail with the supported list inline so
499
+ // the error message itself is the correction.
500
+ throw new RuntimeError("INVALID_ARGS", `Unknown flag ${token} for ${commandName}. Supported flags: --base <ref>, -m/--model <name>. Use \`--\` before flag-shaped focus text to pass it through.`, "args.parse");
501
+ }
502
+ trailingTokens = argv.slice(index);
503
+ break;
504
+ }
505
+ switch (token) {
506
+ case "-m":
507
+ case "--model": {
508
+ const value = argv[index + 1];
509
+ if (!value) {
510
+ throw new RuntimeError("INVALID_ARGS", `${token} requires a model value.`, "args.parse");
511
+ }
512
+ if (value.startsWith("-")) {
513
+ throw new RuntimeError("INVALID_ARGS", `${token} value cannot start with '-'; pass a model name`, "args.parse");
514
+ }
515
+ model = value;
516
+ index += 1;
517
+ break;
518
+ }
519
+ case "--base": {
520
+ const value = argv[index + 1];
521
+ if (!value) {
522
+ throw new RuntimeError("INVALID_ARGS", "--base requires a ref value.", "args.parse");
523
+ }
524
+ if (value.startsWith("-")) {
525
+ throw new RuntimeError("INVALID_ARGS", "--base value cannot start with '-'; pass a branch, tag, or commit ref.", "args.parse");
526
+ }
527
+ base = value;
528
+ index += 1;
529
+ break;
530
+ }
531
+ case "--background":
532
+ background = true;
533
+ break;
534
+ case "--wait":
535
+ wait = true;
536
+ break;
537
+ // The early `if (!knownFlags.has(token))` gate above means we only
538
+ // reach this switch with tokens that ARE in knownFlags, and every
539
+ // member is handled above. A `default` arm would be unreachable.
540
+ // --thinking and --no-thinking are NOT in knownFlags any more (v1.0
541
+ // removed them); the early gate above hard-fails on them with
542
+ // THINKING_FLAG_REMOVED_MESSAGE.
543
+ }
544
+ }
545
+ return {
546
+ base,
547
+ background,
548
+ wait,
549
+ model,
550
+ thinking,
551
+ trailingText: trailingTokens?.join(" "),
552
+ };
553
+ }
@@ -0,0 +1,27 @@
1
+ import { constants } from "node:fs";
2
+ import { access, mkdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { resolvePluginDataRoot } from "./plugin-data.js";
5
+ export function resolvePluginPaths(env) {
6
+ const claudePluginData = resolvePluginDataRoot(env);
7
+ const pluginRoot = path.join(claudePluginData, "kimi-plugin-cc");
8
+ return {
9
+ claudePluginData,
10
+ pluginRoot,
11
+ stateDbPath: path.join(pluginRoot, "state.db"),
12
+ logsDir: path.join(pluginRoot, "logs"),
13
+ artifactsDir: path.join(pluginRoot, "artifacts"),
14
+ worktreesDir: path.join(pluginRoot, "worktrees"),
15
+ configPath: path.join(pluginRoot, "config.json"),
16
+ };
17
+ }
18
+ export async function ensurePluginPaths(paths) {
19
+ await mkdir(paths.pluginRoot, { recursive: true });
20
+ await mkdir(paths.logsDir, { recursive: true });
21
+ await mkdir(paths.artifactsDir, { recursive: true });
22
+ await access(paths.pluginRoot, constants.R_OK | constants.W_OK);
23
+ await access(paths.logsDir, constants.R_OK | constants.W_OK);
24
+ await access(paths.artifactsDir, constants.R_OK | constants.W_OK);
25
+ // worktreesDir is created lazily by the write-swarm path (not every command
26
+ // needs it), so it is intentionally NOT required here.
27
+ }
@@ -0,0 +1,98 @@
1
+ import { existsSync, lstatSync, realpathSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { RuntimeError } from "./errors.js";
5
+ // Use the loaded module, not the shared PLUGIN_ROOT/CLAUDE_PLUGIN_ROOT variables.
6
+ // Both source and compiled modules live one directory below the package root.
7
+ const loadedPackageRoot = fileURLToPath(new URL("../", import.meta.url));
8
+ /** Resolve state without silently trusting a different plugin's shared env. */
9
+ export function resolvePluginDataRoot(env, packageRoot = loadedPackageRoot) {
10
+ const explicit = env.KIMI_PLUGIN_CC_DATA;
11
+ if (explicit !== undefined) {
12
+ if (!path.isAbsolute(explicit)) {
13
+ throw new RuntimeError("INVALID_PLUGIN_DATA", "KIMI_PLUGIN_CC_DATA must be an absolute directory path, not an empty or relative value.", "paths");
14
+ }
15
+ return path.normalize(explicit);
16
+ }
17
+ const expected = installedDataRoot(packageRoot);
18
+ const shared = [env.CLAUDE_PLUGIN_DATA, env.PLUGIN_DATA].filter((value) => Boolean(value));
19
+ if (expected) {
20
+ if (shared.some((value) => !path.isAbsolute(value) || !sameDataRoot(value, expected))) {
21
+ throw new RuntimeError("PLUGIN_DATA_CONFLICT", "Shared plugin data variables do not match this installation. Remove the conflicting variables or set KIMI_PLUGIN_CC_DATA to the intended Kimi data directory. No data was moved.", "paths", { details: { expected_data_root: expected, override_variable: "KIMI_PLUGIN_CC_DATA" } });
22
+ }
23
+ return expected;
24
+ }
25
+ // Checkout/custom launch compatibility: we cannot infer ownership from an
26
+ // arbitrary directory or from a marker that may not exist on first setup.
27
+ // Preserve the old location when unambiguous; require an explicit choice otherwise.
28
+ if (shared.length === 2 && !sameDataRoot(shared[0], shared[1])) {
29
+ throw new RuntimeError("PLUGIN_DATA_CONFLICT", "CLAUDE_PLUGIN_DATA and PLUGIN_DATA disagree. Set KIMI_PLUGIN_CC_DATA to the intended Kimi data directory before retrying. No data was moved.", "paths");
30
+ }
31
+ if (shared[0])
32
+ return shared[0];
33
+ if (env.KIMI_PLUGIN_CC_SHELL_LAUNCH === "1" && (env.CODEX_HOME || env.HOME)) {
34
+ const dataRoot = env.CODEX_HOME
35
+ ? path.join(env.CODEX_HOME, "plugins", "data")
36
+ : path.join(env.HOME, ".codex", "plugins", "data");
37
+ return path.join(dataRoot, "kimi-marketplace-kimi");
38
+ }
39
+ throw new RuntimeError("MISSING_PLUGIN_DATA", "Set KIMI_PLUGIN_CC_DATA to an absolute writable data directory. Custom launches may also use an unambiguous CLAUDE_PLUGIN_DATA or PLUGIN_DATA value.", "paths");
40
+ }
41
+ function installedDataRoot(packageRoot) {
42
+ const root = path.resolve(packageRoot);
43
+ const pluginDir = path.dirname(root);
44
+ const marketplaceDir = path.dirname(pluginDir);
45
+ const cacheDir = path.dirname(marketplaceDir);
46
+ const pluginsDir = path.dirname(cacheDir);
47
+ if (path.basename(pluginDir) !== "kimi" || path.basename(cacheDir) !== "cache" || path.basename(pluginsDir) !== "plugins") {
48
+ return undefined;
49
+ }
50
+ const marketplace = path.basename(marketplaceDir);
51
+ if (existsSync(path.join(root, ".codex-plugin", "plugin.json"))) {
52
+ return path.join(pluginsDir, "data", `${marketplace}-kimi`);
53
+ }
54
+ if (existsSync(path.join(root, ".claude-plugin", "plugin.json"))) {
55
+ // Claude documents plugin-id sanitization for persistent data directories.
56
+ const id = `kimi@${marketplace}`.replace(/[^a-zA-Z0-9_-]/g, "-");
57
+ return path.join(pluginsDir, "data", id);
58
+ }
59
+ return undefined;
60
+ }
61
+ function sameDataRoot(left, right) {
62
+ if (path.resolve(left) === path.resolve(right))
63
+ return true;
64
+ const canonicalLeft = canonicalDataRoot(left);
65
+ return canonicalLeft !== undefined && canonicalLeft === canonicalDataRoot(right);
66
+ }
67
+ /** Resolve existing ancestors without creating a first-run data directory. */
68
+ function canonicalDataRoot(value) {
69
+ let cursor = path.resolve(value);
70
+ const missing = [];
71
+ for (;;) {
72
+ try {
73
+ const resolved = realpathSync(cursor);
74
+ if (missing.length > 0 && !statSync(cursor).isDirectory())
75
+ return undefined;
76
+ return path.join(resolved, ...missing);
77
+ }
78
+ catch (error) {
79
+ if (error.code !== "ENOENT")
80
+ return undefined;
81
+ // ENOENT can also mean a dangling symlink. Do not treat an existing
82
+ // link as a missing directory and accidentally bypass its destination.
83
+ try {
84
+ lstatSync(cursor);
85
+ return undefined;
86
+ }
87
+ catch (statError) {
88
+ if (statError.code !== "ENOENT")
89
+ return undefined;
90
+ }
91
+ const parent = path.dirname(cursor);
92
+ if (parent === cursor)
93
+ return undefined;
94
+ missing.unshift(path.basename(cursor));
95
+ cursor = parent;
96
+ }
97
+ }
98
+ }