toggle-oh-my 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +40 -0
  2. package/index.js +166 -0
  3. package/package.json +24 -0
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # toggle-oh-my
2
+
3
+ Opencode plugin that provides `/toggle-oh-my` — a command to enable/disable **oh-my-opencode** in opencode's configuration.
4
+
5
+ ## How it works
6
+
7
+ The plugin reads your opencode config (`~/.config/opencode/opencode.jsonc` or project-local `.opencode/opencode.json`), finds **oh-my-opencode** in the `plugin` array, and toggles it by adding/removing a `disabled_` prefix:
8
+
9
+ | State | plugin entry |
10
+ |-------|-------------|
11
+ | **ON** | `"oh-my-opencode@latest"` |
12
+ | **OFF** | `"disabled_oh-my-opencode@latest"` |
13
+
14
+ The version string (`@latest`, `@1.2.3`, etc.) is preserved during the toggle, so you never lose the version pin.
15
+
16
+ ## Usage
17
+
18
+ ```
19
+ /toggle-oh-my
20
+ ```
21
+
22
+ The change takes effect on the **next opencode restart**.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ npm install -g toggle-oh-my
28
+ ```
29
+
30
+ Then add to your `opencode.json` / `opencode.jsonc`:
31
+
32
+ ```json
33
+ {
34
+ "plugin": ["toggle-oh-my", "oh-my-opencode@latest"]
35
+ }
36
+ ```
37
+
38
+ ## Compatibility
39
+
40
+ Works with any version of oh-my-opencode. The version specifier (`@latest`, `@1.x`, etc.) is preserved through toggles.
package/index.js ADDED
@@ -0,0 +1,166 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
+ import { homedir, platform } from 'os';
3
+ import { join, sep } from 'path';
4
+
5
+ const PLUGIN_NAME = 'oh-my-opencode';
6
+ const DISABLED_PREFIX = 'disabled_';
7
+ const PLUGIN_DISPLAY = 'oh-my-opencode';
8
+
9
+ /**
10
+ * Find and read the opencode config file that contains the plugin entry.
11
+ * Checks project-level config first, then user-level config.
12
+ */
13
+ function findConfig() {
14
+ // project-level: <cwd>/.opencode/opencode.json
15
+ const projectCandidates = [
16
+ join(process.cwd(), '.opencode', 'opencode.json'),
17
+ join(process.cwd(), '.opencode', 'opencode.jsonc'),
18
+ ];
19
+ for (const fp of projectCandidates) {
20
+ if (!existsSync(fp)) continue;
21
+ const raw = readFileSync(fp, 'utf-8');
22
+ if (raw.includes(PLUGIN_NAME) || raw.includes(DISABLED_PREFIX + PLUGIN_NAME)) {
23
+ return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
24
+ }
25
+ }
26
+
27
+ // user-level: ~/.config/opencode/opencode.jsonc (or opencode.json)
28
+ const home = homedir();
29
+ const userCandidates = [
30
+ join(home, '.config', 'opencode', 'opencode.jsonc'),
31
+ join(home, '.config', 'opencode', 'opencode.json'),
32
+ join(home, '.opencode.json'),
33
+ ];
34
+ for (const fp of userCandidates) {
35
+ if (!existsSync(fp)) continue;
36
+ const raw = readFileSync(fp, 'utf-8');
37
+ return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
38
+ }
39
+
40
+ // Linux/macOS fallback: $XDG_CONFIG_HOME/opencode/opencode.jsonc
41
+ if (platform() !== 'win32') {
42
+ const xdg = process.env.XDG_CONFIG_HOME || join(home, '.config');
43
+ const fp = join(xdg, 'opencode', 'opencode.jsonc');
44
+ if (existsSync(fp)) {
45
+ const raw = readFileSync(fp, 'utf-8');
46
+ return { path: fp, raw, format: 'jsonc' };
47
+ }
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Toggle oh-my-opencode in the raw config text.
55
+ * Uses regex to find the plugin string and toggle its name.
56
+ * Preserves original formatting, comments, and whitespace.
57
+ */
58
+ function toggleInText(raw) {
59
+ // Match: "oh-my-opencode@version" or "disabled_oh-my-opencode@version"
60
+ // Captures: the full string including quotes, and the version part
61
+ const enabledPattern = new RegExp(
62
+ `"(${escapeRegex(PLUGIN_NAME)}(?:@[^"]*)?)"`,
63
+ 'g'
64
+ );
65
+ const disabledPattern = new RegExp(
66
+ `"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(PLUGIN_NAME)}(?:@[^"]*)?)"`,
67
+ 'g'
68
+ );
69
+
70
+ let isCurrentlyEnabled = false;
71
+ let version = '';
72
+
73
+ // Check if currently enabled
74
+ let match;
75
+ enabledPattern.lastIndex = 0;
76
+ while ((match = enabledPattern.exec(raw)) !== null) {
77
+ isCurrentlyEnabled = true;
78
+ // Extract version from the match
79
+ const atIdx = match[1].indexOf('@');
80
+ version = atIdx >= 0 ? match[1].slice(atIdx) : '';
81
+ break;
82
+ }
83
+
84
+ // Check if currently disabled
85
+ disabledPattern.lastIndex = 0;
86
+ let disabledVersion = '';
87
+ while ((match = disabledPattern.exec(raw)) !== null) {
88
+ isCurrentlyEnabled = false;
89
+ const atIdx = match[1].indexOf('@');
90
+ disabledVersion = atIdx >= 0 ? match[1].slice(atIdx) : '';
91
+ break;
92
+ }
93
+
94
+ let toggledText;
95
+ if (isCurrentlyEnabled) {
96
+ // Toggle OFF: add disabled_ prefix
97
+ toggledText = raw.replace(
98
+ enabledPattern,
99
+ `"${DISABLED_PREFIX}${PLUGIN_NAME}${version}"`
100
+ );
101
+ return { raw: toggledText, newState: 'disabled', version };
102
+ } else if (disabledVersion !== '') {
103
+ // Toggle ON: remove disabled_ prefix
104
+ toggledText = raw.replace(
105
+ disabledPattern,
106
+ `"${PLUGIN_NAME}${disabledVersion}"`
107
+ );
108
+ return { raw: toggledText, newState: 'enabled', version: disabledVersion };
109
+ }
110
+
111
+ // Not found in any form
112
+ return { raw: null, newState: 'not-found', version: '' };
113
+ }
114
+
115
+ function escapeRegex(str) {
116
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
117
+ }
118
+
119
+ export const ToggleOhMyPlugin = async () => {
120
+ return {
121
+ config: async (opencodeConfig) => {
122
+ // Register the /toggle-oh-my command
123
+ opencodeConfig.command ??= {};
124
+ opencodeConfig.command['toggle-oh-my'] = {
125
+ template: '',
126
+ description: 'Toggle oh-my-opencode on/off (modified on next opencode restart)',
127
+ };
128
+ },
129
+
130
+ 'command.execute.before': async (input, output) => {
131
+ if (input.command !== 'toggle-oh-my') return;
132
+
133
+ const configFile = findConfig();
134
+ if (!configFile) {
135
+ output.parts = [{
136
+ type: 'text',
137
+ text: '❌ Could not find opencode config file. Checked:\n' +
138
+ ' - .opencode/opencode.json\n' +
139
+ ' - ~/.config/opencode/opencode.jsonc\n' +
140
+ ' - ~/.opencode.json',
141
+ }];
142
+ return;
143
+ }
144
+
145
+ const result = toggleInText(configFile.raw);
146
+ if (result.newState === 'not-found') {
147
+ output.parts = [{
148
+ type: 'text',
149
+ text: `⚠️ "${PLUGIN_DISPLAY}" not found in ${configFile.path}. Nothing to toggle.`,
150
+ }];
151
+ return;
152
+ }
153
+
154
+ // Write the modified config back
155
+ writeFileSync(configFile.path, result.raw, 'utf-8');
156
+
157
+ const message = result.newState === 'enabled'
158
+ ? `✅ **${PLUGIN_DISPLAY}${result.version}** is now **ENABLED**\n Restart opencode to apply.`
159
+ : `❌ **${PLUGIN_DISPLAY}${result.version}** is now **DISABLED**\n Restart opencode to apply.`;
160
+
161
+ output.parts = [{ type: 'text', text: message }];
162
+ },
163
+ };
164
+ };
165
+
166
+ export default ToggleOhMyPlugin;
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "toggle-oh-my",
3
+ "version": "1.0.0",
4
+ "description": "Opencode plugin to toggle oh-my-opencode on/off with a /toggle-oh-my command",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "files": [
8
+ "index.js",
9
+ "README.md"
10
+ ],
11
+ "keywords": [
12
+ "opencode",
13
+ "plugin",
14
+ "oh-my-opencode",
15
+ "toggle"
16
+ ],
17
+ "license": "MIT",
18
+ "engines": {
19
+ "node": ">=18.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ }
24
+ }