@pragma-sh/kimi-plugin 0.1.0-alpha.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.
Binary file
@@ -0,0 +1,86 @@
1
+ // scripts/install-local.ts
2
+ import {
3
+ cpSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readFileSync,
8
+ renameSync,
9
+ rmSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import { homedir } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ var PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
16
+ var RUNTIME_PATHS = ["assets", "dist", "hooks", "kimi.plugin.json", "package.json"];
17
+ function installLocal(kimiHome = process.env.KIMI_CODE_HOME ?? join(homedir(), ".kimi-code"), packageRoot = PACKAGE_ROOT) {
18
+ const manifest = JSON.parse(readFileSync(join(packageRoot, "kimi.plugin.json"), "utf8"));
19
+ if (typeof manifest.name !== "string" || manifest.name.length === 0) {
20
+ throw new Error('kimi.plugin.json must contain a non-empty "name"');
21
+ }
22
+ for (const relativePath of RUNTIME_PATHS) {
23
+ if (!existsSync(join(packageRoot, relativePath))) {
24
+ throw new Error(`Missing runtime path: ${relativePath}`);
25
+ }
26
+ }
27
+ const id = manifest.name.toLowerCase();
28
+ const pluginsDir = join(kimiHome, "plugins");
29
+ const managedDir = join(pluginsDir, "managed");
30
+ const target = join(managedDir, id);
31
+ mkdirSync(managedDir, { recursive: true });
32
+ const staging = mkdtempSync(join(managedDir, `${id}-`));
33
+ try {
34
+ for (const relativePath of RUNTIME_PATHS) {
35
+ cpSync(join(packageRoot, relativePath), join(staging, relativePath), { recursive: true });
36
+ }
37
+ rmSync(target, { recursive: true, force: true });
38
+ renameSync(staging, target);
39
+ } catch (error) {
40
+ rmSync(staging, { recursive: true, force: true });
41
+ throw error;
42
+ }
43
+ const installedPath = join(pluginsDir, "installed.json");
44
+ const installed = readInstalled(installedPath);
45
+ const existingIndex = installed.plugins.findIndex((plugin) => plugin.id === id);
46
+ const existing = existingIndex < 0 ? undefined : installed.plugins[existingIndex];
47
+ const now = new Date().toISOString();
48
+ const record = {
49
+ ...existing,
50
+ id,
51
+ root: target,
52
+ source: "local-path",
53
+ enabled: existing?.enabled ?? true,
54
+ installedAt: typeof existing?.installedAt === "string" && existing.installedAt.length > 0 ? existing.installedAt : now,
55
+ updatedAt: now,
56
+ originalSource: packageRoot
57
+ };
58
+ if (existingIndex < 0)
59
+ installed.plugins.push(record);
60
+ else
61
+ installed.plugins[existingIndex] = record;
62
+ const tempPath = `${installedPath}.${process.pid}.tmp`;
63
+ writeFileSync(tempPath, `${JSON.stringify(installed, null, 2)}
64
+ `);
65
+ renameSync(tempPath, installedPath);
66
+ return target;
67
+ }
68
+ function readInstalled(path) {
69
+ if (!existsSync(path))
70
+ return { version: 1, plugins: [] };
71
+ const value = JSON.parse(readFileSync(path, "utf8"));
72
+ if (value.version !== 1 || !Array.isArray(value.plugins)) {
73
+ throw new Error(`${path} is not a valid Kimi installed.json file`);
74
+ }
75
+ return { version: 1, plugins: value.plugins };
76
+ }
77
+ var entryPath = process.argv[1];
78
+ if (entryPath !== undefined && resolve(entryPath) === fileURLToPath(import.meta.url)) {
79
+ const target = installLocal();
80
+ process.stdout.write(`Installed Pragma Kimi plugin to ${target}.
81
+ `);
82
+ process.stdout.write("Run `/plugins reload` or start a new Kimi session to load it.\n");
83
+ }
84
+ export {
85
+ installLocal
86
+ };
@@ -0,0 +1,437 @@
1
+ // ../plugin/dist/shared/chunk-k5kqv14t.js
2
+ var PLUGIN_API_VERSION = "0.0.0";
3
+
4
+ // ../plugin/dist/shared/chunk-0rpz2gg0.js
5
+ function defineAgent(input) {
6
+ return input;
7
+ }
8
+ function definePlugin(input) {
9
+ return { ...input, __apiVersion: PLUGIN_API_VERSION };
10
+ }
11
+
12
+ // ../watcher-kit/src/index.ts
13
+ var DEFAULT_APPROVE_KEYS = "\r";
14
+ var RIGHT_ARROW = "\x1B[C";
15
+ var DOWN_ARROW = "\x1B[B";
16
+ var DEFAULT_DENY_KEYS = `${RIGHT_ARROW}${RIGHT_ARROW}\r`;
17
+ var DEFAULT_SUBMIT_KEYS = "\r";
18
+ var BRACKETED_PASTE_START = "\x1B[200~";
19
+ var BRACKETED_PASTE_END = "\x1B[201~";
20
+ var QUESTION_REJECT_KEYS = "\x1B";
21
+ var QUESTION_DIGIT_MAX = 9;
22
+ var QUESTION_PROMPT_MOUNT_DELAY_MS = 150;
23
+ var QUESTION_OTHER_INPUT_DELAY_MS = 150;
24
+ var QUESTION_NEXT_PROMPT_DELAY_MS = 500;
25
+ var ANSWER_SEPARATOR = "\x1F";
26
+ var RESUBSCRIBE_DELAY_MS = 500;
27
+ var DEFAULT_INTERJECT_SUBMIT_DELAY_MS = 200;
28
+ function createTuiWatcher(options) {
29
+ const {
30
+ agent,
31
+ handleDecisions,
32
+ handleQuestionAnswers = handleDecisions,
33
+ interjectSubmitDelayMs = DEFAULT_INTERJECT_SUBMIT_DELAY_MS,
34
+ interjectMode = "bracketed",
35
+ interjectSubmitKeys,
36
+ questionSelectMode = "digit",
37
+ questionOtherMode = "navigate-enter",
38
+ questionDismissKeys = QUESTION_REJECT_KEYS,
39
+ questionFinalizeKeys = ""
40
+ } = options;
41
+ return {
42
+ agent,
43
+ async watch(ctx) {
44
+ const watcherContext = ctx;
45
+ const runtime = {
46
+ keys: resolveKeys(watcherContext.config, interjectSubmitKeys),
47
+ handleDecisions,
48
+ handleQuestionAnswers,
49
+ interjectSubmitDelayMs,
50
+ interjectMode,
51
+ questionSelectMode,
52
+ questionOtherMode,
53
+ questionDismissKeys,
54
+ questionFinalizeKeys,
55
+ seenRequestIds: new Set,
56
+ questionsByRequestId: new Map,
57
+ outstandingCommandRequestId: null
58
+ };
59
+ let failures = 0;
60
+ while (!ctx.signal.aborted) {
61
+ try {
62
+ await consumeControlEvents(watcherContext, runtime);
63
+ failures = 0;
64
+ } catch (error) {
65
+ failures += 1;
66
+ reportStreamFailure(agent, failures, error);
67
+ }
68
+ if (ctx.signal.aborted) {
69
+ return;
70
+ }
71
+ await delay(RESUBSCRIBE_DELAY_MS, ctx.signal);
72
+ }
73
+ }
74
+ };
75
+ }
76
+ function reportStreamFailure(agent, consecutive, error) {
77
+ if (consecutive !== 1 && consecutive % STREAM_FAILURE_LOG_EVERY !== 0) {
78
+ return;
79
+ }
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ console.warn(JSON.stringify({ type: "watcher.streamError", agent, consecutive, error: message }));
82
+ }
83
+ var STREAM_FAILURE_LOG_EVERY = 240;
84
+ function resolveKeys(config, interjectSubmitKeys) {
85
+ const c = config ?? {};
86
+ return {
87
+ approveKeys: c.approveKeys ?? DEFAULT_APPROVE_KEYS,
88
+ denyKeys: c.denyKeys ?? DEFAULT_DENY_KEYS,
89
+ submitKeys: c.submitKeys ?? interjectSubmitKeys ?? DEFAULT_SUBMIT_KEYS
90
+ };
91
+ }
92
+ async function consumeControlEvents(ctx, runtime) {
93
+ const connection = await ctx.sdk.agents.connect({
94
+ agent: ctx.agentId,
95
+ tabId: ctx.session.tabId,
96
+ worktreeId: ctx.session.worktreeId,
97
+ signal: ctx.signal
98
+ });
99
+ for await (const event of connection) {
100
+ if (ctx.signal.aborted) {
101
+ return;
102
+ }
103
+ await handleControlEvent(ctx, runtime, event);
104
+ }
105
+ }
106
+ async function handleControlEvent(ctx, runtime, event) {
107
+ if (event.type === "agent") {
108
+ if (runtime.handleQuestionAnswers)
109
+ rememberQuestion(runtime.questionsByRequestId, event);
110
+ if (runtime.handleDecisions)
111
+ rememberCommandAttention(runtime, event);
112
+ return;
113
+ }
114
+ if (runtime.handleDecisions && await handleDecision(ctx, runtime, event)) {
115
+ return;
116
+ }
117
+ if (runtime.handleQuestionAnswers && await handleAnswer(ctx, runtime, event)) {
118
+ return;
119
+ }
120
+ if (event.type === "agentInput") {
121
+ await handleInterjection(ctx, runtime.keys.submitKeys, runtime.interjectSubmitDelayMs, runtime.interjectMode, event.input.text);
122
+ }
123
+ }
124
+ async function handleDecision(ctx, runtime, event) {
125
+ if (event.type !== "agentDecision")
126
+ return false;
127
+ if (runtime.seenRequestIds.has(event.decision.requestId))
128
+ return true;
129
+ runtime.seenRequestIds.add(event.decision.requestId);
130
+ if (runtime.outstandingCommandRequestId !== null && runtime.outstandingCommandRequestId !== event.decision.requestId) {
131
+ return true;
132
+ }
133
+ runtime.outstandingCommandRequestId = null;
134
+ await writeKeys(ctx, event.decision.approved ? runtime.keys.approveKeys : runtime.keys.denyKeys);
135
+ return true;
136
+ }
137
+ async function handleAnswer(ctx, runtime, event) {
138
+ if (event.type !== "agentAnswer")
139
+ return false;
140
+ const { answer } = event;
141
+ const cached = runtime.questionsByRequestId.get(answer.requestId);
142
+ if (!cached)
143
+ return true;
144
+ if (runtime.seenRequestIds.has(answer.requestId))
145
+ return true;
146
+ runtime.seenRequestIds.add(answer.requestId);
147
+ runtime.questionsByRequestId.delete(answer.requestId);
148
+ await delay(QUESTION_PROMPT_MOUNT_DELAY_MS, ctx.signal);
149
+ if (ctx.signal.aborted)
150
+ return true;
151
+ const reply = answer.answer?.trim() ?? null;
152
+ if (cached.questions.length > 1) {
153
+ if (!answer.dismissed && reply) {
154
+ await writeMultipleQuestionAnswers(ctx, runtime, cached.questions, reply);
155
+ } else if (answer.dismissed || !reply) {
156
+ await writeKeys(ctx, runtime.questionDismissKeys);
157
+ }
158
+ return true;
159
+ }
160
+ const single = cached.questions[0] ?? { question: "", options: [] };
161
+ await writeQuestionAnswer(ctx, runtime, single, reply, answer.dismissed);
162
+ if (!answer.dismissed && reply)
163
+ await finalizeQuestionAnswers(ctx, runtime);
164
+ return true;
165
+ }
166
+ async function writeQuestionAnswer(ctx, runtime, question, reply, dismissed) {
167
+ if (dismissed || !reply) {
168
+ await writeKeys(ctx, runtime.questionDismissKeys);
169
+ return;
170
+ }
171
+ if (!dismissed && reply && !question.options.includes(reply)) {
172
+ await writeFreeTextAnswer(ctx, question.options.length, reply, runtime.questionSelectMode, runtime.questionOtherMode);
173
+ return;
174
+ }
175
+ const strokes = questionAnswerKeys({
176
+ dismissed,
177
+ reply,
178
+ options: question.options,
179
+ selectMode: runtime.questionSelectMode
180
+ });
181
+ if (strokes)
182
+ await writeKeys(ctx, strokes);
183
+ }
184
+ async function writeFreeTextAnswer(ctx, optionCount, reply, selectMode, otherMode) {
185
+ await writeKeys(ctx, openOtherEditorKeys(optionCount, selectMode, otherMode));
186
+ await delay(QUESTION_OTHER_INPUT_DELAY_MS, ctx.signal);
187
+ if (!ctx.signal.aborted)
188
+ await writeKeys(ctx, `${reply}\r`);
189
+ }
190
+ async function writeMultipleQuestionAnswers(ctx, runtime, questions, combined) {
191
+ const replies = combined.split(ANSWER_SEPARATOR).map((reply) => reply.trim());
192
+ for (const [index, question] of questions.entries()) {
193
+ const reply = replies[index];
194
+ if (!reply || ctx.signal.aborted)
195
+ return;
196
+ await writeQuestionAnswer(ctx, runtime, question, reply, false);
197
+ if (index < questions.length - 1) {
198
+ await delay(QUESTION_NEXT_PROMPT_DELAY_MS, ctx.signal);
199
+ }
200
+ }
201
+ await finalizeQuestionAnswers(ctx, runtime);
202
+ }
203
+ async function finalizeQuestionAnswers(ctx, runtime) {
204
+ if (!runtime.questionFinalizeKeys || ctx.signal.aborted)
205
+ return;
206
+ await delay(QUESTION_NEXT_PROMPT_DELAY_MS, ctx.signal);
207
+ if (!ctx.signal.aborted)
208
+ await writeKeys(ctx, runtime.questionFinalizeKeys);
209
+ }
210
+ async function handleInterjection(ctx, submitKeys, submitDelayMs, mode, text) {
211
+ const input = mode === "plain" ? text : `${BRACKETED_PASTE_START}${text}${BRACKETED_PASTE_END}`;
212
+ await writeKeys(ctx, input);
213
+ if (!submitKeys) {
214
+ return;
215
+ }
216
+ await delay(submitDelayMs, ctx.signal);
217
+ if (!ctx.signal.aborted)
218
+ await writeKeys(ctx, submitKeys);
219
+ }
220
+ function rememberCommandAttention(runtime, event) {
221
+ if (event.status === "attention" && event.attentionKind === "command" && typeof event.requestId === "string" && event.requestId.length > 0) {
222
+ runtime.outstandingCommandRequestId = event.requestId;
223
+ return;
224
+ }
225
+ if (event.status !== "attention") {
226
+ runtime.outstandingCommandRequestId = null;
227
+ }
228
+ }
229
+ function rememberQuestion(cache, event) {
230
+ if (event.status === "attention" && event.attentionKind === "question" && typeof event.requestId === "string" && event.requestId.length > 0) {
231
+ const entries = (event.questions ?? []).flatMap(questionEntry);
232
+ if (entries.length > 0) {
233
+ cache.set(event.requestId, { questions: entries });
234
+ return;
235
+ }
236
+ const options = (event.options ?? []).map((option) => option.label).filter((option) => option.trim() !== "");
237
+ cache.set(event.requestId, {
238
+ questions: [{ question: event.question ?? "", options }]
239
+ });
240
+ }
241
+ }
242
+ function questionEntry(value) {
243
+ if (typeof value !== "object" || value === null || !("question" in value)) {
244
+ return [];
245
+ }
246
+ const record = value;
247
+ if (typeof record.question !== "string" || !record.question.trim()) {
248
+ return [];
249
+ }
250
+ const options = Array.isArray(record.options) ? record.options.filter((option) => Boolean(option) && typeof option === "object").map((option) => option.label).filter((label) => typeof label === "string" && label.trim() !== "") : [];
251
+ return [{ question: record.question, options }];
252
+ }
253
+ function questionAnswerKeys(input) {
254
+ if (input.dismissed || input.reply === null) {
255
+ return QUESTION_REJECT_KEYS;
256
+ }
257
+ const reply = input.reply.trim();
258
+ if (!reply) {
259
+ return QUESTION_REJECT_KEYS;
260
+ }
261
+ const options = input.options;
262
+ const selectMode = input.selectMode ?? "digit";
263
+ const matchIndex = options.findIndex((option) => option === reply);
264
+ if (matchIndex >= 0) {
265
+ return selectOptionKeys(matchIndex, options.length, selectMode);
266
+ }
267
+ return `${openOtherEditorKeys(options.length, selectMode, "navigate-enter")}${reply}\r`;
268
+ }
269
+ function openOtherEditorKeys(optionCount, selectMode, otherMode) {
270
+ if (otherMode === "shortcut-z")
271
+ return "z";
272
+ const navigate = DOWN_ARROW.repeat(optionCount);
273
+ return selectMode === "arrow-space" || otherMode === "navigate" ? navigate : `${navigate}\r`;
274
+ }
275
+ function selectOptionKeys(index, optionCount, selectMode) {
276
+ if (selectMode === "arrow-space") {
277
+ return `${DOWN_ARROW.repeat(index)} \r`;
278
+ }
279
+ const total = optionCount + 1;
280
+ if (index < QUESTION_DIGIT_MAX && index < total) {
281
+ return String(index + 1);
282
+ }
283
+ return `${DOWN_ARROW.repeat(index)}\r`;
284
+ }
285
+ async function writeKeys(ctx, data) {
286
+ try {
287
+ await ctx.sendKeys(data);
288
+ } catch {}
289
+ }
290
+ function delay(ms, signal) {
291
+ if (signal.aborted) {
292
+ return Promise.resolve();
293
+ }
294
+ return new Promise((resolve) => {
295
+ const finish = () => resolve();
296
+ const timer = setTimeout(finish, ms);
297
+ signal.addEventListener("abort", () => {
298
+ clearTimeout(timer);
299
+ finish();
300
+ }, { once: true });
301
+ });
302
+ }
303
+
304
+ // src/models.ts
305
+ var KIMI_BIN_DIR = "$HOME/.kimi-code/bin";
306
+ var KIMI_MODELS_COMMAND = `kimi provider list --json || "${KIMI_BIN_DIR}/kimi" provider list --json`;
307
+ var KIMI_DEFAULT_MODEL_COMMAND = `kimi provider list || "${KIMI_BIN_DIR}/kimi" provider list`;
308
+ async function loadKimiModels(ctx) {
309
+ try {
310
+ const results = await ctx.sdk.exec.run({
311
+ cwd: ctx.project?.path ?? "/tmp",
312
+ commands: [KIMI_MODELS_COMMAND, KIMI_DEFAULT_MODEL_COMMAND]
313
+ });
314
+ return kimiModelsFromConfig(parseKimiProviderModels(results[0]?.stdout ?? ""), parseKimiDefaultModel(results[1]?.stdout ?? ""));
315
+ } catch {
316
+ return [];
317
+ }
318
+ }
319
+ function kimiModelsFromConfig(config, defaultModel) {
320
+ const entries = Object.entries(config).map(([id, model]) => ({
321
+ id,
322
+ name: model.displayName ?? model.model
323
+ }));
324
+ const defaultIndex = entries.findIndex((entry) => entry.id === defaultModel);
325
+ if (defaultIndex > 0) {
326
+ const [entry] = entries.splice(defaultIndex, 1);
327
+ if (entry !== undefined)
328
+ entries.unshift(entry);
329
+ }
330
+ return entries;
331
+ }
332
+ function parseKimiProviderModels(output) {
333
+ let value;
334
+ try {
335
+ value = JSON.parse(output);
336
+ } catch {
337
+ return {};
338
+ }
339
+ const models = recordValue(recordValue(value)?.models);
340
+ if (models === undefined)
341
+ return {};
342
+ const parsed = {};
343
+ for (const [alias, candidate] of Object.entries(models)) {
344
+ const model = recordValue(candidate);
345
+ if (model === undefined || typeof model.model !== "string" || model.model.length === 0) {
346
+ continue;
347
+ }
348
+ if (model.disabled === true)
349
+ continue;
350
+ parsed[alias] = {
351
+ provider: typeof model.provider === "string" ? model.provider : "",
352
+ model: model.model,
353
+ ...typeof model.displayName === "string" ? { displayName: model.displayName } : {}
354
+ };
355
+ }
356
+ return parsed;
357
+ }
358
+ function parseKimiDefaultModel(output) {
359
+ const match = /^Default model:\s*(.+)$/m.exec(output);
360
+ return match?.[1]?.trim() || undefined;
361
+ }
362
+ function recordValue(value) {
363
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
364
+ }
365
+
366
+ // src/pragma-plugin.ts
367
+ var INTERJECT_SUBMIT_DELAY_MS = 200;
368
+ var PREFILL_DELAY_MS = 2500;
369
+ var baseWatcher = createTuiWatcher({
370
+ agent: "kimi",
371
+ handleDecisions: false,
372
+ handleQuestionAnswers: true,
373
+ questionFinalizeKeys: "1",
374
+ interjectSubmitDelayMs: INTERJECT_SUBMIT_DELAY_MS
375
+ });
376
+ var kimiAgentPlugin = definePlugin({
377
+ name: "Kimi Code",
378
+ description: "Launch Kimi Code from Pragma.",
379
+ watchers: [
380
+ {
381
+ agent: "kimi",
382
+ async watch(ctx) {
383
+ try {
384
+ await baseWatcher.watch(ctx);
385
+ } finally {
386
+ try {
387
+ await ctx.sdk.agents.report({
388
+ agent: ctx.agentId,
389
+ tabId: ctx.session.tabId,
390
+ worktreeId: ctx.session.worktreeId,
391
+ status: "cleared",
392
+ attentionKind: null
393
+ });
394
+ } catch {}
395
+ }
396
+ }
397
+ }
398
+ ],
399
+ agents: [
400
+ defineAgent({
401
+ id: "kimi",
402
+ name: "Kimi Code",
403
+ icon: () => null,
404
+ iconPath: "assets/kimi.png",
405
+ launch: { command: ["kimi", "-y"] },
406
+ prefillDelayMs: PREFILL_DELAY_MS,
407
+ prefillMode: "plain",
408
+ prefillSubmit: "\r",
409
+ models: loadKimiModels,
410
+ permissionModes: [
411
+ { id: "yolo", name: "Auto-approve tools" },
412
+ { id: "default", name: "Ask for approval" },
413
+ { id: "auto", name: "Fully autonomous" },
414
+ { id: "plan", name: "Plan mode" }
415
+ ],
416
+ excludeFeatures: ["commandApproval"],
417
+ args: {
418
+ model: (modelId) => ["-m", modelId],
419
+ reasoning: () => [],
420
+ permissionMode: (permissionModeId) => {
421
+ if (permissionModeId === "yolo") {
422
+ return [];
423
+ }
424
+ if (permissionModeId === "auto") {
425
+ return ["--auto"];
426
+ }
427
+ return permissionModeId === "plan" ? ["--plan"] : [];
428
+ }
429
+ }
430
+ })
431
+ ]
432
+ });
433
+ var pragma_plugin_default = kimiAgentPlugin;
434
+ export {
435
+ kimiAgentPlugin,
436
+ pragma_plugin_default as default
437
+ };