@kisev/agentomatic 11.0.0-dev.58.gc136a777fcbb → 11.0.0-dev.60.gea605e5cb0b5

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.
@@ -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", "tui", "kilo", "mimo"];
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: "Add the tui.json schema and stacked diffs";
30
- readonly targets: readonly ["tui"];
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 = {
@@ -37,40 +37,68 @@ const SECRET_PATHS = [
37
37
  ];
38
38
  export class ConfigSetupError extends LifecycleError {
39
39
  }
40
- export const CONFIG_TARGETS = ["opencode", "tui", "kilo", "mimo"];
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: "Add the tui.json schema and stacked diffs",
70
- targets: ["tui"],
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
- { kind: "set-if-absent", path: ["$schema"], value: "https://opencode.ai/tui.json" },
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
- let file;
266
- let current;
267
- try {
268
- file = await resolveTargetFile(target, scope, cwd, home);
269
- current = file.exists ? await readRegular(file.absolute) : undefined;
270
- }
271
- catch (error) {
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
- parsed = parseJsonc(text);
342
+ file = await resolveTargetFile(target, scope, cwd, home, kind);
343
+ current = file.exists ? await readRegular(file.absolute) : undefined;
298
344
  }
299
- catch {
300
- parsed = undefined;
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
- try {
303
- const applied = applyJsoncEdits(text, fragmentEdits(fragment, target, parsed));
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: file.exists ? "update" : "create",
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
- else {
315
- operations.push({ target, path: file.absolute, fragment, operation: "unchanged" });
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
- catch (error) {
319
- operations.push({
320
- target,
321
- path: file.absolute,
322
- fragment,
323
- operation: "conflict",
324
- reason: error instanceof LifecycleError || error instanceof Error
325
- ? error.message
326
- : String(error),
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) => !selection.targets.some((target) => applicable(fragment, target)))
344
- .map((fragment) => ({ fragment, reason: "no selected target supports this fragment" }));
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.58.gc136a777fcbb",
3
+ "version": "11.0.0-dev.60.gea605e5cb0b5",
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.58.gc136a777fcbb",
54
- "@kisev/safe-fs": "1.0.0-dev.58.gc136a777fcbb"
53
+ "@kisev/memomatic": "1.0.0-dev.60.gea605e5cb0b5",
54
+ "@kisev/safe-fs": "1.0.0-dev.60.gea605e5cb0b5"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@opencode-ai/plugin": "1.18.29",