mednotes-opencode 0.1.0 → 0.1.1

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "medical-notes-workbench",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "medical-notes-workbench",
9
- "version": "0.1.0",
9
+ "version": "0.1.1",
10
10
  "dependencies": {
11
11
  "mddb": "^0.9.5"
12
12
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medical-notes-workbench",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Gemini CLI extension packaging for medical notes creation and processing.",
5
5
  "private": true,
6
6
  "engines": {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "medical-notes-workbench"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Workbench para criar, enriquecer e processar notas médicas Markdown/Obsidian."
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -51,9 +51,9 @@
51
51
  ".opencode/mednotes/docs/vocabulary-db-recovery.md": "sha256:8182d3b064cd81576f5f471b4db526f17a37b43b3c5f2a509fb4d56685cd54e4",
52
52
  ".opencode/mednotes/docs/workflow-output-contract.md": "sha256:a3e12d90fad21ec9ba068b7b83903dee4219f704687ecdad7fd7510ab591cfb7",
53
53
  ".opencode/mednotes/hooks/hooks.json": "sha256:8645faf10794e8a2ac06a54d7ba703872c2fe15f5d4ea7e64b58f60074951009",
54
- ".opencode/mednotes/package-lock.json": "sha256:33d8543fb19d9eff8b5d3b0da86b569b1ff007c2d6c90ca449b018aa6437d815",
55
- ".opencode/mednotes/package.json": "sha256:c286968311caadaa1f6a3f9ed0ecf5b37b4661d4e10d668521e7aa6df72489cb",
56
- ".opencode/mednotes/pyproject.toml": "sha256:c0a843c3bbdd7878987485a780aa7de5a7ef4140f486f8888552ceb11ba48b4f",
54
+ ".opencode/mednotes/package-lock.json": "sha256:add5fedbfaaffbd580e71a6ac8a3d7fa527c5d1b73ee4da723e747d90a92b0b6",
55
+ ".opencode/mednotes/package.json": "sha256:6284bec4738bf3c20f17fbd3c359da6df0684656074169a1d49867f666030aed",
56
+ ".opencode/mednotes/pyproject.toml": "sha256:f56ccec981f2433777771303a5f9f50947ed0f0883e93d6ca279e1c28e432a45",
57
57
  ".opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd": "sha256:7ed23e9086b84aa161439a20814930d8d93c7dc3c7b77d9818477ef6690db3d3",
58
58
  ".opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1": "sha256:148a5eada17bc4a2a395eddb3019d437b93429730d75f8962907a24492d8770e",
59
59
  ".opencode/mednotes/scripts/enrich_notes.py": "sha256:68eae00f72cf1b4b93989f29397f0fe0dea510ae25d76131489428d86e77f380",
package/README.md CHANGED
@@ -11,63 +11,39 @@ hashes, recibos e validações, mas a experiência humana deve parecer direta.
11
11
 
12
12
  ## Instalação
13
13
 
14
- Enquanto o pacote `mednotes-opencode` ainda não estiver publicado no registry
15
- npm, instale pelo repo público GitHub:
16
-
17
- ```bash
18
- npm install -g github:augustocaruso/mednotes
19
- ```
20
-
21
- Registre o plugin no OpenCode apontando para o mesmo spec GitHub:
22
-
23
- ```bash
24
- mednotes-opencode install --plugin github:augustocaruso/mednotes
25
- ```
26
-
27
- Quando o pacote npm estiver publicado, o caminho equivalente pelo registry será:
14
+ Instale pelo pacote npm público:
28
15
 
29
16
  ```bash
30
17
  npm install -g mednotes-opencode
31
18
  ```
32
19
 
33
- E o registro do plugin poderá usar o spec curto:
20
+ Registre a superfície MedNotes no OpenCode:
34
21
 
35
22
  ```bash
36
23
  mednotes-opencode install
37
24
  ```
38
25
 
39
- Esse comando atualiza `~/.config/opencode/opencode.json` no macOS/Linux, ou o
40
- caminho equivalente em `%APPDATA%` no Windows. Ele cria backup antes de alterar
41
- um arquivo existente e pode ser auditado sem escrever nada:
26
+ Esse comando instala comandos, agentes, runtime e hook em
27
+ `~/.config/opencode` no macOS/Linux, ou no caminho equivalente em `%APPDATA%`
28
+ no Windows. Ele mescla `opencode.json`, cria backup antes de alterar um arquivo
29
+ existente e pode ser auditado sem escrever nada:
42
30
 
43
31
  ```bash
44
32
  mednotes-opencode install --dry-run
45
33
  ```
46
34
 
47
35
  Depois disso, abra o OpenCode normalmente. O plugin é carregado pelo próprio
48
- OpenCode como pacote npm/GitHub e sincroniza a configuração de runtime no boot.
36
+ OpenCode a partir do arquivo local instalado em `plugins/mednotes-fsm.mjs` e
37
+ sincroniza a configuração de runtime no boot.
49
38
 
50
39
  ## Atualização
51
40
 
52
- Se instalado pelo GitHub, atualize reinstalando o spec público:
53
-
54
- ```bash
55
- npm install -g github:augustocaruso/mednotes
56
- mednotes-opencode install --plugin github:augustocaruso/mednotes
57
- ```
58
-
59
- Quando o pacote registry estiver publicado, atualize como qualquer pacote npm:
41
+ Atualize como qualquer pacote npm e rode o instalador novamente para copiar a
42
+ nova superfície gerada para o diretório de configuração do OpenCode:
60
43
 
61
44
  ```bash
62
45
  npm update -g mednotes-opencode
63
- ```
64
-
65
- Se o `opencode.json` usar o spec `mednotes-opencode`, o OpenCode também pode
66
- resolver versões novas pelo mecanismo nativo de plugins npm. Para congelar uma
67
- versão, use um spec com versão explícita no instalador:
68
-
69
- ```bash
70
- mednotes-opencode install --plugin mednotes-opencode@0.1.0
46
+ mednotes-opencode install
71
47
  ```
72
48
 
73
49
  ## Configuração
@@ -149,7 +125,9 @@ O pacote npm exporta o plugin OpenCode por:
149
125
 
150
126
  Arquivos principais:
151
127
 
152
- - `.opencode/`: plugin, agentes, comandos e runtime OpenCode gerados.
128
+ - `.opencode/`: plugin, agentes, comandos e runtime OpenCode gerados. O
129
+ instalador copia essa superfície para o diretório de configuração do
130
+ OpenCode sem sobrescrever a configuração inteira do usuário.
153
131
  - `core/`: fontes canônicas públicas de agentes, comandos e skills.
154
132
  - `contracts/`: contratos de agentes usados pelos geradores.
155
133
  - `adapters/`: projeções secundárias mantidas por compatibilidade.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medical-notes-workbench",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Gemini CLI workbench for creating, organizing, and processing medical Markdown notes for Obsidian.",
5
5
  "contextFileName": "GEMINI.md",
6
6
  "settings": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medical-notes-workbench",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Gemini CLI extension packaging for medical notes creation and processing.",
5
5
  "private": true,
6
6
  "engines": {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "medical-notes-workbench"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Workbench para criar, enriquecer e processar notas médicas Markdown/Obsidian."
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -1,11 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync } from "node:fs";
3
- import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { copyFile, cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
7
 
7
8
  const PACKAGE_SPEC = "mednotes-opencode";
8
9
  const OPENCODE_SCHEMA = "https://opencode.ai/config.json";
10
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11
+ const PACKAGE_OPENCODE = findPackagedOpenCodeDir();
12
+ const GENERATED_CONFIG = path.join(PACKAGE_OPENCODE, "opencode.json");
13
+ const MEDNOTES_RUNTIME_REF = ".opencode/mednotes";
14
+ const REQUIRED_INSTALL_FILES = [
15
+ ["commands", "mednotes", "status.md"],
16
+ ["agents", "med-knowledge-architect.md"],
17
+ ["mednotes", "AGENTS.md"],
18
+ ["plugins", "mednotes-fsm.mjs"],
19
+ ];
20
+
21
+ function findPackagedOpenCodeDir() {
22
+ const candidates = [
23
+ path.join(PACKAGE_ROOT, ".opencode"),
24
+ path.join(PACKAGE_ROOT, "manifests", "opencode-plugin", ".opencode"),
25
+ ];
26
+ for (const candidate of candidates) {
27
+ if (existsSync(path.join(candidate, "opencode.json"))) {
28
+ return candidate;
29
+ }
30
+ }
31
+ return candidates[0];
32
+ }
9
33
 
10
34
  function usage() {
11
35
  return [
@@ -19,6 +43,9 @@ function configPathFromEnv() {
19
43
  if (process.env.OPENCODE_CONFIG) {
20
44
  return process.env.OPENCODE_CONFIG;
21
45
  }
46
+ if (process.env.OPENCODE_CONFIG_DIR) {
47
+ return path.join(process.env.OPENCODE_CONFIG_DIR, "opencode.json");
48
+ }
22
49
  if (process.platform === "win32") {
23
50
  const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
24
51
  return path.join(appData, "opencode", "opencode.json");
@@ -88,18 +115,106 @@ function normalizePlugins(value, pluginSpec) {
88
115
  return [...preserved, pluginSpec];
89
116
  }
90
117
 
118
+ function normalizeInstructions(value, instructionPath) {
119
+ const existing = Array.isArray(value) ? value : [];
120
+ const preserved = existing.filter((entry) => {
121
+ if (typeof entry !== "string") {
122
+ return true;
123
+ }
124
+ return !entry.includes("mednotes/AGENTS.md") && !entry.includes("mednotes\\AGENTS.md");
125
+ });
126
+ return [...preserved, instructionPath];
127
+ }
128
+
129
+ function objectValue(value) {
130
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
131
+ }
132
+
133
+ async function readGeneratedConfig() {
134
+ return JSON.parse(await readFile(GENERATED_CONFIG, "utf8"));
135
+ }
136
+
137
+ function localPluginSpec(configDir) {
138
+ return pathToFileURL(path.join(configDir, "plugins", "mednotes-fsm.mjs")).href;
139
+ }
140
+
141
+ function requiredInstallFilesPresent(configDir) {
142
+ return REQUIRED_INSTALL_FILES.every((parts) => existsSync(path.join(configDir, ...parts)));
143
+ }
144
+
145
+ async function copyOpenCodeSurface(configDir, dryRun) {
146
+ const installs = [
147
+ ["commands", "commands"],
148
+ ["agents", "agents"],
149
+ ["mednotes", "mednotes"],
150
+ ["plugins", "plugins"],
151
+ ["mednotes.generated.json", "mednotes.generated.json"],
152
+ ];
153
+ if (dryRun) {
154
+ return installs.map(([, target]) => path.join(configDir, target));
155
+ }
156
+ for (const [source, target] of installs) {
157
+ const sourcePath = path.join(PACKAGE_OPENCODE, source);
158
+ const targetPath = path.join(configDir, target);
159
+ await mkdir(path.dirname(targetPath), { recursive: true });
160
+ await cp(sourcePath, targetPath, { recursive: true, force: true });
161
+ }
162
+ await rewriteRuntimeReferences(path.join(configDir, "commands"), path.join(configDir, "mednotes"));
163
+ await rewriteRuntimeReferences(path.join(configDir, "agents"), path.join(configDir, "mednotes"));
164
+ return installs.map(([, target]) => path.join(configDir, target));
165
+ }
166
+
167
+ async function rewriteRuntimeReferences(root, runtimeRoot) {
168
+ if (!existsSync(root)) {
169
+ return;
170
+ }
171
+ const entries = await readdir(root, { withFileTypes: true });
172
+ for (const entry of entries) {
173
+ const entryPath = path.join(root, entry.name);
174
+ if (entry.isDirectory()) {
175
+ await rewriteRuntimeReferences(entryPath, runtimeRoot);
176
+ continue;
177
+ }
178
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
179
+ continue;
180
+ }
181
+ const current = await readFile(entryPath, "utf8");
182
+ const next = current.replaceAll(MEDNOTES_RUNTIME_REF, runtimeRoot);
183
+ if (next !== current) {
184
+ await writeFile(entryPath, next, "utf8");
185
+ }
186
+ }
187
+ }
188
+
189
+ function mergeAgentConfig(config, generatedConfig) {
190
+ const currentAgent = objectValue(config.agent);
191
+ const generatedAgent = objectValue(generatedConfig.agent);
192
+ const nextAgent = { ...currentAgent };
193
+ for (const [agentId, runtimeConfig] of Object.entries(generatedAgent)) {
194
+ nextAgent[agentId] = { ...objectValue(currentAgent[agentId]), ...objectValue(runtimeConfig) };
195
+ }
196
+ return nextAgent;
197
+ }
198
+
91
199
  async function install(options) {
92
200
  const configPath = path.resolve(options.configPath);
201
+ const configDir = path.dirname(configPath);
202
+ const generatedConfig = await readGeneratedConfig();
203
+ const pluginSpec = options.pluginSpec === PACKAGE_SPEC ? localPluginSpec(configDir) : options.pluginSpec;
204
+ const assetsWereMissing = !requiredInstallFilesPresent(configDir);
93
205
  const config = await readConfig(configPath);
94
206
  const before = `${JSON.stringify(config, null, 2)}\n`;
95
207
  config.$schema = typeof config.$schema === "string" ? config.$schema : OPENCODE_SCHEMA;
96
- config.plugin = normalizePlugins(config.plugin, options.pluginSpec);
208
+ config.plugin = normalizePlugins(config.plugin, pluginSpec);
209
+ config.instructions = normalizeInstructions(config.instructions, path.join(configDir, "mednotes", "AGENTS.md"));
210
+ config.agent = mergeAgentConfig(config, generatedConfig);
97
211
  const after = `${JSON.stringify(config, null, 2)}\n`;
98
- const changed = before !== after;
212
+ const changed = before !== after || assetsWereMissing;
99
213
  const backupPath = `${configPath}.bak.${new Date().toISOString().replace(/[:.]/g, "-")}`;
214
+ const installedPaths = await copyOpenCodeSurface(configDir, options.dryRun);
100
215
 
101
216
  if (changed && !options.dryRun) {
102
- await mkdir(path.dirname(configPath), { recursive: true });
217
+ await mkdir(configDir, { recursive: true });
103
218
  if (existsSync(configPath)) {
104
219
  await copyFile(configPath, backupPath);
105
220
  }
@@ -109,22 +224,27 @@ async function install(options) {
109
224
  return {
110
225
  status: changed ? "updated" : "already_configured",
111
226
  config_path: configPath,
112
- plugin: options.pluginSpec,
227
+ plugin: pluginSpec,
113
228
  dry_run: options.dryRun,
229
+ installed_paths: installedPaths,
114
230
  backup_path: changed && !options.dryRun && existsSync(backupPath) ? backupPath : null,
115
231
  };
116
232
  }
117
233
 
118
234
  async function doctor(options) {
119
235
  const configPath = path.resolve(options.configPath);
236
+ const configDir = path.dirname(configPath);
120
237
  const config = await readConfig(configPath);
121
238
  const plugin = Array.isArray(config.plugin) ? config.plugin : [];
239
+ const pluginConfigured = plugin.some(
240
+ (entry) => typeof entry === "string" && (entry.includes("mednotes-opencode") || entry.includes("mednotes-fsm.mjs")),
241
+ );
242
+ const surfaceConfigured = requiredInstallFilesPresent(configDir);
122
243
  return {
123
- status: plugin.some((entry) => typeof entry === "string" && entry.includes("mednotes-opencode"))
124
- ? "configured"
125
- : "missing",
244
+ status: pluginConfigured && surfaceConfigured ? "configured" : pluginConfigured ? "incomplete" : "missing",
126
245
  config_path: configPath,
127
246
  plugin,
247
+ surface_configured: surfaceConfigured,
128
248
  };
129
249
  }
130
250
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mednotes-opencode",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "MedNotes plugin for OpenCode: FSM-first medical notes workflows for Obsidian and Anki.",
5
5
  "type": "module",
6
6
  "private": false,