agent-notes 2.20.0__tar.gz → 2.21.0__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.
- {agent_notes-2.20.0 → agent_notes-2.21.0}/PKG-INFO +1 -1
- agent_notes-2.21.0/agent_notes/VERSION +1 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/cli.py +1 -1
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/memory.py +132 -0
- agent_notes-2.21.0/agent_notes/data/skills/ingest/SKILL.md +73 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/wiki_backend.py +190 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/PKG-INFO +1 -1
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/SOURCES.txt +1 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_wiki_backend.py +225 -0
- agent_notes-2.20.0/agent_notes/VERSION +0 -1
- {agent_notes-2.20.0 → agent_notes-2.21.0}/LICENSE +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/README.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/__main__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/_install_helpers.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/build.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/config.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/doctor.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/info.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/install.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/list.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/regenerate.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/set_role.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/uninstall.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/validate.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/commands/wizard.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/config.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/agents.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/analyst.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/api-reviewer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/architect.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/coder.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/database-specialist.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/debugger.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/devil.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/devops.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/explorer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/integrations.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/lead.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/performance-profiler.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/refactorer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/reviewer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/security-auditor.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/cost_reporting.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/execution.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/guardrails.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/hard_limits.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/phase0.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/pipelines.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/review.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/shared/verification.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/system-auditor.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/tech-writer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/test-runner.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/agents/test-writer.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/cli/claude.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/cli/copilot.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/cli/opencode.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/commands/brainstorm.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/commands/debug.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/commands/review.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/global-claude.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/global-copilot.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/global-opencode.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/hooks/session-context.md.tpl +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-haiku-4-5.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-opus-4-1.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-opus-4-5.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-opus-4-6.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-opus-4-7.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-sonnet-4-5.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-sonnet-4-6.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/models/claude-sonnet-4.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/plugin/claude.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/plugin/opencode-index.js.template +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/plugin/opencode.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/pricing.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/roles/orchestrator.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/roles/reasoner.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/roles/scout.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/roles/worker.yaml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/rules/code-quality.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/rules/safety.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/brainstorming/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/caveman/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/code-review/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/debugging-protocol/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/docker/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/docker/compose.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/docker/dockerfile.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/git/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/grill-me/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/grill-with-docs/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/improve-codebase-architecture/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/obsidian-memory/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/controllers.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/frontend.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/infra.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/models.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/testing.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/rails/views.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/refactoring-protocol/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/setup-project-context/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/tdd/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/skills/zoom-out/SKILL.md +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/claude.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/data/templates/frontmatter/opencode.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/doctor_checks.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/agent.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/cli_backend.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/diagnostics.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/diff.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/model.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/role.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/rule.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/skill.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/domain/state.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/install_state.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/_base.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/agent_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/cli_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/model_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/role_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/rule_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/registries/skill_registry.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/_claude_backend.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/_formatting.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/_opencode_backend.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/_pricing.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/scripts/cost_report.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/counts.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/credentials.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/diagnostics/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/diagnostics/_checks.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/diagnostics/_display.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/diagnostics/_fix.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/diff.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/fs.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/install_state_builder.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/installer.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/memory_backend.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/rendering.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/session_context.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/settings_writer.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/state_store.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/ui.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/user_config.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/services/validation.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes/state.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/dependency_links.txt +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/entry_points.txt +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/requires.txt +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/agent_notes.egg-info/top_level.txt +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/pyproject.toml +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/setup.cfg +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/conftest.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_config_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_doctor_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_info_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_install_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_list_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_regenerate_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_uninstall_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/commands/test_validate_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/memory/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/memory/test_memory_command.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/scripts/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/functional/scripts/test_release_script.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/build_output/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/build_output/test_build_output.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/install/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/install/test_install_methods.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/plugin_builders/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/integration/plugin_builders/test_plugin_builders.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/plugins/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/plugins/claude/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/plugins/claude/test_agents.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/plugins/test_skills.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/test_cost_report_subcommand.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/test_count_agents.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/test_memory_migrate.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/test_wizard_orchestrator_skip.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/commands/test_wizard_preflight.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/registries/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/registries/test_registries.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/scripts/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/scripts/test_cost_report.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/scripts/test_cost_report_scoping.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/scripts/test_formatting_tty.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/scripts/test_time_aggregation.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/__init__.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_build_functions.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_credentials.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_fs.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_installer_plan.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_memory_backend.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_memory_backend_io.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_rendering_includes.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_session_context.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/services/test_settings_writer.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/test_import_health.py +0 -0
- {agent_notes-2.20.0 → agent_notes-2.21.0}/tests/unit/test_memory_dir_for_backend.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.21.0
|
|
@@ -273,7 +273,7 @@ def main():
|
|
|
273
273
|
# memory
|
|
274
274
|
p_memory = subparsers.add_parser("memory", help="Manage agent memory")
|
|
275
275
|
p_memory.add_argument("action", nargs="?", default="list",
|
|
276
|
-
choices=["init", "list", "vault", "index", "add", "size", "show", "reset", "export", "import", "ingest", "query", "lint"],
|
|
276
|
+
choices=["init", "list", "vault", "index", "add", "size", "show", "reset", "export", "import", "ingest", "ingest-file", "ingest-url", "ingest-folder", "query", "lint"],
|
|
277
277
|
help="Memory action")
|
|
278
278
|
p_memory.add_argument("name", nargs="?", help="Agent name / note title (for show/reset/add)")
|
|
279
279
|
p_memory.add_argument("extra", nargs="*", help="Additional args (for add: body [type] [agent] [project])")
|
|
@@ -508,6 +508,102 @@ def do_ingest(title: str, body: str, concepts: Optional[list] = None, entities:
|
|
|
508
508
|
print(f" entity: {p}")
|
|
509
509
|
|
|
510
510
|
|
|
511
|
+
def do_ingest_file(file_path: str, title: str = "", body: str = "",
|
|
512
|
+
concepts: Optional[list] = None, entities: Optional[list] = None,
|
|
513
|
+
tags: Optional[list] = None) -> None:
|
|
514
|
+
"""Ingest a local file into the wiki backend."""
|
|
515
|
+
backend, path = _load_memory_config()
|
|
516
|
+
if backend != "wiki":
|
|
517
|
+
print("The `ingest-file` subcommand is only available for the wiki backend.")
|
|
518
|
+
return
|
|
519
|
+
if path is None:
|
|
520
|
+
print("Memory path not configured.")
|
|
521
|
+
return
|
|
522
|
+
fp = Path(file_path)
|
|
523
|
+
if not fp.exists():
|
|
524
|
+
print(f"Error: file not found: {file_path}")
|
|
525
|
+
exit(1)
|
|
526
|
+
from ..services.wiki_backend import wiki_ingest_file
|
|
527
|
+
result = wiki_ingest_file(
|
|
528
|
+
path,
|
|
529
|
+
file_path=fp,
|
|
530
|
+
title=title,
|
|
531
|
+
body=body,
|
|
532
|
+
concepts=concepts or [],
|
|
533
|
+
entities=entities or [],
|
|
534
|
+
tags=tags or [],
|
|
535
|
+
)
|
|
536
|
+
_print_ingest_result(result)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def do_ingest_url(url: str, title: str = "", body: str = "",
|
|
540
|
+
concepts: Optional[list] = None, entities: Optional[list] = None,
|
|
541
|
+
tags: Optional[list] = None) -> None:
|
|
542
|
+
"""Ingest a URL into the wiki backend."""
|
|
543
|
+
backend, path = _load_memory_config()
|
|
544
|
+
if backend != "wiki":
|
|
545
|
+
print("The `ingest-url` subcommand is only available for the wiki backend.")
|
|
546
|
+
return
|
|
547
|
+
if path is None:
|
|
548
|
+
print("Memory path not configured.")
|
|
549
|
+
return
|
|
550
|
+
from ..services.wiki_backend import wiki_ingest_url
|
|
551
|
+
result = wiki_ingest_url(
|
|
552
|
+
path,
|
|
553
|
+
url=url,
|
|
554
|
+
title=title,
|
|
555
|
+
body=body,
|
|
556
|
+
concepts=concepts or [],
|
|
557
|
+
entities=entities or [],
|
|
558
|
+
tags=tags or [],
|
|
559
|
+
)
|
|
560
|
+
_print_ingest_result(result)
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def do_ingest_folder(folder_path: str, title: str = "", body: str = "",
|
|
564
|
+
concepts: Optional[list] = None, entities: Optional[list] = None,
|
|
565
|
+
tags: Optional[list] = None) -> None:
|
|
566
|
+
"""Ingest a local folder into the wiki backend."""
|
|
567
|
+
backend, path = _load_memory_config()
|
|
568
|
+
if backend != "wiki":
|
|
569
|
+
print("The `ingest-folder` subcommand is only available for the wiki backend.")
|
|
570
|
+
return
|
|
571
|
+
if path is None:
|
|
572
|
+
print("Memory path not configured.")
|
|
573
|
+
return
|
|
574
|
+
fp = Path(folder_path)
|
|
575
|
+
if not fp.exists():
|
|
576
|
+
print(f"Error: folder not found: {folder_path}")
|
|
577
|
+
exit(1)
|
|
578
|
+
from ..services.wiki_backend import wiki_ingest_folder
|
|
579
|
+
result = wiki_ingest_folder(
|
|
580
|
+
path,
|
|
581
|
+
folder_path=fp,
|
|
582
|
+
title=title,
|
|
583
|
+
body=body,
|
|
584
|
+
concepts=concepts or [],
|
|
585
|
+
entities=entities or [],
|
|
586
|
+
tags=tags or [],
|
|
587
|
+
)
|
|
588
|
+
_print_ingest_result(result)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _print_ingest_result(result: dict) -> None:
|
|
592
|
+
source_paths = result.get("source", [])
|
|
593
|
+
concept_paths = result.get("concepts", [])
|
|
594
|
+
entity_paths = result.get("entities", [])
|
|
595
|
+
title = ""
|
|
596
|
+
if source_paths:
|
|
597
|
+
title = source_paths[0].stem.replace("-", " ").title()
|
|
598
|
+
print(f"{Color.GREEN}Ingested: {title}{Color.NC}")
|
|
599
|
+
for p in source_paths:
|
|
600
|
+
print(f" source: {p}")
|
|
601
|
+
for p in concept_paths:
|
|
602
|
+
print(f" concept: {p}")
|
|
603
|
+
for p in entity_paths:
|
|
604
|
+
print(f" entity: {p}")
|
|
605
|
+
|
|
606
|
+
|
|
511
607
|
def do_query(keyword: str) -> None:
|
|
512
608
|
"""Search wiki pages by keyword."""
|
|
513
609
|
backend, path = _load_memory_config()
|
|
@@ -796,6 +892,42 @@ def memory(action: str = "list", name: Optional[str] = None, extra: Optional[lis
|
|
|
796
892
|
entities = [e.strip() for e in entities_csv.split(",") if e.strip()] if entities_csv else None
|
|
797
893
|
tags = [t.strip() for t in tags_csv.split(",") if t.strip()] if tags_csv else None
|
|
798
894
|
do_ingest(name, body, concepts=concepts, entities=entities, tags=tags)
|
|
895
|
+
elif action == "ingest-file":
|
|
896
|
+
if not name:
|
|
897
|
+
print("Error: ingest-file requires a file path.")
|
|
898
|
+
exit(1)
|
|
899
|
+
body = extra[0] if extra else ""
|
|
900
|
+
concepts_csv = extra[1] if extra and len(extra) > 1 else ""
|
|
901
|
+
entities_csv = extra[2] if extra and len(extra) > 2 else ""
|
|
902
|
+
tags_csv = extra[3] if extra and len(extra) > 3 else ""
|
|
903
|
+
concepts = [c.strip() for c in concepts_csv.split(",") if c.strip()] if concepts_csv else None
|
|
904
|
+
entities = [e.strip() for e in entities_csv.split(",") if e.strip()] if entities_csv else None
|
|
905
|
+
tags = [t.strip() for t in tags_csv.split(",") if t.strip()] if tags_csv else None
|
|
906
|
+
do_ingest_file(name, body=body, concepts=concepts, entities=entities, tags=tags)
|
|
907
|
+
elif action == "ingest-url":
|
|
908
|
+
if not name:
|
|
909
|
+
print("Error: ingest-url requires a URL.")
|
|
910
|
+
exit(1)
|
|
911
|
+
body = extra[0] if extra else ""
|
|
912
|
+
concepts_csv = extra[1] if extra and len(extra) > 1 else ""
|
|
913
|
+
entities_csv = extra[2] if extra and len(extra) > 2 else ""
|
|
914
|
+
tags_csv = extra[3] if extra and len(extra) > 3 else ""
|
|
915
|
+
concepts = [c.strip() for c in concepts_csv.split(",") if c.strip()] if concepts_csv else None
|
|
916
|
+
entities = [e.strip() for e in entities_csv.split(",") if e.strip()] if entities_csv else None
|
|
917
|
+
tags = [t.strip() for t in tags_csv.split(",") if t.strip()] if tags_csv else None
|
|
918
|
+
do_ingest_url(name, body=body, concepts=concepts, entities=entities, tags=tags)
|
|
919
|
+
elif action == "ingest-folder":
|
|
920
|
+
if not name:
|
|
921
|
+
print("Error: ingest-folder requires a folder path.")
|
|
922
|
+
exit(1)
|
|
923
|
+
body = extra[0] if extra else ""
|
|
924
|
+
concepts_csv = extra[1] if extra and len(extra) > 1 else ""
|
|
925
|
+
entities_csv = extra[2] if extra and len(extra) > 2 else ""
|
|
926
|
+
tags_csv = extra[3] if extra and len(extra) > 3 else ""
|
|
927
|
+
concepts = [c.strip() for c in concepts_csv.split(",") if c.strip()] if concepts_csv else None
|
|
928
|
+
entities = [e.strip() for e in entities_csv.split(",") if e.strip()] if entities_csv else None
|
|
929
|
+
tags = [t.strip() for t in tags_csv.split(",") if t.strip()] if tags_csv else None
|
|
930
|
+
do_ingest_folder(name, body=body, concepts=concepts, entities=entities, tags=tags)
|
|
799
931
|
elif action == "query":
|
|
800
932
|
if not name:
|
|
801
933
|
print("Error: query requires a keyword.")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ingest
|
|
3
|
+
description: "Ingest a URL, local file, or folder into the wiki brain. Fetches content, extracts concepts and entities with AI analysis, and stores in the wiki. Use when user says 'ingest', provides a URL to study, or wants to add external knowledge."
|
|
4
|
+
group: memory
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ingest
|
|
8
|
+
|
|
9
|
+
Ingest external sources into the wiki brain for persistent, queryable knowledge.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
The user provides one of:
|
|
14
|
+
- A **URL** (starts with `http://` or `https://`)
|
|
15
|
+
- A **file path** (path to a single file)
|
|
16
|
+
- A **folder path** (path to a directory)
|
|
17
|
+
|
|
18
|
+
## Workflow
|
|
19
|
+
|
|
20
|
+
### Step 1 — Fetch the source
|
|
21
|
+
|
|
22
|
+
| Source type | How to read |
|
|
23
|
+
|---|---|
|
|
24
|
+
| URL | Use `WebFetch` tool to retrieve the page content |
|
|
25
|
+
| File | Use `Read` tool to read the file |
|
|
26
|
+
| Folder | Use `Bash` to list files (`find <path> -type f`), then `Read` key files. Skip: `__pycache__`, `.git`, `node_modules`, `.venv`, `dist`, `build`, `.egg-info` |
|
|
27
|
+
|
|
28
|
+
### Step 2 — AI analysis
|
|
29
|
+
|
|
30
|
+
Analyze the content and extract:
|
|
31
|
+
|
|
32
|
+
1. **Title** — a concise, descriptive name for this source
|
|
33
|
+
2. **Summary** — 2-5 sentence overview of what this source contains and why it matters
|
|
34
|
+
3. **Concepts** — key ideas, patterns, techniques, or abstractions (e.g., "dependency injection", "event sourcing", "fan-out pattern")
|
|
35
|
+
4. **Entities** — specific named things: tools, libraries, people, projects, APIs (e.g., "PostgreSQL", "Karpathy", "wiki_backend.py")
|
|
36
|
+
5. **Tags** — categorization labels (e.g., "python", "architecture", "api")
|
|
37
|
+
|
|
38
|
+
### Step 3 — Ingest via CLI
|
|
39
|
+
|
|
40
|
+
Call the appropriate command:
|
|
41
|
+
|
|
42
|
+
**For a URL:**
|
|
43
|
+
```bash
|
|
44
|
+
agent-notes memory ingest-url "<url>" "<summary>" "<concepts_csv>" "<entities_csv>" "<tags_csv>"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**For a file:**
|
|
48
|
+
```bash
|
|
49
|
+
agent-notes memory ingest-file "<file_path>" "<summary>" "<concepts_csv>" "<entities_csv>" "<tags_csv>"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**For a folder:**
|
|
53
|
+
```bash
|
|
54
|
+
agent-notes memory ingest-folder "<folder_path>" "<summary>" "<concepts_csv>" "<entities_csv>" "<tags_csv>"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The CLI archives the raw content and creates the wiki source page. The AI-extracted concepts and entities are fanned out into their own wiki pages with cross-references.
|
|
58
|
+
|
|
59
|
+
### Step 4 — Report
|
|
60
|
+
|
|
61
|
+
After ingestion, report to the user:
|
|
62
|
+
- What was ingested (title, source type)
|
|
63
|
+
- Key concepts and entities extracted
|
|
64
|
+
- Number of wiki pages created/updated
|
|
65
|
+
|
|
66
|
+
## Example
|
|
67
|
+
|
|
68
|
+
User: `/ingest https://karpathy.github.io/2023/01/20/llm-wiki/`
|
|
69
|
+
|
|
70
|
+
1. Fetch URL with WebFetch
|
|
71
|
+
2. Analyze: Title="LLM Wiki by Karpathy", Summary="Proposes using LLMs to maintain personal knowledge wikis...", Concepts=["LLM Wiki", "knowledge management", "fan-out pattern"], Entities=["Andrej Karpathy"], Tags=["ai", "knowledge-management"]
|
|
72
|
+
3. Run: `agent-notes memory ingest-url "https://karpathy.github.io/2023/01/20/llm-wiki/" "Proposes using LLMs to maintain personal knowledge wikis..." "LLM Wiki,knowledge management,fan-out pattern" "Andrej Karpathy" "ai,knowledge-management"`
|
|
73
|
+
4. Report results
|
|
@@ -215,6 +215,196 @@ def wiki_ingest(
|
|
|
215
215
|
return result
|
|
216
216
|
|
|
217
217
|
|
|
218
|
+
# ── Ingest from file ──────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
def wiki_ingest_file(
|
|
221
|
+
wiki_root: Path,
|
|
222
|
+
*,
|
|
223
|
+
file_path: Path,
|
|
224
|
+
title: str = "",
|
|
225
|
+
body: str = "",
|
|
226
|
+
concepts: list[str] | None = None,
|
|
227
|
+
entities: list[str] | None = None,
|
|
228
|
+
tags: list[str] | None = None,
|
|
229
|
+
) -> dict[str, list[Path]]:
|
|
230
|
+
"""Ingest a local file into the wiki. Reads content, derives title, delegates to wiki_ingest."""
|
|
231
|
+
if not file_path.is_file():
|
|
232
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
233
|
+
raw_content = file_path.read_text(errors="replace")
|
|
234
|
+
if not title:
|
|
235
|
+
title = file_path.stem.replace("-", " ").replace("_", " ").title()
|
|
236
|
+
if not body:
|
|
237
|
+
body = f"Ingested from local file: {file_path}"
|
|
238
|
+
return wiki_ingest(
|
|
239
|
+
wiki_root,
|
|
240
|
+
title=title,
|
|
241
|
+
body=body,
|
|
242
|
+
raw_content=raw_content,
|
|
243
|
+
raw_filename=file_path.name,
|
|
244
|
+
concepts=concepts,
|
|
245
|
+
entities=entities,
|
|
246
|
+
tags=tags,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
# ── Ingest from folder ────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
_SKIP_DIRS = {"__pycache__", ".git", "node_modules", ".venv", "dist", "build"}
|
|
253
|
+
_DEFAULT_EXTENSIONS = {".py", ".md", ".yaml", ".yml", ".toml", ".json", ".txt", ".rs", ".ts", ".js", ".rb", ".go", ".java"}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _parse_gitignore_patterns(gitignore_path: Path) -> list[str]:
|
|
257
|
+
"""Return non-empty, non-comment patterns from a .gitignore file."""
|
|
258
|
+
patterns = []
|
|
259
|
+
for line in gitignore_path.read_text(errors="replace").splitlines():
|
|
260
|
+
line = line.rstrip()
|
|
261
|
+
if line and not line.startswith("#"):
|
|
262
|
+
patterns.append(line)
|
|
263
|
+
return patterns
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _matches_gitignore(rel_path: str, patterns: list[str]) -> bool:
|
|
267
|
+
"""Return True if rel_path matches any gitignore pattern (simplified fnmatch)."""
|
|
268
|
+
import fnmatch
|
|
269
|
+
parts = rel_path.replace("\\", "/").split("/")
|
|
270
|
+
name = parts[-1]
|
|
271
|
+
for pattern in patterns:
|
|
272
|
+
# Strip leading slash for anchored patterns — treat as simple fnmatch
|
|
273
|
+
clean = pattern.lstrip("/")
|
|
274
|
+
if not clean:
|
|
275
|
+
continue
|
|
276
|
+
if fnmatch.fnmatch(name, clean):
|
|
277
|
+
return True
|
|
278
|
+
if fnmatch.fnmatch(rel_path, clean):
|
|
279
|
+
return True
|
|
280
|
+
if fnmatch.fnmatch(rel_path, f"**/{clean}"):
|
|
281
|
+
return True
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def wiki_ingest_folder(
|
|
286
|
+
wiki_root: Path,
|
|
287
|
+
*,
|
|
288
|
+
folder_path: Path,
|
|
289
|
+
title: str = "",
|
|
290
|
+
body: str = "",
|
|
291
|
+
concepts: list[str] | None = None,
|
|
292
|
+
entities: list[str] | None = None,
|
|
293
|
+
tags: list[str] | None = None,
|
|
294
|
+
extensions: list[str] | None = None,
|
|
295
|
+
respect_gitignore: bool = True,
|
|
296
|
+
) -> dict[str, list[Path]]:
|
|
297
|
+
"""Ingest a local folder recursively into the wiki. Concatenates file contents."""
|
|
298
|
+
if not folder_path.is_dir():
|
|
299
|
+
raise FileNotFoundError(f"Folder not found: {folder_path}")
|
|
300
|
+
allowed_exts = set(extensions) if extensions is not None else _DEFAULT_EXTENSIONS
|
|
301
|
+
|
|
302
|
+
gitignore_patterns: list[str] = []
|
|
303
|
+
if respect_gitignore:
|
|
304
|
+
gitignore_path = folder_path / ".gitignore"
|
|
305
|
+
if gitignore_path.exists():
|
|
306
|
+
gitignore_patterns = _parse_gitignore_patterns(gitignore_path)
|
|
307
|
+
|
|
308
|
+
parts: list[str] = []
|
|
309
|
+
file_count = 0
|
|
310
|
+
|
|
311
|
+
for file in sorted(folder_path.rglob("*")):
|
|
312
|
+
if not file.is_file():
|
|
313
|
+
continue
|
|
314
|
+
# Skip junk directories
|
|
315
|
+
if any(skip in file.parts for skip in _SKIP_DIRS):
|
|
316
|
+
continue
|
|
317
|
+
# Skip files with egg-info in path
|
|
318
|
+
if any(part.endswith(".egg-info") for part in file.parts):
|
|
319
|
+
continue
|
|
320
|
+
# Extension filter
|
|
321
|
+
if file.suffix not in allowed_exts:
|
|
322
|
+
continue
|
|
323
|
+
# Gitignore filter
|
|
324
|
+
rel = str(file.relative_to(folder_path))
|
|
325
|
+
if gitignore_patterns and _matches_gitignore(rel, gitignore_patterns):
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
content = file.read_text(errors="replace")
|
|
330
|
+
except OSError:
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
parts.append(f"\n\n--- FILE: {rel} ---\n\n{content}\n")
|
|
334
|
+
file_count += 1
|
|
335
|
+
|
|
336
|
+
raw_content = "".join(parts).lstrip("\n")
|
|
337
|
+
|
|
338
|
+
if not title:
|
|
339
|
+
title = folder_path.name.replace("-", " ").replace("_", " ").title()
|
|
340
|
+
if not body:
|
|
341
|
+
body = f"Ingested from local folder: {folder_path} ({file_count} files)"
|
|
342
|
+
|
|
343
|
+
raw_filename = f"{_slug(title)}-folder.md"
|
|
344
|
+
|
|
345
|
+
return wiki_ingest(
|
|
346
|
+
wiki_root,
|
|
347
|
+
title=title,
|
|
348
|
+
body=body,
|
|
349
|
+
raw_content=raw_content,
|
|
350
|
+
raw_filename=raw_filename,
|
|
351
|
+
concepts=concepts,
|
|
352
|
+
entities=entities,
|
|
353
|
+
tags=tags,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# ── Ingest from URL ───────────────────────────────────────────────────────────
|
|
358
|
+
|
|
359
|
+
def wiki_ingest_url(
|
|
360
|
+
wiki_root: Path,
|
|
361
|
+
*,
|
|
362
|
+
url: str,
|
|
363
|
+
title: str = "",
|
|
364
|
+
body: str = "",
|
|
365
|
+
concepts: list[str] | None = None,
|
|
366
|
+
entities: list[str] | None = None,
|
|
367
|
+
tags: list[str] | None = None,
|
|
368
|
+
) -> dict[str, list[Path]]:
|
|
369
|
+
"""Fetch a URL and ingest its content into the wiki."""
|
|
370
|
+
import urllib.request
|
|
371
|
+
import urllib.error
|
|
372
|
+
from urllib.parse import urlparse
|
|
373
|
+
|
|
374
|
+
with urllib.request.urlopen(url) as response:
|
|
375
|
+
raw_bytes = response.read()
|
|
376
|
+
try:
|
|
377
|
+
raw_content = raw_bytes.decode("utf-8")
|
|
378
|
+
except UnicodeDecodeError:
|
|
379
|
+
raw_content = raw_bytes.decode("latin-1")
|
|
380
|
+
|
|
381
|
+
if not title:
|
|
382
|
+
m = re.search(r"<title[^>]*>([^<]+)</title>", raw_content, re.IGNORECASE)
|
|
383
|
+
if m:
|
|
384
|
+
title = m.group(1).strip()
|
|
385
|
+
else:
|
|
386
|
+
parsed = urlparse(url)
|
|
387
|
+
title = (parsed.hostname or "") + (parsed.path.rstrip("/") or "")
|
|
388
|
+
|
|
389
|
+
if not body:
|
|
390
|
+
body = f"Ingested from URL: {url}"
|
|
391
|
+
|
|
392
|
+
parsed = urlparse(url)
|
|
393
|
+
url_slug = _slug((parsed.hostname or "") + "-" + parsed.path.strip("/").replace("/", "-"))
|
|
394
|
+
raw_filename = f"{url_slug}.html"
|
|
395
|
+
|
|
396
|
+
return wiki_ingest(
|
|
397
|
+
wiki_root,
|
|
398
|
+
title=title,
|
|
399
|
+
body=body,
|
|
400
|
+
raw_content=raw_content,
|
|
401
|
+
raw_filename=raw_filename,
|
|
402
|
+
concepts=concepts,
|
|
403
|
+
entities=entities,
|
|
404
|
+
tags=tags,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
|
|
218
408
|
# ── Query ─────────────────────────────────────────────────────────────────────
|
|
219
409
|
|
|
220
410
|
def wiki_query(wiki_root: Path, keyword: str) -> list[dict]:
|
|
@@ -96,6 +96,7 @@ agent_notes/data/skills/git/SKILL.md
|
|
|
96
96
|
agent_notes/data/skills/grill-me/SKILL.md
|
|
97
97
|
agent_notes/data/skills/grill-with-docs/SKILL.md
|
|
98
98
|
agent_notes/data/skills/improve-codebase-architecture/SKILL.md
|
|
99
|
+
agent_notes/data/skills/ingest/SKILL.md
|
|
99
100
|
agent_notes/data/skills/obsidian-memory/SKILL.md
|
|
100
101
|
agent_notes/data/skills/rails/SKILL.md
|
|
101
102
|
agent_notes/data/skills/rails/controllers.md
|