toggle-oh-my 1.0.1 → 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 +85 -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,155 +24,133 @@ 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: ~/.config/opencode/opencode.jsonc (or opencode.json)
35
30
  const home = homedir();
36
31
  const userCandidates = [
37
32
  join(home, '.config', 'opencode', 'opencode.jsonc'),
38
33
  join(home, '.config', 'opencode', 'opencode.json'),
39
34
  join(home, '.opencode.json'),
40
35
  ];
41
- for (const fp of userCandidates) {
42
- if (!existsSync(fp)) continue;
43
- const raw = readFileSync(fp, 'utf-8');
44
- return { path: fp, raw, format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
36
+ if (platform() === 'win32' && process.env.APPDATA) {
37
+ userCandidates.push(join(process.env.APPDATA, 'opencode', 'config.json'));
38
+ userCandidates.push(join(process.env.APPDATA, 'opencode', 'opencode.jsonc'));
45
39
  }
46
-
47
- // Linux/macOS fallback: $XDG_CONFIG_HOME/opencode/opencode.jsonc
48
40
  if (platform() !== 'win32') {
49
41
  const xdg = process.env.XDG_CONFIG_HOME || join(home, '.config');
50
- const fp = join(xdg, 'opencode', 'opencode.jsonc');
51
- if (existsSync(fp)) {
52
- const raw = readFileSync(fp, 'utf-8');
53
- return { path: fp, raw, format: 'jsonc' };
54
- }
42
+ userCandidates.push(join(xdg, 'opencode', 'opencode.jsonc'));
55
43
  }
56
44
 
45
+ for (const fp of userCandidates) {
46
+ if (!existsSync(fp)) continue;
47
+ try {
48
+ return { path: fp, raw: readFileSync(fp, 'utf-8'), format: fp.endsWith('.jsonc') ? 'jsonc' : 'json' };
49
+ } catch { /* try next */ }
50
+ }
57
51
  return null;
58
52
  }
59
53
 
60
- /**
61
- * Detect which plugin name is currently in the config (enabled or disabled).
62
- * Returns the actual name found, or null.
63
- */
64
54
  function detectActiveName(raw) {
65
55
  for (const name of PLUGIN_NAMES) {
66
- const enabledRE = new RegExp(`"(${escapeRegex(name)}(?:@[^"]*)?)"`);
67
- const disabledRE = new RegExp(`"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(name)}(?:@[^"]*)?)"`);
68
- if (enabledRE.test(raw) || disabledRE.test(raw)) {
69
- return name;
70
- }
56
+ const re = new RegExp(`"(${escapeRegex(name)}|${escapeRegex(DISABLED_PREFIX)}${escapeRegex(name)})(?:@[^"]*)?"`);
57
+ if (re.test(raw)) return name;
71
58
  }
72
59
  return null;
73
60
  }
74
61
 
75
62
  /**
76
- * Toggle the plugin in the raw config text.
77
- * Preserves version and original formatting.
63
+ * Toggle the plugin name in the config text.
64
+ * Preserves version and original formatting via regex replacement.
78
65
  */
79
66
  function toggleInText(raw) {
80
67
  const activeName = detectActiveName(raw);
81
- if (!activeName) {
82
- return { raw: null, newState: 'not-found', version: '' };
83
- }
68
+ if (!activeName) return { raw: null, newState: 'not-found', version: '' };
84
69
 
85
- const enabledPattern = new RegExp(
86
- `"(${escapeRegex(activeName)}(?:@[^"]*)?)"`,
87
- 'g'
88
- );
89
- const disabledPattern = new RegExp(
90
- `"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(activeName)}(?:@[^"]*)?)"`,
91
- 'g'
92
- );
93
-
94
- let isCurrentlyEnabled = false;
95
- let version = '';
96
-
97
- let match;
98
- enabledPattern.lastIndex = 0;
99
- while ((match = enabledPattern.exec(raw)) !== null) {
100
- isCurrentlyEnabled = true;
101
- const atIdx = match[1].indexOf('@');
102
- version = atIdx >= 0 ? match[1].slice(atIdx) : '';
103
- break;
104
- }
70
+ const enabledRE = new RegExp(`"(${escapeRegex(activeName)}(?:@[^"]*)?)"`, 'g');
71
+ const disabledRE = new RegExp(`"(${escapeRegex(DISABLED_PREFIX)}${escapeRegex(activeName)}(?:@[^"]*)?)"`, 'g');
105
72
 
106
- disabledPattern.lastIndex = 0;
107
- let disabledVersion = '';
108
- while ((match = disabledPattern.exec(raw)) !== null) {
109
- isCurrentlyEnabled = false;
110
- const atIdx = match[1].indexOf('@');
111
- disabledVersion = atIdx >= 0 ? match[1].slice(atIdx) : '';
112
- 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 };
113
78
  }
114
79
 
115
- if (isCurrentlyEnabled) {
116
- const toggledText = raw.replace(
117
- enabledPattern,
118
- `"${DISABLED_PREFIX}${activeName}${version}"`
119
- );
120
- return { raw: toggledText, newState: 'disabled', version, activeName };
121
- } else if (disabledVersion !== '') {
122
- const toggledText = raw.replace(
123
- disabledPattern,
124
- `"${activeName}${disabledVersion}"`
125
- );
126
- 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 };
127
84
  }
