askmycode 0.1.0__tar.gz → 0.1.1__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.
@@ -19,7 +19,7 @@ jobs:
19
19
  - name: Checkout PR branch
20
20
  uses: actions/checkout@v4
21
21
  with:
22
- ref: ${{ github.event.pull_request.head.sha }}
22
+ ref: refs/pull/${{ github.event.issue.number }}/head
23
23
  fetch-depth: 0
24
24
 
25
25
  - name: Set up Python
@@ -37,8 +37,9 @@ jobs:
37
37
 
38
38
  - name: Extract question from comment
39
39
  id: question
40
+ env:
41
+ COMMENT: ${{ github.event.comment.body }}
40
42
  run: |
41
- COMMENT="${{ github.event.comment.body }}"
42
43
  QUESTION="${COMMENT#/ask }"
43
44
  echo "question=$QUESTION" >> "$GITHUB_OUTPUT"
44
45
 
@@ -46,8 +47,9 @@ jobs:
46
47
  id: answer
47
48
  env:
48
49
  GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
50
+ QUESTION: ${{ steps.question.outputs.question }}
49
51
  run: |
50
- ANSWER=$(askmycode ask "${{ steps.question.outputs.question }}" --sources)
52
+ ANSWER=$(askmycode ask "$QUESTION" --sources)
51
53
  # Store multi-line output safely
