aiscript 1.3.0__tar.gz → 1.5.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.
- aiscript-1.5.0/PKG-INFO +206 -0
- aiscript-1.5.0/README.md +189 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/pyproject.toml +14 -1
- {aiscript-1.3.0 → aiscript-1.5.0}/pyproject.toml.orig +15 -1
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/__init__.py +1 -1
- aiscript-1.5.0/src/aiscript/_shim/__init__.py +0 -0
- aiscript-1.5.0/src/aiscript/_shim/aiscript/__init__.py +181 -0
- aiscript-1.5.0/src/aiscript/ask.py +217 -0
- aiscript-1.5.0/src/aiscript/ask_cli.py +112 -0
- aiscript-1.5.0/src/aiscript/ask_counter.py +57 -0
- aiscript-1.5.0/src/aiscript/ask_openai.py +173 -0
- aiscript-1.5.0/src/aiscript/ask_schema.py +128 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/builder.py +23 -3
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/cli.py +83 -10
- aiscript-1.5.0/src/aiscript/config.py +749 -0
- aiscript-1.5.0/src/aiscript/errors.py +96 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/init_cmd.py +59 -5
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/listing.py +13 -5
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/llm.py +84 -12
- aiscript-1.5.0/src/aiscript/onboarding.py +112 -0
- aiscript-1.5.0/src/aiscript/provider_factory.py +111 -0
- aiscript-1.5.0/src/aiscript/providers/__init__.py +1 -0
- aiscript-1.5.0/src/aiscript/providers/acp.py +436 -0
- aiscript-1.5.0/src/aiscript/providers/anthropic.py +111 -0
- aiscript-1.5.0/src/aiscript/providers/contracts.py +80 -0
- aiscript-1.5.0/src/aiscript/providers/gemini.py +70 -0
- aiscript-1.5.0/src/aiscript/qsiv.py +78 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/reviewer.py +290 -23
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/runner.py +36 -1
- aiscript-1.5.0/src/aiscript/secret_scan.py +506 -0
- aiscript-1.5.0/src/aiscript/sweep.py +92 -0
- aiscript-1.3.0/PKG-INFO +0 -277
- aiscript-1.3.0/README.md +0 -263
- aiscript-1.3.0/src/aiscript/config.py +0 -314
- aiscript-1.3.0/src/aiscript/errors.py +0 -11
- {aiscript-1.3.0 → aiscript-1.5.0}/LICENSE +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/__main__.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/decisions.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/env.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/file_contracts.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/paths.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/prompt_file.py +0 -0
- {aiscript-1.3.0 → aiscript-1.5.0}/src/aiscript/pysetup.py +0 -0
aiscript-1.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aiscript
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: JIT-compile plain-English intents into cached, reviewed shell or Python scripts via an LLM.
|
|
5
|
+
Author: Daniel Illescas Romero
|
|
6
|
+
Author-email: Daniel Illescas Romero <illescas.daniel@protonmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: httpx2>=2.14.0
|
|
10
|
+
Requires-Dist: openai>=3.35.1
|
|
11
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
12
|
+
Requires-Dist: typesafe-sdk>=0.7.5
|
|
13
|
+
Requires-Dist: qsiv-runtime>=0.1,<0.2 ; extra == 'qsiv'
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Provides-Extra: qsiv
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# aiscript
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/aiscript/)
|
|
21
|
+
[](https://pypi.org/project/aiscript/)
|
|
22
|
+
|
|
23
|
+
Write scripts in plain English and run them directly. `aiscript` generates and reviews a shell
|
|
24
|
+
or Python program from your description, caches it, and runs it. Later runs reuse the cached build.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
Requires Python 3.10+. Install using one of:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
uv tool install aiscript
|
|
32
|
+
# or
|
|
33
|
+
pipx install aiscript
|
|
34
|
+
# or
|
|
35
|
+
pip install aiscript
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To update to the latest version, use the same tool you installed with:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
uv tool upgrade aiscript
|
|
42
|
+
# or
|
|
43
|
+
pipx upgrade aiscript
|
|
44
|
+
# or
|
|
45
|
+
pip install --upgrade aiscript
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See [installation and PATH setup](docs/installation.md) for troubleshooting.
|
|
49
|
+
|
|
50
|
+
## Quickstart
|
|
51
|
+
|
|
52
|
+
Run `aiscript --init` to choose a provider and create a starter configuration at
|
|
53
|
+
`~/.config/aiscript/config.yaml`. On an interactive first run that needs script generation,
|
|
54
|
+
aiscript also offers this choice if there is no configuration yet. The menu checks for local
|
|
55
|
+
executables and environment keys; a detected item is not necessarily signed in or ready to use.
|
|
56
|
+
You can select an undetected provider and finish its setup afterwards.
|
|
57
|
+
|
|
58
|
+
Existing configuration and unattended runs do not show the menu. Non-interactive `--init`
|
|
59
|
+
creates the OpenAI starter configuration. See [configuration](#configuration) and the
|
|
60
|
+
[configuration reference](docs/configuration.md) for provider setup and API keys.
|
|
61
|
+
|
|
62
|
+
You can create a named command interactively:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
aiscript sizes
|
|
66
|
+
# Describe what it should do when prompted, then pass arguments on later runs:
|
|
67
|
+
aiscript sizes ~/Downloads --top 5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Executable prompt files
|
|
71
|
+
|
|
72
|
+
On Linux or macOS, save this plain-English script as `biggest` in a folder such as `~/Documents`:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
#!/usr/bin/env aiscript
|
|
76
|
+
|
|
77
|
+
List the 10 largest immediate subfolders of the directory given as the first argument. Show their sizes in human-readable form, largest first. Do not modify any files.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In that folder, make it executable and run it:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
chmod +x biggest
|
|
84
|
+
./biggest ~/Downloads
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The first run generates and reviews the program. Inspect it if approval is requested; later runs
|
|
88
|
+
reuse the cached build. See [prompt files](docs/prompt-files.md) for PATH setup, Windows,
|
|
89
|
+
arguments, caching and rebuilding.
|
|
90
|
+
|
|
91
|
+
## AI inside scripts
|
|
92
|
+
|
|
93
|
+
Generated scripts can ask your configured model to summarise, classify or extract data through
|
|
94
|
+
`aiscript.ask` (Python) or `aiscript --ask` (shell). Their input is sent to the selected provider
|
|
95
|
+
on every run. A status line on stderr reports the configured timeout when a request starts; the
|
|
96
|
+
answer remains on stdout or is returned to the Python caller. API timeouts apply per attempt, so
|
|
97
|
+
retries can take longer. ACP timeouts are total request deadlines. See
|
|
98
|
+
[usage](docs/usage.md#ai-inside-scripts) and [privacy](docs/safety.md#privacy).
|
|
99
|
+
|
|
100
|
+
For example, save this as `standup`. Git can list commits but can't summarise them, so the prompt
|
|
101
|
+
asks for that step explicitly:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
#!/usr/bin/env aiscript
|
|
105
|
+
|
|
106
|
+
Print a short standup note for the git repository in the current directory. Collect my commits
|
|
107
|
+
from the last 24 hours (by the configured git user, all branches), then use AI to summarise them
|
|
108
|
+
into "Done" and "Next" bullet points, at most 5 bullets in total. If there are no commits, say so
|
|
109
|
+
without calling AI. Do not modify the repository.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
chmod +x standup
|
|
114
|
+
cd ~/projects/my-app && ~/Documents/standup
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The generated program runs `git log` itself and hands only that output to the model through
|
|
118
|
+
`aiscript.ask`. Only the commit data is sent to your provider, and only when the script runs.
|
|
119
|
+
|
|
120
|
+
Another example: save this as `explain-error` to get a plain-language diagnosis of any failing
|
|
121
|
+
command:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
#!/usr/bin/env aiscript
|
|
125
|
+
|
|
126
|
+
Run the command given as the arguments and capture its output and exit code. If it succeeds, print
|
|
127
|
+
its output unchanged and exit 0. If it fails, print its output, then use AI to explain the likely
|
|
128
|
+
cause in one short paragraph and suggest one concrete fix. Exit with the command's exit code. Do
|
|
129
|
+
not modify any files.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
chmod +x explain-error
|
|
134
|
+
./explain-error npm run build
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Only the failing command's output is sent to your provider, so avoid running it on commands that
|
|
138
|
+
print secrets.
|
|
139
|
+
|
|
140
|
+
### How it works
|
|
141
|
+
|
|
142
|
+
```mermaid
|
|
143
|
+
flowchart TD
|
|
144
|
+
A["./standup"] --> B{"Cached build?"}
|
|
145
|
+
B -- no --> C["Generate, review and<br/>approve if risky, then cache"]
|
|
146
|
+
B -- yes --> D["Run the program"]
|
|
147
|
+
C --> D
|
|
148
|
+
D --> E["aiscript.ask(task, data)"]
|
|
149
|
+
E --> F["Your configured provider<br/>(API key stays in aiscript)"]
|
|
150
|
+
F --> G["Script prints the answer"]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Generation and review happen once. Every later run skips straight to the cached program, and only
|
|
154
|
+
the `ask` call talks to the model.
|
|
155
|
+
|
|
156
|
+
## Safety review / decision models
|
|
157
|
+
|
|
158
|
+
Generated programs are checked before they are saved or run. Risky programs need approval; review
|
|
159
|
+
failures and unavailable reviewers also require approval. Review is not a sandbox. The reviewer can be a hosted or local decision model, including the
|
|
160
|
+
optional local GPU model QSIV (`aiscript[qsiv]`). See
|
|
161
|
+
[safety review](docs/safety.md) for the full behavior.
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
Set `default_provider` in `config.yaml` to choose the provider that writes your scripts and
|
|
166
|
+
answers AI calls inside them. The starter config (`aiscript --init`) has an entry for each choice.
|
|
167
|
+
|
|
168
|
+
- **APIs:** `openai` (or any OpenAI-compatible endpoint), `anthropic` and `gemini`. Put the API key
|
|
169
|
+
and model in the config.
|
|
170
|
+
- **Installed agents (ACP):** `codex-acp`, `claude-acp`, `gemini-acp`, `opencode-go-acp` and
|
|
171
|
+
`glm-acp` use the agent you already have installed and signed in to, with no `providers:` entry.
|
|
172
|
+
The agent chooses its model. OpenCode Go needs `"model": "opencode-go/<model>"` in your OpenCode
|
|
173
|
+
config. Agents are trusted local programs, not a sandbox. Install and sign in to the agent
|
|
174
|
+
first: see [installing ACP agents](docs/configuration.md#installing-acp-agents).
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
default_provider: claude-acp
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
See the [configuration reference](docs/configuration.md) for API keys, ACP limits, the optional
|
|
181
|
+
safety reviewer and environment variables, and [safety](docs/safety.md#acp-agents) for agent trust.
|
|
182
|
+
|
|
183
|
+
## Usage
|
|
184
|
+
|
|
185
|
+
Run `aiscript --help` for available commands. See [usage and storage](docs/usage.md) for command
|
|
186
|
+
management, Python dependencies and cache locations.
|
|
187
|
+
|
|
188
|
+
## Development
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
uv sync # install dependencies
|
|
192
|
+
uv run task test # tests
|
|
193
|
+
uv run task dev --init # try your checkout with a private, gitignored config in .dev/
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Edit `.dev/config.yaml` to add an API key, then run any command with `uv run task dev` (for
|
|
197
|
+
example `uv run task dev sizes ~/Downloads`). See [development](docs/development.md).
|
|
198
|
+
|
|
199
|
+
## Docs
|
|
200
|
+
|
|
201
|
+
- [Installation](docs/installation.md)
|
|
202
|
+
- [Executable prompt files](docs/prompt-files.md)
|
|
203
|
+
- [Configuration reference](docs/configuration.md)
|
|
204
|
+
- [Safety review](docs/safety.md)
|
|
205
|
+
- [Usage and storage](docs/usage.md)
|
|
206
|
+
- [Development](docs/development.md)
|
aiscript-1.5.0/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# aiscript
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/aiscript/)
|
|
4
|
+
[](https://pypi.org/project/aiscript/)
|
|
5
|
+
|
|
6
|
+
Write scripts in plain English and run them directly. `aiscript` generates and reviews a shell
|
|
7
|
+
or Python program from your description, caches it, and runs it. Later runs reuse the cached build.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Requires Python 3.10+. Install using one of:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
uv tool install aiscript
|
|
15
|
+
# or
|
|
16
|
+
pipx install aiscript
|
|
17
|
+
# or
|
|
18
|
+
pip install aiscript
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To update to the latest version, use the same tool you installed with:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
uv tool upgrade aiscript
|
|
25
|
+
# or
|
|
26
|
+
pipx upgrade aiscript
|
|
27
|
+
# or
|
|
28
|
+
pip install --upgrade aiscript
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See [installation and PATH setup](docs/installation.md) for troubleshooting.
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
Run `aiscript --init` to choose a provider and create a starter configuration at
|
|
36
|
+
`~/.config/aiscript/config.yaml`. On an interactive first run that needs script generation,
|
|
37
|
+
aiscript also offers this choice if there is no configuration yet. The menu checks for local
|
|
38
|
+
executables and environment keys; a detected item is not necessarily signed in or ready to use.
|
|
39
|
+
You can select an undetected provider and finish its setup afterwards.
|
|
40
|
+
|
|
41
|
+
Existing configuration and unattended runs do not show the menu. Non-interactive `--init`
|
|
42
|
+
creates the OpenAI starter configuration. See [configuration](#configuration) and the
|
|
43
|
+
[configuration reference](docs/configuration.md) for provider setup and API keys.
|
|
44
|
+
|
|
45
|
+
You can create a named command interactively:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
aiscript sizes
|
|
49
|
+
# Describe what it should do when prompted, then pass arguments on later runs:
|
|
50
|
+
aiscript sizes ~/Downloads --top 5
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Executable prompt files
|
|
54
|
+
|
|
55
|
+
On Linux or macOS, save this plain-English script as `biggest` in a folder such as `~/Documents`:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
#!/usr/bin/env aiscript
|
|
59
|
+
|
|
60
|
+
List the 10 largest immediate subfolders of the directory given as the first argument. Show their sizes in human-readable form, largest first. Do not modify any files.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
In that folder, make it executable and run it:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
chmod +x biggest
|
|
67
|
+
./biggest ~/Downloads
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The first run generates and reviews the program. Inspect it if approval is requested; later runs
|
|
71
|
+
reuse the cached build. See [prompt files](docs/prompt-files.md) for PATH setup, Windows,
|
|
72
|
+
arguments, caching and rebuilding.
|
|
73
|
+
|
|
74
|
+
## AI inside scripts
|
|
75
|
+
|
|
76
|
+
Generated scripts can ask your configured model to summarise, classify or extract data through
|
|
77
|
+
`aiscript.ask` (Python) or `aiscript --ask` (shell). Their input is sent to the selected provider
|
|
78
|
+
on every run. A status line on stderr reports the configured timeout when a request starts; the
|
|
79
|
+
answer remains on stdout or is returned to the Python caller. API timeouts apply per attempt, so
|
|
80
|
+
retries can take longer. ACP timeouts are total request deadlines. See
|
|
81
|
+
[usage](docs/usage.md#ai-inside-scripts) and [privacy](docs/safety.md#privacy).
|
|
82
|
+
|
|
83
|
+
For example, save this as `standup`. Git can list commits but can't summarise them, so the prompt
|
|
84
|
+
asks for that step explicitly:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
#!/usr/bin/env aiscript
|
|
88
|
+
|
|
89
|
+
Print a short standup note for the git repository in the current directory. Collect my commits
|
|
90
|
+
from the last 24 hours (by the configured git user, all branches), then use AI to summarise them
|
|
91
|
+
into "Done" and "Next" bullet points, at most 5 bullets in total. If there are no commits, say so
|
|
92
|
+
without calling AI. Do not modify the repository.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
chmod +x standup
|
|
97
|
+
cd ~/projects/my-app && ~/Documents/standup
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The generated program runs `git log` itself and hands only that output to the model through
|
|
101
|
+
`aiscript.ask`. Only the commit data is sent to your provider, and only when the script runs.
|
|
102
|
+
|
|
103
|
+
Another example: save this as `explain-error` to get a plain-language diagnosis of any failing
|
|
104
|
+
command:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
#!/usr/bin/env aiscript
|
|
108
|
+
|
|
109
|
+
Run the command given as the arguments and capture its output and exit code. If it succeeds, print
|
|
110
|
+
its output unchanged and exit 0. If it fails, print its output, then use AI to explain the likely
|
|
111
|
+
cause in one short paragraph and suggest one concrete fix. Exit with the command's exit code. Do
|
|
112
|
+
not modify any files.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
chmod +x explain-error
|
|
117
|
+
./explain-error npm run build
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Only the failing command's output is sent to your provider, so avoid running it on commands that
|
|
121
|
+
print secrets.
|
|
122
|
+
|
|
123
|
+
### How it works
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
flowchart TD
|
|
127
|
+
A["./standup"] --> B{"Cached build?"}
|
|
128
|
+
B -- no --> C["Generate, review and<br/>approve if risky, then cache"]
|
|
129
|
+
B -- yes --> D["Run the program"]
|
|
130
|
+
C --> D
|
|
131
|
+
D --> E["aiscript.ask(task, data)"]
|
|
132
|
+
E --> F["Your configured provider<br/>(API key stays in aiscript)"]
|
|
133
|
+
F --> G["Script prints the answer"]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Generation and review happen once. Every later run skips straight to the cached program, and only
|
|
137
|
+
the `ask` call talks to the model.
|
|
138
|
+
|
|
139
|
+
## Safety review / decision models
|
|
140
|
+
|
|
141
|
+
Generated programs are checked before they are saved or run. Risky programs need approval; review
|
|
142
|
+
failures and unavailable reviewers also require approval. Review is not a sandbox. The reviewer can be a hosted or local decision model, including the
|
|
143
|
+
optional local GPU model QSIV (`aiscript[qsiv]`). See
|
|
144
|
+
[safety review](docs/safety.md) for the full behavior.
|
|
145
|
+
|
|
146
|
+
## Configuration
|
|
147
|
+
|
|
148
|
+
Set `default_provider` in `config.yaml` to choose the provider that writes your scripts and
|
|
149
|
+
answers AI calls inside them. The starter config (`aiscript --init`) has an entry for each choice.
|
|
150
|
+
|
|
151
|
+
- **APIs:** `openai` (or any OpenAI-compatible endpoint), `anthropic` and `gemini`. Put the API key
|
|
152
|
+
and model in the config.
|
|
153
|
+
- **Installed agents (ACP):** `codex-acp`, `claude-acp`, `gemini-acp`, `opencode-go-acp` and
|
|
154
|
+
`glm-acp` use the agent you already have installed and signed in to, with no `providers:` entry.
|
|
155
|
+
The agent chooses its model. OpenCode Go needs `"model": "opencode-go/<model>"` in your OpenCode
|
|
156
|
+
config. Agents are trusted local programs, not a sandbox. Install and sign in to the agent
|
|
157
|
+
first: see [installing ACP agents](docs/configuration.md#installing-acp-agents).
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
default_provider: claude-acp
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
See the [configuration reference](docs/configuration.md) for API keys, ACP limits, the optional
|
|
164
|
+
safety reviewer and environment variables, and [safety](docs/safety.md#acp-agents) for agent trust.
|
|
165
|
+
|
|
166
|
+
## Usage
|
|
167
|
+
|
|
168
|
+
Run `aiscript --help` for available commands. See [usage and storage](docs/usage.md) for command
|
|
169
|
+
management, Python dependencies and cache locations.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
uv sync # install dependencies
|
|
175
|
+
uv run task test # tests
|
|
176
|
+
uv run task dev --init # try your checkout with a private, gitignored config in .dev/
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Edit `.dev/config.yaml` to add an API key, then run any command with `uv run task dev` (for
|
|
180
|
+
example `uv run task dev sizes ~/Downloads`). See [development](docs/development.md).
|
|
181
|
+
|
|
182
|
+
## Docs
|
|
183
|
+
|
|
184
|
+
- [Installation](docs/installation.md)
|
|
185
|
+
- [Executable prompt files](docs/prompt-files.md)
|
|
186
|
+
- [Configuration reference](docs/configuration.md)
|
|
187
|
+
- [Safety review](docs/safety.md)
|
|
188
|
+
- [Usage and storage](docs/usage.md)
|
|
189
|
+
- [Development](docs/development.md)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aiscript"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.5.0"
|
|
4
4
|
description = "JIT-compile plain-English intents into cached, reviewed shell or Python scripts via an LLM."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
dependencies = [
|
|
10
|
+
"httpx2>=2.14.0",
|
|
10
11
|
"openai>=3.35.1",
|
|
11
12
|
"pyyaml>=6.0.3",
|
|
12
13
|
"typesafe-sdk>=0.7.5",
|
|
@@ -16,6 +17,9 @@ dependencies = [
|
|
|
16
17
|
name = "Daniel Illescas Romero"
|
|
17
18
|
email = "illescas.daniel@protonmail.com"
|
|
18
19
|
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
qsiv = ["qsiv-runtime>=0.1,<0.2"]
|
|
22
|
+
|
|
19
23
|
[project.scripts]
|
|
20
24
|
aiscript = "aiscript.cli:main"
|
|
21
25
|
|
|
@@ -28,6 +32,7 @@ dev = [
|
|
|
28
32
|
"mypy>=2.4.0",
|
|
29
33
|
"pytest>=9.1.1",
|
|
30
34
|
"ruff>=0.17.1",
|
|
35
|
+
"taskipy>=1.14.1",
|
|
31
36
|
]
|
|
32
37
|
|
|
33
38
|
[tool.ruff]
|
|
@@ -40,6 +45,9 @@ extend-exclude = [
|
|
|
40
45
|
".codex/skills",
|
|
41
46
|
]
|
|
42
47
|
|
|
48
|
+
[tool.ruff.per-file-target-version]
|
|
49
|
+
"src/aiscript/_shim/**" = "py38"
|
|
50
|
+
|
|
43
51
|
[tool.ruff.lint]
|
|
44
52
|
select = [
|
|
45
53
|
"E",
|
|
@@ -60,3 +68,8 @@ testpaths = ["tests"]
|
|
|
60
68
|
python_version = "3.10"
|
|
61
69
|
ignore_missing_imports = true
|
|
62
70
|
warn_unused_ignores = true
|
|
71
|
+
|
|
72
|
+
[tool.taskipy.tasks]
|
|
73
|
+
dev = "python scripts/dev.py"
|
|
74
|
+
test = "pytest -q"
|
|
75
|
+
lint = "ruff check && ruff format --check && mypy src tests"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aiscript"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.5.0"
|
|
4
4
|
description = "JIT-compile plain-English intents into cached, reviewed shell or Python scripts via an LLM."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -10,11 +10,16 @@ authors = [
|
|
|
10
10
|
]
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
12
|
dependencies = [
|
|
13
|
+
"httpx2>=2.14.0",
|
|
13
14
|
"openai>=3.35.1",
|
|
14
15
|
"pyyaml>=6.0.3",
|
|
15
16
|
"typesafe-sdk>=0.7.5",
|
|
16
17
|
]
|
|
17
18
|
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
# Local QSIV safety reviewer: Linux x86_64 and an NVIDIA GPU only, so it is not a default dependency.
|
|
21
|
+
qsiv = ["qsiv-runtime>=0.1,<0.2"]
|
|
22
|
+
|
|
18
23
|
[project.scripts]
|
|
19
24
|
aiscript = "aiscript.cli:main"
|
|
20
25
|
|
|
@@ -27,6 +32,7 @@ dev = [
|
|
|
27
32
|
"mypy>=2.4.0",
|
|
28
33
|
"pytest>=9.1.1",
|
|
29
34
|
"ruff>=0.17.1",
|
|
35
|
+
"taskipy>=1.14.1",
|
|
30
36
|
]
|
|
31
37
|
|
|
32
38
|
[tool.ruff]
|
|
@@ -35,6 +41,9 @@ target-version = "py310"
|
|
|
35
41
|
# Installed skills carry supporting code with their own style conventions.
|
|
36
42
|
extend-exclude = [".agents/skills", ".claude/skills", ".cursor/skills", ".codex/skills"]
|
|
37
43
|
|
|
44
|
+
# The shim runs on the system Python, which may be as old as 3.8.
|
|
45
|
+
per-file-target-version = { "src/aiscript/_shim/**" = "py38" }
|
|
46
|
+
|
|
38
47
|
[tool.ruff.lint]
|
|
39
48
|
select = ["E", "F", "I", "UP", "B", "ANN"]
|
|
40
49
|
|
|
@@ -49,3 +58,8 @@ testpaths = ["tests"]
|
|
|
49
58
|
python_version = "3.10"
|
|
50
59
|
ignore_missing_imports = true
|
|
51
60
|
warn_unused_ignores = true
|
|
61
|
+
|
|
62
|
+
[tool.taskipy.tasks]
|
|
63
|
+
dev = "python scripts/dev.py"
|
|
64
|
+
test = "pytest -q"
|
|
65
|
+
lint = "ruff check && ruff format --check && mypy src tests"
|
|
File without changes
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Python shim for AI calls inside generated scripts (stdlib only, Python >= 3.8).
|
|
2
|
+
|
|
3
|
+
``ask()`` runs ``aiscript --ask=TASK`` as a subprocess with the data on stdin.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from typing import Any, Dict, List, Optional, Union
|
|
14
|
+
|
|
15
|
+
_STDERR_LIMIT = 500
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AskError(Exception):
|
|
19
|
+
"""Base class of every failure of ``ask()``; ``exit_code`` is the aiscript exit code."""
|
|
20
|
+
|
|
21
|
+
exit_code = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AskUsageError(AskError):
|
|
25
|
+
exit_code = 2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AskConfigError(AskError):
|
|
29
|
+
exit_code = 3
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AskInputError(AskError):
|
|
33
|
+
exit_code = 4
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AskProviderError(AskError):
|
|
37
|
+
exit_code = 5
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AskRateLimitError(AskError):
|
|
41
|
+
exit_code = 6
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AskRefusalError(AskError):
|
|
45
|
+
exit_code = 7
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AskOutputError(AskError):
|
|
49
|
+
exit_code = 8
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AskCapError(AskError):
|
|
53
|
+
exit_code = 9
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AskNotFoundError(AskError):
|
|
57
|
+
exit_code = 127
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
EXIT_CODES = {
|
|
61
|
+
"AskUsageError": 2,
|
|
62
|
+
"AskConfigError": 3,
|
|
63
|
+
"AskInputError": 4,
|
|
64
|
+
"AskProviderError": 5,
|
|
65
|
+
"AskRateLimitError": 6,
|
|
66
|
+
"AskRefusalError": 7,
|
|
67
|
+
"AskOutputError": 8,
|
|
68
|
+
"AskCapError": 9,
|
|
69
|
+
"AskNotFoundError": 127,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _shim_root() -> str:
|
|
74
|
+
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _child_env() -> Dict[str, str]:
|
|
78
|
+
"""Environment for the real aiscript: this shim's directory is removed from PYTHONPATH."""
|
|
79
|
+
env = dict(os.environ)
|
|
80
|
+
root = os.path.normcase(_shim_root())
|
|
81
|
+
entries = [
|
|
82
|
+
e
|
|
83
|
+
for e in env.get("PYTHONPATH", "").split(os.pathsep)
|
|
84
|
+
if e and os.path.normcase(os.path.abspath(e)) != root
|
|
85
|
+
]
|
|
86
|
+
if entries:
|
|
87
|
+
env["PYTHONPATH"] = os.pathsep.join(entries)
|
|
88
|
+
else:
|
|
89
|
+
env.pop("PYTHONPATH", None)
|
|
90
|
+
return env
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _error_for(code: int, stderr: bytes) -> AskError:
|
|
94
|
+
detail = stderr.decode("utf-8", errors="replace").strip()
|
|
95
|
+
if len(detail) > _STDERR_LIMIT:
|
|
96
|
+
detail = detail[:_STDERR_LIMIT] + "..."
|
|
97
|
+
if code == 2 and "unrecognized arguments" in detail:
|
|
98
|
+
return AskUsageError(
|
|
99
|
+
"The aiscript on PATH is older than 1.4.0 and has no --ask; upgrade aiscript."
|
|
100
|
+
)
|
|
101
|
+
message = f"aiscript --ask failed (exit {code}): {detail}"
|
|
102
|
+
for name, known in EXIT_CODES.items():
|
|
103
|
+
if known == code:
|
|
104
|
+
return globals()[name](message)
|
|
105
|
+
error = AskError(message)
|
|
106
|
+
error.exit_code = code
|
|
107
|
+
return error
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _run_child(argv: List[str], data: bytes, env: Dict[str, str]) -> subprocess.CompletedProcess:
|
|
111
|
+
"""Stream only the known status while subprocess.run drains stdout and stdin."""
|
|
112
|
+
read_fd, write_fd = os.pipe()
|
|
113
|
+
stderr = bytearray()
|
|
114
|
+
notice = re.compile(
|
|
115
|
+
rb"aiscript: (?:This request can take up to [0-9]+ seconds\.|"
|
|
116
|
+
rb"Timeout [0-9]+ seconds per attempt; retries may take longer\.)\r?\n$"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def read_errors() -> None:
|
|
120
|
+
shown = False
|
|
121
|
+
with os.fdopen(read_fd, "rb") as stream:
|
|
122
|
+
while True:
|
|
123
|
+
line = stream.readline(1024)
|
|
124
|
+
if not line:
|
|
125
|
+
break
|
|
126
|
+
if not shown and notice.fullmatch(line):
|
|
127
|
+
sys.stderr.write(line.decode("ascii"))
|
|
128
|
+
sys.stderr.flush()
|
|
129
|
+
shown = True
|
|
130
|
+
elif line.startswith(b"aiscript: error: ") and len(stderr) < _STDERR_LIMIT:
|
|
131
|
+
stderr.extend(line[: _STDERR_LIMIT - len(stderr)])
|
|
132
|
+
|
|
133
|
+
thread = threading.Thread(target=read_errors, daemon=True)
|
|
134
|
+
thread.start()
|
|
135
|
+
try:
|
|
136
|
+
result = subprocess.run(argv, input=data, stdout=subprocess.PIPE, stderr=write_fd, env=env)
|
|
137
|
+
finally:
|
|
138
|
+
os.close(write_fd)
|
|
139
|
+
thread.join()
|
|
140
|
+
return subprocess.CompletedProcess(argv, result.returncode, result.stdout, bytes(stderr))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def ask(
|
|
144
|
+
task: str,
|
|
145
|
+
data: str,
|
|
146
|
+
data_description: str,
|
|
147
|
+
schema: Optional[Dict[str, Any]] = None,
|
|
148
|
+
) -> Union[str, Dict[str, Any]]:
|
|
149
|
+
"""Run ``aiscript --ask`` and return its answer (a string, or a dict with a schema)."""
|
|
150
|
+
if not isinstance(task, str) or not task.strip():
|
|
151
|
+
raise AskUsageError("task must be a non-empty string")
|
|
152
|
+
if not isinstance(data_description, str) or not data_description.strip():
|
|
153
|
+
raise AskUsageError("data_description must be a non-empty string")
|
|
154
|
+
if not isinstance(data, str):
|
|
155
|
+
raise AskUsageError("data must be a str (decode bytes first)")
|
|
156
|
+
args = ["--ask=" + task, "--data-description=" + data_description]
|
|
157
|
+
if schema is not None:
|
|
158
|
+
try:
|
|
159
|
+
args.append("--schema=" + json.dumps(schema, separators=(",", ":")))
|
|
160
|
+
except (TypeError, ValueError) as exc:
|
|
161
|
+
raise AskUsageError(f"schema is not JSON serialisable: {exc}") from None
|
|
162
|
+
exe = shutil.which("aiscript")
|
|
163
|
+
if exe is None:
|
|
164
|
+
raise AskNotFoundError(
|
|
165
|
+
"aiscript was not found on PATH; install aiscript so that `aiscript` is on PATH."
|
|
166
|
+
)
|
|
167
|
+
proc = _run_child([exe] + args, data.encode("utf-8"), _child_env())
|
|
168
|
+
if proc.returncode != 0:
|
|
169
|
+
raise _error_for(proc.returncode, proc.stderr)
|
|
170
|
+
out = proc.stdout.decode("utf-8")
|
|
171
|
+
if out.endswith("\n"):
|
|
172
|
+
out = out[:-1]
|
|
173
|
+
if schema is None:
|
|
174
|
+
return out
|
|
175
|
+
try:
|
|
176
|
+
result = json.loads(out)
|
|
177
|
+
except ValueError:
|
|
178
|
+
raise AskOutputError("aiscript returned invalid JSON for the schema") from None
|
|
179
|
+
if not isinstance(result, dict):
|
|
180
|
+
raise AskOutputError("aiscript returned JSON that is not an object")
|
|
181
|
+
return result
|