Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions skills/find-literature/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ Verify installation with `asta literature --help`

Run in background for comprehensive searches (30-60s):
```bash
# Saves to .asta/literature/find/YYYY-MM-DD-HH-MM-SS-{query-slug}.json by default
Bash(command="asta literature find 'query' --timeout 300", run_in_background=true)
# Save to a temporary file with explicit -o parameter (required)
Bash(command="asta literature find 'query' -o /tmp/literature-search-result.json --timeout 300", run_in_background=true)
```
The command saves results to `.asta/literature/find/` (in current working directory) by default with an auto-generated filename.
The filename is printed to the console when the search is finished. Use the TaskOutput tool to capture the filename for later processing

After the file has been created, index it using the [asta-documents](../asta-documents/SKILL.md) skill.
After the search completes, move the file to `.asta/documents/literature/find/` and index it:

```bash
Bash(command="asta documents add file://<filename> --name=... --summary=...")
# Create directory if it doesn't exist
mkdir -p .asta/documents/literature/find

# Move the result file to the documents directory
mv /tmp/literature-search-result.json .asta/documents/literature/find/

# Index the file using asta-documents
Bash(command="asta documents add file://.asta/documents/literature/find/literature-search-result.json --name='Literature Search: <query>' --summary='Search results for: <query>'")
```

Browse results with jq:
Expand Down
35 changes: 17 additions & 18 deletions src/asta/literature/find.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Find papers command"""

import json
import re
from datetime import datetime
from pathlib import Path

import click
Expand All @@ -13,6 +11,13 @@

@click.command()
@click.argument("query")
@click.option(
"-o",
"--output",
type=click.Path(),
required=True,
help="Output file path for the results (required)",
)
@click.option(
"--timeout",
type=int,
Expand All @@ -25,24 +30,24 @@
default="infer",
help="Search strategy: infer (auto-detect), fast (quick results), or diligent (comprehensive)",
)
def find(query: str, timeout: int, mode: str):
def find(query: str, output: str, timeout: int, mode: str):
"""Find papers matching QUERY using Asta Paper Finder.

Saves results to .asta/literature/find/ with an auto-generated filename.
Requires an output file path to save results.

Examples:

# Save to default location
asta literature find "machine learning in healthcare"
# Save to specific file
asta literature find "machine learning in healthcare" -o results.json

# With custom timeout
asta literature find "transformers" --timeout 60
asta literature find "transformers" -o results.json --timeout 60

# Use fast mode for quick results
asta literature find "deep learning" --mode fast
asta literature find "deep learning" -o results.json --mode fast

