opalacoder 0.2.0__tar.gz → 0.2.2__tar.gz

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 (163) hide show
  1. opalacoder-0.2.2/AppIcons/icon.png +0 -0
  2. opalacoder-0.2.2/AppIcons/opalaicon.png +0 -0
  3. {opalacoder-0.2.0 → opalacoder-0.2.2}/PKG-INFO +1 -1
  4. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/07-configuracao.md +217 -1
  5. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/src/App.jsx +66 -2
  6. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/src/index.css +5 -0
  7. opalacoder-0.2.2/icon.png +0 -0
  8. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/agent_stdin.py +16 -2
  9. opalacoder-0.2.2/opalacoder/assetstore/modelconfigs/gpt_oss__latest.metadata +4 -0
  10. opalacoder-0.2.2/opalacoder/assetstore/modelconfigs/gpt_oss__latest.zip +0 -0
  11. opalacoder-0.2.2/opalacoder/assetstore/skills/skill_html_css_js.metadata +4 -0
  12. opalacoder-0.2.2/opalacoder/assetstore/skills/skill_html_css_js.zip +0 -0
  13. opalacoder-0.2.2/opalacoder/assetstore/skills/skill_implement_feature.metadata +4 -0
  14. opalacoder-0.2.2/opalacoder/assetstore/skills/skill_implement_feature.zip +0 -0
  15. opalacoder-0.2.2/opalacoder/assetstore.py +179 -0
  16. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/cli_commands.py +78 -0
  17. opalacoder-0.2.2/opalacoder/gui/assets/index-CD7_Sjss.css +32 -0
  18. opalacoder-0.2.2/opalacoder/gui/assets/index-D2mJbeF2.js +159 -0
  19. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/gui/index.html +2 -2
  20. opalacoder-0.2.2/opalacoder/icon.png +0 -0
  21. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/ide_server.py +58 -1
  22. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/memgpt_runtime.py +2 -1
  23. {opalacoder-0.2.0 → opalacoder-0.2.2}/pyproject.toml +1 -1
  24. opalacoder-0.2.0/opalacoder/gui/assets/index-BqTKFzta.js +0 -159
  25. opalacoder-0.2.0/opalacoder/gui/assets/index-_ON06SYj.css +0 -32
  26. opalacoder-0.2.0/opalacoder/icon.png +0 -0
  27. {opalacoder-0.2.0 → opalacoder-0.2.2}/.claude/rules/RULES.md +0 -0
  28. {opalacoder-0.2.0 → opalacoder-0.2.2}/.claude/settings.json +0 -0
  29. {opalacoder-0.2.0 → opalacoder-0.2.2}/.github/workflows/publish.yml +0 -0
  30. {opalacoder-0.2.0 → opalacoder-0.2.2}/.gitignore +0 -0
  31. {opalacoder-0.2.0 → opalacoder-0.2.2}/AGENTS.md +0 -0
  32. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/100.png +0 -0
  33. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/102.png +0 -0
  34. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/1024.png +0 -0
  35. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/108.png +0 -0
  36. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/114.png +0 -0
  37. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/120.png +0 -0
  38. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/128.png +0 -0
  39. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/144.png +0 -0
  40. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/152.png +0 -0
  41. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/16.png +0 -0
  42. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/167.png +0 -0
  43. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/172.png +0 -0
  44. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/180.png +0 -0
  45. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/196.png +0 -0
  46. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/20.png +0 -0
  47. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/216.png +0 -0
  48. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/234.png +0 -0
  49. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/256.png +0 -0
  50. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/258.png +0 -0
  51. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/29.png +0 -0
  52. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/32.png +0 -0
  53. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/40.png +0 -0
  54. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/48.png +0 -0
  55. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/50.png +0 -0
  56. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/512.png +0 -0
  57. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/55.png +0 -0
  58. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/57.png +0 -0
  59. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/58.png +0 -0
  60. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/60.png +0 -0
  61. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/64.png +0 -0
  62. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/66.png +0 -0
  63. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/72.png +0 -0
  64. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/76.png +0 -0
  65. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/80.png +0 -0
  66. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/87.png +0 -0
  67. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/88.png +0 -0
  68. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/92.png +0 -0
  69. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -0
  70. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/android/mipmap-hdpi/ic_launcher.png +0 -0
  71. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/android/mipmap-mdpi/ic_launcher.png +0 -0
  72. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/android/mipmap-xhdpi/ic_launcher.png +0 -0
  73. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/android/mipmap-xxhdpi/ic_launcher.png +0 -0
  74. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/android/mipmap-xxxhdpi/ic_launcher.png +0 -0
  75. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/appstore.png +0 -0
  76. {opalacoder-0.2.0 → opalacoder-0.2.2}/AppIcons/playstore.png +0 -0
  77. {opalacoder-0.2.0 → opalacoder-0.2.2}/CLAUDE.md +0 -0
  78. {opalacoder-0.2.0 → opalacoder-0.2.2}/GEMINI.md +0 -0
  79. {opalacoder-0.2.0 → opalacoder-0.2.2}/README.md +0 -0
  80. {opalacoder-0.2.0 → opalacoder-0.2.2}/agents.yaml +0 -0
  81. {opalacoder-0.2.0 → opalacoder-0.2.2}/config.yaml +0 -0
  82. {opalacoder-0.2.0 → opalacoder-0.2.2/debug}/debug_litellm_callbacks.py +0 -0
  83. {opalacoder-0.2.0 → opalacoder-0.2.2}/debug/scratch_tail.py +0 -0
  84. {opalacoder-0.2.0 → opalacoder-0.2.2/debug}/test_thought_stream.py +0 -0
  85. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/guide/analysis_results.md +0 -0
  86. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/guide/figs/logotipo.png +0 -0
  87. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/guide/paper_memplan.md +0 -0
  88. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/guide/skills-plugin-system.md +0 -0
  89. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/01-arquitetura.md +0 -0
  90. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/02-memgpt-orquestrador.md +0 -0
  91. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/03-skill-implement-feature.md +0 -0
  92. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/04-memoria.md +0 -0
  93. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/05-vcs-sombra.md +0 -0
  94. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/06-skills-e-plugins.md +0 -0
  95. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/08-ide.md +0 -0
  96. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/09-ide-projectcreationinterface.md +0 -0
  97. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/README.md +0 -0
  98. {opalacoder-0.2.0 → opalacoder-0.2.2}/docs/specs/backlog_register.md +0 -0
  99. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/index.html +0 -0
  100. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/package-lock.json +0 -0
  101. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/package.json +0 -0
  102. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/src/main.jsx +0 -0
  103. {opalacoder-0.2.0 → opalacoder-0.2.2}/gui_src/vite.config.js +0 -0
  104. {opalacoder-0.2.0 → opalacoder-0.2.2}/main.py +0 -0
  105. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/__init__.py +0 -0
  106. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/agents.py +0 -0
  107. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/api_keys.py +0 -0
  108. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/archival.py +0 -0
  109. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/cli.py +0 -0
  110. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/code_index.py +0 -0
  111. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/config.py +0 -0
  112. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/embeddings.py +0 -0
  113. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/gui/assets/vendor-monaco-BkbU5OES.js +0 -0
  114. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/gui/assets/vendor-react-B2vUwPEE.js +0 -0
  115. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/gui/assets/vendor-xterm-3VOAfa_q.js +0 -0
  116. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/i18n.py +0 -0
  117. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/orchestrator.py +0 -0
  118. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/planner.py +0 -0
  119. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/plugins/__init__.py +0 -0
  120. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/plugins/html_css_js_tools.py +0 -0
  121. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/project.py +0 -0
  122. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/session.py +0 -0
  123. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/skills.py +0 -0
  124. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/structured.py +0 -0
  125. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/terminal.py +0 -0
  126. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/terminal_manager.py +0 -0
  127. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/tools.py +0 -0
  128. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/vcs.py +0 -0
  129. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/vector_index.py +0 -0
  130. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/workflow_orchestrator.py +0 -0
  131. {opalacoder-0.2.0 → opalacoder-0.2.2}/opalacoder/workflow_tools.py +0 -0
  132. {opalacoder-0.2.0 → opalacoder-0.2.2}/pytest.ini +0 -0
  133. {opalacoder-0.2.0 → opalacoder-0.2.2}/requirements.txt +0 -0
  134. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/chat-orchestrator/SKILL.md +0 -0
  135. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/command-line/SKILL.md +0 -0
  136. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/command-line/scripts/command_executor.py +0 -0
  137. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/skills_store/html-css-js/SKILL.md +0 -0
  138. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/skills_store/html-css-js/scripts/check_contracts.py +0 -0
  139. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/skills_store/implement-feature/SKILL.md +0 -0
  140. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/skills_store/implement-feature/scripts/run_workflow.py +0 -0
  141. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/view-editor/SKILL.md +0 -0
  142. {opalacoder-0.2.0 → opalacoder-0.2.2}/skills/view-editor/scripts/run_view_editor.py +0 -0
  143. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_agent_config.py +0 -0
  144. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_agent_stdin.py +0 -0
  145. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_code_index_integration.py +0 -0
  146. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_command_line_skill.py +0 -0
  147. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_complexity_evaluator.py +0 -0
  148. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_context_guard.py +0 -0
  149. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_file_delete.py +0 -0
  150. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_i18n_coverage.py +0 -0
  151. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_implement_feature_script.py +0 -0
  152. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_memgpt_runtime.py +0 -0
  153. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_model_commands.py +0 -0
  154. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_planner_oracle_live.py +0 -0
  155. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_planner_output.py +0 -0
  156. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_project_store.py +0 -0
  157. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_refinement_loop.py +0 -0
  158. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_search_bugs.py +0 -0
  159. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_skills_directory_loader.py +0 -0
  160. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_unhashable_dict_bug.py +0 -0
  161. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_verification_strategies.py +0 -0
  162. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_view_editor_skill.py +0 -0
  163. {opalacoder-0.2.0 → opalacoder-0.2.2}/tests/test_workflow_pipeline.py +0 -0
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opalacoder
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Autonomous coding agent with interactive planning and modular execution
5
5
  Project-URL: Homepage, https://github.com/gilzamir/OpalaCoder
