@kisev/agentomatic 11.0.2-dev.86.g5908817e39ee → 11.0.2-dev.87.g5a92e74c6754

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.
@@ -1,17 +1,14 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { randomUUID } from "node:crypto";
3
2
  import { lstat } from "node:fs/promises";
4
3
  import { homedir } from "node:os";
5
- import { dirname, join, resolve } from "node:path";
6
- import { fileURLToPath } from "node:url";
4
+ import { join, resolve } from "node:path";
7
5
  import { archiveMutations } from "./installer.js";
8
6
  import { requirePackageVersion } from "./package-metadata.js";
9
7
  import { ensureDependency, planDependency } from "./self-install.js";
10
8
  import { applyTransaction, archiveRoot, deploymentRoot, LifecycleError, lifecycleRoot, inspectTransaction, readRegular, recoverTransaction, sha256, withLifecycleLock, } from "./lifecycle.js";
11
9
  import { applyJsoncEdits, parseJsonc } from "./jsonc.js";
10
+ import { corePluginEdits, permissionEdits } from "./opencode-config.js";
12
11
  const PACKAGE_NAME = "@kisev/agentomatic";
13
- const LEGACY_PACKAGE_NAME = "@kisev/skills-opencode";
14
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
15
12
  const XDG_STATE_GLOB = "~/.local/state/agent-skills/**";
16
13
  const OPENCODE_SKILLS_GLOB = "~/.config/opencode/skills/**";