52
54
  {
53
55
  echo "answer<<EOF"
@@ -57,10 +59,13 @@ jobs:
57
59
 
58
60
  - name: Post answer as PR comment
59
61
  uses: actions/github-script@v7
62
+ env:
63
+ QUESTION: ${{ steps.question.outputs.question }}
64
+ ANSWER: ${{ steps.answer.outputs.answer }}
60
65
  with:
61
66
  script: |
62
- const question = `${{ steps.question.outputs.question }}`;
63
- const answer = `${{ steps.answer.outputs.answer }}`;
67
+ const question = process.env.QUESTION;
68
+ const answer = process.env.ANSWER;
64
69
  const body = [
65
70
  `**Q: ${question}**`,
66
71
  "",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: askmycode
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Ask questions about any codebase using AI — your code stays private
5
5
  Project-URL: Homepage, https://github.com/NewtYao/askmycode
6
6
  Project-URL: Issues, https://github.com/NewtYao/askmycode/issues
@@ -0,0 +1 @@
1
+ __version__ = "0.1.1"
@@ -6,8 +6,6 @@ from google.genai.errors import ClientError, ServerError
6
6
  from rich.console import Console
7
7
  from rich.progress import Progress, SpinnerColumn, TextColumn
8
8
 
9
- from google.genai import types as genai_types
10
-
11
9
  from askmycode import config, indexer, llm, manifest, project_config, retriever, store
12
10
 
13
11
  app = typer.Typer(
@@ -108,6 +106,9 @@ def ask(
108
106
  except ServerError as e:
109
107
  console.print(f"\n[red]Gemini server error:[/red] {e}")
110
108
  raise typer.Exit(1)
109
+ except Exception as e:
110
+ console.print(f"\n[red]Error:[/red] {e}")
111
+ raise typer.Exit(1)
111
112
 
112
113
  if sources and metadatas:
113
114
  seen: set = set()
@@ -202,7 +203,7 @@ def chat(
202
203
 
203
204
  console.print("[bold]askmycode chat[/bold] — type your question, [dim]exit[/dim] or Ctrl+C to quit.\n")
204
205
 
205
- history: list[genai_types.Content] = []
206
+ history: list[dict] = []
206
207
  file_tree = llm.build_file_tree(list(manifest.load(project).keys()))
207
208
 
208
209
  while True:
@@ -238,11 +239,14 @@ def chat(
238
239
  except (ClientError, ServerError) as e:
239
240
  console.print(f"\n[red]Error:[/red] {e}\n")
240
241
  continue
242
+ except Exception as e:
243
+ console.print(f"\n[red]Error:[/red] {e}\n")
244
+ continue
241
245
 
242
246
  # Append this turn to history for follow-up context
243
247
  user_turn = f"Context:\n{context}\n\nQuestion: {question}"
244
- history.append(llm._make_turn("user", user_turn))
245
- history.append(llm._make_turn("model", "".join(answer_parts)))
248
+ history.append({"role": "user", "content": user_turn})
249
+ history.append({"role": "model", "content": "".join(answer_parts)})
246
250
 
247
251
 
248
252
  @app.command()
@@ -282,10 +286,12 @@ app.add_typer(_config_app, name="config")
282
286
  @_config_app.command("set-key")
283
287
  def config_set_key(
284
288
  api_key: str = typer.Argument(..., help="Your API key"),
289
+ provider: str = typer.Option("", "--provider", "-p", help="Provider this key is for (default: current provider)"),
285
290
  ) -> None:
286
291
  """Save your API key to ~/.config/askmycode/config.json."""
287
- config.set_api_key(api_key)
288
- console.print("[green]API key saved.[/green]")
292
+ prov = provider or config.get_provider()
293
+ config.set_api_key(api_key, prov)
294
+ console.print(f"[green]API key saved for provider '{prov}'.[/green]")
289
295
 
290
296
 
291
297
  @_config_app.command("set-provider")
@@ -27,12 +27,14 @@ def get_api_key(provider: str = "gemini") -> str | None:
27
27
  env_var = _ENV_KEYS.get(provider, f"{provider.upper()}_API_KEY")
28
28
  if key := os.environ.get(env_var):
29
29
  return key
30
- return _load().get("api_key")
30
+ data = _load()
31
+ # per-provider store, fallback to legacy "api_key" for backwards compat
32
+ return data.get("api_keys", {}).get(provider) or data.get("api_key")
31
33
 
32
34
 
33
- def set_api_key(key: str) -> None:
35
+ def set_api_key(key: str, provider: str = "gemini") -> None:
34
36
  data = _load()
35
- data["api_key"] = key
37
+ data.setdefault("api_keys", {})[provider] = key
36
38
  _save(data)
37
39
 
38
40
 
@@ -56,9 +56,8 @@ def _ast_chunks(content: str, lines: list[str], relative: str) -> list[dict] | N
56
56
  return None
57
57
 
58
58
  top_nodes = [
59
- n for n in ast.walk(tree)
59
+ n for n in tree.body
60
60
  if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
61
- and isinstance(getattr(n, "col_offset", -1), int) and n.col_offset == 0
62
61
  ]
63
62
  if not top_nodes:
64
63
  return None
@@ -2,6 +2,9 @@ from typing import Iterator
2
2
  from google import genai
3
3
  from google.genai import types
4
4
 
5
+ _GEMINI_ROLE = {"user": "user", "model": "model", "assistant": "model"}
6
+ _OPENAI_ROLE = {"user": "user", "model": "assistant", "assistant": "assistant"}
7
+
5
8
  _SYSTEM = """\
6
9
  You are a helpful assistant that answers questions about a software codebase.
7
10
  You are given relevant code snippets retrieved from the codebase and a question.
@@ -41,13 +44,16 @@ def _stream_gemini(
41
44
  question: str,
42
45
  context: str,
43
46
  api_key: str,
44
- history: list[types.Content] | None,
47
+ history: list[dict] | None,
45
48
  file_tree: str,
46
49
  model: str,
47
50
  ) -> Iterator[str]:
48
51
  client = genai.Client(api_key=api_key)
49
52
  user_turn = _build_user_turn(question, context, file_tree)
50
- contents = list(history or []) + [_make_turn("user", user_turn)]
53
+ contents = [
54
+ _make_turn(_GEMINI_ROLE.get(h["role"], h["role"]), h["content"])
55
+ for h in (history or [])
56
+ ] + [_make_turn("user", user_turn)]
51
57
  for chunk in client.models.generate_content_stream(
52
58
  model=model,
53
59
  contents=contents,
@@ -72,7 +78,7 @@ def _stream_openai(
72
78
  user_turn = _build_user_turn(question, context, file_tree)
73
79
  messages = [{"role": "system", "content": _SYSTEM}]
74
80
  for h in history or []:
75
- messages.append({"role": h["role"], "content": h["content"]})
81
+ messages.append({"role": _OPENAI_ROLE.get(h["role"], h["role"]), "content": h["content"]})
76
82
  messages.append({"role": "user", "content": user_turn})
77
83
  with client.chat.completions.create(model=model, messages=messages, stream=True) as stream:
78
84
  for chunk in stream:
@@ -96,7 +102,7 @@ def _stream_anthropic(
96
102
  user_turn = _build_user_turn(question, context, file_tree)
97
103
  messages = []
98
104
  for h in history or []:
99
- messages.append({"role": h["role"], "content": h["content"]})
105
+ messages.append({"role": _OPENAI_ROLE.get(h["role"], h["role"]), "content": h["content"]})
100
106
  messages.append({"role": "user", "content": user_turn})
101
107
  with client.messages.stream(
102
108
  model=model,
@@ -122,7 +128,7 @@ def _stream_ollama(
122
128
  user_turn = _build_user_turn(question, context, file_tree)
123
129
  messages = [{"role": "system", "content": _SYSTEM}]
124
130
  for h in history or []:
125
- messages.append({"role": h["role"], "content": h["content"]})
131
+ messages.append({"role": _OPENAI_ROLE.get(h["role"], h["role"]), "content": h["content"]})
126
132
  messages.append({"role": "user", "content": user_turn})
127
133
  resp = requests.post(
128
134
  f"{base_url}/api/chat",
@@ -145,7 +151,7 @@ def answer_stream(
145
151
  question: str,
146
152
  context: str,
147
153
  api_key: str,
148
- history=None,
154
+ history: list[dict] | None = None,
149
155
  file_tree: str = "",
150
156
  provider: str = "gemini",
151
157
  model: str = "",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "askmycode"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Ask questions about any codebase using AI — your code stays private"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes