nyanpasu 0.1.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.
- nyanpasu-0.1.0/LICENSE +21 -0
- nyanpasu-0.1.0/PKG-INFO +172 -0
- nyanpasu-0.1.0/README.md +141 -0
- nyanpasu-0.1.0/pyproject.toml +115 -0
- nyanpasu-0.1.0/src/nyanpasu/__init__.py +3 -0
- nyanpasu-0.1.0/src/nyanpasu/__main__.py +105 -0
- nyanpasu-0.1.0/src/nyanpasu/agent.py +377 -0
- nyanpasu-0.1.0/src/nyanpasu/codex.py +447 -0
- nyanpasu-0.1.0/src/nyanpasu/config.py +186 -0
- nyanpasu-0.1.0/src/nyanpasu/git_ops.py +141 -0
- nyanpasu-0.1.0/src/nyanpasu/models.py +158 -0
- nyanpasu-0.1.0/src/nyanpasu/plugins.py +100 -0
- nyanpasu-0.1.0/src/nyanpasu/py.typed +1 -0
- nyanpasu-0.1.0/src/nyanpasu/store.py +444 -0
- nyanpasu-0.1.0/src/nyanpasu/web.py +113 -0
nyanpasu-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present, Nyakku Shigure
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
nyanpasu-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nyanpasu
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A plugin-oriented Codex-powered agent service.
|
|
5
|
+
Keywords:
|
|
6
|
+
Author: Nyakku Shigure
|
|
7
|
+
Author-email: Nyakku Shigure <sigure.qaq@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
|
+
Requires-Dist: anyio>=4.12.0
|
|
20
|
+
Requires-Dist: fastapi>=0.125.0
|
|
21
|
+
Requires-Dist: loguru>=0.7.3
|
|
22
|
+
Requires-Dist: pydantic>=2.12.0
|
|
23
|
+
Requires-Dist: typer>=0.20.0
|
|
24
|
+
Requires-Dist: uvicorn>=0.38.0
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Project-URL: Homepage, https://github.com/ShigureLab/nyanpasu
|
|
27
|
+
Project-URL: Documentation, https://github.com/ShigureLab/nyanpasu
|
|
28
|
+
Project-URL: Repository, https://github.com/ShigureLab/nyanpasu
|
|
29
|
+
Project-URL: Issues, https://github.com/ShigureLab/nyanpasu/issues
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Nyanpasu
|
|
33
|
+
|
|
34
|
+
Nyanpasu is a plugin-oriented Codex agent service. The core runtime is deliberately generic: it accepts events from plugins, turns them into `AgentTask` objects, prepares one reusable workspace per context, reuses persistent Codex threads per context, records state in SQLite, and runs Codex under a constrained runtime policy.
|
|
35
|
+
|
|
36
|
+
GitHub PR review is implemented by the `nyanpasu-github-reviewer` plugin, not by the core package.
|
|
37
|
+
|
|
38
|
+
## Core Responsibilities
|
|
39
|
+
|
|
40
|
+
- Async task execution with per-context serialization and bounded concurrency.
|
|
41
|
+
- Context workspace management. By default, one `context_key` owns one reusable worktree that is reset to the task revision before each run.
|
|
42
|
+
- Optional event snapshots for plugins that explicitly need per-event isolation.
|
|
43
|
+
- Persistent task, thread, and context state.
|
|
44
|
+
- Codex backend management through `codex app-server` or `codex exec`.
|
|
45
|
+
- Plugin lifecycle hooks, HTTP router registration, and post-process hooks.
|
|
46
|
+
- Runtime safety defaults: `sandbox = "workspace-write"` and `approval_policy = "never"`.
|
|
47
|
+
|
|
48
|
+
Anything domain-specific belongs in a plugin. GitHub event parsing, polling, webhook signatures, `gh-llm` prompts, and review submission live in `packages/nyanpasu-github-reviewer`.
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Nyanpasu uses TOML and Pydantic models. Core config lives at the top level; plugin config lives under `plugins.<plugin_id>`.
|
|
53
|
+
|
|
54
|
+
Nyanpasu has one user-facing home directory. Set `NYANPASU_HOME` to choose it; otherwise it defaults to `~/.nyanpasu`. Config is always read from `$NYANPASU_HOME/config.toml`, and runtime state, logs, SQLite, and managed worktrees also live under `$NYANPASU_HOME`.
|
|
55
|
+
|
|
56
|
+
`state_dir` is intentionally not a TOML option. To move both config and state, move `NYANPASU_HOME`.
|
|
57
|
+
|
|
58
|
+
```toml
|
|
59
|
+
enabled_plugins = ["github_reviewer"]
|
|
60
|
+
|
|
61
|
+
[server]
|
|
62
|
+
host = "127.0.0.1"
|
|
63
|
+
port = 8765
|
|
64
|
+
|
|
65
|
+
[codex]
|
|
66
|
+
backend = "app-server"
|
|
67
|
+
sandbox = "workspace-write"
|
|
68
|
+
approval_policy = "never"
|
|
69
|
+
command_timeout_seconds = 3600
|
|
70
|
+
|
|
71
|
+
[runtime]
|
|
72
|
+
concurrency = 4
|
|
73
|
+
coalesce_window_seconds = 600
|
|
74
|
+
clean_event_snapshots = true
|
|
75
|
+
|
|
76
|
+
[plugins.github_reviewer]
|
|
77
|
+
github_login = "your-github-login"
|
|
78
|
+
poll_interval_seconds = 600
|
|
79
|
+
poll_event_pages = 3
|
|
80
|
+
poll_max_events_per_cycle = 0
|
|
81
|
+
review_language = "Chinese"
|
|
82
|
+
|
|
83
|
+
[[plugins.github_reviewer.instruction_docs]]
|
|
84
|
+
name = "SOUL.md"
|
|
85
|
+
path = "/path/to/SOUL.md"
|
|
86
|
+
|
|
87
|
+
[plugins.github_reviewer.repos."owner/repo"]
|
|
88
|
+
local_path = "/path/to/repo"
|
|
89
|
+
github_remote = "https://github.com/owner/repo.git"
|
|
90
|
+
base_branches = ["main"]
|
|
91
|
+
|
|
92
|
+
[[plugins.github_reviewer.repos."owner/repo".instruction_docs]]
|
|
93
|
+
name = "AGENTS.md"
|
|
94
|
+
path = "/path/to/repo/AGENTS.md"
|
|
95
|
+
required = false
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Instruction documents are task-scoped. A plugin can attach files such as `SOUL.md`, `AGENTS.md`, or project policy notes to an `AgentTask`; the core runtime appends them only for that task before invoking Codex. They are not global Nyanpasu identity and are not hardcoded into the core or GitHub reviewer prompt.
|
|
99
|
+
|
|
100
|
+
## Run
|
|
101
|
+
|
|
102
|
+
Create `$NYANPASU_HOME/config.toml` from `examples/config.toml`, then start the agent service:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
export NYANPASU_HOME="$HOME/.nyanpasu"
|
|
106
|
+
uv run nyanpasu serve
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Inspect runtime state:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv run nyanpasu status
|
|
113
|
+
curl http://127.0.0.1:8765/tasks
|
|
114
|
+
curl http://127.0.0.1:8765/contexts
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The GitHub reviewer plugin mounts its webhook at:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
POST /plugins/github-reviewer/webhook
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The plugin can also start its poller during plugin setup. Events poll uses GitHub repository events as the source of truth: the first poll records the current repo event cursor without handling older events, later polls process matching events after that cursor, and already processed delivery ids are skipped. `poll_max_events_per_cycle = 0` means process every matching event in the poll window; set it to a positive number only when you intentionally want a per-cycle cap.
|
|
124
|
+
|
|
125
|
+
## Plugin Contract
|
|
126
|
+
|
|
127
|
+
A plugin exposes a `NyanpasuPlugin` through the `nyanpasu.plugins` entry point group.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
class MyPlugin:
|
|
131
|
+
id = "my_plugin"
|
|
132
|
+
config_model = MyPluginConfig
|
|
133
|
+
|
|
134
|
+
async def setup(self, runtime, config):
|
|
135
|
+
runtime.add_router(router, prefix="/plugins/my-plugin")
|
|
136
|
+
runtime.add_post_process_hook(self.id, self.after_task)
|
|
137
|
+
await runtime.submit(task)
|
|
138
|
+
|
|
139
|
+
async def shutdown(self):
|
|
140
|
+
...
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Plugins send work to the core by creating `AgentTask`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
AgentTask(
|
|
147
|
+
task_id="event-123",
|
|
148
|
+
action=TaskAction.RUN,
|
|
149
|
+
context_key="my-domain:object-456",
|
|
150
|
+
prompt="Review or handle this event.",
|
|
151
|
+
instruction_docs=[
|
|
152
|
+
InstructionDocument(
|
|
153
|
+
name="AGENTS.md",
|
|
154
|
+
source="/path/to/repo/AGENTS.md",
|
|
155
|
+
content="Follow this repository's local conventions.",
|
|
156
|
+
),
|
|
157
|
+
],
|
|
158
|
+
workspace=WorkspaceRef(
|
|
159
|
+
key="owner/repo",
|
|
160
|
+
local_path=Path("/path/to/repo"),
|
|
161
|
+
remote="https://github.com/owner/repo.git",
|
|
162
|
+
ref="pull/123/head",
|
|
163
|
+
revision="abc123",
|
|
164
|
+
),
|
|
165
|
+
dedupe_key="event-123",
|
|
166
|
+
metadata={"plugin_id": "my_plugin"},
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Core executes the task and calls post-process hooks registered for `metadata["plugin_id"]`.
|
|
171
|
+
|
|
172
|
+
By default, the task uses `workspace_policy = "context"`: Nyanpasu resets the context worktree to `workspace.revision` or `workspace.ref`, runs Codex there, and keeps that workspace for the next event in the same context. Plugins can opt into `workspace_policy = "event_snapshot"` only when they need a disposable per-event worktree.
|
nyanpasu-0.1.0/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Nyanpasu
|
|
2
|
+
|
|
3
|
+
Nyanpasu is a plugin-oriented Codex agent service. The core runtime is deliberately generic: it accepts events from plugins, turns them into `AgentTask` objects, prepares one reusable workspace per context, reuses persistent Codex threads per context, records state in SQLite, and runs Codex under a constrained runtime policy.
|
|
4
|
+
|
|
5
|
+
GitHub PR review is implemented by the `nyanpasu-github-reviewer` plugin, not by the core package.
|
|
6
|
+
|
|
7
|
+
## Core Responsibilities
|
|
8
|
+
|
|
9
|
+
- Async task execution with per-context serialization and bounded concurrency.
|
|
10
|
+
- Context workspace management. By default, one `context_key` owns one reusable worktree that is reset to the task revision before each run.
|
|
11
|
+
- Optional event snapshots for plugins that explicitly need per-event isolation.
|
|
12
|
+
- Persistent task, thread, and context state.
|
|
13
|
+
- Codex backend management through `codex app-server` or `codex exec`.
|
|
14
|
+
- Plugin lifecycle hooks, HTTP router registration, and post-process hooks.
|
|
15
|
+
- Runtime safety defaults: `sandbox = "workspace-write"` and `approval_policy = "never"`.
|
|
16
|
+
|
|
17
|
+
Anything domain-specific belongs in a plugin. GitHub event parsing, polling, webhook signatures, `gh-llm` prompts, and review submission live in `packages/nyanpasu-github-reviewer`.
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
Nyanpasu uses TOML and Pydantic models. Core config lives at the top level; plugin config lives under `plugins.<plugin_id>`.
|
|
22
|
+
|
|
23
|
+
Nyanpasu has one user-facing home directory. Set `NYANPASU_HOME` to choose it; otherwise it defaults to `~/.nyanpasu`. Config is always read from `$NYANPASU_HOME/config.toml`, and runtime state, logs, SQLite, and managed worktrees also live under `$NYANPASU_HOME`.
|
|
24
|
+
|
|
25
|
+
`state_dir` is intentionally not a TOML option. To move both config and state, move `NYANPASU_HOME`.
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
enabled_plugins = ["github_reviewer"]
|
|
29
|
+
|
|
30
|
+
[server]
|
|
31
|
+
host = "127.0.0.1"
|
|
32
|
+
port = 8765
|
|
33
|
+
|
|
34
|
+
[codex]
|
|
35
|
+
backend = "app-server"
|
|
36
|
+
sandbox = "workspace-write"
|
|
37
|
+
approval_policy = "never"
|
|
38
|
+
command_timeout_seconds = 3600
|
|
39
|
+
|
|
40
|
+
[runtime]
|
|
41
|
+
concurrency = 4
|
|
42
|
+
coalesce_window_seconds = 600
|
|
43
|
+
clean_event_snapshots = true
|
|
44
|
+
|
|
45
|
+
[plugins.github_reviewer]
|
|
46
|
+
github_login = "your-github-login"
|
|
47
|
+
poll_interval_seconds = 600
|
|
48
|
+
poll_event_pages = 3
|
|
49
|
+
poll_max_events_per_cycle = 0
|
|
50
|
+
review_language = "Chinese"
|
|
51
|
+
|
|
52
|
+
[[plugins.github_reviewer.instruction_docs]]
|
|
53
|
+
name = "SOUL.md"
|
|
54
|
+
path = "/path/to/SOUL.md"
|
|
55
|
+
|
|
56
|
+
[plugins.github_reviewer.repos."owner/repo"]
|
|
57
|
+
local_path = "/path/to/repo"
|
|
58
|
+
github_remote = "https://github.com/owner/repo.git"
|
|
59
|
+
base_branches = ["main"]
|
|
60
|
+
|
|
61
|
+
[[plugins.github_reviewer.repos."owner/repo".instruction_docs]]
|
|
62
|
+
name = "AGENTS.md"
|
|
63
|
+
path = "/path/to/repo/AGENTS.md"
|
|
64
|
+
required = false
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Instruction documents are task-scoped. A plugin can attach files such as `SOUL.md`, `AGENTS.md`, or project policy notes to an `AgentTask`; the core runtime appends them only for that task before invoking Codex. They are not global Nyanpasu identity and are not hardcoded into the core or GitHub reviewer prompt.
|
|
68
|
+
|
|
69
|
+
## Run
|
|
70
|
+
|
|
71
|
+
Create `$NYANPASU_HOME/config.toml` from `examples/config.toml`, then start the agent service:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
export NYANPASU_HOME="$HOME/.nyanpasu"
|
|
75
|
+
uv run nyanpasu serve
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Inspect runtime state:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
uv run nyanpasu status
|
|
82
|
+
curl http://127.0.0.1:8765/tasks
|
|
83
|
+
curl http://127.0.0.1:8765/contexts
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The GitHub reviewer plugin mounts its webhook at:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
POST /plugins/github-reviewer/webhook
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The plugin can also start its poller during plugin setup. Events poll uses GitHub repository events as the source of truth: the first poll records the current repo event cursor without handling older events, later polls process matching events after that cursor, and already processed delivery ids are skipped. `poll_max_events_per_cycle = 0` means process every matching event in the poll window; set it to a positive number only when you intentionally want a per-cycle cap.
|
|
93
|
+
|
|
94
|
+
## Plugin Contract
|
|
95
|
+
|
|
96
|
+
A plugin exposes a `NyanpasuPlugin` through the `nyanpasu.plugins` entry point group.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
class MyPlugin:
|
|
100
|
+
id = "my_plugin"
|
|
101
|
+
config_model = MyPluginConfig
|
|
102
|
+
|
|
103
|
+
async def setup(self, runtime, config):
|
|
104
|
+
runtime.add_router(router, prefix="/plugins/my-plugin")
|
|
105
|
+
runtime.add_post_process_hook(self.id, self.after_task)
|
|
106
|
+
await runtime.submit(task)
|
|
107
|
+
|
|
108
|
+
async def shutdown(self):
|
|
109
|
+
...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Plugins send work to the core by creating `AgentTask`:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
AgentTask(
|
|
116
|
+
task_id="event-123",
|
|
117
|
+
action=TaskAction.RUN,
|
|
118
|
+
context_key="my-domain:object-456",
|
|
119
|
+
prompt="Review or handle this event.",
|
|
120
|
+
instruction_docs=[
|
|
121
|
+
InstructionDocument(
|
|
122
|
+
name="AGENTS.md",
|
|
123
|
+
source="/path/to/repo/AGENTS.md",
|
|
124
|
+
content="Follow this repository's local conventions.",
|
|
125
|
+
),
|
|
126
|
+
],
|
|
127
|
+
workspace=WorkspaceRef(
|
|
128
|
+
key="owner/repo",
|
|
129
|
+
local_path=Path("/path/to/repo"),
|
|
130
|
+
remote="https://github.com/owner/repo.git",
|
|
131
|
+
ref="pull/123/head",
|
|
132
|
+
revision="abc123",
|
|
133
|
+
),
|
|
134
|
+
dedupe_key="event-123",
|
|
135
|
+
metadata={"plugin_id": "my_plugin"},
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Core executes the task and calls post-process hooks registered for `metadata["plugin_id"]`.
|
|
140
|
+
|
|
141
|
+
By default, the task uses `workspace_policy = "context"`: Nyanpasu resets the context worktree to `workspace.revision` or `workspace.ref`, runs Codex there, and keeps that workspace for the next event in the same context. Plugins can opt into `workspace_policy = "event_snapshot"` only when they need a disposable per-event worktree.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "nyanpasu"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A plugin-oriented Codex-powered agent service."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"anyio>=4.12.0",
|
|
9
|
+
"fastapi>=0.125.0",
|
|
10
|
+
"loguru>=0.7.3",
|
|
11
|
+
"pydantic>=2.12.0",
|
|
12
|
+
"typer>=0.20.0",
|
|
13
|
+
"uvicorn>=0.38.0",
|
|
14
|
+
]
|
|
15
|
+
authors = [{ name = "Nyakku Shigure", email = "sigure.qaq@gmail.com" }]
|
|
16
|
+
keywords = []
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
"Programming Language :: Python",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/ShigureLab/nyanpasu"
|
|
33
|
+
Documentation = "https://github.com/ShigureLab/nyanpasu"
|
|
34
|
+
Repository = "https://github.com/ShigureLab/nyanpasu"
|
|
35
|
+
Issues = "https://github.com/ShigureLab/nyanpasu/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
nyanpasu = "nyanpasu.__main__:app"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"httpx>=0.28.0",
|
|
43
|
+
"nyanpasu-github-reviewer",
|
|
44
|
+
"ty>=0.0.39",
|
|
45
|
+
"ruff>=0.15.14",
|
|
46
|
+
"pytest>=9.0.3",
|
|
47
|
+
"pytest-rerunfailures>=16.3",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.uv.workspace]
|
|
51
|
+
members = ["packages/nyanpasu-github-reviewer"]
|
|
52
|
+
|
|
53
|
+
[tool.uv.sources]
|
|
54
|
+
nyanpasu = { workspace = true }
|
|
55
|
+
nyanpasu-github-reviewer = { workspace = true }
|
|
56
|
+
|
|
57
|
+
[tool.ruff]
|
|
58
|
+
line-length = 120
|
|
59
|
+
target-version = "py311"
|
|
60
|
+
|
|
61
|
+
[tool.ruff.lint]
|
|
62
|
+
select = [
|
|
63
|
+
# Pyflakes
|
|
64
|
+
"F",
|
|
65
|
+
# Pycodestyle
|
|
66
|
+
"E",
|
|
67
|
+
"W",
|
|
68
|
+
# Isort
|
|
69
|
+
"I",
|
|
70
|
+
# Comprehensions
|
|
71
|
+
"C4",
|
|
72
|
+
# Debugger
|
|
73
|
+
"T100",
|
|
74
|
+
# Pyupgrade
|
|
75
|
+
"UP",
|
|
76
|
+
# Flake8-pyi
|
|
77
|
+
"PYI",
|
|
78
|
+
# Bugbear
|
|
79
|
+
"B",
|
|
80
|
+
# Pylint
|
|
81
|
+
"PLE",
|
|
82
|
+
# Flake8-simplify
|
|
83
|
+
"SIM101",
|
|
84
|
+
# Flake8-use-pathlib
|
|
85
|
+
"PTH",
|
|
86
|
+
# Pygrep-hooks
|
|
87
|
+
"PGH004",
|
|
88
|
+
# Flake8-type-checking
|
|
89
|
+
"TC",
|
|
90
|
+
# Flake8-raise
|
|
91
|
+
"RSE",
|
|
92
|
+
# Refurb
|
|
93
|
+
"FURB",
|
|
94
|
+
# Flake8-future-annotations
|
|
95
|
+
"FA",
|
|
96
|
+
# Yesqa
|
|
97
|
+
"RUF100",
|
|
98
|
+
]
|
|
99
|
+
ignore = [
|
|
100
|
+
"E501", # line too long, duplicate with ruff fmt
|
|
101
|
+
]
|
|
102
|
+
future-annotations = true
|
|
103
|
+
|
|
104
|
+
[tool.ruff.lint.isort]
|
|
105
|
+
required-imports = ["from __future__ import annotations"]
|
|
106
|
+
known-first-party = ["nyanpasu", "nyanpasu_github_reviewer"]
|
|
107
|
+
combine-as-imports = true
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint.flake8-type-checking]
|
|
110
|
+
runtime-evaluated-decorators = ["typing.runtime_checkable", "runtime_checkable"]
|
|
111
|
+
runtime-evaluated-base-classes = ["pydantic.BaseModel"]
|
|
112
|
+
|
|
113
|
+
[build-system]
|
|
114
|
+
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
115
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import TYPE_CHECKING, Annotated, Any
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
import typer
|
|
9
|
+
import uvicorn
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from nyanpasu.agent import AgentService
|
|
13
|
+
from nyanpasu.config import ensure_state_dirs, load_config
|
|
14
|
+
from nyanpasu.models import AgentTask
|
|
15
|
+
from nyanpasu.store import StateStore
|
|
16
|
+
from nyanpasu.web import create_app
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(no_args_is_help=True)
|
|
22
|
+
LOG_FORMAT = (
|
|
23
|
+
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS Z}</green> | "
|
|
24
|
+
"<level>{level: <8}</level> | "
|
|
25
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
|
26
|
+
"<level>{message}</level>"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def configure_logging() -> None:
|
|
31
|
+
logger.remove()
|
|
32
|
+
logger.add(sys.stderr, level="INFO", format=LOG_FORMAT, backtrace=False, diagnose=False)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command()
|
|
36
|
+
def serve() -> None:
|
|
37
|
+
configure_logging()
|
|
38
|
+
resolved = load_config()
|
|
39
|
+
ensure_state_dirs(resolved)
|
|
40
|
+
uvicorn.run(create_app(resolved), host=resolved.server.host, port=resolved.server.port, log_level="info")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command()
|
|
44
|
+
def run_task(path: Annotated[Path, typer.Argument(help="Path to a JSON task file.")]) -> None:
|
|
45
|
+
configure_logging()
|
|
46
|
+
resolved = load_config()
|
|
47
|
+
ensure_state_dirs(resolved)
|
|
48
|
+
task = _task_from_json(json.loads(path.read_text(encoding="utf-8")))
|
|
49
|
+
|
|
50
|
+
async def run() -> None:
|
|
51
|
+
agent = AgentService(resolved)
|
|
52
|
+
try:
|
|
53
|
+
result = await agent.run_now(task)
|
|
54
|
+
typer.echo(
|
|
55
|
+
json.dumps(
|
|
56
|
+
{
|
|
57
|
+
"task_id": result.task_id,
|
|
58
|
+
"status": result.status.value,
|
|
59
|
+
"thread_id": result.thread_id,
|
|
60
|
+
"turn_id": result.turn_id,
|
|
61
|
+
},
|
|
62
|
+
ensure_ascii=False,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
finally:
|
|
66
|
+
await agent.shutdown()
|
|
67
|
+
|
|
68
|
+
anyio.run(run)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command()
|
|
72
|
+
def status(limit: int = 20) -> None:
|
|
73
|
+
resolved = load_config()
|
|
74
|
+
store = StateStore(resolved.db_path)
|
|
75
|
+
typer.echo(
|
|
76
|
+
json.dumps(
|
|
77
|
+
{
|
|
78
|
+
"contexts": [
|
|
79
|
+
{
|
|
80
|
+
"context_key": context.context_key,
|
|
81
|
+
"thread_id": context.thread_id,
|
|
82
|
+
"session_worktree": str(context.session_worktree) if context.session_worktree else None,
|
|
83
|
+
"workspace_key": context.workspace_key,
|
|
84
|
+
"revision": context.revision,
|
|
85
|
+
}
|
|
86
|
+
for context in store.list_contexts()
|
|
87
|
+
],
|
|
88
|
+
"tasks": [task.model_dump(mode="json") for task in store.recent_tasks(limit)],
|
|
89
|
+
},
|
|
90
|
+
indent=2,
|
|
91
|
+
ensure_ascii=False,
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _task_from_json(data: dict[str, Any]) -> AgentTask:
|
|
97
|
+
return AgentTask.model_validate(data)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main() -> None:
|
|
101
|
+
app()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|