# Use diligent mode for comprehensive search
asta literature find "neural networks" --mode diligent
asta literature find "neural networks" -o results.json --mode diligent
"""
try:
client = AstaPaperFinder()
Expand All @@ -58,15 +63,9 @@ def find(query: str, timeout: int, mode: str):
# Convert to dict for output
output_data = literature_result.model_dump(mode="json", exclude_none=False)

# Generate default path: .asta/literature/find/YYYY-MM-DD-HH-MM-SS-query-slug.json
default_dir = Path.cwd() / ".asta" / "literature" / "find"
default_dir.mkdir(parents=True, exist_ok=True)

timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
# Create a slug from the query (lowercase, alphanumeric and hyphens only, max 50 chars)
query_slug = re.sub(r"[^a-z0-9]+", "-", query.lower()).strip("-")[:50]
filename = f"{timestamp}-{query_slug}.json"
output_path = default_dir / filename
# Use the specified output path
output_path = Path(output)
output_path.parent.mkdir(parents=True, exist_ok=True)

# Save to file
with open(output_path, "w") as f:
Expand Down
6 changes: 3 additions & 3 deletions src/asta/utils/passthrough.conf
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
passthrough {
documents {
tool_name = "asta-documents"
install_type = "git"
install_source = "git+ssh://git@github.com/allenai/asta-resource-repo.git"
minimum_version = "0.2.0"
install_type = "pypi"
install_source = "asta-resource-repository"
minimum_version = "0.3.0"
command_name = "documents"
friendly_name = "asta-documents"
docstring = "Manage a local library of documents known to Asta"
Expand Down
72 changes: 47 additions & 25 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,30 @@ def test_find_success_file_output(self, runner, tmp_path):
}

with patch("asta.literature.find.AstaPaperFinder") as MockFinder:
with patch("asta.literature.find.Path.cwd", return_value=tmp_path):
mock_instance = MagicMock()
mock_instance.find_papers.return_value = mock_result
MockFinder.return_value = mock_instance
mock_instance = MagicMock()
mock_instance.find_papers.return_value = mock_result
MockFinder.return_value = mock_instance

result = runner.invoke(cli, ["literature", "find", "test query"])
output_file = tmp_path / "results.json"
result = runner.invoke(
cli, ["literature", "find", "test query", "-o", str(output_file)]
)

assert result.exit_code == 0

# Verify file was created in default location
output_dir = tmp_path / ".asta" / "literature" / "find"
assert output_dir.exists()

# Find the created file (should match pattern: YYYY-MM-DD-HH-MM-SS-test-query.json)
json_files = list(output_dir.glob("*-test-query.json"))
assert len(json_files) == 1
# Verify file was created at specified location
assert output_file.exists()

# Verify file contents
with open(json_files[0]) as f:
with open(output_file) as f:
data = json.load(f)

assert data["query"] == "test query"
assert len(data["results"]) == 2
assert data["results"][0]["corpusId"] == 123
assert data["results"][1]["corpusId"] == 456

def test_find_timeout_error(self, runner):
def test_find_timeout_error(self, runner, tmp_path):
"""Test find command with timeout error."""
with patch("asta.literature.find.AstaPaperFinder") as MockFinder:
mock_instance = MagicMock()
Expand All @@ -95,22 +92,28 @@ def test_find_timeout_error(self, runner):
)
MockFinder.return_value = mock_instance

result = runner.invoke(cli, ["literature", "find", "test query"])
output_file = tmp_path / "results.json"
result = runner.invoke(
cli, ["literature", "find", "test query", "-o", str(output_file)]
)

assert result.exit_code == 2

def test_find_general_error(self, runner):
def test_find_general_error(self, runner, tmp_path):
"""Test find command with general error."""
with patch("asta.literature.find.AstaPaperFinder") as MockFinder:
mock_instance = MagicMock()
mock_instance.find_papers.side_effect = Exception("API error")
MockFinder.return_value = mock_instance

result = runner.invoke(cli, ["literature", "find", "test query"])
output_file = tmp_path / "results.json"
result = runner.invoke(
cli, ["literature", "find", "test query", "-o", str(output_file)]
)

assert result.exit_code == 1

def test_find_custom_timeout(self, runner):
def test_find_custom_timeout(self, runner, tmp_path):
"""Test find command with custom timeout."""
mock_result = {
"query": "test query",
Expand All @@ -134,16 +137,26 @@ def test_find_custom_timeout(self, runner):
mock_instance.find_papers.return_value = mock_result
MockFinder.return_value = mock_instance

output_file = tmp_path / "results.json"
result = runner.invoke(
cli, ["literature", "find", "test query", "--timeout", "60"]
cli,
[
"literature",
"find",
"test query",
"-o",
str(output_file),
"--timeout",
"60",
],
)

assert result.exit_code == 0
mock_instance.find_papers.assert_called_once_with(
"test query", timeout=60, save_to_file=None, operation_mode="infer"
)

def test_find_with_mode_option(self, runner):
def test_find_with_mode_option(self, runner, tmp_path):
"""Test find command with different operation modes."""
mock_result = {
"query": "test query",
Expand All @@ -166,9 +179,19 @@ def test_find_with_mode_option(self, runner):
mock_instance.find_papers.return_value = mock_result
MockFinder.return_value = mock_instance

output_file = tmp_path / "results.json"
# Test fast mode
result = runner.invoke(
cli, ["literature", "find", "test query", "--mode", "fast"]
cli,
[
"literature",
"find",
"test query",
"-o",
str(output_file),
"--mode",
"fast",
],
)

assert result.exit_code == 0
Expand Down Expand Up @@ -413,11 +436,10 @@ def test_documents_config(self):

# Should have required fields
assert config["tool_name"] == "asta-documents"
assert "install_type" in config
assert config["install_type"] in ("pypi", "git", "local")
assert "minimum_version" in config
assert config["install_type"] == "pypi"
assert config["install_source"] == "asta-resource-repository"
assert config["minimum_version"] == "0.3.0"
assert validate_semver(config["minimum_version"])
assert "install_source" in config
assert config["command_name"] == "documents"

def test_documents_help_requires_installation(self, runner):
Expand Down
Loading