@kisev/agentomatic 11.0.0-dev.58.gc136a777fcbb → 11.0.0-dev.59.g0506803c61e5
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/config-setup.d.ts +10 -3
- package/dist/config-setup.js +164 -80
- package/package.json +3 -3
package/dist/config-setup.d.ts
CHANGED
|
@@ -2,32 +2,39 @@ import { type DependencyRunner } from "./self-install.js";
|
|
|
2
2
|
import { LifecycleError, type Scope } from "./lifecycle.js";
|
|
3
3
|
export declare class ConfigSetupError extends LifecycleError {
|
|
4
4
|
}
|
|
5
|
-
export declare const CONFIG_TARGETS: readonly ["opencode", "
|
|
5
|
+
export declare const CONFIG_TARGETS: readonly ["opencode", "kilo", "mimo"];
|
|
6
6
|
export type ConfigTargetName = (typeof CONFIG_TARGETS)[number];
|
|
7
|
+
export type TargetFileKind = "main" | "tui";
|
|
7
8
|
export declare const CONFIG_FRAGMENTS: readonly [{
|
|
8
9
|
readonly name: "core-plugin";
|
|
9
10
|
readonly description: "Register @kisev/agentomatic in the plugin array (OpenCode only)";
|
|
10
11
|
readonly targets: readonly ["opencode"];
|
|
12
|
+
readonly file: "main";
|
|
11
13
|
}, {
|
|
12
14
|
readonly name: "skills-state-permissions";
|
|
13
15
|
readonly description: "Allow the standard skills XDG state paths without per-run prompts";
|
|
14
16
|
readonly targets: readonly ["opencode", "kilo", "mimo"];
|
|
17
|
+
readonly file: "main";
|
|
15
18
|
}, {
|
|
16
19
|
readonly name: "lsp-preset";
|
|
17
20
|
readonly description: "Add LSP servers from the shared catalog (OpenCode only)";
|
|
18
21
|
readonly targets: readonly ["opencode"];
|
|
22
|
+
readonly file: "main";
|
|
19
23
|
}, {
|
|
20
24
|
readonly name: "secrets-guard";
|
|
21
25
|
readonly description: "Deny reads and edits of common secret files";
|
|
22
26
|
readonly targets: readonly ["opencode", "kilo", "mimo"];
|
|
27
|
+
readonly file: "main";
|
|
23
28
|
}, {
|
|
24
29
|
readonly name: "kilo-display";
|
|
25
30
|
readonly description: "Expand Kilo reasoning, terminal, edit, and tool blocks";
|
|
26
31
|
readonly targets: readonly ["kilo"];
|
|
32
|
+
readonly file: "main";
|
|
27
33
|
}, {
|
|
28
34
|
readonly name: "tui-schema";
|
|
29
|
-
readonly description: "
|
|
30
|
-
readonly targets: readonly ["
|
|
35
|
+
readonly description: "Unify each agent TUI file: schema, theme, stacked diffs, and leader keybinds";
|
|
36
|
+
readonly targets: readonly ["opencode", "kilo", "mimo"];
|
|
37
|
+
readonly file: "tui";
|
|
31
38
|
}];
|
|
32
39
|
export type FragmentName = (typeof CONFIG_FRAGMENTS)[number]["name"];
|
|
33
40
|
export type ConfigSetupSelection = {
|
package/dist/config-setup.js
CHANGED
|
@@ -37,40 +37,68 @@ const SECRET_PATHS = [
|
|
|
37
37
|
];
|
|
38
38
|
export class ConfigSetupError extends LifecycleError {
|
|
39
39
|
}
|
|
40
|
-
export const CONFIG_TARGETS = ["opencode", "
|
|
40
|
+
export const CONFIG_TARGETS = ["opencode", "kilo", "mimo"];
|
|
41
41
|
export const CONFIG_FRAGMENTS = [
|
|
42
42
|
{
|
|
43
43
|
name: "core-plugin",
|
|
44
44
|
description: "Register @kisev/agentomatic in the plugin array (OpenCode only)",
|
|
45
45
|
targets: ["opencode"],
|
|
46
|
+
file: "main",
|
|
46
47
|
},
|
|
47
48
|
{
|
|
48
49
|
name: "skills-state-permissions",
|
|
49
50
|
description: "Allow the standard skills XDG state paths without per-run prompts",
|
|
50
51
|
targets: ["opencode", "kilo", "mimo"],
|
|
52
|
+
file: "main",
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
name: "lsp-preset",
|
|
54
56
|
description: "Add LSP servers from the shared catalog (OpenCode only)",
|
|
55
57
|
targets: ["opencode"],
|
|
58
|
+
file: "main",
|
|
56
59
|
},
|
|
57
60
|
{
|
|
58
61
|
name: "secrets-guard",
|
|
59
62
|
description: "Deny reads and edits of common secret files",
|
|
60
63
|
targets: ["opencode", "kilo", "mimo"],
|
|
64
|
+
file: "main",
|
|
61
65
|
},
|
|
62
66
|
{
|
|
63
67
|
name: "kilo-display",
|
|
64
68
|
description: "Expand Kilo reasoning, terminal, edit, and tool blocks",
|
|
65
69
|
targets: ["kilo"],
|
|
70
|
+
file: "main",
|
|
66
71
|
},
|
|
67
72
|
{
|
|
68
73
|
name: "tui-schema",
|
|
69
|
-
description: "
|
|
70
|
-
targets: ["
|
|
74
|
+
description: "Unify each agent TUI file: schema, theme, stacked diffs, and leader keybinds",
|
|
75
|
+
targets: ["opencode", "kilo", "mimo"],
|
|
76
|
+
file: "tui",
|
|
71
77
|
},
|
|
72
78
|
];
|
|
73
79
|
const FRAGMENT_NAMES = CONFIG_FRAGMENTS.map((fragment) => fragment.name);
|
|
80
|
+
const UNIFIED_TUI_KEYBINDS = {
|
|
81
|
+
command_list: "alt+p",
|
|
82
|
+
app_exit: "ctrl+d,<leader>q,<leader>й",
|
|
83
|
+
messages_first: "ctrl+g",
|
|
84
|
+
messages_last: "ctrl+alt+g",
|
|
85
|
+
editor_open: "<leader>e,<leader>у",
|
|
86
|
+
theme_list: "<leader>t,<leader>е",
|
|
87
|
+
sidebar_toggle: "<leader>b,<leader>и",
|
|
88
|
+
status_view: "<leader>s,<leader>ы",
|
|
89
|
+
session_export: "<leader>x,<leader>ч",
|
|
90
|
+
session_new: "<leader>n,<leader>т",
|
|
91
|
+
session_list: "<leader>l,<leader>д",
|
|
92
|
+
session_timeline: "<leader>g,<leader>п",
|
|
93
|
+
session_compact: "<leader>c,<leader>с",
|
|
94
|
+
model_list: "<leader>m,<leader>ь",
|
|
95
|
+
agent_list: "<leader>a,<leader>ф",
|
|
96
|
+
messages_copy: "<leader>y,<leader>н",
|
|
97
|
+
messages_undo: "<leader>u,<leader>г",
|
|
98
|
+
messages_redo: "<leader>r,<leader>к",
|
|
99
|
+
messages_toggle_conceal: "<leader>h,<leader>р",
|
|
100
|
+
tips_toggle: "<leader>h,<leader>р",
|
|
101
|
+
};
|
|
74
102
|
const LSP_SERVER_COMMANDS = {
|
|
75
103
|
python: ["basedpyright-langserver", "--stdio"],
|
|
76
104
|
typescript: ["typescript-language-server", "--stdio"],
|
|
@@ -153,20 +181,39 @@ function fragmentEdits(fragment, target, current) {
|
|
|
153
181
|
{ kind: "set-if-absent", path: ["mcp_tool_display"], value: "expanded" },
|
|
154
182
|
];
|
|
155
183
|
}
|
|
184
|
+
const tuiSchemas = {
|
|
185
|
+
opencode: "https://opencode.ai/tui.json",
|
|
186
|
+
kilo: undefined,
|
|
187
|
+
mimo: "https://mimo.xiaomi.com/mimocode/tui.json",
|
|
188
|
+
};
|
|
156
189
|
return [
|
|
157
|
-
|
|
190
|
+
...(tuiSchemas[target]
|
|
191
|
+
? [{ kind: "set-if-absent", path: ["$schema"], value: tuiSchemas[target] }]
|
|
192
|
+
: []),
|
|
193
|
+
{ kind: "set-if-absent", path: ["theme"], value: "ayu" },
|
|
158
194
|
{ kind: "set-if-absent", path: ["diff_style"], value: "stacked" },
|
|
195
|
+
...Object.entries(UNIFIED_TUI_KEYBINDS).map(([key, value]) => ({
|
|
196
|
+
kind: "set-if-absent",
|
|
197
|
+
path: ["keybinds", key],
|
|
198
|
+
value,
|
|
199
|
+
})),
|
|
159
200
|
];
|
|
160
201
|
}
|
|
161
202
|
function applicable(fragment, target) {
|
|
162
203
|
const targets = CONFIG_FRAGMENTS.find((item) => item.name === fragment)?.targets ?? [];
|
|
163
204
|
return targets.includes(target);
|
|
164
205
|
}
|
|
206
|
+
function fragmentFileKind(fragment) {
|
|
207
|
+
return CONFIG_FRAGMENTS.find((item) => item.name === fragment)?.file ?? "main";
|
|
208
|
+
}
|
|
165
209
|
export function normalizeConfigSelection(scope, value = {}) {
|
|
166
210
|
const requestedTargets = value.targets ?? ["opencode"];
|
|
167
211
|
const targets = CONFIG_TARGETS.filter((target) => requestedTargets.includes(target));
|
|
168
|
-
if (targets.length !== requestedTargets.length)
|
|
212
|
+
if (targets.length !== requestedTargets.length) {
|
|
213
|
+
if (requestedTargets.includes("tui"))
|
|
214
|
+
throw new ConfigSetupError("invalid_selection", "The tui target moved into opencode, kilo, and mimo as the tui-schema fragment");
|
|
169
215
|
throw new ConfigSetupError("invalid_selection", "Unknown config target selection");
|
|
216
|
+
}
|
|
170
217
|
if (scope === "project" && (targets.length !== 1 || targets[0] !== "opencode"))
|
|
171
218
|
throw new ConfigSetupError("invalid_selection", "Project scope supports only the opencode target");
|
|
172
219
|
const requestedFragments = value.fragments ?? [];
|
|
@@ -179,8 +226,6 @@ export async function defaultConfigSelection(scope, cwd = process.cwd(), home =
|
|
|
179
226
|
const exists = async (path) => Boolean(await readRegular(path).catch(() => undefined));
|
|
180
227
|
const targets = ["opencode"];
|
|
181
228
|
if (scope === "global") {
|
|
182
|
-
if (await exists(join(home, ".config", "opencode", "tui.json")))
|
|
183
|
-
targets.push("tui");
|
|
184
229
|
if (await exists(join(home, ".config", "kilo", "kilo.json")))
|
|
185
230
|
targets.push("kilo");
|
|
186
231
|
if (await exists(join(home, ".config", "kilo", "kilo.jsonc")))
|
|
@@ -195,9 +240,41 @@ export async function defaultConfigSelection(scope, cwd = process.cwd(), home =
|
|
|
195
240
|
fragments: [...FRAGMENT_NAMES],
|
|
196
241
|
});
|
|
197
242
|
}
|
|
198
|
-
async function resolveTargetFile(target, scope, cwd, home) {
|
|
243
|
+
async function resolveTargetFile(target, scope, cwd, home, kind = "main") {
|
|
199
244
|
const existing = async (root, path) => Boolean(await readRegular(join(root, path)).catch(() => undefined));
|
|
200
245
|
const globalRoot = deploymentRoot("global", cwd, home);
|
|
246
|
+
if (kind === "tui") {
|
|
247
|
+
if (target === "kilo") {
|
|
248
|
+
const kiloRoot = join(home, ".config", "kilo");
|
|
249
|
+
for (const path of ["tui.jsonc", "tui.json"])
|
|
250
|
+
if (await existing(kiloRoot, path))
|
|
251
|
+
return { target, root: kiloRoot, path, absolute: join(kiloRoot, path), exists: true };
|
|
252
|
+
return {
|
|
253
|
+
target,
|
|
254
|
+
root: kiloRoot,
|
|
255
|
+
path: "tui.jsonc",
|
|
256
|
+
absolute: join(kiloRoot, "tui.jsonc"),
|
|
257
|
+
exists: false,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (target === "mimo") {
|
|
261
|
+
const mimoRoot = join(home, ".config", "mimocode");
|
|
262
|
+
return {
|
|
263
|
+
target,
|
|
264
|
+
root: mimoRoot,
|
|
265
|
+
path: "tui.json",
|
|
266
|
+
absolute: join(mimoRoot, "tui.json"),
|
|
267
|
+
exists: await existing(mimoRoot, "tui.json"),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
target,
|
|
272
|
+
root: globalRoot,
|
|
273
|
+
path: "tui.json",
|
|
274
|
+
absolute: join(globalRoot, "tui.json"),
|
|
275
|
+
exists: await existing(globalRoot, "tui.json"),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
201
278
|
if (target === "opencode") {
|
|
202
279
|
if (scope === "global") {
|
|
203
280
|
for (const path of ["opencode.json", "opencode.jsonc"])
|
|
@@ -228,14 +305,6 @@ async function resolveTargetFile(target, scope, cwd, home) {
|
|
|
228
305
|
exists: false,
|
|
229
306
|
};
|
|
230
307
|
}
|
|
231
|
-
if (target === "tui")
|
|
232
|
-
return {
|
|
233
|
-
target,
|
|
234
|
-
root: globalRoot,
|
|
235
|
-
path: "tui.json",
|
|
236
|
-
absolute: join(globalRoot, "tui.json"),
|
|
237
|
-
exists: await existing(globalRoot, "tui.json"),
|
|
238
|
-
};
|
|
239
308
|
if (target === "kilo") {
|
|
240
309
|
const root = join(home, ".config", "kilo");
|
|
241
310
|
for (const path of ["kilo.jsonc", "kilo.json"])
|
|
@@ -262,86 +331,101 @@ async function build(selection, scope, cwd = process.cwd(), home = homedir()) {
|
|
|
262
331
|
const allowedRoots = new Set([root]);
|
|
263
332
|
const files = [];
|
|
264
333
|
for (const target of CONFIG_TARGETS.filter((name) => selection.targets.includes(name))) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const reason = error instanceof LifecycleError ? error.message : "target is unreadable";
|
|
273
|
-
for (const fragment of selection.fragments.filter((item) => applicable(item, target)))
|
|
274
|
-
operations.push({ target, path: "unknown", fragment, operation: "conflict", reason });
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
if (file.exists && current === undefined) {
|
|
278
|
-
for (const fragment of selection.fragments.filter((item) => applicable(item, target)))
|
|
279
|
-
operations.push({
|
|
280
|
-
target,
|
|
281
|
-
path: file.absolute,
|
|
282
|
-
fragment,
|
|
283
|
-
operation: "conflict",
|
|
284
|
-
reason: "target is unreadable",
|
|
285
|
-
});
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
files.push(file);
|
|
289
|
-
allowedRoots.add(file.root);
|
|
290
|
-
let text = current ? current.toString("utf8") : "{}\n";
|
|
291
|
-
let changed = false;
|
|
292
|
-
for (const fragment of selection.fragments) {
|
|
293
|
-
if (!applicable(fragment, target))
|
|
294
|
-
continue;
|
|
295
|
-
let parsed = undefined;
|
|
334
|
+
const kinds = ["main"];
|
|
335
|
+
const wantsTui = selection.fragments.some((fragment) => applicable(fragment, target) && fragmentFileKind(fragment) === "tui");
|
|
336
|
+
if (wantsTui && scope === "global")
|
|
337
|
+
kinds.push("tui");
|
|
338
|
+
for (const kind of kinds) {
|
|
339
|
+
let file;
|
|
340
|
+
let current;
|
|
296
341
|
try {
|
|
297
|
-
|
|
342
|
+
file = await resolveTargetFile(target, scope, cwd, home, kind);
|
|
343
|
+
current = file.exists ? await readRegular(file.absolute) : undefined;
|
|
298
344
|
}
|
|
299
|
-
catch {
|
|
300
|
-
|
|
345
|
+
catch (error) {
|
|
346
|
+
const reason = error instanceof LifecycleError ? error.message : "target is unreadable";
|
|
347
|
+
for (const fragment of selection.fragments.filter((item) => applicable(item, target) && fragmentFileKind(item) === kind))
|
|
348
|
+
operations.push({ target, path: "unknown", fragment, operation: "conflict", reason });
|
|
349
|
+
continue;
|
|
301
350
|
}
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
if (applied.changed) {
|
|
305
|
-
text = applied.text;
|
|
306
|
-
changed = true;
|
|
351
|
+
if (file.exists && current === undefined) {
|
|
352
|
+
for (const fragment of selection.fragments.filter((item) => applicable(item, target) && fragmentFileKind(item) === kind))
|
|
307
353
|
operations.push({
|
|
308
354
|
target,
|
|
309
355
|
path: file.absolute,
|
|
310
356
|
fragment,
|
|
311
|
-
operation:
|
|
357
|
+
operation: "conflict",
|
|
358
|
+
reason: "target is unreadable",
|
|
312
359
|
});
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
files.push(file);
|
|
363
|
+
allowedRoots.add(file.root);
|
|
364
|
+
let text = current ? current.toString("utf8") : "{}\n";
|
|
365
|
+
let changed = false;
|
|
366
|
+
for (const fragment of selection.fragments) {
|
|
367
|
+
if (!applicable(fragment, target) || fragmentFileKind(fragment) !== kind)
|
|
368
|
+
continue;
|
|
369
|
+
let parsed = undefined;
|
|
370
|
+
try {
|
|
371
|
+
parsed = parseJsonc(text);
|
|
313
372
|
}
|
|
314
|
-
|
|
315
|
-
|
|
373
|
+
catch {
|
|
374
|
+
parsed = undefined;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const applied = applyJsoncEdits(text, fragmentEdits(fragment, target, parsed));
|
|
378
|
+
if (applied.changed) {
|
|
379
|
+
text = applied.text;
|
|
380
|
+
changed = true;
|
|
381
|
+
operations.push({
|
|
382
|
+
target,
|
|
383
|
+
path: file.absolute,
|
|
384
|
+
fragment,
|
|
385
|
+
operation: file.exists ? "update" : "create",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
operations.push({ target, path: file.absolute, fragment, operation: "unchanged" });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
operations.push({
|
|
394
|
+
target,
|
|
395
|
+
path: file.absolute,
|
|
396
|
+
fragment,
|
|
397
|
+
operation: "conflict",
|
|
398
|
+
reason: error instanceof LifecycleError || error instanceof Error
|
|
399
|
+
? error.message
|
|
400
|
+
: String(error),
|
|
401
|
+
});
|
|
316
402
|
}
|
|
317
403
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
operation: "
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
404
|
+
if (changed) {
|
|
405
|
+
const content = Buffer.from(text.endsWith("\n") ? text : `${text}\n`);
|
|
406
|
+
mutations.push({
|
|
407
|
+
...(file.root === root ? {} : { root: file.root }),
|
|
408
|
+
path: file.path,
|
|
409
|
+
operation: "write",
|
|
410
|
+
content,
|
|
411
|
+
mode: 0o644,
|
|
412
|
+
expected: current ? { sha256: sha256(current) } : { absent: true },
|
|
327
413
|
});
|
|
328
414
|
}
|
|
329
415
|
}
|
|
330
|
-
if (changed) {
|
|
331
|
-
const content = Buffer.from(text.endsWith("\n") ? text : `${text}\n`);
|
|
332
|
-
mutations.push({
|
|
333
|
-
...(file.root === root ? {} : { root: file.root }),
|
|
334
|
-
path: file.path,
|
|
335
|
-
operation: "write",
|
|
336
|
-
content,
|
|
337
|
-
mode: 0o644,
|
|
338
|
-
expected: current ? { sha256: sha256(current) } : { absent: true },
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
416
|
}
|
|
342
417
|
const skipped = selection.fragments
|
|
343
|
-
.filter((fragment) =>
|
|
344
|
-
|
|
418
|
+
.filter((fragment) => {
|
|
419
|
+
if (fragmentFileKind(fragment) === "tui" && scope === "project")
|
|
420
|
+
return true;
|
|
421
|
+
return !selection.targets.some((target) => applicable(fragment, target));
|
|
422
|
+
})
|
|
423
|
+
.map((fragment) => ({
|
|
424
|
+
fragment,
|
|
425
|
+
reason: fragmentFileKind(fragment) === "tui" && scope === "project"
|
|
426
|
+
? "TUI files are global-only"
|
|
427
|
+
: "no selected target supports this fragment",
|
|
428
|
+
}));
|
|
345
429
|
const sorted = operations.sort((left, right) => CONFIG_TARGETS.indexOf(left.target) - CONFIG_TARGETS.indexOf(right.target) ||
|
|
346
430
|
FRAGMENT_NAMES.indexOf(left.fragment) -
|
|
347
431
|
FRAGMENT_NAMES.indexOf(right.fragment) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kisev/agentomatic",
|
|
3
|
-
"version": "11.0.0-dev.
|
|
3
|
+
"version": "11.0.0-dev.59.g0506803c61e5",
|
|
4
4
|
"description": "OpenCode integration and opt-in installer for portable Agent Skills.",
|
|
5
5
|
"homepage": "https://github.com/kisev/skills#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@clack/prompts": "^1.8.1",
|
|
53
|
-
"@kisev/memomatic": "1.0.0-dev.
|
|
54
|
-
"@kisev/safe-fs": "1.0.0-dev.
|
|
53
|
+
"@kisev/memomatic": "1.0.0-dev.59.g0506803c61e5",
|
|
54
|
+
"@kisev/safe-fs": "1.0.0-dev.59.g0506803c61e5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@opencode-ai/plugin": "1.18.29",
|