zenox 1.0.0 → 1.0.2
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/README.md +39 -12
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/constants.d.ts +10 -0
- package/dist/cli/index.js +690 -79
- package/dist/cli/model-picker.d.ts +7 -0
- package/dist/cli/types.d.ts +1 -0
- package/dist/cli/zenox-config.d.ts +16 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ OpenCode plugin for intelligent agent orchestration with specialized subagents a
|
|
|
9
9
|
- **Auto-Update**: Automatic update checking with toast notifications
|
|
10
10
|
- **Auto-Loaded MCP Servers**: exa, grep_app, sequential-thinking
|
|
11
11
|
- **Smart Orchestration**: Automatically teaches Build/Plan agents how to delegate
|
|
12
|
-
- **CLI Installer**: Easy setup via `bunx zenox install`
|
|
12
|
+
- **CLI Installer**: Easy setup with model configuration via `bunx zenox install`
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
@@ -22,7 +22,8 @@ bunx zenox install
|
|
|
22
22
|
This will:
|
|
23
23
|
- Create `opencode.json` if it doesn't exist
|
|
24
24
|
- Add zenox to your plugins array
|
|
25
|
-
-
|
|
25
|
+
- Let you configure custom models for each agent (optional)
|
|
26
|
+
- Detect models from your existing opencode.json config
|
|
26
27
|
|
|
27
28
|
### Manual Install
|
|
28
29
|
|
|
@@ -34,19 +35,23 @@ Add to your `opencode.json`:
|
|
|
34
35
|
}
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
> **Note**: Don't pin the version (e.g., `zenox@
|
|
38
|
+
> **Note**: Don't pin the version (e.g., `zenox@1.0.0`) if you want automatic updates.
|
|
38
39
|
|
|
39
40
|
Restart OpenCode and the plugin is ready to use.
|
|
40
41
|
|
|
41
42
|
## CLI Commands
|
|
42
43
|
|
|
43
44
|
```bash
|
|
44
|
-
# Interactive install with
|
|
45
|
+
# Interactive install with model configuration
|
|
45
46
|
bunx zenox install
|
|
46
47
|
|
|
47
|
-
# Non-interactive mode (
|
|
48
|
+
# Non-interactive mode (uses default models)
|
|
48
49
|
bunx zenox install --no-tui
|
|
49
50
|
|
|
51
|
+
# Reconfigure agent models later
|
|
52
|
+
bunx zenox config
|
|
53
|
+
bunx zenox models # alias
|
|
54
|
+
|
|
50
55
|
# Specify config path
|
|
51
56
|
bunx zenox install --config ./path/to/opencode.json
|
|
52
57
|
|
|
@@ -54,6 +59,18 @@ bunx zenox install --config ./path/to/opencode.json
|
|
|
54
59
|
bunx zenox --help
|
|
55
60
|
```
|
|
56
61
|
|
|
62
|
+
### Model Configuration
|
|
63
|
+
|
|
64
|
+
During installation, you can choose to customize models for each agent:
|
|
65
|
+
|
|
66
|
+
1. **Use recommended defaults** - No config file needed
|
|
67
|
+
2. **Customize models** - Pick from:
|
|
68
|
+
- Recommended default model
|
|
69
|
+
- Models defined in your opencode.json
|
|
70
|
+
- Enter a custom model name (see [models.dev](https://models.dev))
|
|
71
|
+
|
|
72
|
+
Custom models are saved to `~/.config/opencode/zenox.json`.
|
|
73
|
+
|
|
57
74
|
## Agents
|
|
58
75
|
|
|
59
76
|
### @explorer
|
|
@@ -62,7 +79,7 @@ Fast codebase search specialist.
|
|
|
62
79
|
- "Find all files containing Y"
|
|
63
80
|
- Pattern matching and locating implementations
|
|
64
81
|
|
|
65
|
-
**Model**: `anthropic/claude-haiku-4-5`
|
|
82
|
+
**Default Model**: `anthropic/claude-haiku-4-5`
|
|
66
83
|
|
|
67
84
|
### @librarian
|
|
68
85
|
Open-source research agent.
|
|
@@ -70,7 +87,7 @@ Open-source research agent.
|
|
|
70
87
|
- "Show me implementation examples"
|
|
71
88
|
- Finding official documentation
|
|
72
89
|
|
|
73
|
-
**Model**: `anthropic/claude-sonnet-4-5`
|
|
90
|
+
**Default Model**: `anthropic/claude-sonnet-4-5`
|
|
74
91
|
|
|
75
92
|
### @oracle
|
|
76
93
|
Strategic technical advisor.
|
|
@@ -78,7 +95,7 @@ Strategic technical advisor.
|
|
|
78
95
|
- Code review and debugging strategy
|
|
79
96
|
- Technical trade-offs analysis
|
|
80
97
|
|
|
81
|
-
**Model**: `openai/gpt-5.2
|
|
98
|
+
**Default Model**: `openai/gpt-5.2`
|
|
82
99
|
|
|
83
100
|
### @ui-planner
|
|
84
101
|
Designer-turned-developer.
|
|
@@ -86,7 +103,7 @@ Designer-turned-developer.
|
|
|
86
103
|
- Frontend aesthetics and animations
|
|
87
104
|
- Visual design without mockups
|
|
88
105
|
|
|
89
|
-
**Model**: `google/gemini-3-pro-high`
|
|
106
|
+
**Default Model**: `google/gemini-3-pro-high`
|
|
90
107
|
|
|
91
108
|
## Background Tasks
|
|
92
109
|
|
|
@@ -122,11 +139,21 @@ Zenox automatically checks for updates on startup:
|
|
|
122
139
|
3. **Cache Invalidation**: If update available, clears Bun cache
|
|
123
140
|
4. **Update Toast**: Notifies you to restart OpenCode
|
|
124
141
|
|
|
125
|
-
To disable auto-updates, pin your version: `"zenox@
|
|
142
|
+
To disable auto-updates, pin your version: `"zenox@1.0.0"`
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
### Via CLI (Recommended)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
bunx zenox config
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Interactive UI to select models for each agent.
|
|
126
153
|
|
|
127
|
-
|
|
154
|
+
### Manual Configuration
|
|
128
155
|
|
|
129
|
-
Create `~/.config/opencode/zenox.json
|
|
156
|
+
Create `~/.config/opencode/zenox.json`:
|
|
130
157
|
|
|
131
158
|
```json
|
|
132
159
|
{
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runConfig(): Promise<void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "zenox";
|
|
2
|
+
export type AgentName = "explorer" | "librarian" | "oracle" | "ui-planner";
|
|
3
|
+
export interface AgentInfo {
|
|
4
|
+
name: AgentName;
|
|
5
|
+
displayName: string;
|
|
6
|
+
defaultModel: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const AGENTS: AgentInfo[];
|
|
10
|
+
export declare const DEFAULT_MODELS: Record<AgentName, string>;
|
package/dist/cli/index.js
CHANGED
|
@@ -2258,6 +2258,7 @@ var {
|
|
|
2258
2258
|
|
|
2259
2259
|
// node_modules/@clack/core/dist/index.mjs
|
|
2260
2260
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
2261
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
2261
2262
|
import { stdin as j, stdout as M } from "node:process";
|
|
2262
2263
|
import * as g from "node:readline";
|
|
2263
2264
|
import O from "node:readline";
|
|
@@ -2632,9 +2633,89 @@ class dD extends x {
|
|
|
2632
2633
|
}
|
|
2633
2634
|
var A;
|
|
2634
2635
|
A = new WeakMap;
|
|
2636
|
+
var kD = Object.defineProperty;
|
|
2637
|
+
var $D = (e, u, t) => (u in e) ? kD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t;
|
|
2638
|
+
var H = (e, u, t) => ($D(e, typeof u != "symbol" ? u + "" : u, t), t);
|
|
2639
|
+
var SD = class extends x {
|
|
2640
|
+
constructor(u) {
|
|
2641
|
+
super(u, false), H(this, "options"), H(this, "cursor", 0), this.options = u.options, this.value = [...u.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: t }) => t === u.cursorAt), 0), this.on("key", (t) => {
|
|
2642
|
+
t === "a" && this.toggleAll();
|
|
2643
|
+
}), this.on("cursor", (t) => {
|
|
2644
|
+
switch (t) {
|
|
2645
|
+
case "left":
|
|
2646
|
+
case "up":
|
|
2647
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
2648
|
+
break;
|
|
2649
|
+
case "down":
|
|
2650
|
+
case "right":
|
|
2651
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
2652
|
+
break;
|
|
2653
|
+
case "space":
|
|
2654
|
+
this.toggleValue();
|
|
2655
|
+
break;
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
get _value() {
|
|
2660
|
+
return this.options[this.cursor].value;
|
|
2661
|
+
}
|
|
2662
|
+
toggleAll() {
|
|
2663
|
+
const u = this.value.length === this.options.length;
|
|
2664
|
+
this.value = u ? [] : this.options.map((t) => t.value);
|
|
2665
|
+
}
|
|
2666
|
+
toggleValue() {
|
|
2667
|
+
const u = this.value.includes(this._value);
|
|
2668
|
+
this.value = u ? this.value.filter((t) => t !== this._value) : [...this.value, this._value];
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
var OD = Object.defineProperty;
|
|
2672
|
+
var PD = (e, u, t) => (u in e) ? OD(e, u, { enumerable: true, configurable: true, writable: true, value: t }) : e[u] = t;
|
|
2673
|
+
var J = (e, u, t) => (PD(e, typeof u != "symbol" ? u + "" : u, t), t);
|
|
2674
|
+
|
|
2675
|
+
class LD extends x {
|
|
2676
|
+
constructor(u) {
|
|
2677
|
+
super(u, false), J(this, "options"), J(this, "cursor", 0), this.options = u.options, this.cursor = this.options.findIndex(({ value: t }) => t === u.initialValue), this.cursor === -1 && (this.cursor = 0), this.changeValue(), this.on("cursor", (t) => {
|
|
2678
|
+
switch (t) {
|
|
2679
|
+
case "left":
|
|
2680
|
+
case "up":
|
|
2681
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
2682
|
+
break;
|
|
2683
|
+
case "down":
|
|
2684
|
+
case "right":
|
|
2685
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
2686
|
+
break;
|
|
2687
|
+
}
|
|
2688
|
+
this.changeValue();
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
get _value() {
|
|
2692
|
+
return this.options[this.cursor];
|
|
2693
|
+
}
|
|
2694
|
+
changeValue() {
|
|
2695
|
+
this.value = this._value.value;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
class RD extends x {
|
|
2699
|
+
get valueWithCursor() {
|
|
2700
|
+
if (this.state === "submit")
|
|
2701
|
+
return this.value;
|
|
2702
|
+
if (this.cursor >= this.value.length)
|
|
2703
|
+
return `${this.value}█`;
|
|
2704
|
+
const u = this.value.slice(0, this.cursor), [t, ...F] = this.value.slice(this.cursor);
|
|
2705
|
+
return `${u}${import_picocolors.default.inverse(t)}${F.join("")}`;
|
|
2706
|
+
}
|
|
2707
|
+
get cursor() {
|
|
2708
|
+
return this._cursor;
|
|
2709
|
+
}
|
|
2710
|
+
constructor(u) {
|
|
2711
|
+
super(u), this.on("finalize", () => {
|
|
2712
|
+
this.value || (this.value = u.defaultValue);
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2635
2716
|
|
|
2636
2717
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
2637
|
-
var
|
|
2718
|
+
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
2638
2719
|
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
2639
2720
|
import y2 from "node:process";
|
|
2640
2721
|
function ce() {
|
|
@@ -2667,83 +2748,187 @@ var b2 = (t) => {
|
|
|
2667
2748
|
switch (t) {
|
|
2668
2749
|
case "initial":
|
|
2669
2750
|
case "active":
|
|
2670
|
-
return
|
|
2751
|
+
return import_picocolors2.default.cyan(le);
|
|
2671
2752
|
case "cancel":
|
|
2672
|
-
return
|
|
2753
|
+
return import_picocolors2.default.red(L2);
|
|
2673
2754
|
case "error":
|
|
2674
|
-
return
|
|
2755
|
+
return import_picocolors2.default.yellow(W2);
|
|
2675
2756
|
case "submit":
|
|
2676
|
-
return
|
|
2757
|
+
return import_picocolors2.default.green(C);
|
|
2677
2758
|
}
|
|
2678
2759
|
};
|
|
2760
|
+
var G2 = (t) => {
|
|
2761
|
+
const { cursor: n, options: r2, style: i } = t, s = t.maxItems ?? Number.POSITIVE_INFINITY, c = Math.max(process.stdout.rows - 4, 0), a = Math.min(c, Math.max(s, 5));
|
|
2762
|
+
let l2 = 0;
|
|
2763
|
+
n >= l2 + a - 3 ? l2 = Math.max(Math.min(n - a + 3, r2.length - a), 0) : n < l2 + 2 && (l2 = Math.max(n - 2, 0));
|
|
2764
|
+
const $2 = a < r2.length && l2 > 0, g2 = a < r2.length && l2 + a < r2.length;
|
|
2765
|
+
return r2.slice(l2, l2 + a).map((p2, v2, f) => {
|
|
2766
|
+
const j2 = v2 === 0 && $2, E = v2 === f.length - 1 && g2;
|
|
2767
|
+
return j2 || E ? import_picocolors2.default.dim("...") : i(p2, v2 + l2 === n);
|
|
2768
|
+
});
|
|
2769
|
+
};
|
|
2770
|
+
var he = (t) => new RD({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, render() {
|
|
2771
|
+
const n = `${import_picocolors2.default.gray(o)}
|
|
2772
|
+
${b2(this.state)} ${t.message}
|
|
2773
|
+
`, r2 = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), i = this.value ? this.valueWithCursor : r2;
|
|
2774
|
+
switch (this.state) {
|
|
2775
|
+
case "error":
|
|
2776
|
+
return `${n.trim()}
|
|
2777
|
+
${import_picocolors2.default.yellow(o)} ${i}
|
|
2778
|
+
${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(this.error)}
|
|
2779
|
+
`;
|
|
2780
|
+
case "submit":
|
|
2781
|
+
return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(this.value || t.placeholder)}`;
|
|
2782
|
+
case "cancel":
|
|
2783
|
+
return `${n}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(this.value ?? ""))}${this.value?.trim() ? `
|
|
2784
|
+
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
2785
|
+
default:
|
|
2786
|
+
return `${n}${import_picocolors2.default.cyan(o)} ${i}
|
|
2787
|
+
${import_picocolors2.default.cyan(d2)}
|
|
2788
|
+
`;
|
|
2789
|
+
}
|
|
2790
|
+
} }).prompt();
|
|
2679
2791
|
var ye = (t) => {
|
|
2680
2792
|
const n = t.active ?? "Yes", r2 = t.inactive ?? "No";
|
|
2681
2793
|
return new dD({ active: n, inactive: r2, initialValue: t.initialValue ?? true, render() {
|
|
2682
|
-
const i = `${
|
|
2794
|
+
const i = `${import_picocolors2.default.gray(o)}
|
|
2683
2795
|
${b2(this.state)} ${t.message}
|
|
2684
2796
|
`, s = this.value ? n : r2;
|
|
2685
2797
|
switch (this.state) {
|
|
2686
2798
|
case "submit":
|
|
2687
|
-
return `${i}${
|
|
2799
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.dim(s)}`;
|
|
2800
|
+
case "cancel":
|
|
2801
|
+
return `${i}${import_picocolors2.default.gray(o)} ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}
|
|
2802
|
+
${import_picocolors2.default.gray(o)}`;
|
|
2803
|
+
default:
|
|
2804
|
+
return `${i}${import_picocolors2.default.cyan(o)} ${this.value ? `${import_picocolors2.default.green(k2)} ${n}` : `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(n)}`} ${import_picocolors2.default.dim("/")} ${this.value ? `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(r2)}` : `${import_picocolors2.default.green(k2)} ${r2}`}
|
|
2805
|
+
${import_picocolors2.default.cyan(d2)}
|
|
2806
|
+
`;
|
|
2807
|
+
}
|
|
2808
|
+
} }).prompt();
|
|
2809
|
+
};
|
|
2810
|
+
var ve = (t) => {
|
|
2811
|
+
const n = (r2, i) => {
|
|
2812
|
+
const s = r2.label ?? String(r2.value);
|
|
2813
|
+
switch (i) {
|
|
2814
|
+
case "selected":
|
|
2815
|
+
return `${import_picocolors2.default.dim(s)}`;
|
|
2816
|
+
case "active":
|
|
2817
|
+
return `${import_picocolors2.default.green(k2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}`;
|
|
2818
|
+
case "cancelled":
|
|
2819
|
+
return `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}`;
|
|
2820
|
+
default:
|
|
2821
|
+
return `${import_picocolors2.default.dim(P2)} ${import_picocolors2.default.dim(s)}`;
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
return new LD({ options: t.options, initialValue: t.initialValue, render() {
|
|
2825
|
+
const r2 = `${import_picocolors2.default.gray(o)}
|
|
2826
|
+
${b2(this.state)} ${t.message}
|
|
2827
|
+
`;
|
|
2828
|
+
switch (this.state) {
|
|
2829
|
+
case "submit":
|
|
2830
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "selected")}`;
|
|
2688
2831
|
case "cancel":
|
|
2689
|
-
return `${
|
|
2690
|
-
${
|
|
2832
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${n(this.options[this.cursor], "cancelled")}
|
|
2833
|
+
${import_picocolors2.default.gray(o)}`;
|
|
2691
2834
|
default:
|
|
2692
|
-
return `${
|
|
2693
|
-
${
|
|
2835
|
+
return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ cursor: this.cursor, options: this.options, maxItems: t.maxItems, style: (i, s) => n(i, s ? "active" : "inactive") }).join(`
|
|
2836
|
+
${import_picocolors2.default.cyan(o)} `)}
|
|
2837
|
+
${import_picocolors2.default.cyan(d2)}
|
|
2838
|
+
`;
|
|
2839
|
+
}
|
|
2840
|
+
} }).prompt();
|
|
2841
|
+
};
|
|
2842
|
+
var fe = (t) => {
|
|
2843
|
+
const n = (r2, i) => {
|
|
2844
|
+
const s = r2.label ?? String(r2.value);
|
|
2845
|
+
return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r2.hint ? import_picocolors2.default.dim(`(${r2.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
|
|
2846
|
+
};
|
|
2847
|
+
return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? true, cursorAt: t.cursorAt, validate(r2) {
|
|
2848
|
+
if (this.required && r2.length === 0)
|
|
2849
|
+
return `Please select at least one option.
|
|
2850
|
+
${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
|
|
2851
|
+
}, render() {
|
|
2852
|
+
const r2 = `${import_picocolors2.default.gray(o)}
|
|
2853
|
+
${b2(this.state)} ${t.message}
|
|
2854
|
+
`, i = (s, c) => {
|
|
2855
|
+
const a = this.value.includes(s.value);
|
|
2856
|
+
return c && a ? n(s, "active-selected") : a ? n(s, "selected") : n(s, c ? "active" : "inactive");
|
|
2857
|
+
};
|
|
2858
|
+
switch (this.state) {
|
|
2859
|
+
case "submit":
|
|
2860
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
|
|
2861
|
+
case "cancel": {
|
|
2862
|
+
const s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => n(c, "cancelled")).join(import_picocolors2.default.dim(", "));
|
|
2863
|
+
return `${r2}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
|
|
2864
|
+
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
2865
|
+
}
|
|
2866
|
+
case "error": {
|
|
2867
|
+
const s = this.error.split(`
|
|
2868
|
+
`).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
|
|
2869
|
+
`);
|
|
2870
|
+
return `${r2 + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
2871
|
+
${import_picocolors2.default.yellow(o)} `)}
|
|
2872
|
+
${s}
|
|
2873
|
+
`;
|
|
2874
|
+
}
|
|
2875
|
+
default:
|
|
2876
|
+
return `${r2}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
2877
|
+
${import_picocolors2.default.cyan(o)} `)}
|
|
2878
|
+
${import_picocolors2.default.cyan(d2)}
|
|
2694
2879
|
`;
|
|
2695
2880
|
}
|
|
2696
2881
|
} }).prompt();
|
|
2697
2882
|
};
|
|
2698
2883
|
var xe = (t = "") => {
|
|
2699
|
-
process.stdout.write(`${
|
|
2884
|
+
process.stdout.write(`${import_picocolors2.default.gray(d2)} ${import_picocolors2.default.red(t)}
|
|
2700
2885
|
|
|
2701
2886
|
`);
|
|
2702
2887
|
};
|
|
2703
2888
|
var Ie = (t = "") => {
|
|
2704
|
-
process.stdout.write(`${
|
|
2889
|
+
process.stdout.write(`${import_picocolors2.default.gray(ue)} ${t}
|
|
2705
2890
|
`);
|
|
2706
2891
|
};
|
|
2707
2892
|
var Se = (t = "") => {
|
|
2708
|
-
process.stdout.write(`${
|
|
2709
|
-
${
|
|
2893
|
+
process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
2894
|
+
${import_picocolors2.default.gray(d2)} ${t}
|
|
2710
2895
|
|
|
2711
2896
|
`);
|
|
2712
2897
|
};
|
|
2713
|
-
var M2 = { message: (t = "", { symbol: n =
|
|
2714
|
-
const r2 = [`${
|
|
2898
|
+
var M2 = { message: (t = "", { symbol: n = import_picocolors2.default.gray(o) } = {}) => {
|
|
2899
|
+
const r2 = [`${import_picocolors2.default.gray(o)}`];
|
|
2715
2900
|
if (t) {
|
|
2716
2901
|
const [i, ...s] = t.split(`
|
|
2717
2902
|
`);
|
|
2718
|
-
r2.push(`${n} ${i}`, ...s.map((c) => `${
|
|
2903
|
+
r2.push(`${n} ${i}`, ...s.map((c) => `${import_picocolors2.default.gray(o)} ${c}`));
|
|
2719
2904
|
}
|
|
2720
2905
|
process.stdout.write(`${r2.join(`
|
|
2721
2906
|
`)}
|
|
2722
2907
|
`);
|
|
2723
2908
|
}, info: (t) => {
|
|
2724
|
-
M2.message(t, { symbol:
|
|
2909
|
+
M2.message(t, { symbol: import_picocolors2.default.blue(q) });
|
|
2725
2910
|
}, success: (t) => {
|
|
2726
|
-
M2.message(t, { symbol:
|
|
2911
|
+
M2.message(t, { symbol: import_picocolors2.default.green(D) });
|
|
2727
2912
|
}, step: (t) => {
|
|
2728
|
-
M2.message(t, { symbol:
|
|
2913
|
+
M2.message(t, { symbol: import_picocolors2.default.green(C) });
|
|
2729
2914
|
}, warn: (t) => {
|
|
2730
|
-
M2.message(t, { symbol:
|
|
2915
|
+
M2.message(t, { symbol: import_picocolors2.default.yellow(U) });
|
|
2731
2916
|
}, warning: (t) => {
|
|
2732
2917
|
M2.warn(t);
|
|
2733
2918
|
}, error: (t) => {
|
|
2734
|
-
M2.message(t, { symbol:
|
|
2919
|
+
M2.message(t, { symbol: import_picocolors2.default.red(K2) });
|
|
2735
2920
|
} };
|
|
2736
|
-
var
|
|
2921
|
+
var J2 = `${import_picocolors2.default.gray(o)} `;
|
|
2737
2922
|
var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
2738
2923
|
const n = V2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"], r2 = V2 ? 80 : 120, i = process.env.CI === "true";
|
|
2739
2924
|
let s, c, a = false, l2 = "", $2, g2 = performance.now();
|
|
2740
2925
|
const p2 = (m2) => {
|
|
2741
2926
|
const h2 = m2 > 1 ? "Something went wrong" : "Canceled";
|
|
2742
2927
|
a && N2(h2, m2);
|
|
2743
|
-
},
|
|
2744
|
-
process.on("uncaughtExceptionMonitor",
|
|
2928
|
+
}, v2 = () => p2(2), f = () => p2(1), j2 = () => {
|
|
2929
|
+
process.on("uncaughtExceptionMonitor", v2), process.on("unhandledRejection", v2), process.on("SIGINT", f), process.on("SIGTERM", f), process.on("exit", p2);
|
|
2745
2930
|
}, E = () => {
|
|
2746
|
-
process.removeListener("uncaughtExceptionMonitor",
|
|
2931
|
+
process.removeListener("uncaughtExceptionMonitor", v2), process.removeListener("unhandledRejection", v2), process.removeListener("SIGINT", f), process.removeListener("SIGTERM", f), process.removeListener("exit", p2);
|
|
2747
2932
|
}, B2 = () => {
|
|
2748
2933
|
if ($2 === undefined)
|
|
2749
2934
|
return;
|
|
@@ -2755,15 +2940,15 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
2755
2940
|
}, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
|
|
2756
2941
|
const h2 = (performance.now() - m2) / 1000, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
|
|
2757
2942
|
return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
|
|
2758
|
-
},
|
|
2759
|
-
a = true, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${
|
|
2943
|
+
}, H2 = (m2 = "") => {
|
|
2944
|
+
a = true, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
2760
2945
|
`);
|
|
2761
2946
|
let h2 = 0, w2 = 0;
|
|
2762
2947
|
j2(), c = setInterval(() => {
|
|
2763
2948
|
if (i && l2 === $2)
|
|
2764
2949
|
return;
|
|
2765
2950
|
B2(), $2 = l2;
|
|
2766
|
-
const I2 =
|
|
2951
|
+
const I2 = import_picocolors2.default.magenta(n[h2]);
|
|
2767
2952
|
if (i)
|
|
2768
2953
|
process.stdout.write(`${I2} ${l2}...`);
|
|
2769
2954
|
else if (t === "timer")
|
|
@@ -2776,24 +2961,152 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
2776
2961
|
}, r2);
|
|
2777
2962
|
}, N2 = (m2 = "", h2 = 0) => {
|
|
2778
2963
|
a = false, clearInterval(c), B2();
|
|
2779
|
-
const w2 = h2 === 0 ?
|
|
2964
|
+
const w2 = h2 === 0 ? import_picocolors2.default.green(C) : h2 === 1 ? import_picocolors2.default.red(L2) : import_picocolors2.default.red(W2);
|
|
2780
2965
|
l2 = R2(m2 ?? l2), t === "timer" ? process.stdout.write(`${w2} ${l2} ${O2(g2)}
|
|
2781
2966
|
`) : process.stdout.write(`${w2} ${l2}
|
|
2782
2967
|
`), E(), s();
|
|
2783
2968
|
};
|
|
2784
|
-
return { start:
|
|
2969
|
+
return { start: H2, stop: N2, message: (m2 = "") => {
|
|
2785
2970
|
l2 = R2(m2 ?? l2);
|
|
2786
2971
|
} };
|
|
2787
2972
|
};
|
|
2788
2973
|
|
|
2789
2974
|
// src/cli/install.ts
|
|
2790
|
-
var
|
|
2975
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
2791
2976
|
|
|
2792
2977
|
// src/cli/config-manager.ts
|
|
2793
2978
|
import { existsSync } from "node:fs";
|
|
2794
2979
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2795
2980
|
import { join } from "node:path";
|
|
2981
|
+
|
|
2982
|
+
// node_modules/strip-json-comments/index.js
|
|
2983
|
+
var singleComment = Symbol("singleComment");
|
|
2984
|
+
var multiComment = Symbol("multiComment");
|
|
2985
|
+
var stripWithoutWhitespace = () => "";
|
|
2986
|
+
var stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/[^ \t\r\n]/g, " ");
|
|
2987
|
+
var isEscaped = (jsonString, quotePosition) => {
|
|
2988
|
+
let index = quotePosition - 1;
|
|
2989
|
+
let backslashCount = 0;
|
|
2990
|
+
while (jsonString[index] === "\\") {
|
|
2991
|
+
index -= 1;
|
|
2992
|
+
backslashCount += 1;
|
|
2993
|
+
}
|
|
2994
|
+
return Boolean(backslashCount % 2);
|
|
2995
|
+
};
|
|
2996
|
+
function stripJsonComments(jsonString, { whitespace = true, trailingCommas = false } = {}) {
|
|
2997
|
+
if (typeof jsonString !== "string") {
|
|
2998
|
+
throw new TypeError(`Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``);
|
|
2999
|
+
}
|
|
3000
|
+
const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
|
|
3001
|
+
let isInsideString = false;
|
|
3002
|
+
let isInsideComment = false;
|
|
3003
|
+
let offset = 0;
|
|
3004
|
+
let buffer = "";
|
|
3005
|
+
let result = "";
|
|
3006
|
+
let commaIndex = -1;
|
|
3007
|
+
for (let index = 0;index < jsonString.length; index++) {
|
|
3008
|
+
const currentCharacter = jsonString[index];
|
|
3009
|
+
const nextCharacter = jsonString[index + 1];
|
|
3010
|
+
if (!isInsideComment && currentCharacter === '"') {
|
|
3011
|
+
const escaped = isEscaped(jsonString, index);
|
|
3012
|
+
if (!escaped) {
|
|
3013
|
+
isInsideString = !isInsideString;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
if (isInsideString) {
|
|
3017
|
+
continue;
|
|
3018
|
+
}
|
|
3019
|
+
if (!isInsideComment && currentCharacter + nextCharacter === "//") {
|
|
3020
|
+
buffer += jsonString.slice(offset, index);
|
|
3021
|
+
offset = index;
|
|
3022
|
+
isInsideComment = singleComment;
|
|
3023
|
+
index++;
|
|
3024
|
+
} else if (isInsideComment === singleComment && currentCharacter + nextCharacter === `\r
|
|
3025
|
+
`) {
|
|
3026
|
+
index++;
|
|
3027
|
+
isInsideComment = false;
|
|
3028
|
+
buffer += strip(jsonString, offset, index);
|
|
3029
|
+
offset = index;
|
|
3030
|
+
continue;
|
|
3031
|
+
} else if (isInsideComment === singleComment && currentCharacter === `
|
|
3032
|
+
`) {
|
|
3033
|
+
isInsideComment = false;
|
|
3034
|
+
buffer += strip(jsonString, offset, index);
|
|
3035
|
+
offset = index;
|
|
3036
|
+
} else if (!isInsideComment && currentCharacter + nextCharacter === "/*") {
|
|
3037
|
+
buffer += jsonString.slice(offset, index);
|
|
3038
|
+
offset = index;
|
|
3039
|
+
isInsideComment = multiComment;
|
|
3040
|
+
index++;
|
|
3041
|
+
continue;
|
|
3042
|
+
} else if (isInsideComment === multiComment && currentCharacter + nextCharacter === "*/") {
|
|
3043
|
+
index++;
|
|
3044
|
+
isInsideComment = false;
|
|
3045
|
+
buffer += strip(jsonString, offset, index + 1);
|
|
3046
|
+
offset = index + 1;
|
|
3047
|
+
continue;
|
|
3048
|
+
} else if (trailingCommas && !isInsideComment) {
|
|
3049
|
+
if (commaIndex !== -1) {
|
|
3050
|
+
if (currentCharacter === "}" || currentCharacter === "]") {
|
|
3051
|
+
buffer += jsonString.slice(offset, index);
|
|
3052
|
+
result += strip(buffer, 0, 1) + buffer.slice(1);
|
|
3053
|
+
buffer = "";
|
|
3054
|
+
offset = index;
|
|
3055
|
+
commaIndex = -1;
|
|
3056
|
+
} else if (currentCharacter !== " " && currentCharacter !== "\t" && currentCharacter !== "\r" && currentCharacter !== `
|
|
3057
|
+
`) {
|
|
3058
|
+
buffer += jsonString.slice(offset, index);
|
|
3059
|
+
offset = index;
|
|
3060
|
+
commaIndex = -1;
|
|
3061
|
+
}
|
|
3062
|
+
} else if (currentCharacter === ",") {
|
|
3063
|
+
result += buffer + jsonString.slice(offset, index);
|
|
3064
|
+
buffer = "";
|
|
3065
|
+
offset = index;
|
|
3066
|
+
commaIndex = index;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
const remaining = isInsideComment === singleComment ? strip(jsonString, offset) : jsonString.slice(offset);
|
|
3071
|
+
return result + buffer + remaining;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
// src/cli/constants.ts
|
|
2796
3075
|
var PACKAGE_NAME = "zenox";
|
|
3076
|
+
var AGENTS = [
|
|
3077
|
+
{
|
|
3078
|
+
name: "explorer",
|
|
3079
|
+
displayName: "Explorer",
|
|
3080
|
+
defaultModel: "anthropic/claude-haiku-4-5",
|
|
3081
|
+
description: "Fast codebase search specialist"
|
|
3082
|
+
},
|
|
3083
|
+
{
|
|
3084
|
+
name: "librarian",
|
|
3085
|
+
displayName: "Librarian",
|
|
3086
|
+
defaultModel: "anthropic/claude-sonnet-4-5",
|
|
3087
|
+
description: "Open-source research agent"
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
name: "oracle",
|
|
3091
|
+
displayName: "Oracle",
|
|
3092
|
+
defaultModel: "openai/gpt-5.2",
|
|
3093
|
+
description: "Strategic technical advisor"
|
|
3094
|
+
},
|
|
3095
|
+
{
|
|
3096
|
+
name: "ui-planner",
|
|
3097
|
+
displayName: "UI-Planner",
|
|
3098
|
+
defaultModel: "google/gemini-3-pro-high",
|
|
3099
|
+
description: "Designer-turned-developer"
|
|
3100
|
+
}
|
|
3101
|
+
];
|
|
3102
|
+
var DEFAULT_MODELS = {
|
|
3103
|
+
explorer: "anthropic/claude-haiku-4-5",
|
|
3104
|
+
librarian: "anthropic/claude-sonnet-4-5",
|
|
3105
|
+
oracle: "openai/gpt-5.2",
|
|
3106
|
+
"ui-planner": "google/gemini-3-pro-high"
|
|
3107
|
+
};
|
|
3108
|
+
|
|
3109
|
+
// src/cli/config-manager.ts
|
|
2797
3110
|
var CONFIG_FILENAMES = ["opencode.json", "opencode.jsonc"];
|
|
2798
3111
|
function findConfigFile(directory) {
|
|
2799
3112
|
for (const filename of CONFIG_FILENAMES) {
|
|
@@ -2807,16 +3120,16 @@ function findConfigFile(directory) {
|
|
|
2807
3120
|
}
|
|
2808
3121
|
async function readConfig(configPath) {
|
|
2809
3122
|
const content = await readFile(configPath, "utf-8");
|
|
2810
|
-
const jsonContent = content
|
|
3123
|
+
const jsonContent = stripJsonComments(content, { trailingCommas: true });
|
|
2811
3124
|
const config = JSON.parse(jsonContent);
|
|
2812
3125
|
return { content, config };
|
|
2813
3126
|
}
|
|
2814
3127
|
function isPluginInstalled(config) {
|
|
2815
|
-
const plugins = config.plugins ?? [];
|
|
3128
|
+
const plugins = config.plugins ?? config.plugin ?? [];
|
|
2816
3129
|
return plugins.some((p2) => p2 === PACKAGE_NAME || p2.startsWith(`${PACKAGE_NAME}@`));
|
|
2817
3130
|
}
|
|
2818
3131
|
function addPluginToJsonc(content) {
|
|
2819
|
-
const pluginsMatch = content.match(/"plugins"\s*:\s*\[/);
|
|
3132
|
+
const pluginsMatch = content.match(/"plugins"\s*:\s*\[/) ?? content.match(/"plugin"\s*:\s*\[/);
|
|
2820
3133
|
if (pluginsMatch) {
|
|
2821
3134
|
const insertIndex = content.indexOf("[", pluginsMatch.index) + 1;
|
|
2822
3135
|
const beforeBracket = content.slice(0, insertIndex);
|
|
@@ -2841,14 +3154,17 @@ function addPluginToJsonc(content) {
|
|
|
2841
3154
|
}`;
|
|
2842
3155
|
} else {
|
|
2843
3156
|
return `${beforeBrace}
|
|
2844
|
-
"plugins": ["${PACKAGE_NAME}"]
|
|
3157
|
+
"plugins": ["${PACKAGE_NAME}"],
|
|
3158
|
+
${afterBrace.trimStart()}`;
|
|
2845
3159
|
}
|
|
2846
3160
|
}
|
|
2847
3161
|
}
|
|
2848
3162
|
function addPluginToJson(config) {
|
|
3163
|
+
const existingKey = config.plugins ? "plugins" : config.plugin ? "plugin" : "plugins";
|
|
3164
|
+
const existingPlugins = config.plugins ?? config.plugin ?? [];
|
|
2849
3165
|
const newConfig = {
|
|
2850
|
-
|
|
2851
|
-
...Object.fromEntries(Object.entries(config).filter(([key]) => key !== "plugins"))
|
|
3166
|
+
[existingKey]: [PACKAGE_NAME, ...existingPlugins],
|
|
3167
|
+
...Object.fromEntries(Object.entries(config).filter(([key]) => key !== "plugins" && key !== "plugin"))
|
|
2852
3168
|
};
|
|
2853
3169
|
return JSON.stringify(newConfig, null, 2) + `
|
|
2854
3170
|
`;
|
|
@@ -2873,8 +3189,189 @@ async function createDefaultConfig(configPath) {
|
|
|
2873
3189
|
`, "utf-8");
|
|
2874
3190
|
}
|
|
2875
3191
|
|
|
3192
|
+
// src/cli/zenox-config.ts
|
|
3193
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
3194
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir } from "node:fs/promises";
|
|
3195
|
+
import { join as join2, dirname } from "node:path";
|
|
3196
|
+
import { homedir } from "node:os";
|
|
3197
|
+
function getZenoxConfigPath() {
|
|
3198
|
+
return join2(homedir(), ".config", "opencode", "zenox.json");
|
|
3199
|
+
}
|
|
3200
|
+
async function readZenoxConfig() {
|
|
3201
|
+
const configPath = getZenoxConfigPath();
|
|
3202
|
+
if (!existsSync2(configPath))
|
|
3203
|
+
return null;
|
|
3204
|
+
try {
|
|
3205
|
+
const content = await readFile2(configPath, "utf-8");
|
|
3206
|
+
return JSON.parse(content);
|
|
3207
|
+
} catch {
|
|
3208
|
+
return null;
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
async function writeZenoxConfig(config) {
|
|
3212
|
+
const configPath = getZenoxConfigPath();
|
|
3213
|
+
const configDir = dirname(configPath);
|
|
3214
|
+
if (!existsSync2(configDir)) {
|
|
3215
|
+
await mkdir(configDir, { recursive: true });
|
|
3216
|
+
}
|
|
3217
|
+
await writeFile2(configPath, JSON.stringify(config, null, 2) + `
|
|
3218
|
+
`, "utf-8");
|
|
3219
|
+
}
|
|
3220
|
+
async function updateAgentModels(models) {
|
|
3221
|
+
const existing = await readZenoxConfig() ?? {};
|
|
3222
|
+
const agents = { ...existing.agents };
|
|
3223
|
+
for (const [agent, model] of Object.entries(models)) {
|
|
3224
|
+
if (model) {
|
|
3225
|
+
agents[agent] = { model };
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
await writeZenoxConfig({
|
|
3229
|
+
...existing,
|
|
3230
|
+
agents
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
function getCurrentModels(config) {
|
|
3234
|
+
if (!config?.agents)
|
|
3235
|
+
return {};
|
|
3236
|
+
const result = {};
|
|
3237
|
+
for (const [agent, settings] of Object.entries(config.agents)) {
|
|
3238
|
+
if (settings?.model) {
|
|
3239
|
+
result[agent] = settings.model;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
return result;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
// src/cli/model-picker.ts
|
|
3246
|
+
var CUSTOM_MODEL_OPTION = "__custom__";
|
|
3247
|
+
function extractUserModels(config) {
|
|
3248
|
+
const models = new Set;
|
|
3249
|
+
const provider = config.provider;
|
|
3250
|
+
if (!provider)
|
|
3251
|
+
return [];
|
|
3252
|
+
for (const providerConfig of Object.values(provider)) {
|
|
3253
|
+
if (typeof providerConfig !== "object" || providerConfig === null)
|
|
3254
|
+
continue;
|
|
3255
|
+
const providerModels = providerConfig.models;
|
|
3256
|
+
if (typeof providerModels !== "object" || providerModels === null)
|
|
3257
|
+
continue;
|
|
3258
|
+
for (const modelId of Object.keys(providerModels)) {
|
|
3259
|
+
models.add(modelId);
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
return Array.from(models).sort();
|
|
3263
|
+
}
|
|
3264
|
+
function buildModelOptions(agent, userModels, currentModel) {
|
|
3265
|
+
const options = [];
|
|
3266
|
+
const isDefault = !currentModel || currentModel === agent.defaultModel;
|
|
3267
|
+
options.push({
|
|
3268
|
+
value: agent.defaultModel,
|
|
3269
|
+
label: `${agent.defaultModel}${isDefault ? " (Current)" : ""}`,
|
|
3270
|
+
hint: "Recommended - Default"
|
|
3271
|
+
});
|
|
3272
|
+
for (const model of userModels) {
|
|
3273
|
+
if (model === agent.defaultModel)
|
|
3274
|
+
continue;
|
|
3275
|
+
const isCurrent = currentModel === model;
|
|
3276
|
+
options.push({
|
|
3277
|
+
value: model,
|
|
3278
|
+
label: `${model}${isCurrent ? " (Current)" : ""}`,
|
|
3279
|
+
hint: "From your config"
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
if (currentModel && currentModel !== agent.defaultModel && !userModels.includes(currentModel)) {
|
|
3283
|
+
options.push({
|
|
3284
|
+
value: currentModel,
|
|
3285
|
+
label: `${currentModel} (Current)`,
|
|
3286
|
+
hint: "Custom"
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
options.push({
|
|
3290
|
+
value: CUSTOM_MODEL_OPTION,
|
|
3291
|
+
label: "Enter custom model name",
|
|
3292
|
+
hint: "Get names from models.dev"
|
|
3293
|
+
});
|
|
3294
|
+
return options;
|
|
3295
|
+
}
|
|
3296
|
+
async function pickModelForAgent(agent, userModels, currentModel) {
|
|
3297
|
+
const options = buildModelOptions(agent, userModels, currentModel);
|
|
3298
|
+
const selected = await ve({
|
|
3299
|
+
message: `${agent.displayName} agent model:`,
|
|
3300
|
+
options
|
|
3301
|
+
});
|
|
3302
|
+
if (pD(selected))
|
|
3303
|
+
return null;
|
|
3304
|
+
if (selected === CUSTOM_MODEL_OPTION) {
|
|
3305
|
+
const customModel = await he({
|
|
3306
|
+
message: "Enter custom model name:",
|
|
3307
|
+
placeholder: "e.g., anthropic/claude-sonnet-4 (see models.dev)",
|
|
3308
|
+
validate: (value) => {
|
|
3309
|
+
if (!value.trim())
|
|
3310
|
+
return "Model name is required";
|
|
3311
|
+
if (!value.includes("/"))
|
|
3312
|
+
return "Model should be in format: provider/model-name";
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
});
|
|
3316
|
+
if (pD(customModel))
|
|
3317
|
+
return null;
|
|
3318
|
+
return customModel;
|
|
3319
|
+
}
|
|
3320
|
+
return selected;
|
|
3321
|
+
}
|
|
3322
|
+
async function pickModelsForAllAgents(userModels, currentModels) {
|
|
3323
|
+
const result = {};
|
|
3324
|
+
for (const agent of AGENTS) {
|
|
3325
|
+
const current = currentModels[agent.name];
|
|
3326
|
+
const model = await pickModelForAgent(agent, userModels, current);
|
|
3327
|
+
if (model === null)
|
|
3328
|
+
return null;
|
|
3329
|
+
if (model !== agent.defaultModel) {
|
|
3330
|
+
result[agent.name] = model;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
return result;
|
|
3334
|
+
}
|
|
3335
|
+
async function askConfigureModels() {
|
|
3336
|
+
const result = await ve({
|
|
3337
|
+
message: "Configure sub-agent models?",
|
|
3338
|
+
options: [
|
|
3339
|
+
{
|
|
3340
|
+
value: "defaults",
|
|
3341
|
+
label: "Use recommended defaults",
|
|
3342
|
+
hint: "No config file needed"
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
value: "customize",
|
|
3346
|
+
label: "Customize models",
|
|
3347
|
+
hint: "Choose models for each agent"
|
|
3348
|
+
}
|
|
3349
|
+
]
|
|
3350
|
+
});
|
|
3351
|
+
if (pD(result))
|
|
3352
|
+
return null;
|
|
3353
|
+
return result;
|
|
3354
|
+
}
|
|
3355
|
+
async function selectAgentsToReconfigure(currentModels) {
|
|
3356
|
+
const options = AGENTS.map((agent) => {
|
|
3357
|
+
const current = currentModels[agent.name] ?? agent.defaultModel;
|
|
3358
|
+
return {
|
|
3359
|
+
value: agent.name,
|
|
3360
|
+
label: agent.displayName,
|
|
3361
|
+
hint: `current: ${current}`
|
|
3362
|
+
};
|
|
3363
|
+
});
|
|
3364
|
+
const selected = await fe({
|
|
3365
|
+
message: "Which agents to configure?",
|
|
3366
|
+
options,
|
|
3367
|
+
required: true
|
|
3368
|
+
});
|
|
3369
|
+
if (pD(selected))
|
|
3370
|
+
return null;
|
|
3371
|
+
return selected;
|
|
3372
|
+
}
|
|
3373
|
+
|
|
2876
3374
|
// src/cli/install.ts
|
|
2877
|
-
var PACKAGE_NAME2 = "zenox";
|
|
2878
3375
|
async function runInstall(options = {}) {
|
|
2879
3376
|
const { noTui = false, configPath: customConfigPath } = options;
|
|
2880
3377
|
const cwd = process.cwd();
|
|
@@ -2885,8 +3382,9 @@ async function runInstall(options = {}) {
|
|
|
2885
3382
|
await runInteractive(cwd, customConfigPath);
|
|
2886
3383
|
}
|
|
2887
3384
|
async function runInteractive(cwd, customConfigPath) {
|
|
2888
|
-
Ie(
|
|
3385
|
+
Ie(import_picocolors3.default.cyan(`${PACKAGE_NAME} installer`));
|
|
2889
3386
|
const configFile = customConfigPath ? { path: customConfigPath, format: "json" } : findConfigFile(cwd);
|
|
3387
|
+
let opencodeConfig = null;
|
|
2890
3388
|
if (!configFile) {
|
|
2891
3389
|
const shouldCreate = await ye({
|
|
2892
3390
|
message: "No opencode.json found. Create one?",
|
|
@@ -2897,73 +3395,178 @@ async function runInteractive(cwd, customConfigPath) {
|
|
|
2897
3395
|
process.exit(0);
|
|
2898
3396
|
}
|
|
2899
3397
|
const configPath = getDefaultConfigPath(cwd);
|
|
2900
|
-
const
|
|
2901
|
-
|
|
3398
|
+
const spinner = Y2();
|
|
3399
|
+
spinner.start("Creating opencode.json");
|
|
2902
3400
|
try {
|
|
2903
3401
|
await createDefaultConfig(configPath);
|
|
2904
|
-
|
|
2905
|
-
Se(import_picocolors2.default.green(`${PACKAGE_NAME2} installed successfully!`));
|
|
3402
|
+
spinner.stop("Created opencode.json");
|
|
2906
3403
|
} catch (err) {
|
|
2907
|
-
|
|
3404
|
+
spinner.stop("Failed to create config");
|
|
2908
3405
|
M2.error(err instanceof Error ? err.message : "Unknown error");
|
|
2909
3406
|
process.exit(1);
|
|
2910
3407
|
}
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
3408
|
+
} else {
|
|
3409
|
+
try {
|
|
3410
|
+
const result = await readConfig(configFile.path);
|
|
3411
|
+
opencodeConfig = result.config;
|
|
3412
|
+
if (isPluginInstalled(opencodeConfig)) {
|
|
3413
|
+
M2.info(`${PACKAGE_NAME} is already in ${configFile.path}`);
|
|
3414
|
+
} else {
|
|
3415
|
+
const shouldInstall = await ye({
|
|
3416
|
+
message: `Add ${PACKAGE_NAME} to ${configFile.path}?`,
|
|
3417
|
+
initialValue: true
|
|
3418
|
+
});
|
|
3419
|
+
if (pD(shouldInstall) || !shouldInstall) {
|
|
3420
|
+
xe("Installation cancelled");
|
|
3421
|
+
process.exit(0);
|
|
3422
|
+
}
|
|
3423
|
+
const spinner = Y2();
|
|
3424
|
+
spinner.start(`Adding ${PACKAGE_NAME} to plugins`);
|
|
3425
|
+
try {
|
|
3426
|
+
await installPlugin(configFile.path);
|
|
3427
|
+
spinner.stop(`Added ${PACKAGE_NAME} to plugins`);
|
|
3428
|
+
} catch (err) {
|
|
3429
|
+
spinner.stop("Failed to install");
|
|
3430
|
+
M2.error(err instanceof Error ? err.message : "Unknown error");
|
|
3431
|
+
process.exit(1);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
} catch (err) {
|
|
3435
|
+
M2.error(`Failed to read config: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
3436
|
+
process.exit(1);
|
|
2919
3437
|
}
|
|
2920
|
-
} catch (err) {
|
|
2921
|
-
M2.error(`Failed to read config: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
2922
|
-
process.exit(1);
|
|
2923
3438
|
}
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
initialValue: true
|
|
2927
|
-
});
|
|
2928
|
-
if (pD(shouldInstall) || !shouldInstall) {
|
|
3439
|
+
const configureChoice = await askConfigureModels();
|
|
3440
|
+
if (configureChoice === null) {
|
|
2929
3441
|
xe("Installation cancelled");
|
|
2930
3442
|
process.exit(0);
|
|
2931
3443
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3444
|
+
if (configureChoice === "customize") {
|
|
3445
|
+
const userModels = opencodeConfig ? extractUserModels(opencodeConfig) : [];
|
|
3446
|
+
const zenoxConfig = await readZenoxConfig();
|
|
3447
|
+
const currentModels = getCurrentModels(zenoxConfig);
|
|
3448
|
+
const selectedModels = await pickModelsForAllAgents(userModels, currentModels);
|
|
3449
|
+
if (selectedModels === null) {
|
|
3450
|
+
xe("Installation cancelled");
|
|
3451
|
+
process.exit(0);
|
|
3452
|
+
}
|
|
3453
|
+
if (Object.keys(selectedModels).length > 0) {
|
|
3454
|
+
const spinner = Y2();
|
|
3455
|
+
spinner.start("Saving model configuration");
|
|
3456
|
+
try {
|
|
3457
|
+
await updateAgentModels(selectedModels);
|
|
3458
|
+
spinner.stop(`Created ${getZenoxConfigPath()}`);
|
|
3459
|
+
} catch (err) {
|
|
3460
|
+
spinner.stop("Failed to save config");
|
|
3461
|
+
M2.error(err instanceof Error ? err.message : "Unknown error");
|
|
3462
|
+
process.exit(1);
|
|
3463
|
+
}
|
|
3464
|
+
} else {
|
|
3465
|
+
M2.info("Using default models - no zenox.json needed");
|
|
3466
|
+
}
|
|
2942
3467
|
}
|
|
3468
|
+
Se(import_picocolors3.default.green(`${PACKAGE_NAME} installed successfully!`));
|
|
2943
3469
|
}
|
|
2944
3470
|
async function runNonInteractive(cwd, customConfigPath) {
|
|
2945
3471
|
const configFile = customConfigPath ? { path: customConfigPath, format: "json" } : findConfigFile(cwd);
|
|
2946
3472
|
if (!configFile) {
|
|
2947
|
-
console.log(`Creating opencode.json with ${
|
|
3473
|
+
console.log(`Creating opencode.json with ${PACKAGE_NAME}...`);
|
|
2948
3474
|
const configPath = getDefaultConfigPath(cwd);
|
|
2949
3475
|
await createDefaultConfig(configPath);
|
|
2950
|
-
console.log(`Created opencode.json with ${
|
|
3476
|
+
console.log(`Created opencode.json with ${PACKAGE_NAME}`);
|
|
3477
|
+
console.log("Using default models (run 'bunx zenox config' to customize)");
|
|
2951
3478
|
return;
|
|
2952
3479
|
}
|
|
2953
3480
|
const { config } = await readConfig(configFile.path);
|
|
2954
3481
|
if (isPluginInstalled(config)) {
|
|
2955
|
-
console.log(`${
|
|
3482
|
+
console.log(`${PACKAGE_NAME} is already installed`);
|
|
2956
3483
|
return;
|
|
2957
3484
|
}
|
|
2958
|
-
console.log(`Adding ${
|
|
3485
|
+
console.log(`Adding ${PACKAGE_NAME} to ${configFile.path}...`);
|
|
2959
3486
|
await installPlugin(configFile.path);
|
|
2960
|
-
console.log(`${
|
|
3487
|
+
console.log(`${PACKAGE_NAME} installed successfully!`);
|
|
3488
|
+
console.log("Using default models (run 'bunx zenox config' to customize)");
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// src/cli/config.ts
|
|
3492
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
3493
|
+
async function runConfig() {
|
|
3494
|
+
Ie(import_picocolors4.default.cyan(`${PACKAGE_NAME} model configurator`));
|
|
3495
|
+
const cwd = process.cwd();
|
|
3496
|
+
let userModels = [];
|
|
3497
|
+
const configFile = findConfigFile(cwd);
|
|
3498
|
+
if (configFile) {
|
|
3499
|
+
try {
|
|
3500
|
+
const { config } = await readConfig(configFile.path);
|
|
3501
|
+
userModels = extractUserModels(config);
|
|
3502
|
+
} catch {}
|
|
3503
|
+
}
|
|
3504
|
+
const zenoxConfig = await readZenoxConfig();
|
|
3505
|
+
const currentModels = getCurrentModels(zenoxConfig);
|
|
3506
|
+
M2.info("Current model configuration:");
|
|
3507
|
+
for (const agent of AGENTS) {
|
|
3508
|
+
const model = currentModels[agent.name] ?? agent.defaultModel;
|
|
3509
|
+
const isCustom = currentModels[agent.name] !== undefined;
|
|
3510
|
+
const suffix = isCustom ? import_picocolors4.default.yellow(" (custom)") : import_picocolors4.default.dim(" (default)");
|
|
3511
|
+
M2.message(` ${agent.displayName}: ${model}${suffix}`);
|
|
3512
|
+
}
|
|
3513
|
+
const selectedAgents = await selectAgentsToReconfigure(currentModels);
|
|
3514
|
+
if (selectedAgents === null) {
|
|
3515
|
+
xe("Configuration cancelled");
|
|
3516
|
+
process.exit(0);
|
|
3517
|
+
}
|
|
3518
|
+
if (selectedAgents.length === 0) {
|
|
3519
|
+
M2.warn("No agents selected");
|
|
3520
|
+
Se(import_picocolors4.default.yellow("No changes made"));
|
|
3521
|
+
return;
|
|
3522
|
+
}
|
|
3523
|
+
const newModels = {};
|
|
3524
|
+
for (const agentName of selectedAgents) {
|
|
3525
|
+
const agent = AGENTS.find((a) => a.name === agentName);
|
|
3526
|
+
const current = currentModels[agentName];
|
|
3527
|
+
const model = await pickModelForAgent(agent, userModels, current);
|
|
3528
|
+
if (model === null) {
|
|
3529
|
+
xe("Configuration cancelled");
|
|
3530
|
+
process.exit(0);
|
|
3531
|
+
}
|
|
3532
|
+
if (model !== agent.defaultModel) {
|
|
3533
|
+
newModels[agentName] = model;
|
|
3534
|
+
} else {
|
|
3535
|
+
newModels[agentName] = model;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
const finalModels = {};
|
|
3539
|
+
for (const [agent, model] of Object.entries(currentModels)) {
|
|
3540
|
+
if (!selectedAgents.includes(agent)) {
|
|
3541
|
+
finalModels[agent] = model;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
for (const [agent, model] of Object.entries(newModels)) {
|
|
3545
|
+
if (model !== DEFAULT_MODELS[agent]) {
|
|
3546
|
+
finalModels[agent] = model;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
if (Object.keys(finalModels).length > 0) {
|
|
3550
|
+
const spinner = Y2();
|
|
3551
|
+
spinner.start("Saving model configuration");
|
|
3552
|
+
try {
|
|
3553
|
+
await updateAgentModels(finalModels);
|
|
3554
|
+
spinner.stop(`Updated ${getZenoxConfigPath()}`);
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
spinner.stop("Failed to save config");
|
|
3557
|
+
M2.error(err instanceof Error ? err.message : "Unknown error");
|
|
3558
|
+
process.exit(1);
|
|
3559
|
+
}
|
|
3560
|
+
} else {
|
|
3561
|
+
M2.info("All agents using default models - zenox.json not needed");
|
|
3562
|
+
}
|
|
3563
|
+
Se(import_picocolors4.default.green("Configuration updated!"));
|
|
2961
3564
|
}
|
|
2962
3565
|
|
|
2963
3566
|
// src/cli/index.ts
|
|
2964
3567
|
var program2 = new Command;
|
|
2965
|
-
program2.name("zenox").description("Zenox - OpenCode plugin for intelligent agent orchestration").version("
|
|
2966
|
-
program2.command("install").description("Add zenox to your opencode.json plugins").option("--no-tui", "Run in non-interactive mode").option("-c, --config <path>", "Path to opencode.json").action(async (options) => {
|
|
3568
|
+
program2.name("zenox").description("Zenox - OpenCode plugin for intelligent agent orchestration").version("1.0.0");
|
|
3569
|
+
program2.command("install").description("Add zenox to your opencode.json plugins and configure models").option("--no-tui", "Run in non-interactive mode (uses default models)").option("-c, --config <path>", "Path to opencode.json").action(async (options) => {
|
|
2967
3570
|
try {
|
|
2968
3571
|
await runInstall({
|
|
2969
3572
|
noTui: !options.tui,
|
|
@@ -2974,4 +3577,12 @@ program2.command("install").description("Add zenox to your opencode.json plugins
|
|
|
2974
3577
|
process.exit(1);
|
|
2975
3578
|
}
|
|
2976
3579
|
});
|
|
3580
|
+
program2.command("config").alias("models").description("Reconfigure sub-agent models").action(async () => {
|
|
3581
|
+
try {
|
|
3582
|
+
await runConfig();
|
|
3583
|
+
} catch (err) {
|
|
3584
|
+
console.error(err instanceof Error ? err.message : "Unknown error");
|
|
3585
|
+
process.exit(1);
|
|
3586
|
+
}
|
|
3587
|
+
});
|
|
2977
3588
|
program2.parse();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OpencodeConfig } from "./types";
|
|
2
|
+
import type { AgentName, AgentInfo } from "./constants";
|
|
3
|
+
export declare function extractUserModels(config: OpencodeConfig): string[];
|
|
4
|
+
export declare function pickModelForAgent(agent: AgentInfo, userModels: string[], currentModel?: string): Promise<string | null>;
|
|
5
|
+
export declare function pickModelsForAllAgents(userModels: string[], currentModels: Partial<Record<AgentName, string>>): Promise<Partial<Record<AgentName, string>> | null>;
|
|
6
|
+
export declare function askConfigureModels(): Promise<"defaults" | "customize" | null>;
|
|
7
|
+
export declare function selectAgentsToReconfigure(currentModels: Partial<Record<AgentName, string>>): Promise<AgentName[] | null>;
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentName } from "./constants";
|
|
2
|
+
export interface ZenoxConfigAgents {
|
|
3
|
+
[key: string]: {
|
|
4
|
+
model: string;
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export interface ZenoxConfig {
|
|
8
|
+
agents?: ZenoxConfigAgents;
|
|
9
|
+
disabled_agents?: string[];
|
|
10
|
+
disabled_mcps?: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function getZenoxConfigPath(): string;
|
|
13
|
+
export declare function readZenoxConfig(): Promise<ZenoxConfig | null>;
|
|
14
|
+
export declare function writeZenoxConfig(config: ZenoxConfig): Promise<void>;
|
|
15
|
+
export declare function updateAgentModels(models: Partial<Record<AgentName, string>>): Promise<void>;
|
|
16
|
+
export declare function getCurrentModels(config: ZenoxConfig | null): Partial<Record<AgentName, string>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zenox",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "OpenCode plugin with specialized agents (explorer, librarian, oracle, ui-planner), background tasks for parallel execution, and smart orchestration",
|
|
5
5
|
"author": "Ayush",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@opencode-ai/sdk": "^1.1.12",
|
|
55
55
|
"commander": "^14.0.0",
|
|
56
56
|
"picocolors": "^1.1.1",
|
|
57
|
+
"strip-json-comments": "^5.0.3",
|
|
57
58
|
"zod": "^3.24.1"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|