# dialoghelper.utils


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

``` python
from dialoghelper import *
```

``` python
from fastcore import tools
from fastcore.test import *
```

## Helpers

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L58"
target="_blank" style="float:right; font-size:smaller">source</a>

### add_styles

``` python

def add_styles(
    s:str, cls_map:dict=None
):

```

*Add solveit styles to `s`*

</div>

``` python
import mistletoe
from fasthtml.common import show
```

``` python
s = mistletoe.markdown("### hi\n\n- first\n- *second*")
s
```

    '<h3>hi</h3>\n<ul>\n<li>first</li>\n<li><em>second</em></li>\n</ul>\n'

``` python
show(s)
```

<h3>hi</h3>
<ul>
<li>first</li>
<li><em>second</em></li>
</ul>

``` python
show(add_styles(s))
```

<h3 class="uk-h3">hi</h3>
<ul class="uk-list uk-list-bullet space-y-0">
<li class="leading-tight">first</li>
<li class="leading-tight"><em>second</em></li>
</ul>

## ast-grep

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L63"
target="_blank" style="float:right; font-size:smaller">source</a>

### ast_py

``` python

def ast_py(
    code:str
):

```

*Get an SgRoot root node for python `code`*

</div>

``` python
node = ast_py("print('hello world')")
stmt = node.find(pattern="print($A)")
res = stmt.get_match('A')
res.text(),res.range()
```

    ("'hello world'",
     Range(start=Pos(line=0, col=6, index=6), end=Pos(line=0, col=19, index=19)))

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L69"
target="_blank" style="float:right; font-size:smaller">source</a>

### ast_grep

``` python

def ast_grep(
    pattern:str, # ast-grep pattern to search, e.g "post($A, data=$B, $$$)"
    path:str='.', # path to recursively search for files
    lang:str='python', # language to search/scan
):

```

*Use `ast-grep` to find code patterns by AST structure (not text).*

Pattern syntax: - $VAR captures single nodes,
$$$ captures multiple
- Match structure directly: \`def $FUNC($$
$)`finds any function;`class
$CLASS`finds classes regardless of inheritance - DON'T include`:\` -
it’s concrete syntax, not AST structure - Whitespace/formatting
ignored - matches structural equivalence

Examples: `import $MODULE` (find imports); `$OBJ.$METHOD($$$)` (find
method calls); `await $EXPR` (find await expressions)

Useful for: Refactoring—find all uses of deprecated APIs or changed
signatures; Security review—locate SQL queries, file operations, eval
calls; Code exploration—understand how libraries are used across
codebase; Pattern analysis—find async functions, error handlers,
decorators; Better than regex—handles multi-line code, nested
structures, respects syntax

</div>