6
6
  Project-URL: Bug Tracker, https://github.com/gilzamir/OpalaCoder/issues
@@ -141,7 +141,223 @@ De `build_parser` ([cli.py:419](../../opalacoder/cli.py#L419)):
141
141
 
142
142
  ---
143
143
 
144
- ## 5. Comandos do REPL
144
+ ## 5. AssetStore Repositório de Assets Reutilizáveis
145
+
146
+ > **Estado: IMPLEMENTADO**
147
+
148
+ ### Conceito
149
+
150
+ A AssetStore é um **repositório local de assets reutilizáveis** empacotados com o OpalaCoder. Ela serve como banco de dados de casos bem-sucedidos de configuração para modelos locais e como biblioteca de skills extras que podem ser instaladas sob demanda em qualquer projeto.
151
+
152
+ ```
153
+ opalacoder/assetstore/ ← instalado junto com o pacote pip
154
+ skills/
155
+ <ID>.zip ← árvore completa da skill
156
+ <ID>.metadata ← metadados YAML
157
+ modelconfigs/
158
+ <ID>.zip ← único arquivo .yaml de configuração
159
+ <ID>.metadata ← metadados YAML
160
+ ```
161
+
162
+ O caminho base é sempre `Path(__file__).parent / "assetstore"`, portanto funciona corretamente quando instalado via `pip install opalacoder` em qualquer máquina.
163
+
164
+ ### Formato dos metadados (`.metadata`)
165
+
166
+ Arquivo YAML com os campos:
167
+
168
+ | Campo | Obrigatório | Descrição |
169
+ |---|---|---|
170
+ | `id` | ✓ | Identificador único do asset (ex: `skill_implement_feature`) |
171
+ | `type` | ✓ | `skill` ou `modelconfig` |
172
+ | `desc` | ✓ | Descrição legível usada como critério de busca |
173
+ | `name` | skill | Nome da skill (igual ao diretório dentro do zip) |
174
+ | `model` | modelconfig | Identificador do modelo (ex: `ollama/gpt-oss:latest`) |
175
+
176
+ **Exemplo — skill:**
177
+ ```yaml
178
+ id: skill_implement_feature
179
+ type: skill
180
+ name: implement-feature
181
+ desc: Plan-Execute-Verify loop for implementing features and fixing bugs in project files
182
+ ```
183
+
184
+ **Exemplo — modelconfig:**
185
+ ```yaml
186
+ id: model_ollama_gpt_oss__latest
187
+ type: modelconfig
188
+ desc: gpt-oss:latest via Ollama — think=false, tool calling stable
189
+ model: ollama/gpt-oss:latest
190
+ ```
191
+
192
+ ### Destino de instalação
193
+
194
+ | Tipo | Destino no projeto |
195
+ |---|---|
196
+ | `skill` | `<projeto>/.opalacoder/skills/<name>/` |
197
+ | `modelconfig` | `<projeto>/.opalacoder/modelsconfig/<provider>/<modelo>.yaml` |
198
+
199
+ Para modelconfigs, o provider é normalizado: `ollama_chat/` e `ollama/` → diretório `ollama/`. Os dois pontos no nome do modelo viram `__`.
200
+
201
+ ### Comandos REPL
202
+
203
+ ```
204
+ /list_assets [tipo] — lista todos os assets (ou só do tipo especificado)
205
+ /load_asset <tipo> <desc|id|*> — instala asset(s) no projeto ativo
206
+ ```
207
+
208
+ `<desc>` pode ser o `id` exato ou o valor do campo `desc`. Usar `*` instala todos os assets do tipo.
209
+
210
+ **Exemplos:**
211
+ ```
212
+ /list_assets
213
+ /list_assets modelconfig
214
+ /load_asset skill skill_implement_feature
215
+ /load_asset modelconfig model_ollama_gpt_oss__latest
216
+ /load_asset skill *
217
+ ```
218
+
219
+ Após instalar uma skill, ativá-la no projeto com `/addskill <name>`.
220
+
221
+ ---
222
+
223
+ ## 5.1. Banco de Configurações Refinadas de Modelos
224
+
225
+ > **Estado: IMPLEMENTADO**
226
+
227
+ ### Conceito
228
+
229
+ O diretório `.opalacoder/modelsconfig/` dentro de cada projeto funciona como um
230
+ **banco de dados de casos bem-sucedidos de configuração para modelos locais**. Cada
231
+ arquivo YAML registra os parâmetros que produziram comportamento correto e estável
232
+ para um modelo específico naquele projeto — resultado de testes empíricos, não de
233
+ valores genéricos.
234
+
235
+ A motivação é que modelos locais (Ollama, LM Studio, etc.) exigem combinações
236
+ precisas de parâmetros para funcionar bem: `think` e `stream` podem quebrar
237
+ tool calling em certos modelos; `num_ctx` muito baixo degrada o raciocínio;
238
+ `temperature` alta pode destabilizar modelos de instrução. Esses parâmetros
239
+ **não são portáveis** — o que funciona para `deepseek-r1:14b` não funciona para
240
+ `ministral-3:14b`. O banco de configurações resolve isso por projeto.
241
+
242
+ ### Estrutura de diretórios
243
+
244
+ ```
245
+ <projeto>/
246
+ └── .opalacoder/
247
+ └── modelsconfig/
248
+ └── <provider>/
249
+ └── <nome_do_modelo>.yaml
250
+ ```
251
+
252
+ - **`<provider>`**: prefixo do modelo, com normalização — `ollama_chat/` e `ollama/`
253
+ mapeiam ambos para o diretório `ollama/`.
254
+ - **`<nome_do_modelo>.yaml`**: nome do modelo com `:` substituído por `__`
255
+ (hífens mantidos).
256
+
257
+ **Exemplos de mapeamento:**
258
+
259
+ | Modelo digitado | Diretório | Arquivo |
260
+ |---|---|---|
261
+ | `ollama/deepseek-r1:14b` | `ollama/` | `deepseek-r1__14b.yaml` |
262
+ | `ollama_chat/deepseek-r1:14b` | `ollama/` | `deepseek-r1__14b.yaml` |
263
+ | `ollama/ministral-3:14b` | `ollama/` | `ministral-3__14b.yaml` |
264
+ | `ollama/qwen3:14b` | `ollama/` | `qwen3__14b.yaml` |
265
+
266
+ ### Formato do arquivo YAML
267
+
268
+ O arquivo pode conter qualquer chave de `model_params` aceita pelo sistema, mais
269
+ uma chave especial `provider`:
270
+
271
+ ```yaml
272
+ # .opalacoder/modelsconfig/ollama/deepseek-r1__14b.yaml
273
+ # Configuração refinada para DeepSeek-R1 14B via Ollama
274
+ # Obtida empiricamente — funciona com tool calling e thinking em tempo real
275
+
276
+ provider: ollama_chat # opcional: substitui o prefixo do modelo na sessão
277
+ # aqui muda de ollama/ para ollama_chat/ para habilitar
278
+ # o endpoint nativo do Ollama com thinking por chunk
279
+
280
+ # LiteLLM / model_kwargs
281
+ think: true
282
+ stream: true
283
+ temperature: 0.6
284
+ num_ctx: 32768
285
+ max_tokens: 8192
286
+
287
+ # Parâmetros do agente
288
+ max_heartbeats: 15
289
+ ```
290
+
291
+ ```yaml
292
+ # .opalacoder/modelsconfig/ollama/ministral-3__14b.yaml
293
+ # Ministral 3 14B — sem thinking (não suportado), tool calling estável
294
+
295
+ think: false
296
+ stream: false
297
+ temperature: 0.7
298
+ num_ctx: 16384
299
+ max_tokens: 8128
300
+ max_heartbeats: 20
301
+ ```
302
+
303
+ **Chave `provider`** (opcional): quando presente, substitui o prefixo do modelo
304
+ na interface. Útil para forçar `ollama_chat/` em modelos com suporte a thinking
305
+ nativo, sem o usuário precisar digitar o prefixo correto.
306
+
307
+ ### Como usar
308
+
309
+ Na janela de **criação** ou **configuração** de projeto, após definir o modelo,
310
+ clique em **Load Refined Config**:
311
+
312
+ - Se existir um arquivo para o modelo: carrega e substitui completamente o
313
+ `model_params` do projeto. Se o YAML tiver `provider:`, atualiza o campo
314
+ modelo com o novo prefixo.
315
+ - Se não existir: exibe `--- ainda não temos parâmetros refinados para este modelo`.
316
+
317
+ O backend resolve o arquivo via `GET/POST /api/opalacoder/model-config`
318
+ ([ide_server.py](../../opalacoder/ide_server.py)).
319
+
320
+ ### Critério para adicionar uma entrada
321
+
322
+ Uma entrada deve ser adicionada ao banco quando:
323
+
324
+ 1. O modelo foi testado com a configuração e **tool calling funciona** sem erros.
325
+ 2. O comportamento de **thinking/reflection** (se aplicável) está correto.
326
+ 3. Os valores de **contexto e tokens** são adequados ao hardware disponível.
327
+
328
+ O arquivo é mantido manualmente pelo usuário/equipe e versionado junto com o
329
+ projeto. Não é gerado automaticamente.
330
+
331
+ ### Exemplo completo — `gpt-oss:latest` (Ollama)
332
+
333
+ ```yaml
334
+ # .opalacoder/modelsconfig/ollama/gpt-oss__latest.yaml
335
+ #
336
+ # Configuração refinada para gpt-oss:latest via Ollama
337
+ # Testado em: 2026-06-02 | Hardware: RTX 4090 24GB
338
+ # Status: tool calling OK, thinking OK, stream OK
339
+ #
340
+ provider: ollama_chat # endpoint nativo: thinking por chunk em tempo real
341
+
342
+ # LiteLLM / model_kwargs
343
+ think: false # desativar thinking evita bug de tool_calls vazio (ollama #15288)
344
+ stream: false # stream false: mais estável com tool calling
345
+ temperature: 0.1 # baixo para respostas determinísticas em tarefas de código
346
+ num_ctx: 32768 # contexto amplo para projetos grandes
347
+ max_tokens: 8192
348
+
349
+ # Parâmetros do agente MemGPT (chat-orquestrador)
350
+ max_heartbeats: 20
351
+ max_context_tokens: 32768
352
+
353
+ # Parâmetros do agente LLMAgentBlock (workers)
354
+ max_tool_calls: 10
355
+ debug: false
356
+ ```
357
+
358
+ ---
359
+
360
+ ## 6. Comandos do REPL
145
361
 
146
362
  Registrados em [cli_commands.py](../../opalacoder/cli_commands.py):
147
363
 
@@ -75,6 +75,7 @@ export default function App() {
75
75
  const [thinkingLogs, setThinkingLogs] = useState([]);
76
76
  const [showAdvancedParams, setShowAdvancedParams] = useState(false);
77
77
  const [newProjError, setNewProjError] = useState('');
78
+ const [modelConfigMsg, setModelConfigMsg] = useState('');
78
79
 
79
80
  // Directory picker state: { target: 'new'|'edit', current: '/path', dirs: [{name,path}] } | null
80
81
  const [dirPicker, setDirPicker] = useState(null);
@@ -182,6 +183,7 @@ export default function App() {
182
183
  const [newProjDesc, setNewProjDesc] = useState('');
183
184
  const [newProjModel, setNewProjModel] = useState('gemini/gemini-2.5-flash');
184
185
  const [newProjMode, setNewProjMode] = useState('auto');
186
+ const [newProjModelParams, setNewProjModelParams] = useState({});
185
187
  const [newProjApiKey, setNewProjApiKey] = useState('');
186
188
  const [newProjApiBase, setNewProjApiBase] = useState('http://localhost:11434/v1');
187
189
 
@@ -980,7 +982,8 @@ export default function App() {
980
982
  model: newProjModel,
981
983
  mode: newProjMode,
982
984
  api_key: newProjApiKey,
983
- api_base: newProjApiBase
985
+ api_base: newProjApiBase,
986
+ model_params: Object.keys(newProjModelParams).length ? newProjModelParams : undefined
984
987
  })
985
988
  });
986
989
  if (res.ok) {
@@ -1035,6 +1038,7 @@ export default function App() {
1035
1038
  if (found) fresh = found;
1036
1039
  }
1037
1040
  } catch (_) { }
1041
+ setModelConfigMsg('');
1038
1042
  setEditingProject({
1039
1043
  name: fresh.name,
1040
1044
  project_name: fresh.project_name || fresh.name,
@@ -1087,6 +1091,30 @@ export default function App() {
1087
1091
  }
1088
1092
  };
1089
1093
 
1094
+ const loadModelConfig = async (projectPath, model, applyFn) => {
1095
+ setModelConfigMsg('');
1096
+ if (!projectPath || !model) {
1097
+ setModelConfigMsg('⚠️ Defina o caminho do projeto e o modelo antes de carregar.');
1098
+ return;
1099
+ }
1100
+ try {
1101
+ const res = await fetch('/api/opalacoder/model-config', {
1102
+ method: 'POST',
1103
+ headers: { 'Content-Type': 'application/json' },
1104
+ body: JSON.stringify({ projectPath, model }),
1105
+ });
1106
+ const data = await res.json();
1107
+ if (!data.found) {
1108
+ setModelConfigMsg(data.message);
1109
+ return;
1110
+ }
1111
+ applyFn(data);
1112
+ setModelConfigMsg('✅ Configuração refinada carregada.');
1113
+ } catch (e) {
1114
+ setModelConfigMsg(`⚠️ Erro ao carregar: ${e.message}`);
1115
+ }
1116
+ };
1117
+
1090
1118
  const openDirPicker = async (target, startPath) => {
1091
1119
  const path = startPath || '~';
1092
1120
  try {
@@ -1194,6 +1222,7 @@ export default function App() {
1194
1222
  setChatMessages(prev => [...prev, { role: 'assistant', content: `🔴 Falha: ${err.message}` }]);
1195
1223
  } finally {
1196
1224
  setIsAgentRunning(false);
1225
+ fetchFiles();
1197
1226
  }
1198
1227
  return;
1199
1228
  }
@@ -1474,7 +1503,7 @@ export default function App() {
1474
1503
  <div className="vscode-sidebar-header">
1475
1504
  <span className="vscode-sidebar-title">EXPLORER: PROJECTS</span>
1476
1505
  <button
1477
- onClick={() => setShowNewProjectModal(true)}
1506
+ onClick={() => { setShowNewProjectModal(true); setModelConfigMsg(''); setNewProjModelParams({}); }}
1478
1507
  style={{ background: 'transparent', border: 'none', cursor: 'pointer', color: '#c5c5c5' }}
1479
1508
  title="Novo Projeto..."
1480
1509
  >
@@ -2194,6 +2223,22 @@ export default function App() {
2194
2223
  </div>
2195
2224
  </div>
2196
2225
 
2226
+ {/* Load refined model config */}
2227
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
2228
+ <button type="button" className="vscode-button" style={{ background: '#3c3c3c', fontSize: '12px' }}
2229
+ onClick={() => loadModelConfig(newProjPath, newProjModel, (cfg) => {
2230
+ if (cfg.model_params) setNewProjModelParams(cfg.model_params);
2231
+ if (cfg.model) setNewProjModel(cfg.model);
2232
+ })}>
2233
+ Load Refined Config
2234
+ </button>
2235
+ {modelConfigMsg && (
2236
+ <span style={{ fontSize: '11px', color: modelConfigMsg.startsWith('✅') ? '#4ec9b0' : '#f48771' }}>
2237
+ {modelConfigMsg}
2238
+ </span>
2239
+ )}
2240
+ </div>
2241
+
2197
2242
  {newProjError && (
2198
2243
  <div style={{ color: '#f48771', fontSize: '11px', marginTop: '4px', whiteSpace: 'pre-wrap' }}>
2199
2244
  ⚠️ {newProjError}
@@ -2359,6 +2404,25 @@ export default function App() {
2359
2404
  </div>
2360
2405
  </div>
2361
2406
 
2407
+ {/* Load refined model config */}
2408
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>
2409
+ <button type="button" className="vscode-button" style={{ background: '#3c3c3c', fontSize: '12px' }}
2410
+ onClick={() => loadModelConfig(editingProject.project_path, editingProject.model, (cfg) => {
2411
+ setEditingProject(p => ({
2412
+ ...p,
2413
+ model_params: cfg.model_params || p.model_params,
2414
+ model: cfg.model || p.model,
2415
+ }));
2416
+ })}>
2417
+ Load Refined Config
2418
+ </button>
2419
+ {modelConfigMsg && (
2420
+ <span style={{ fontSize: '11px', color: modelConfigMsg.startsWith('✅') ? '#4ec9b0' : '#f48771' }}>
2421
+ {modelConfigMsg}
2422
+ </span>
2423
+ )}
2424
+ </div>
2425
+
2362
2426
  {/* Alternative model */}
2363
2427
  <div className="flex flex-col" style={{ gap: '4px' }}>
2364
2428
  <label className="vscode-sidebar-section-title" style={{ padding: 0 }}>Modelo Alternativo</label>
@@ -92,6 +92,11 @@ input, select, textarea {
92
92
  outline: none;
93
93
  }
94
94
 
95
+ select option {
96
+ background-color: var(--vscode-input-bg);
97
+ color: var(--vscode-text-fg);
98
+ }
99
+
95
100
  input:focus, select:focus, textarea:focus {
96
101
  border-color: var(--vscode-active-border);
97
102
  }
Binary file
@@ -286,11 +286,22 @@ async def handle_slash_command(data: dict) -> dict:
286
286
  T.confirm = lambda p, default=True: default # sync fallback never used in GUI
287
287
  T.ask = lambda p: ""
288
288
 
289
+ def _render_rich(obj) -> str:
290
+ from rich.console import Console as _Console
291
+ from rich.table import Table as _Table
292
+ from io import StringIO
293
+ if isinstance(obj, _Table):
294
+ buf = StringIO()
295
+ c = _Console(file=buf, highlight=False, no_color=True)
296
+ c.print(obj)
297
+ return buf.getvalue()
298
+ return str(obj)
299
+
289
300
  def _console_print(*args_c, **kwargs_c):
290
301
  if not args_c:
291
302
  messages.append("")
292
303
  return
293
- raw = " ".join(str(x) for x in args_c)
304
+ raw = " ".join(_render_rich(x) for x in args_c)
294
305
  for line in raw.split("\n"):
295
306
  stripped = line.strip()
296
307
  if not stripped:
@@ -302,6 +313,9 @@ async def handle_slash_command(data: dict) -> dict:
302
313
  messages.append("### 🧠 Active skills for this project\n"); continue
303
314
  if "Available skills" in stripped:
304
315
  messages.append("### 📚 Available skills\n"); continue
316
+ clean = re.sub(r'\[/?[\w\s]+\]', '', stripped)
317
+ if clean and clean == clean.upper() and clean.replace(" ", "").isalpha():
318
+ messages.append(f"### {clean}\n"); continue
305
319
  m = re.match(r'\s*(?:\*\s*)?\[(green|cyan)\]\s*(.*?)\s*\[/\1\]\s*(.*)', line)
306
320
  if m:
307
321
  name = m.group(2).strip()
@@ -316,7 +330,7 @@ async def handle_slash_command(data: dict) -> dict:
316
330
  if sm:
317
331
  star = "⭐ " if has_star else "🔹 "
318
332
  messages.append(f"{star}**`{sm.group(1).strip()}`** — {sm.group(2).strip()}"); continue
319
- messages.append(re.sub(r'\[/?\w+\]', '', line))
333
+ messages.append(re.sub(r'\[/?[\w\s]+\]', '', line))
320
334
 
321
335
  T.console.print = _console_print
322
336
 
@@ -0,0 +1,4 @@
1
+ id: gpt_oss__latest
2
+ type: modelconfig
3
+ desc: "GPT OSS latest model via Ollama"
4
+ model: ollama/gpt-oss:latest
@@ -0,0 +1,4 @@
1
+ id: skill_html_css_js
2
+ type: skill
3
+ name: html-css-js
4
+ desc: Best practice rules and contract detector for vanilla HTML/CSS/JavaScript projects
@@ -0,0 +1,4 @@
1
+ id: skill_implement_feature
2
+ type: skill
3
+ name: implement-feature
4
+ desc: Plan-Execute-Verify loop for implementing features and fixing bugs in project files
@@ -0,0 +1,179 @@
1
+ """AssetStore — local repository of reusable assets (skills and model configs).
2
+
3
+ Structure
4
+ ---------
5
+ opalacoder/assetstore/
6
+ skills/
7
+ <ID>.zip — full skill directory tree
8
+ <ID>.metadata — YAML: id, type, name, desc
9
+ modelconfigs/
10
+ <ID>.zip — single YAML file for the model config
11
+ <ID>.metadata — YAML: id, type, desc, model
12
+
13
+ Installation targets (relative to project root)
14
+ ---------
15
+ skill → <project>/.opalacoder/skills/<name>/
16
+ modelconfig → <project>/.opalacoder/modelsconfig/<provider>/<model_file>.yaml
17
+ """
18
+
19
+ import os
20
+ import zipfile
21
+ from pathlib import Path
22
+ from typing import Optional
23
+
24
+ import yaml
25
+
26
+ # Root of the assetstore bundled with the package
27
+ _STORE_ROOT = Path(__file__).parent / "assetstore"
28
+
29
+ VALID_TYPES = {"skill", "modelconfig"}
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Internal helpers
34
+ # ---------------------------------------------------------------------------
35
+
36
+ def _store_dir(asset_type: str) -> Path:
37
+ return _STORE_ROOT / (asset_type + "s") # skills/ or modelconfigs/
38
+
39
+
40
+ def _parse_metadata(path: Path) -> dict:
41
+ with open(path, "r", encoding="utf-8") as f:
42
+ return yaml.safe_load(f) or {}
43
+
44
+
45
+ def _iter_assets(asset_type: str) -> list[dict]:
46
+ """Return list of metadata dicts for all assets of the given type."""
47
+ d = _store_dir(asset_type)
48
+ if not d.exists():
49
+ return []
50
+ results = []
51
+ for meta_file in sorted(d.glob("*.metadata")):
52
+ try:
53
+ meta = _parse_metadata(meta_file)
54
+ meta["_zip"] = meta_file.with_suffix(".zip")
55
+ meta["_meta"] = meta_file
56
+ results.append(meta)
57
+ except Exception:
58
+ continue
59
+ return results
60
+
61
+
62
+ def _match(meta: dict, desc: str) -> bool:
63
+ """True if desc matches the asset's id or desc field (case-insensitive)."""
64
+ desc_l = desc.lower()
65
+ return (
66
+ meta.get("id", "").lower() == desc_l
67
+ or meta.get("desc", "").lower() == desc_l
68
+ )
69
+
70
+
71
+ def _model_to_path(model: str) -> tuple[str, str]:
72
+ """'ollama/gpt-oss:latest' → ('ollama', 'gpt-oss__latest.yaml')"""
73
+ _ALIASES = {"ollama_chat": "ollama"}
74
+ if "/" in model:
75
+ raw_provider, model_name = model.split("/", 1)
76
+ else:
77
+ raw_provider, model_name = "", model
78
+ provider = _ALIASES.get(raw_provider, raw_provider)
79
+ filename = model_name.replace(":", "__") + ".yaml"
80
+ return provider, filename
81
+
82
+
83
+ # ---------------------------------------------------------------------------
84
+ # Public API
85
+ # ---------------------------------------------------------------------------
86
+
87
+ def list_assets(asset_type: Optional[str] = None) -> list[dict]:
88
+ """Return all assets, optionally filtered by type."""
89
+ types = [asset_type] if asset_type else list(VALID_TYPES)
90
+ result = []
91
+ for t in types:
92
+ result.extend(_iter_assets(t))
93
+ return result
94
+
95
+
96
+ def find_assets(asset_type: str, desc: str) -> list[dict]:
97
+ """Return matching assets. desc='*' returns all of the type."""
98
+ assets = _iter_assets(asset_type)
99
+ if desc == "*":
100
+ return assets
101
+ return [a for a in assets if _match(a, desc)]
102
+
103
+
104
+ def install_asset(meta: dict, project_path: str) -> str:
105
+ """Extract asset zip into the correct location inside project_path.
106
+
107
+ Returns a human-readable summary of what was installed.
108
+ """
109
+ zip_path: Path = meta["_zip"]
110
+ if not zip_path.exists():
111
+ raise FileNotFoundError(f"Zip not found: {zip_path}")
112
+
113
+ asset_type = meta.get("type", "")
114
+ project = Path(os.path.abspath(project_path))
115
+
116
+ if asset_type == "skill":
117
+ dest = project / ".opalacoder" / "skills"
118
+ dest.mkdir(parents=True, exist_ok=True)
119
+ with zipfile.ZipFile(zip_path, "r") as zf:
120
+ zf.extractall(dest)
121
+ skill_name = meta.get("name", zip_path.stem)
122
+ return f"skill '{skill_name}' installed at {dest / skill_name}"
123
+
124
+ elif asset_type == "modelconfig":
125
+ model = meta.get("model", "")
126
+ if not model:
127
+ raise ValueError(f"Asset {meta.get('id')} has no 'model' field in metadata")
128
+ provider, filename = _model_to_path(model)
129
+ dest_dir = project / ".opalacoder" / "modelsconfig" / provider
130
+ dest_dir.mkdir(parents=True, exist_ok=True)
131
+ with zipfile.ZipFile(zip_path, "r") as zf:
132
+ names = zf.namelist()
133
+ if len(names) != 1:
134
+ raise ValueError(f"modelconfig zip must contain exactly one file, got: {names}")
135
+ zf.extractall(dest_dir)
136
+ # Rename to canonical model filename
137
+ extracted = dest_dir / names[0]
138
+ target = dest_dir / filename
139
+ if extracted != target:
140
+ extracted.rename(target)
141
+ return f"modelconfig for '{model}' installed at {target}"
142
+
143
+ else:
144
+ raise ValueError(f"Unknown asset type '{asset_type}'")
145
+
146
+
147
+ def register_asset(asset_type: str, source_path: str, metadata: dict) -> Path:
148
+ """Package a local directory/file as an asset and register it in the store.
149
+
150
+ source_path: directory (for skill) or .yaml file (for modelconfig)
151
+ metadata: dict with at least id, type, desc, and name or model
152
+ Returns the path of the created zip.
153
+ """
154
+ if asset_type not in VALID_TYPES:
155
+ raise ValueError(f"type must be one of {VALID_TYPES}")
156
+
157
+ asset_id = metadata.get("id")
158
+ if not asset_id:
159
+ raise ValueError("metadata must have an 'id' field")
160
+
161
+ store_dir = _store_dir(asset_type)
162
+ store_dir.mkdir(parents=True, exist_ok=True)
163
+
164
+ zip_path = store_dir / f"{asset_id}.zip"
165
+ meta_path = store_dir / f"{asset_id}.metadata"
166
+
167
+ source = Path(source_path)
168
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
169
+ if source.is_dir():
170
+ for f in sorted(source.rglob("*")):
171
+ if f.is_file() and "__pycache__" not in str(f) and not f.name.endswith(".pyc"):
172
+ zf.write(f, f.relative_to(source.parent))
173
+ else:
174
+ zf.write(source, source.name)
175
+
176
+ with open(meta_path, "w", encoding="utf-8") as f:
177
+ yaml.dump(metadata, f, allow_unicode=True, default_flow_style=False)
178
+
179
+ return zip_path