opencode-gemiterm-skills 0.6.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@ All notable changes to `opencode-gemiterm-skills` will be documented in this fil
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.6.2] - 2026-06-12
8
+
9
+ ### Fixed
10
+ - `install` no longer adds a duplicate plugin entry when `opencode-gemiterm-skills` is already present under a different form (e.g. `opencode-gemiterm-skills@latest` or different casing). Added a shared `normalizePluginName`/`isOurPluginEntry` helper that strips version tags (`@latest`, `@1.2.3`) and lowercases before matching, used by `addPluginToConfig`, `removePluginFromConfig`, and `isPluginInConfig`. Scoped packages (`@scope/pkg`) are handled correctly so the leading `@` isn't mistaken for a version separator.
11
+
12
+ ### Added
13
+ - Exported `normalizePluginName` and `isOurPluginEntry` from `src/installer.ts` for direct unit testing.
14
+ - 6 new tests in `tests/skills.test.ts` covering version-spec stripping, case-insensitivity, whitespace trimming, scoped-package handling, and matching/rejecting the right entries.
15
+
16
+ ## [0.6.1] - 2026-06-12
17
+
18
+ ### Changed
19
+ - Fixed frontmatter for `debate-with-gemini` skill.
20
+
7
21
  ## [0.6.0] - 2026-06-10
8
22
 
9
23
  ### Changed
package/README.md CHANGED
@@ -37,14 +37,15 @@ Both skills are loaded on demand. Metadata (name + description) is pre-loaded at
37
37
  ## Quick start
38
38
 
39
39
  ```bash
40
- # Install skills via bunx
40
+ # Install via skills.sh
41
+ npx skills add expert-vision-software/opencode-gemiterm-skills [--skill debate-with-gemini --skill gemiterm]
42
+
43
+ # Or with bunx
41
44
  bunx opencode-gemiterm-skills install [--scope global]
42
45
 
43
46
  # Or with npx
44
47
  npx opencode-gemiterm-skills install [--scope global]
45
48
 
46
- # Or skills.sh
47
- npx skills add expert-vision-software/opencode-gemiterm-skills --skill [gemiterm/debate-with-gemini]
48
49
  ```
49
50
 
50
51
  That's it — skills are available immediately.
@@ -106,6 +107,9 @@ Gemini conceded on the replication point but raised WAL-mode mitigations.
106
107
  ### CLI install (any agent)
107
108
 
108
109
  ```bash
110
+ # Install via skills.sh
111
+ npx skills add expert-vision-software/opencode-gemiterm-skills [--skill debate-with-gemini --skill gemiterm]
112
+
109
113
  # Via bunx
110
114
  bunx opencode-gemiterm-skills install
111
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-gemiterm-skills",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "module": "index.ts",
6
6
  "description": "AI agent skills for Google Gemini — list, search, export Gemini chats and run structured debates with Gemini. Works with OpenCode, Claude Code, and any skill-compatible AI agent.",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: debate-with-gemini
3
- description: Conduct structured multi-turn technical debates with Gemini AI via gemiterm CLI. Delegates a subagent to argue a position (for/against) autonomously for up to N turns. Use when user says "debate gemini", "argue with gemini", "have gemini defend/attack X", "continue debate", or wants a technical position stress-tested against Gemini. Triggers on: debate, argue, gemini, position, for/against, stress-test, counter-argument. Requires gemiterm CLI installed and authenticated.
3
+ description: 'Conduct structured multi-turn technical debates with Gemini AI via the gemiterm CLI. Delegates a subagent to argue a position (for or against) autonomously for up to N turns. Use when the user says - debate gemini, argue with gemini, have gemini defend or attack X, continue debate, or wants a technical position stress-tested against Gemini. Requires the gemiterm CLI to be installed and authenticated.'
4
4
  license: MIT
5
5
  compatibility: opencode, claude-code, and any skill-compatible agent
6
6
  metadata:
package/src/installer.ts CHANGED
@@ -30,6 +30,33 @@ export interface StatusResult {
30
30
  const SKILL_NAMES = ["gemiterm", "debate-with-gemini"] as const;
31
31
  const PACKAGE_NAME = "opencode-gemiterm-skills";
32
32
 
33
+ /**
34
+ * Normalize a plugin entry to its bare, lowercased package name.
35
+ *
36
+ * Plugin entries may carry a version spec (e.g. "pkg@latest",
37
+ * "pkg@1.2.3") and arbitrary casing. This strips any trailing
38
+ * "@version" and lowercases the result so that "opencode-gemiterm-skills",
39
+ * "Opencode-Gemiterm-Skills", and "opencode-gemiterm-skills@latest" all
40
+ * resolve to the same canonical name.
41
+ */
42
+ export function normalizePluginName(entry: string): string {
43
+ let name = entry.trim().toLowerCase();
44
+ const atIdx = name.indexOf("@");
45
+ if (atIdx === 0) {
46
+ // Scoped package ("@scope/pkg"): the version separator is the second "@".
47
+ const secondAt = name.indexOf("@", 1);
48
+ if (secondAt !== -1) name = name.slice(0, secondAt);
49
+ } else if (atIdx !== -1) {
50
+ // Unscoped package: everything after the first "@" is a version spec.
51
+ name = name.slice(0, atIdx);
52
+ }
53
+ return name;
54
+ }
55
+
56
+ export function isOurPluginEntry(entry: string): boolean {
57
+ return normalizePluginName(entry) === PACKAGE_NAME.toLowerCase();
58
+ }
59
+
33
60
  function getPackageDir(): string {
34
61
  return join(fileURLToPath(new URL("../", import.meta.url)));
35
62
  }
@@ -100,7 +127,7 @@ async function addPluginToConfig(configPath: string): Promise<boolean> {
100
127
  const config = await readJsonConfig(configPath);
101
128
  if (!config.plugin) config.plugin = [];
102
129
  const plugins = config.plugin as string[];
103
- if (plugins.includes(PACKAGE_NAME)) return false;
130
+ if (plugins.some(isOurPluginEntry)) return false;
104
131
  plugins.push(PACKAGE_NAME);
105
132
  await mkdir(join(configPath, ".."), { recursive: true });
106
133
  await writeFile(configPath, JSON.stringify(config, null, 2));
@@ -111,10 +138,10 @@ async function removePluginFromConfig(configPath: string): Promise<boolean> {
111
138
  const config = await readJsonConfig(configPath);
112
139
  if (!config.plugin) return false;
113
140
  const plugins = config.plugin as string[];
114
- const idx = plugins.indexOf(PACKAGE_NAME);
115
- if (idx === -1) return false;
116
- plugins.splice(idx, 1);
117
- if (plugins.length === 0) delete config.plugin;
141
+ const filtered = plugins.filter((p) => !isOurPluginEntry(p));
142
+ if (filtered.length === plugins.length) return false;
143
+ if (filtered.length === 0) delete config.plugin;
144
+ else config.plugin = filtered;
118
145
  await mkdir(join(configPath, ".."), { recursive: true });
119
146
  await writeFile(configPath, JSON.stringify(config, null, 2));
120
147
  return true;
@@ -123,7 +150,7 @@ async function removePluginFromConfig(configPath: string): Promise<boolean> {
123
150
  async function isPluginInConfig(configPath: string): Promise<boolean> {
124
151
  const config = await readJsonConfig(configPath);
125
152
  if (!config.plugin) return false;
126
- return (config.plugin as string[]).includes(PACKAGE_NAME);
153
+ return (config.plugin as string[]).some(isOurPluginEntry);
127
154
  }
128
155
 
129
156
  async function checkMigrationNeeded(projectDir: string) {