toggle-oh-my 1.0.2 → 1.0.3

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 (2) hide show
  1. package/index.js +79 -109
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2,23 +2,21 @@ import { readFileSync, writeFileSync, existsSync } from 'fs';
2
2
  import { homedir, platform } from 'os';
3
3
  import { join } from 'path';
4
4
 
5
+ // ============================================================
6
+ // Core toggle logic (shared between server and TUI plugins)
7
+ // ============================================================
8
+
5
9
  const PLUGIN_NAMES = ['oh-my-openagent', 'oh-my-opencode'];
6
10
  const DISABLED_PREFIX = 'disabled_';
7
- const PLUGIN_DISPLAY = 'oh-my-openagent';
8
11
 
9
12
  function escapeRegex(str) {
10
13
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11
14
  }
12
15
 
13
- /**
14
- * Find and read the opencode config file that contains any of the known plugin names.
15
- * Checks project-level config first, then user-level config.
16
- */
17
16
  function findConfig() {
18
17
  const hasAnyPlugin = (raw) =>
19
18
  PLUGIN_NAMES.some((n) => raw.includes(n) || raw.includes(DISABLED_PREFIX + n));
20
19
 
21
- // project-level: <cwd>/.opencode/opencode.json
22
20
  const projectCandidates = [
23
21
  join(process.cwd(), '.opencode', 'opencode.json'),
24
22
  join(process.cwd(), '.opencode', 'opencode.jsonc'),
@@ -26,27 +24,19 @@ function findConfig() {
26
24
  for (const fp of projectCandidates) {
27
25
  if (!existsSync(fp)) continue;
28
26
  const raw = readFileSync(fp, 'utf-8');
29
- if (hasAnyPlugin(raw)) {
30
- return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
31
- }
27
+ if (hasAnyPlugin(raw)) return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
32
28
  }
33
29
 
34
- // user-level configs: check ALL known platform paths
35
30
  const home = homedir();
36
31
  const userCandidates = [
37
- // Windows paths (opencode on Windows may use any of these)
38
32
  join(home, '.config', 'opencode', 'opencode.jsonc'),
39
33
  join(home, '.config', 'opencode', 'opencode.json'),
40
34
  join(home, '.opencode.json'),
41
35
  ];
42
- // Windows: %APPDATA%/opencode/config.json
43
36
  if (platform() === 'win32' && process.env.APPDATA) {
44
- userCandidates.push(
45
- join(process.env.APPDATA, 'opencode', 'config.json'),
46
- join(process.env.APPDATA, 'opencode', 'opencode.jsonc'),
47
- );
37
+ userCandidates.push(join(process.env.APPDATA, 'opencode', 'config.json'));
38
+ userCandidates.push(join(process.env.APPDATA, 'opencode', 'opencode.jsonc'));
48
39
  }
49
- // Linux/macOS: $XDG_CONFIG_HOME
50
40
  if (platform() !== 'win32') {
51
41
  const xdg = process.env.XDG_CONFIG_HOME || join(home, '.config');
52
42
  userCandidates.push(join(xdg, 'opencode', 'opencode.jsonc'));
@@ -55,132 +45,112 @@ function findConfig() {
55
45
  for (const fp of userCandidates) {
56
46
  if (!existsSync(fp)) continue;
57
47
  try {
58
- const raw = readFileSync(fp, 'utf-8');
59
- return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
60
- } catch { /* permission issue, try next */ }
48
+ return { path: fp, raw: readFileSync(fp, 'utf-8'), format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
49
+ } catch { /* try next */ }
61
50
  }
62
-
63
51
  return null;
64
52
  }
65
53
 
66
- /**
67
- * Detect which plugin name is currently in the config (enabled or disabled).
68
- * Returns the actual name found, or null.
69
- */
70
54
  function detectActiveName(raw) {
71
55
  for (const name of PLUGIN_NAMES) {
72
- const enabledRE = new RegExp(`"(${escapeRegex(name)}(?:@[^"]*)?)"`);
73
- const disabledRE = new RegExp(`"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(name)}(?:@[^"]*)?)"`);
74
- if (enabledRE.test(raw) || disabledRE.test(raw)) {
75
- return name;
76
- }
56
+ const re = new RegExp(`"(${escapeRegex(name)}|${escapeRegex(DISABLED_PREFIX)}${escapeRegex(name)})(?:@[^"]*)?"`);
57
+ if (re.test(raw)) return name;
77
58
  }
78
59
  return null;
79
60
  }
80
61
 
81
62
  /**
82
- * Toggle the plugin in the raw config text.
83
- * Preserves version and original formatting.
63
+ * Toggle the plugin name in the config text.
64
+ * Preserves version and original formatting via regex replacement.
84
65
  */
85
66
  function toggleInText(raw) {
86
67
  const activeName = detectActiveName(raw);
87
- if (!activeName) {
88
- return { raw: null, newState: 'not-found', version: '' };
89
- }
68
+ if (!activeName) return { raw: null, newState: 'not-found', version: '' };
90
69
 
91
- const enabledPattern = new RegExp(
92
- `"(${escapeRegex(activeName)}(?:@[^"]*)?)"`,
93
- 'g'
94
- );
95
- const disabledPattern = new RegExp(
96
- `"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(activeName)}(?:@[^"]*)?)"`,
97
- 'g'
98
- );
99
-
100
- let isCurrentlyEnabled = false;
101
- let version = '';
102
-
103
- let match;
104
- enabledPattern.lastIndex = 0;
105
- while ((match = enabledPattern.exec(raw)) !== null) {
106
- isCurrentlyEnabled = true;
107
- const atIdx = match[1].indexOf('@');
108
- version = atIdx >= 0 ? match[1].slice(atIdx) : '';
109
- break;
110
- }
70
+ const enabledRE = new RegExp(`"(${escapeRegex(activeName)}(?:@[^"]*)?)"`, 'g');
71
+ const disabledRE = new RegExp(`"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(activeName)}(?:@[^"]*)?)"`, 'g');
111
72
 
112
- disabledPattern.lastIndex = 0;
113
- let disabledVersion = '';
114
- while ((match = disabledPattern.exec(raw)) !== null) {
115
- isCurrentlyEnabled = false;
116
- const atIdx = match[1].indexOf('@');
117
- disabledVersion = atIdx >= 0 ? match[1].slice(atIdx) : '';
118
- break;
73
+ let m;
74
+ enabledRE.lastIndex = 0;
75
+ if ((m = enabledRE.exec(raw))) {
76
+ const version = m[1].includes('@') ? m[1].slice(m[1].indexOf('@')) : '';
77
+ return { raw: raw.replace(enabledRE, `"${DISABLED_PREFIX}${activeName}${version}"`), newState: 'disabled', version, activeName };
119
78
  }
120
79
 
121
- if (isCurrentlyEnabled) {
122
- const toggledText = raw.replace(
123
- enabledPattern,
124
- `"${DISABLED_PREFIX}${activeName}${version}"`
125
- );
126
- return { raw: toggledText, newState: 'disabled', version, activeName };
127
- } else if (disabledVersion !== '') {
128
- const toggledText = raw.replace(
129
- disabledPattern,
130
- `"${activeName}${disabledVersion}"`
131
- );
132
- return { raw: toggledText, newState: 'enabled', version: disabledVersion, activeName };
80
+ disabledRE.lastIndex = 0;
81
+ if ((m = disabledRE.exec(raw))) {
82
+ const version = m[1].includes('@') ? m[1].slice(m[1].indexOf('@')) : '';
83
+ return { raw: raw.replace(disabledRE, `"${activeName}${version}"`), newState: 'enabled', version, activeName };
133
84
  }
134
85
 
135
86
  return { raw: null, newState: 'not-found', version: '' };
136
87
  }
137
88
 
89
+ // ============================================================
90
+ // Server plugin (hooks) — registers in command palette
91
+ // ============================================================
92
+
138
93
  export const ToggleOhMyPlugin = async () => {
139
94
  return {
140
95
  config: async (opencodeConfig) => {
141
- // Register the /toggle-oh-my command
142
96
  opencodeConfig.command ??= {};
143
97
  opencodeConfig.command['toggle-oh-my'] = {
144
98
  template: '',
145
- description: 'Toggle oh-my-opencode on/off (modified on next opencode restart)',
99
+ description: 'Toggle oh-my-openagent on/off (instant, no LLM needed)',
146
100
  };
147
101
  },
102
+ };
103
+ };
148
104
 
149
- 'command.execute.before': async (input, output) => {
150
- if (input.command !== 'toggle-oh-my') return;
151
-
152
- const configFile = findConfig();
153
- if (!configFile) {
154
- output.parts = [{
155
- type: 'text',
156
- text: '❌ Could not find opencode config file. Checked:\n' +
157
- ' - .opencode/opencode.json\n' +
158
- ' - ~/.config/opencode/opencode.jsonc\n' +
159
- ' - ~/.opencode.json',
160
- }];
161
- return;
162
- }
105
+ // ============================================================
106
+ // TUI plugin — instant command without LLM involvement
107
+ // Uses api.command.register to add a slash command with onSelect
108
+ // ============================================================
163
109
 
164
- const result = toggleInText(configFile.raw);
165
- if (result.newState === 'not-found') {
166
- output.parts = [{
167
- type: 'text',
168
- text: `⚠️ Plugin not found in ${configFile.path}. Nothing to toggle.`,
169
- }];
170
- return;
171
- }
110
+ async function performToggle(api) {
111
+ const file = findConfig();
112
+ if (!file) {
113
+ try { api.ui?.toast?.({ title: 'toggle-oh-my', body: 'Config file not found', type: 'error' }); } catch {}
114
+ return;
115
+ }
172
116
 
173
- // Write the modified config back
174
- writeFileSync(configFile.path, result.raw, 'utf-8');
117
+ const r = toggleInText(file.raw);
118
+ if (r.newState === 'not-found') {
119
+ try { api.ui?.toast?.({ title: 'toggle-oh-my', body: 'Plugin not found in config', type: 'warning' }); } catch {}
120
+ return;
121
+ }
175
122
 
176
- const name = result.activeName || PLUGIN_DISPLAY;
177
- const message = result.newState === 'enabled'
178
- ? `✅ **${name}${result.version}** is now **ENABLED**\n Restart opencode to apply.`
179
- : `❌ **${name}${result.version}** is now **DISABLED**\n Restart opencode to apply.`;
123
+ writeFileSync(file.path, r.raw, 'utf-8');
180
124
 
181
- output.parts = [{ type: 'text', text: message }];
182
- },
183
- };
184
- };
125
+ const status = r.newState === 'enabled' ? 'ENABLED' : 'DISABLED';
126
+ const msg = `${r.activeName}${r.version} → ${status} (restart opencode to apply)`;
185
127
 
186
- export default ToggleOhMyPlugin;
128
+ try {
129
+ api.ui?.toast?.({ title: 'toggle-oh-my', body: msg, type: 'info' });
130
+ } catch {}
131
+ }
132
+
133
+ export default {
134
+ id: 'toggle-oh-my',
135
+ tui: async (api) => {
136
+ try {
137
+ const unregister = api.command.register(() => [
138
+ {
139
+ title: 'Toggle Oh My',
140
+ value: 'toggle-oh-my',
141
+ description: 'Toggle oh-my-openagent on/off instantly (restart to apply)',
142
+ category: 'Plugins',
143
+ slash: { name: 'toggle-oh-my' },
144
+ onSelect: () => performToggle(api),
145
+ },
146
+ ]);
147
+
148
+ // Cleanup on dispose
149
+ if (typeof unregister === 'function') {
150
+ // store for later cleanup if needed
151
+ }
152
+ } catch (e) {
153
+ console.error('toggle-oh-my TUI registration failed:', e);
154
+ }
155
+ },
156
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toggle-oh-my",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Opencode plugin to toggle oh-my-opencode on/off with a /toggle-oh-my command",
5
5
  "type": "module",
6
6
  "main": "index.js",