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.
- package/.opencode/mednotes/package-lock.json +2 -2
- package/.opencode/mednotes/package.json +1 -1
- package/.opencode/mednotes/pyproject.toml +1 -1
- package/.opencode/mednotes.generated.json +3 -3
- package/README.md +14 -36
- package/adapters/gemini-cli/gemini-extension.json +1 -1
- package/adapters/gemini-cli/package.json +1 -1
- package/adapters/gemini-cli/pyproject.toml +1 -1
- package/bin/mednotes-opencode.mjs +128 -8
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "medical-notes-workbench",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
9
|
+
"version": "0.1.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"mddb": "^0.9.5"
|
|
12
12
|
},
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "medical-notes-workbench"
|
|
7
|
-
version = "0.1.
|
|
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:
|
|
55
|
-
".opencode/mednotes/package.json": "sha256:
|
|
56
|
-
".opencode/mednotes/pyproject.toml": "sha256:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
caminho equivalente em `%APPDATA%`
|
|
41
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "medical-notes-workbench"
|
|
7
|
-
version = "0.1.
|
|
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,
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|