The
[`ast_grep`](https://AnswerDotAI.github.io/dialoghelper/utils.html#ast_grep)
function calls the `ast-grep` CLI, which is used for searching code
based on its structure rather than just text patterns. Unlike regular
expressions that match character sequences, `ast-grep` understands the
syntax of programming languages and lets you search for code patterns in
a way that respects the language’s grammar. This means you can find
function calls, variable assignments, or other code constructs even when
they’re formatted differently or have varying amounts of whitespace.

The key advantage is using metavariables (like `$A`, `$B`, `$$$`) as
placeholders in your search patterns. When you search for
`xpost($A, data=$B, $$$)`, you’re asking to find all calls to `xpost`
where the first argument can be anything (captured as `$A`), there’s a
keyword argument `data` with any value (captured as `$B`), and there may
be additional arguments after that (the `$$$` matches zero or more
remaining arguments). This is much more reliable than trying to write a
regex that handles all the variations of how that function might be
called.

In the example below, we search for calls to `xpost` in the parent
directory and extract both the matched code and the specific values of
our metavariables, showing us exactly where and how this function is
being used in the codebase.

``` python
res = ast_grep(r"xpost($A, data=$B, $$$)", '..')
[(o['text'],o['metaVariables']['single'],o['file']) for o in res]
```

    [('xpost(url, data=data, headers=headers, timeout=timeout)',
      {'A': {'text': 'url',
        'range': {'byteOffset': {'start': 5029, 'end': 5032},
         'start': {'line': 111, 'column': 30},
         'end': {'line': 111, 'column': 33}}},
       'B': {'text': 'data',
        'range': {'byteOffset': {'start': 5039, 'end': 5043},
         'start': {'line': 111, 'column': 40},
         'end': {'line': 111, 'column': 44}}}},
      'dialoghelper/core.py')]

**Basic Patterns:** - Match code structure directly:
`console.log($ARG)` - Metavariables capture parts: `$VAR` (single),
`$$$` (multiple) - Patterns match AST structure, not text -
whitespace/formatting doesn’t matter

**The Colon Issue:** - **Don’t include `:` in patterns** - it’s part of
Python’s concrete syntax, not the AST structure - ✅ `def $FUNC($$$)` -
matches function definitions - ❌ `def $FUNC($$$):` - too specific,
looking for the colon token itself

**When to use `kind` vs `pattern`:** - `pattern`: Simple direct matches
(`await $EXPR`) - `kind`: Structural node types
(`kind: function_declaration`)

**Critical rule for relational searches:** Always add `stopBy: end` to
`has`/`inside` rules to search the entire subtree:

``` yaml
has:
  pattern: await $EXPR
  stopBy: end
```

**Escaping in shell:** Use `\$VAR` or single quotes when using
`--inline-rules` from command line

``` python
_ast_id = await add_msg("print('hello')\nprint('world')\nlog('keep')", msg_type='code')
```

``` python
print(await msg_ast_replace(_ast_id, 'print($A)', 'logger.info($A)'))
```

    @@ -1,3 +1,3 @@
    -print('hello')
    -print('world')
    +logger.info('hello')
    +logger.info('world')
     log('keep')

``` python
print((await read_msg(n=0, id=_ast_id, nums=True))['content'])
```

         1 │ logger.info('hello')
         2 │ logger.info('world')
         3 │ log('keep')

``` python
await del_msg(_ast_id)
```

    {'status': 'success'}

## Context

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L118"
target="_blank" style="float:right; font-size:smaller">source</a>

### ctx_folder

``` python

async def ctx_folder(
    path:Path='.', # Path to collect
    types:str | list='py,doc', # list or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
    out:bool=False, # Include notebook cell outputs?
    raw:bool=True, # Add raw message, or note?
    exts:str | list=None, # list or comma-separated str of exts to include (overrides `types`)
    prefix:bool=False, # Include Anthropic's suggested prose intro?
    include_base:bool=True, # Include full path in src?
    title:str=None, # Optional title attr for Documents element
    max_size:int=100000, # Skip files larger than this (bytes)
    max_total:int=10000000, # Max total output size in bytes
    readme_first:bool=False, # Prioritize README files at start of context?
    files_only:bool=False, # Return dict of {filename: size} instead of context?
    sigs_only:bool=False, # Return signatures instead of full text? (where supported by `codesigs` lib)
    ids:bool=True, # Include cell ids in notebooks?
    recursive:bool=True, # search subfolders
    maxdepth:int=None, # max depth to descend (1=just immediate contents; None=unlimited)
    symlinks:bool=True, # follow symlinks?
    file_glob:str=None, # Only include files matching glob
    file_re:str=None, # Only include files matching regex
    folder_re:str=None, # Only enter folders matching regex
    skip_file_glob:str=None, # Skip files matching glob
    skip_file_re:str=None, # Skip files matching regex
    skip_folder_re:str=None, # Skip folders matching regex,
    ret_folders:bool=False, # return folders, not just files
    sort:bool=True, # sort files by name within each folder
):

```

*Convert folder to XML context and place in a new message*

</div>

``` python
# ctx_folder('..', max_total=600, sigs_only=True, exts='py')
```

<documents><document index="4"><src> ../dialoghelper/capture.py
</src><document-content> def setup_share(): “Setup screen sharing”

def start_share(): fire_event(‘shareScreen’)

def \_capture_screen(timeout=15):

def capture_screen(timeout=15): “Capture the screen as a PIL image.”

def capture_tool(timeout:int=15): “Capture the screen. Re-call this
function to get the most recent screenshot, as needed. Use default
timeout where possible”
</document-content></document><document index="5"><src>
../dialoghelper/core.py </src>\<

\[TRUNCATED: output size 24344 exceeded max size 600 bytes\]

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L137"
target="_blank" style="float:right; font-size:smaller">source</a>

### ctx_repo

``` python

async def ctx_repo(
    owner:str, # GitHub repo owner
    repo:str, # GitHub repo name
    types:str | list='py,doc', # list or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
    exts:str | list=None, # list or comma-separated str of exts to include (overrides `types`)
    out:bool=False, # Include notebook cell outputs?
    raw:bool=True, # Add raw message, or note?
    ref:str=None, # Git ref (branch/tag/sha) (get from URL not provided); defaults to repo's default branch
    folder:str=None, # Only include files under this path (get from URL not provided)
    show_filters:bool=True, # Include filter info in title?
    token:str=None, # GitHub token (uses GITHUB_TOKEN env var if None)
    prefix:bool=False, # Include Anthropic's suggested prose intro?
    include_base:bool=True, # Include full path in src?
    title:str=None, # Optional title attr for Documents element
    max_size:int=100000, # Skip files larger than this (bytes)
    max_total:int=10000000, # Max total output size in bytes
    readme_first:bool=False, # Prioritize README files at start of context?
    files_only:bool=False, # Return dict of {filename: size} instead of context?
    sigs_only:bool=False, # Return signatures instead of full text? (where supported by `codesigs` lib)
    ids:bool=True, # Include cell ids in notebooks?
    recursive:bool=True, # search subfolders
    maxdepth:int=None, # max depth to descend (1=just immediate contents; None=unlimited)
    symlinks:bool=True, # follow symlinks?
    file_glob:str=None, # Only include files matching glob
    file_re:str=None, # Only include files matching regex
    folder_re:str=None, # Only enter folders matching regex
    skip_file_glob:str=None, # Skip files matching glob
    skip_file_re:str=None, # Skip files matching regex
    skip_folder_re:str=None, # Skip folders matching regex,
    ret_folders:bool=False, # return folders, not just files
    sort:bool=True, # sort files by name within each folder
): # XML for LM context, or dict of file sizes

```

*Convert GitHub repo to XML context and place in a new message*

</div>

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L153"
target="_blank" style="float:right; font-size:smaller">source</a>

### ctx_symfile

``` python

async def ctx_symfile(
    sym
):

```

*Add note with filepath and contents for a symbol’s source file*

</div>

``` python
# ctx_symfile(TemporaryDirectory)
```

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L160"
target="_blank" style="float:right; font-size:smaller">source</a>

### ctx_symfolder

``` python

async def ctx_symfolder(
    sym, # Symbol to get folder context from
    types:str | list='py', # List or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
    skip_file_re:str='^_mod', # Skip files matching regex
    prefix:bool=False, # Include Anthropic's suggested prose intro?
    out:bool=True, # Include notebook cell outputs?
    include_base:bool=True, # Include full path in src?
    title:str=None, # Optional title attr for Documents element
    max_size:int=100000, # Skip files larger than this (bytes)
    max_total:int=10000000, # Max total output size in bytes
    readme_first:bool=False, # Prioritize README files at start of context?
    files_only:bool=False, # Return dict of {filename: size} instead of context?
    sigs_only:bool=False, # Return signatures instead of full text? (where supported by `codesigs` lib)
    ids:bool=True, # Include cell ids in notebooks?
    recursive:bool=True, # search subfolders
    maxdepth:int=None, # max depth to descend (1=just immediate contents; None=unlimited)
    symlinks:bool=True, # follow symlinks?
    file_glob:str=None, # Only include files matching glob
    file_re:str=None, # Only include files matching regex
    folder_re:str=None, # Only enter folders matching regex
    skip_file_glob:str=None, # Skip files matching glob
    skip_folder_re:str=None, # Skip folders matching regex,
    ret_folders:bool=False, # return folders, not just files
    sort:bool=True, # sort files by name within each folder
    exts:str | list=None, # list or comma-separated str of exts to include
):

```

*Add raw message with folder context for a symbol’s source file
location*

</div>

``` python
# ctx_symfolder(folder2ctx)
```

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L169"
target="_blank" style="float:right; font-size:smaller">source</a>

### ctx_sympkg

``` python

async def ctx_sympkg(
    sym, # Symbol to get folder context from
    types:str | list='py', # List or comma-separated str of ext types from: py, js, java, c, cpp, rb, r, ex, sh, web, doc, cfg
    skip_file_re:str='^_mod', # Skip files matching regex
    prefix:bool=False, # Include Anthropic's suggested prose intro?
    out:bool=True, # Include notebook cell outputs?
    include_base:bool=True, # Include full path in src?
    title:str=None, # Optional title attr for Documents element
    max_size:int=100000, # Skip files larger than this (bytes)
    max_total:int=10000000, # Max total output size in bytes
    readme_first:bool=False, # Prioritize README files at start of context?
    files_only:bool=False, # Return dict of {filename: size} instead of context?
    sigs_only:bool=False, # Return signatures instead of full text? (where supported by `codesigs` lib)
    ids:bool=True, # Include cell ids in notebooks?
    recursive:bool=True, # search subfolders
    maxdepth:int=None, # max depth to descend (1=just immediate contents; None=unlimited)
    symlinks:bool=True, # follow symlinks?
    file_glob:str=None, # Only include files matching glob
    file_re:str=None, # Only include files matching regex
    folder_re:str=None, # Only enter folders matching regex
    skip_file_glob:str=None, # Skip files matching glob
    skip_folder_re:str=None, # Skip folders matching regex,
    ret_folders:bool=False, # return folders, not just files
    sort:bool=True, # sort files by name within each folder
    exts:str | list=None, # list or comma-separated str of exts to include
):

```

*Add raw message with repo context for a symbol’s root package*

</div>

``` python
# ctx_sympkg(folder2ctx)
```

## Gists and github

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L178"
target="_blank" style="float:right; font-size:smaller">source</a>

### load_gist

``` python

def load_gist(
    gist_id:str
):

```

*Retrieve a gist*

</div>

``` python
gistid = 'jph00/e7cfd4ded593e8ef6217e78a0131960c'
gist = load_gist(gistid)
gist.html_url
```

    'https://gist.github.com/jph00/e7cfd4ded593e8ef6217e78a0131960c'

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L186"
target="_blank" style="float:right; font-size:smaller">source</a>

### gist_file

``` python

def gist_file(
    gist_id:str
):

```

*Get the first file from a gist*

</div>

``` python
gfile = gist_file(gistid)
print(gfile.content[:100]+"…")
```

    "This is a test module which makes some simple tools available."
    __all__ = ["hi","whoami"]

    testfoo=…

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L192"
target="_blank" style="float:right; font-size:smaller">source</a>

### import_string

``` python

def import_string(
    code:str, # Code to import as a module
    name:str, # Name of module to create
):

```

*Call self as a function.*

</div>

``` python
def hi(who:str):
    "Say hi to `who`"
    return f"Hello {who}"

def hi2(who):
    "Say hi to `who`"
    return f"Hello {who}"

def hi3(who:str):
    return f"Hello {who}"

bye = "bye"
```

``` python
assert is_usable_tool(hi)
assert not is_usable_tool(hi2)
assert not is_usable_tool(hi3)
assert not is_usable_tool(bye)
```

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L208"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_toollist

``` python

def mk_toollist(
    syms
):

```

*Call self as a function.*

</div>

``` python
print(mk_toollist([hi]))
```

    - &`hi`: Say hi to `who`

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L212"
target="_blank" style="float:right; font-size:smaller">source</a>

### import_gist

``` python

def import_gist(
    gist_id:str, # user/id or just id of gist to import as a module
    mod_name:str=None, # module name to create (taken from gist filename if not passed)
    add_global:bool=True, # add module to caller's globals?
    import_wildcard:bool=False, # import all exported symbols to caller's globals
    create_msg:bool=False, # Add a message that lists usable tools
):

```

*Import gist directly from string without saving to disk*

</div>

``` python
import_gist(gistid)
importtest.testfoo
```

    'testbar'

``` python
import_gist.__doc__
```

    'Import gist directly from string without saving to disk'

``` python
import_gist(gistid, import_wildcard=True)
importtest.testfoo
```

    'testbar'

``` python
hi("Sarah")
```

    'Hello Sarah'

``` python
importtest.__all__
```

    ['hi', 'whoami']

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L236"
target="_blank" style="float:right; font-size:smaller">source</a>

### update_gist

``` python

def update_gist(
    gist_id:str, content:str
):

```

*Update the first file in a gist with new content*

</div>

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L266"
target="_blank" style="float:right; font-size:smaller">source</a>

### read_pr

``` python

def read_pr(
    pr_number:int | str, # Issue/PR number, or GitHub issue/PR URL
    owner:str='answerdotai', # Owner
    repo:str=None, # Repo
    folder:str='', # For diffs, limit to only to files in `folder`
    replies:bool=False, # Include replies
):

```

*Fetch a GitHub PR or issue’s title, body, optionally replies, and diff
(if PR)*

</div>

## Input

[`input`](https://AnswerDotAI.github.io/dialoghelper/utils.html#input)
can take a string prompt as normal. OR, you can supply custom UI. For it
to work, you must:

- Wrap in `<solveit-input>` tags (done automatically by the new
  [`input`](https://AnswerDotAI.github.io/dialoghelper/utils.html#input)
  function defined here)
- Post to `"/input_reply_"` with a value for `user_input`.
- For access keys (shortcuts for buttons), include an `accesskey="y"` in
  the button, and make sure it is in a form/div with id
  `'input-request-form'`.

The `show_prompt` demo here (using the custom
[`InputBtn`](https://AnswerDotAI.github.io/dialoghelper/utils.html#inputbtn))
demonstrates this to give a custom yes/no prompt:

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L309"
target="_blank" style="float:right; font-size:smaller">source</a>

### InputForm

``` python

def InputForm(
    c:VAR_POSITIONAL, kwargs:VAR_KEYWORD
):

```

*Create an
[`input()`](https://AnswerDotAI.github.io/dialoghelper/utils.html#input)
with a `Form` with needed `hx_post` and `id`*

</div>

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L305"
target="_blank" style="float:right; font-size:smaller">source</a>

### input

``` python

def input(
    prompt:str='', args:VAR_POSITIONAL
):

```

*Solveit customised input to handle fasttag prompts*

</div>

<div class="prose" markdown="1">

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/dialoghelper/blob/main/dialoghelper/utils.py#L301"
target="_blank" style="float:right; font-size:smaller">source</a>

### InputBtn

``` python

def InputBtn(
    txt, value:NoneType=None, btncls:tuple=(), kw:VAR_KEYWORD
):

```

*Call self as a function.*

</div>

``` python
def show_prompt():
    return InputForm(
        Div('Ship this change now?'),
        Div(cls='flex gap-2')(
            InputBtn('Yes', btncls='primary', accesskey="y"),
            InputBtn('No', btncls='default', accesskey="n"))
    )
```

``` python
# show_prompt()
```