128
85
 
129
86
  return { raw: null, newState: 'not-found', version: '' };
130
87
  }
131
88
 
89
+ // ============================================================
90
+ // Server plugin (hooks) — registers in command palette
91
+ // ============================================================
92
+
132
93
  export const ToggleOhMyPlugin = async () => {
133
94
  return {
134
95
  config: async (opencodeConfig) => {
135
- // Register the /toggle-oh-my command
136
96
  opencodeConfig.command ??= {};
137
97
  opencodeConfig.command['toggle-oh-my'] = {
138
98
  template: '',
139
- description: 'Toggle oh-my-opencode on/off (modified on next opencode restart)',
99
+ description: 'Toggle oh-my-openagent on/off (instant, no LLM needed)',
140
100
  };
141
101
  },
102
+ };
103
+ };
142
104
 
143
- 'command.execute.before': async (input, output) => {
144
- if (input.command !== 'toggle-oh-my') return;
145
-
146
- const configFile = findConfig();
147
- if (!configFile) {
148
- output.parts = [{
149
- type: 'text',
150
- text: '❌ Could not find opencode config file. Checked:\n' +
151
- ' - .opencode/opencode.json\n' +
152
- ' - ~/.config/opencode/opencode.jsonc\n' +
153
- ' - ~/.opencode.json',
154
- }];
155
- return;
156
- }
105
+ // ============================================================
106
+ // TUI plugin — instant command without LLM involvement
107
+ // Uses api.command.register to add a slash command with onSelect
108
+ // ============================================================
157
109
 
158
- const result = toggleInText(configFile.raw);
159
- if (result.newState === 'not-found') {
160
- output.parts = [{
161
- type: 'text',
162
- text: `⚠️ Plugin not found in ${configFile.path}. Nothing to toggle.`,
163
- }];
164
- return;
165
- }
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
+ }
116
+
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
+ }
166
122
 
167
- // Write the modified config back
168
- writeFileSync(configFile.path, result.raw, 'utf-8');
123
+ writeFileSync(file.path, r.raw, 'utf-8');
169
124
 
170
- const name = result.activeName || PLUGIN_DISPLAY;
171
- const message = result.newState === 'enabled'
172
- ? `✅ **${name}${result.version}** is now **ENABLED**\n Restart opencode to apply.`
173
- : `❌ **${name}${result.version}** is now **DISABLED**\n Restart opencode to apply.`;
125
+ const status = r.newState === 'enabled' ? 'ENABLED' : 'DISABLED';
126
+ const msg = `${r.activeName}${r.version} ${status} (restart opencode to apply)`;
174
127
 
175
- output.parts = [{ type: 'text', text: message }];
176
- },
177
- };
178
- };
128
+ try {
129
+ api.ui?.toast?.({ title: 'toggle-oh-my', body: msg, type: 'info' });
130
+ } catch {}
131
+ }
179
132
 
180
- export default ToggleOhMyPlugin;
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.1",
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",