17
14
  const SECRET_PATHS = [
@@ -52,12 +49,6 @@ export const CONFIG_FRAGMENTS = [
52
49
  targets: ["opencode", "kilo", "mimo"],
53
50
  file: "main",
54
51
  },
55
- {
56
- name: "lsp-preset",
57
- description: "Add LSP servers from the shared catalog (OpenCode only)",
58
- targets: ["opencode"],
59
- file: "main",
60
- },
61
52
  {
62
53
  name: "secrets-guard",
63
54
  description: "Deny reads and edits of common secret files",
@@ -72,7 +63,7 @@ export const CONFIG_FRAGMENTS = [
72
63
  },
73
64
  {
74
65
  name: "tui-schema",
75
- description: "Unify each agent TUI file: schema, theme, stacked diffs, and leader keybinds",
66
+ description: "Configure terminal settings: OpenCode V2 cli.json or Kilo/MiMo TUI files",
76
67
  targets: ["opencode", "kilo", "mimo"],
77
68
  file: "tui",
78
69
  },
@@ -137,23 +128,26 @@ const UNIFIED_TUI_KEYBINDS = {
137
128
  messages_toggle_conceal: "<leader>h,<leader>р",
138
129
  tips_toggle: "<leader>h,<leader>р",
139
130
  };
140
- const LSP_SERVER_COMMANDS = {
141
- python: ["basedpyright-langserver", "--stdio"],
142
- typescript: ["typescript-language-server", "--stdio"],
143
- yaml: ["yaml-language-server", "--stdio"],
144
- shell: ["bash-language-server", "start"],
131
+ const OPENCODE_KEYBINDS = {
132
+ "command.palette.show": "alt+p",
133
+ "app.exit": "ctrl+d,<leader>q,<leader>й",
134
+ "session.first": "ctrl+g",
135
+ "session.last": "ctrl+alt+g",
136
+ "prompt.editor": "<leader>e,<leader>у",
137
+ "theme.switch": "<leader>t,<leader>е",
138
+ "session.sidebar.toggle": "<leader>b,<leader>и",
139
+ "opencode.status": "<leader>s,<leader>ы",
140
+ "session.export": "<leader>x,<leader>ч",
141
+ "session.new": "<leader>n,<leader>т",
142
+ "session.list": "<leader>l,<leader>д",
143
+ "session.timeline": "<leader>g,<leader>п",
144
+ "session.compact": "<leader>c,<leader>с",
145
+ "model.list": "<leader>m,<leader>ь",
146
+ "agent.list": "<leader>a,<leader>ф",
147
+ "messages.copy": "<leader>y,<leader>н",
148
+ "session.undo": "<leader>u,<leader>г",
149
+ "session.redo": "<leader>r,<leader>к",
145
150
  };
146
- function lspCatalog() {
147
- const raw = readFileSync(resolve(packageRoot, "dist", "assets", "lsp-catalog.json"), "utf8");
148
- const catalog = JSON.parse(raw);
149
- if (!Array.isArray(catalog.servers))
150
- throw new ConfigSetupError("invalid_package", "LSP catalog is unavailable");
151
- return catalog.servers
152
- .filter((server) => typeof server.name === "string" &&
153
- Array.isArray(server.extensions) &&
154
- server.extensions.every((extension) => typeof extension === "string"))
155
- .filter((server) => LSP_SERVER_COMMANDS[server.name]);
156
- }
157
151
  function mapAllowsAll(value) {
158
152
  return Boolean(value &&
159
153
  typeof value === "object" &&
@@ -171,21 +165,18 @@ function fragmentEdits(fragment, target, current) {
171
165
  ? value.permission
172
166
  : {};
173
167
  if (fragment === "core-plugin") {
174
- const key = "plugins" in value ? "plugins" : "plugin";
175
- return [
176
- { kind: "set-if-absent", path: ["$schema"], value: "https://opencode.ai/config.json" },
177
- { kind: "set-if-absent", path: [key], value: [] },
178
- {
179
- kind: "replace-array-value",
180
- path: [key],
181
- from: LEGACY_PACKAGE_NAME,
182
- to: PACKAGE_NAME,
183
- },
184
- { kind: "append-unique", path: [key], value: PACKAGE_NAME },
185
- ];
168
+ return corePluginEdits(value);
186
169
  }
187
170
  if (fragment === "skills-state-permissions") {
188
171
  const statePaths = target === "opencode" ? [XDG_STATE_GLOB, OPENCODE_SKILLS_GLOB] : [XDG_STATE_GLOB];
172
+ if (target === "opencode")
173
+ return permissionEdits(value, [
174
+ ...statePaths.flatMap((resource) => [
175
+ { action: "read", resource, effect: "allow" },
176
+ { action: "external_directory", resource, effect: "allow" },
177
+ ]),
178
+ { action: "edit", resource: XDG_STATE_GLOB, effect: "allow" },
179
+ ]);
189
180
  const edits = [];
190
181
  if (!mapAllowsAll(permission.read))
191
182
  edits.push(...permissionMapEdits(["permission", "read"], Object.fromEntries(statePaths.map((glob) => [glob, "allow"]))));
@@ -195,14 +186,15 @@ function fragmentEdits(fragment, target, current) {
195
186
  edits.push(...permissionMapEdits(["permission", "external_directory"], Object.fromEntries(statePaths.map((glob) => [glob, "allow"]))));
196
187
  return edits;
197
188
  }
198
- if (fragment === "lsp-preset") {
199
- return lspCatalog().map((server) => ({
200
- kind: "set-if-absent",
201
- path: ["lsp", server.name],
202
- value: { command: LSP_SERVER_COMMANDS[server.name], extensions: server.extensions },
203
- }));
204
- }
205
189
  if (fragment === "secrets-guard") {
190
+ if (target === "opencode")
191
+ return permissionEdits(value, [
192
+ ...SECRET_PATHS.flatMap((glob) => [
193
+ { action: "read", resource: glob.replace(/^\*\*\//, "*"), effect: "deny" },
194
+ { action: "edit", resource: glob.replace(/^\*\*\//, "*"), effect: "deny" },
195
+ ]),
196
+ { action: "read", resource: "*.env.example", effect: "allow" },
197
+ ]);
206
198
  const deny = Object.fromEntries(SECRET_PATHS.map((glob) => [glob, "deny"]));
207
199
  return [
208
200
  ...permissionMapEdits(["permission", "read"], {
@@ -220,8 +212,18 @@ function fragmentEdits(fragment, target, current) {
220
212
  { kind: "set-if-absent", path: ["mcp_tool_display"], value: "expanded" },
221
213
  ];
222
214
  }
215
+ if (target === "opencode")
216
+ return [
217
+ { kind: "set-if-absent", path: ["$schema"], value: "https://opencode.ai/v2/cli.json" },
218
+ { kind: "set-if-absent", path: ["theme", "name"], value: "ayu" },
219
+ ...Object.entries(OPENCODE_KEYBINDS).map(([key, value]) => ({
220
+ kind: "set-if-absent",
221
+ path: ["keybinds", key],
222
+ value,
223
+ })),
224
+ ];
223
225
  const tuiSchemas = {
224
- opencode: "https://opencode.ai/tui.json",
226
+ opencode: undefined,
225
227
  kilo: undefined,
226
228
  mimo: "https://mimo.xiaomi.com/mimocode/tui.json",
227
229
  };
@@ -309,9 +311,9 @@ async function resolveTargetFile(target, scope, cwd, home, kind = "main") {
309
311
  return {
310
312
  target,
311
313
  root: globalRoot,
312
- path: "tui.json",
313
- absolute: join(globalRoot, "tui.json"),
314
- exists: await existing(globalRoot, "tui.json"),
314
+ path: "cli.json",
315
+ absolute: join(globalRoot, "cli.json"),
316
+ exists: await existing(globalRoot, "cli.json"),
315
317
  };
316
318
  }
317
319
  if (target === "opencode") {
@@ -405,6 +407,20 @@ async function build(selection, scope, cwd = process.cwd(), home = homedir()) {
405
407
  for (const fragment of selection.fragments) {
406
408
  if (!applicable(fragment, target) || fragmentFileKind(fragment) !== kind)
407
409
  continue;
410
+ if (target === "opencode" &&
411
+ kind === "tui" &&
412
+ !file.exists &&
413
+ ((await readRegular(join(file.root, "tui.json"))) ||
414
+ (await readRegular(join(file.root, "tui.jsonc"))))) {
415
+ operations.push({
416
+ target,
417
+ path: file.absolute,
418
+ fragment,
419
+ operation: "conflict",
420
+ reason: "Start OpenCode V2 once to migrate existing TUI preferences into cli.json before applying terminal presets",
421
+ });
422
+ continue;
423
+ }
408
424
  let parsed = undefined;
409
425
  try {
410
426
  parsed = parseJsonc(text);
@@ -575,6 +591,8 @@ export async function applyConfigSetup(selection, scope, cwd = process.cwd(), ho
575
591
  await provision();
576
592
  return built.plan;
577
593
  }
594
+ if (!built.plan.operations.some((operation) => operation.operation === "conflict"))
595
+ return built.plan;
578
596
  throw new ConfigSetupError("invalid_state", "Config setup plan has no applicable changes");
579
597
  }
580
598
  const backups = [];
package/dist/doctor.d.ts CHANGED
@@ -9,7 +9,6 @@ export type DoctorCheck = {
9
9
  };
10
10
  export type DoctorHost = {
11
11
  config?: () => Promise<unknown>;
12
- lsp?: () => Promise<unknown>;
13
12
  };
14
13
  export type DoctorReport = {
15
14
  schema_version: 1;
package/dist/doctor.js CHANGED
@@ -12,6 +12,7 @@ import { archiveRoot, assertSafePath, deploymentRoot, lifecycleRoot, } from "./l
12
12
  import { inspectReconcile } from "./reconcile.js";
13
13
  import { readPackageVersion } from "./package-metadata.js";
14
14
  import { planDependency } from "./self-install.js";
15
+ import { parseJsonc } from "./jsonc.js";
15
16
  import { readRtkStats, rtkCharsSaved, rtkStatsPath, rtkTokensSavedEstimate, } from "./plugins/rtk.js";
16
17
  import { stateRoot } from "./runtime/state.js";
17
18
  const run = promisify(execFile);
@@ -114,13 +115,11 @@ function agentManifestValid(raw) {
114
115
  }
115
116
  function redactedConfig(value) {
116
117
  const object = value && typeof value === "object" && !Array.isArray(value) ? value : {};
117
- const configuredPlugins = object.plugins ?? object.plugin;
118
+ const configuredPlugins = object.plugins;
118
119
  const rawPlugins = Array.isArray(configuredPlugins)
119
120
  ? configuredPlugins.flatMap((item) => {
120
121
  if (typeof item === "string")
121
122
  return [item];
122
- if (Array.isArray(item) && typeof item[0] === "string")
123
- return [item[0]];
124
123
  if (item && typeof item === "object" && typeof item.package === "string")
125
124
  return [item.package];
126
125
  return [];
@@ -138,18 +137,14 @@ function redactedConfig(value) {
138
137
  keys: Object.keys(object)
139
138
  .filter((key) => [
140
139
  "$schema",
141
- "theme",
142
- "keybinds",
143
- "logLevel",
144
- "command",
140
+ "commands",
145
141
  "watcher",
146
- "plugin",
147
142
  "plugins",
148
143
  "lsp",
149
144
  "formatter",
150
- "provider",
151
- "agent",
152
- "permission",
145
+ "providers",
146
+ "agents",
147
+ "permissions",
153
148
  "compaction",
154
149
  "model",
155
150
  ].includes(key))
@@ -159,12 +154,26 @@ function redactedConfig(value) {
159
154
  }
160
155
  async function localConfig(scope, cwd, home) {
161
156
  const paths = scope === "project"
162
- ? [join(cwd, "opencode.json"), join(cwd, ".opencode", "opencode.json")]
163
- : [join(home, ".config", "opencode", "opencode.json")];
157
+ ? [
158
+ join(cwd, "opencode.json"),
159
+ join(cwd, "opencode.jsonc"),
160
+ join(cwd, ".opencode", "opencode.json"),
161
+ join(cwd, ".opencode", "opencode.jsonc"),
162
+ ]
163
+ : [
164
+ join(home, ".config", "opencode", "opencode.json"),
165
+ join(home, ".config", "opencode", "opencode.jsonc"),
166
+ ];
164
167
  for (const path of paths) {
165
168
  const value = await regular(path);
166
169
  if (value.status === "present") {
167
- const parsed = json(value.raw);
170
+ let parsed;
171
+ try {
172
+ parsed = parseJsonc(value.raw.toString("utf8"));
173
+ }
174
+ catch {
175
+ parsed = undefined;
176
+ }
168
177
  return parsed
169
178
  ? { projection: redactedConfig(parsed), source: path }
170
179
  : { incomplete: true, source: path };
@@ -254,7 +263,7 @@ async function executableAvailable(name) {
254
263
  }
255
264
  return false;
256
265
  }
257
- async function lspFacts(project, host, partial) {
266
+ async function lspFacts(project, partial) {
258
267
  const suffixes = new Set();
259
268
  let scanIncomplete = false;
260
269
  const visit = async (root) => {
@@ -281,7 +290,6 @@ async function lspFacts(project, host, partial) {
281
290
  await visit(project);
282
291
  if (scanIncomplete)
283
292
  partial.push("lsp.project-files");
284
- const disabled = process.env.OPENCODE_DISABLE_LSP_DOWNLOAD === "true";
285
293
  const servers = await Promise.all(lspCatalog.servers.map(async (server) => {
286
294
  let available = false;
287
295
  available = await executableAvailable(server.executable);
@@ -289,48 +297,20 @@ async function lspFacts(project, host, partial) {
289
297
  return {
290
298
  name: server.name,
291
299
  applicable,
292
- configured: applicable,
300
+ configured: false,
293
301
  binary_available: available,
294
- active: applicable && available && !disabled,
295
- runtime_status: "unavailable",
302
+ active: false,
303
+ runtime_status: "unsupported",
296
304
  requirement_class: server.requirement_class,
297
- reason: !applicable
298
- ? "not-selected"
299
- : disabled
300
- ? "download-disabled"
301
- : !available
302
- ? "missing-dependency"
303
- : "available",
305
+ reason: "unsupported-in-opencode-v2",
304
306
  missing: available ? [] : [server.executable],
305
307
  install: `Install ${server.executable} with your project toolchain.`,
306
308
  };
307
309
  }));
308
- let runtimeStatus = "unavailable";
309
- if (host?.lsp) {
310
- try {
311
- const value = await host.lsp();
312
- runtimeStatus = Array.isArray(value)
313
- ? value.map((item) => {
314
- const entry = item && typeof item === "object" ? item : {};
315
- return {
316
- id: typeof entry.id === "string" ? entry.id : "unknown",
317
- name: typeof entry.name === "string" ? entry.name : "unknown",
318
- status: entry.status === "connected" || entry.status === "error" ? entry.status : "unknown",
319
- };
320
- })
321
- : { status: "unavailable" };
322
- }
323
- catch {
324
- partial.push("lsp.runtime-status");
325
- }
326
- }
327
- else
328
- partial.push("lsp.runtime-status");
329
310
  return {
330
311
  schema_version: 1,
331
312
  catalog_version: lspCatalog.catalog_version,
332
- download_disabled: disabled,
333
- runtime_status: runtimeStatus,
313
+ runtime_status: "unsupported",
334
314
  servers,
335
315
  };
336
316
  }
@@ -495,7 +475,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
495
475
  source: config.source ?? "host-or-file-unavailable",
496
476
  }, [
497
477
  "Inspect OpenCode configuration without exposing secrets.",
498
- "Local plugin wrappers in the deployment plugins directory load automatically without a plugin array entry.",
478
+ "Local plugin wrappers in the deployment plugins directory load automatically without a plugins array entry.",
499
479
  ]));
500
480
  const wired = (config.projection?.enabled_plugins ?? []).includes(packageName) ||
501
481
  (hostConfig?.enabled_plugins ?? []).includes(packageName);
@@ -627,7 +607,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
627
607
  }, lifecycleFiles.incomplete
628
608
  ? ["Resolve inaccessible lifecycle state before applying a mutation."]
629
609
  : undefined));
630
- const tools = await Promise.all(["opencode", ...lspCatalog.servers.map((server) => server.executable)].map(async (name) => {
610
+ const tools = await Promise.all(["opencode"].map(async (name) => {
631
611
  try {
632
612
  const result = await run(name, ["--version"], { timeout: 1000 });
633
613
  return {
@@ -644,7 +624,7 @@ export async function collectDoctorFacts(scope, cwd = process.cwd(), home = home
644
624
  }
645
625
  }));
646
626
  checks.push(check("host.tools", tools.some((item) => item.name === "opencode" && item.available) ? "pass" : "incomplete", "External tool availability is reported", { tools: tools.map((item) => `${item.name}:${item.available ? "available" : "missing"}`) }, ["Install OpenCode or missing external tools when needed."]));
647
- const lsp = await lspFacts(project, host, partial);
627
+ const lsp = await lspFacts(project, partial);
648
628
  const counts = { pass: 0, warn: 0, fail: 0, incomplete: 0 };
649
629
  for (const item of checks)
650
630
  counts[item.status] += 1;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,4 @@
1
- import type { Plugin as V2Plugin } from "@opencode/plugin";
2
- import { z } from "zod";
3
- import { type Category, type RoutingInput } from "./routing.js";
1
+ import type { Plugin } from "@opencode/plugin";
4
2
  import { rulesInjector, type RulesInjectorOptions } from "./plugins/rules-injector.js";
5
3
  import { rtk, type RtkOptions } from "./plugins/rtk.js";
6
4
  import { zedBell, type ZedBellOptions } from "./plugins/zed-bell.js";
@@ -10,132 +8,8 @@ export type OpenCodeOptions = {
10
8
  rtk?: RtkOptions;
11
9
  zedBell?: ZedBellOptions;
12
10
  };
13
- type RuntimeInput = {
14
- directory: string;
15
- client?: {
16
- app?: {
17
- agents?: (input: {
18
- query: {
19
- directory: string;
20
- };
21
- }) => Promise<{
22
- data?: unknown;
23
- }>;
24
- };
25
- };
26
- };
27
- export declare const server: (input: RuntimeInput) => Promise<{
28
- tool: {
29
- route: {
30
- description: string;
31
- args: {
32
- action: z.ZodEnum<{
33
- dispatch: "dispatch";
34
- preview: "preview";
35
- }>;
36
- category: z.ZodEnum<{
37
- architecture: "architecture";
38
- exploration: "exploration";
39
- implementation: "implementation";
40
- review: "review";
41
- }>;
42
- task: z.ZodString;
43
- requirements: z.ZodDefault<z.ZodArray<z.ZodString>>;
44
- execution_card: z.ZodOptional<z.ZodAny>;
45
- override: z.ZodOptional<z.ZodString>;
46
- trusted_override: z.ZodOptional<z.ZodBoolean>;
47
- budget: z.ZodOptional<z.ZodObject<{
48
- cost_class: z.ZodOptional<z.ZodString>;
49
- latency_class: z.ZodOptional<z.ZodString>;
50
- }, z.core.$strip>>;
51
- decision: z.ZodOptional<z.ZodAny>;
52
- };
53
- execute(args: {
54
- action: "preview" | "dispatch";
55
- category: Category;
56
- task: string;
57
- requirements: string[];
58
- execution_card?: unknown;
59
- override?: string;
60
- trusted_override?: boolean;
61
- budget?: RoutingInput["budget"];
62
- decision?: unknown;
63
- }, context: {
64
- sessionID: string;
65
- }): Promise<string>;
66
- };
67
- };
68
- "tool.execute.before": (input: {
69
- tool: string;
70
- sessionID: string;
71
- }, output: {
72
- args: unknown;
73
- }) => Promise<void>;
74
- "tool.execute.after": (input: {
75
- tool: string;
76
- sessionID: string;
77
- args: unknown;
78
- }, output: {
79
- output: string;
80
- }) => Promise<void>;
81
- }>;
82
11
  declare const _default: {
83
12
  id: string;
84
- server: (input: RuntimeInput) => Promise<{
85
- tool: {
86
- route: {
87
- description: string;
88
- args: {
89
- action: z.ZodEnum<{
90
- dispatch: "dispatch";
91
- preview: "preview";
92
- }>;
93
- category: z.ZodEnum<{
94
- architecture: "architecture";
95
- exploration: "exploration";
96
- implementation: "implementation";
97
- review: "review";
98
- }>;
99
- task: z.ZodString;
100
- requirements: z.ZodDefault<z.ZodArray<z.ZodString>>;
101
- execution_card: z.ZodOptional<z.ZodAny>;
102
- override: z.ZodOptional<z.ZodString>;
103
- trusted_override: z.ZodOptional<z.ZodBoolean>;
104
- budget: z.ZodOptional<z.ZodObject<{
105
- cost_class: z.ZodOptional<z.ZodString>;
106
- latency_class: z.ZodOptional<z.ZodString>;
107
- }, z.core.$strip>>;
108
- decision: z.ZodOptional<z.ZodAny>;
109
- };
110
- execute(args: {
111
- action: "preview" | "dispatch";
112
- category: Category;
113
- task: string;
114
- requirements: string[];
115
- execution_card?: unknown;
116
- override?: string;
117
- trusted_override?: boolean;
118
- budget?: RoutingInput["budget"];
119
- decision?: unknown;
120
- }, context: {
121
- sessionID: string;
122
- }): Promise<string>;
123
- };
124
- };
125
- "tool.execute.before": (input: {
126
- tool: string;
127
- sessionID: string;
128
- }, output: {
129
- args: unknown;
130
- }) => Promise<void>;
131
- "tool.execute.after": (input: {
132
- tool: string;
133
- sessionID: string;
134
- args: unknown;
135
- }, output: {
136
- output: string;
137
- }) => Promise<void>;
138
- }>;
139
- setup(ctx: V2Plugin.Context): Promise<(() => Promise<void>) | undefined>;
13
+ setup(ctx: Plugin.Context): Promise<void>;
140
14
  };
141
15
  export default _default;