multi-agent-prompt 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.
- multi_agent_prompt-0.1.0/.github/workflows/ci.yml +47 -0
- multi_agent_prompt-0.1.0/.github/workflows/publish.yml +52 -0
- multi_agent_prompt-0.1.0/.gitignore +17 -0
- multi_agent_prompt-0.1.0/PKG-INFO +231 -0
- multi_agent_prompt-0.1.0/README.md +220 -0
- multi_agent_prompt-0.1.0/pyproject.toml +39 -0
- multi_agent_prompt-0.1.0/skills/README.md +41 -0
- multi_agent_prompt-0.1.0/skills/prompt/SKILL.md +51 -0
- multi_agent_prompt-0.1.0/src/multi_agent_prompt/__init__.py +3 -0
- multi_agent_prompt-0.1.0/src/multi_agent_prompt/archive.py +91 -0
- multi_agent_prompt-0.1.0/src/multi_agent_prompt/cli.py +338 -0
- multi_agent_prompt-0.1.0/src/multi_agent_prompt/editor.py +315 -0
- multi_agent_prompt-0.1.0/src/multi_agent_prompt/paths.py +99 -0
- multi_agent_prompt-0.1.0/tests/test_archive.py +134 -0
- multi_agent_prompt-0.1.0/tests/test_cli.py +198 -0
- multi_agent_prompt-0.1.0/tests/test_editor.py +305 -0
- multi_agent_prompt-0.1.0/tests/test_editor_integration.py +334 -0
- multi_agent_prompt-0.1.0/tests/test_paths.py +100 -0
- multi_agent_prompt-0.1.0/tests/test_version.py +100 -0
- multi_agent_prompt-0.1.0/uv.lock +414 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
# https://github.com/actions/checkout
|
|
19
|
+
- uses: actions/checkout@v6
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
# The autosave/keymap/fold-on-paste integration tests drive a real
|
|
24
|
+
# Neovim (headless, and over a pseudo-terminal for anything that needs
|
|
25
|
+
# actual UI attachment — TextChanged and startinsert! don't fire
|
|
26
|
+
# without one). They skip gracefully if nvim is missing, but that
|
|
27
|
+
# would silently stop covering the thing this project mostly is.
|
|
28
|
+
- name: Install Neovim
|
|
29
|
+
run: sudo apt-get update && sudo apt-get install -y neovim
|
|
30
|
+
|
|
31
|
+
# https://github.com/astral-sh/setup-uv
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@v7
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
|
|
37
|
+
- name: Set up Python
|
|
38
|
+
run: uv python install
|
|
39
|
+
|
|
40
|
+
- name: Run ruff
|
|
41
|
+
run: uv run ruff check .
|
|
42
|
+
|
|
43
|
+
- name: Run mypy
|
|
44
|
+
run: uv run mypy src
|
|
45
|
+
|
|
46
|
+
- name: Run pytest
|
|
47
|
+
run: uv run pytest
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
env:
|
|
9
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
name: Build and publish
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write
|
|
17
|
+
contents: write
|
|
18
|
+
attestations: write
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
# https://github.com/actions/checkout
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
# See ci.yml: the integration tests need a real Neovim to run at all
|
|
27
|
+
# rather than skip.
|
|
28
|
+
- name: Install Neovim
|
|
29
|
+
run: sudo apt-get update && sudo apt-get install -y neovim
|
|
30
|
+
|
|
31
|
+
# https://github.com/astral-sh/setup-uv
|
|
32
|
+
- name: Install uv
|
|
33
|
+
uses: astral-sh/setup-uv@v7
|
|
34
|
+
with:
|
|
35
|
+
enable-cache: true
|
|
36
|
+
|
|
37
|
+
- name: Set up Python
|
|
38
|
+
run: uv python install
|
|
39
|
+
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: uv sync
|
|
42
|
+
|
|
43
|
+
- name: Run test suite
|
|
44
|
+
run: uv run pytest
|
|
45
|
+
|
|
46
|
+
- name: Build package
|
|
47
|
+
run: uv build
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
with:
|
|
52
|
+
verbose: true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
.venv
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.pyc
|
|
4
|
+
dist/
|
|
5
|
+
*.egg-info/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.ma/prompt/
|
|
10
|
+
|
|
11
|
+
# task-agent auto-registers any git repo it finds under ~/repos/ as one of
|
|
12
|
+
# its own task stores — unrelated to this project, not ours to track.
|
|
13
|
+
# docs/tasks is a symlink it manages, pointing at a machine-specific
|
|
14
|
+
# absolute path — must never be committed as a real symlink.
|
|
15
|
+
.env
|
|
16
|
+
.ta-config.json
|
|
17
|
+
docs/tasks
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: multi-agent-prompt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A crash-proof, autosaving prompt editor for AI coding agent CLIs — compose in your real editor, hand off with a slash command
|
|
5
|
+
Author-email: Mark Stouffer <1802850+InTEGr8or@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: rich>=13.9.4
|
|
9
|
+
Requires-Dist: verkit>=0.1.4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# Multi-Agent Prompt ✍️
|
|
13
|
+
|
|
14
|
+
A crash-proof, autosaving prompt editor for AI coding agent CLIs.
|
|
15
|
+
|
|
16
|
+
Chat text boxes lose long, carefully-composed prompts to a single wrong
|
|
17
|
+
keystroke, and give you almost no editing power while you write. `multi-agent-prompt`
|
|
18
|
+
moves composition out of the chat box entirely: write in a real Neovim, in a
|
|
19
|
+
split pane next to your agent session, autosaved to disk every couple of
|
|
20
|
+
seconds so a crash or a fat-fingered shortcut can never cost you more than a
|
|
21
|
+
moment of typing. When you're ready, hand it to the agent with `/prompt` —
|
|
22
|
+
which reads it, archives it, and clears the file in one step, so there's no
|
|
23
|
+
separate "now go clear it" to remember.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install multi-agent-prompt
|
|
31
|
+
# or
|
|
32
|
+
uv tool install multi-agent-prompt
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires [Neovim](https://neovim.io/) — the autosave hook is Lua, so plain
|
|
36
|
+
Vim isn't supported.
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
Open a split pane next to your agent CLI (Windows Terminal: `Alt+Shift+-`;
|
|
41
|
+
tmux: `<prefix> "`) in the **same working directory**, then run:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
map
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This opens `.ma/prompt/current.md` — resolved to the current project's git
|
|
48
|
+
root, so every pane in the same repo shares one scratch file — in your actual
|
|
49
|
+
`nvim`, your actual `init.lua`, your actual keymaps and plugins. No config
|
|
50
|
+
import step, no reimplemented Vim subset: it's just Neovim. You land in
|
|
51
|
+
**insert mode already**, cursor at the end of the buffer, so there's no `i`
|
|
52
|
+
to press before you start typing (`--no-insert` if you'd rather open in
|
|
53
|
+
normal mode). A buffer-local autocmd (scoped only to this one buffer — it
|
|
54
|
+
never touches how you edit anything else) autosaves ~2 seconds after you stop
|
|
55
|
+
typing, and immediately when you switch away from the pane
|
|
56
|
+
(`FocusLost`/`BufLeave`), so the save always beats you to the chat window.
|
|
57
|
+
|
|
58
|
+
When you're done, switch back to the agent pane and either:
|
|
59
|
+
|
|
60
|
+
- Type `/prompt` (if the agent's install includes the skill — see below) —
|
|
61
|
+
reads the draft, archives it, and clears the file, all in one step, or
|
|
62
|
+
- Reference `@prompt.md` / `@.ma/prompt/current.md` directly, if your agent
|
|
63
|
+
supports file mentions and can see gitignored files — this is a plain
|
|
64
|
+
read, so unlike `/prompt` it doesn't archive or clear anything.
|
|
65
|
+
|
|
66
|
+
Pasting a long CLI transcript or diff in? Anything **6 lines or more**
|
|
67
|
+
(bracketed paste, `"+p`, `"*p` — any paste source) auto-folds, closed, so it
|
|
68
|
+
doesn't bury the rest of what you're writing — `za`/`zo`/`zc` (standard Vim)
|
|
69
|
+
toggle it open.
|
|
70
|
+
|
|
71
|
+
Also want to browse past drafts without leaving the editor? A buffer-local
|
|
72
|
+
keymap — `<leader>ph` by default — lists archived drafts (newest first) in a
|
|
73
|
+
split; `<CR>` opens one, read-only. Forgot what any of this is bound to?
|
|
74
|
+
Press `g?` — it shows exactly the keymaps active for *this* session
|
|
75
|
+
(respecting any `--clear-key`/`--history-key` overrides, and omitting
|
|
76
|
+
whichever ones you disabled):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
map # open the scratch file (same as `map edit`)
|
|
80
|
+
map where # print the resolved file path
|
|
81
|
+
map show # print its current content to stdout (does not clear it)
|
|
82
|
+
map pop # print it, archive it, and clear it — what /prompt uses
|
|
83
|
+
map clear # archive the current draft, then empty it, without printing it
|
|
84
|
+
map --clean # skip your init.lua entirely (-u NONE) for faster startup
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
map edit --clear-key '<F5>' # rebind the in-editor clear keymap
|
|
89
|
+
map edit --no-clear-key # don't register it at all
|
|
90
|
+
map edit --history-key '<F6>' # rebind the in-editor history-browse keymap
|
|
91
|
+
map edit --no-history-key # don't register it at all
|
|
92
|
+
map edit --help-key '<F1>' # rebind the in-editor g? cheatsheet keymap
|
|
93
|
+
map edit --no-help-key # don't register it at all
|
|
94
|
+
map edit --fold-threshold 3 # auto-fold pastes of 3+ lines instead of 6
|
|
95
|
+
map edit --no-fold-paste # don't auto-fold pastes at all
|
|
96
|
+
map edit --no-insert # open in normal mode instead of insert mode
|
|
97
|
+
map clear --keep 20 # override how many archived drafts to retain
|
|
98
|
+
map clear --no-archive # discard instead of archiving (e.g. it had a secret in it)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`map pop` accepts the same `--keep`/`--no-archive` as `map clear`.
|
|
102
|
+
|
|
103
|
+
### The archive
|
|
104
|
+
|
|
105
|
+
`map pop`/`map clear` — from the CLI or the in-editor keymap, which shells
|
|
106
|
+
out to `map clear` — never actually discard a non-empty draft; they move it
|
|
107
|
+
to `.ma/prompt/archive/<timestamp>-<seq>.md` first, then prune that archive
|
|
108
|
+
down to the most recent entries. Pruning is by **count**, not age (`--keep` /
|
|
109
|
+
`MAP_ARCHIVE_KEEP` env var, default **10**) — simpler to reason about than a
|
|
110
|
+
retention window, and it doesn't depend on the clock. Use `--no-archive` for
|
|
111
|
+
the one case that shouldn't be kept anywhere: you pasted a secret and want it
|
|
112
|
+
actually gone.
|
|
113
|
+
|
|
114
|
+
## Performance
|
|
115
|
+
|
|
116
|
+
A `map` session opens your *entire* Neovim config, which is exactly the
|
|
117
|
+
point — but if that config is large, startup isn't free. Two things worth
|
|
118
|
+
knowing, from actually measuring it (not guessing):
|
|
119
|
+
|
|
120
|
+
- **It's not a one-time thing.** Whatever makes your first `map` slow will
|
|
121
|
+
make every subsequent one about equally slow, unless something changes
|
|
122
|
+
(lazy.nvim itself finishes installing/compiling once; LSP servers and
|
|
123
|
+
large plugin trees generally don't get meaningfully faster after that).
|
|
124
|
+
- **On WSL specifically, check for `vim.opt.clipboard = 'unnamedplus'` (or
|
|
125
|
+
similar) in your own init.lua.** Setting that option makes Neovim probe for
|
|
126
|
+
a clipboard provider immediately, and on WSL that probe walks every `PATH`
|
|
127
|
+
entry checking `executable()` — including everything under `/mnt/c/...`,
|
|
128
|
+
which is slow cross-filesystem interop. Measured on this project's own dev
|
|
129
|
+
machine: ~900ms startup with that line in play, ~160ms with the Windows
|
|
130
|
+
`PATH` entries stripped, ~4ms with `-u NONE`. That's not multi-agent-prompt
|
|
131
|
+
overhead; it's one line in a personal init.lua interacting badly with WSL,
|
|
132
|
+
and fixing it (or guarding it — see `MAP_SESSION` below) speeds up *every*
|
|
133
|
+
Neovim session, not just `map`.
|
|
134
|
+
- **`map --clean` (`-u NONE`)** sidesteps all of it by skipping your config
|
|
135
|
+
entirely — the built-in fast path when you don't need your plugins for a
|
|
136
|
+
quick edit.
|
|
137
|
+
- **`MAP_SESSION=1`** is set in the environment of every nvim `map` launches.
|
|
138
|
+
Nothing in this package reads it — it's there so you can guard an
|
|
139
|
+
expensive line in your *own* init.lua behind
|
|
140
|
+
`if not vim.env.MAP_SESSION then ... end`, if you want full-config speed
|
|
141
|
+
back without giving up faster `map` startup. That's your config to edit,
|
|
142
|
+
not something this tool does for you. This isn't just for the clipboard
|
|
143
|
+
line above — a scratch prompt buffer has no use for a file-tree sidebar or
|
|
144
|
+
git tooling either, and with [lazy.nvim](https://github.com/folke/lazy.nvim)
|
|
145
|
+
you can skip those specific plugins the same way, with `cond` on the spec:
|
|
146
|
+
|
|
147
|
+
```lua
|
|
148
|
+
return {
|
|
149
|
+
'nvim-neo-tree/neo-tree.nvim',
|
|
150
|
+
cond = not vim.env.MAP_SESSION,
|
|
151
|
+
-- ...
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Confirmed on this project's own dev config: with `neo-tree.nvim` and
|
|
156
|
+
`gitsigns.nvim` guarded this way, `<leader>e` (NeoTree's toggle) becomes a
|
|
157
|
+
no-op in a `map` session, and their entire module trees (dozens of
|
|
158
|
+
individual `require()` calls each) simply don't load — real startup
|
|
159
|
+
savings, not just fewer keymaps to trip over. `cond` is evaluated on every
|
|
160
|
+
start, so this takes effect immediately; no `:Lazy sync`/restart needed.
|
|
161
|
+
- **It's specifically a startup cost, not a per-save one.** The clipboard
|
|
162
|
+
probe runs once, while your init.lua is sourced, not again on autosave, on
|
|
163
|
+
`FocusLost`, or when you switch panes and type `/prompt` — that last step
|
|
164
|
+
never touches nvim at all (`map pop`/reading the file is a separate,
|
|
165
|
+
plain filesystem read and write). If a session still feels slow at the
|
|
166
|
+
hand-off moment specifically, that's terminal pane-switching or the
|
|
167
|
+
agent's own slash-command overhead, not this tool or Neovim.
|
|
168
|
+
- **`/prompt`'s own overhead is small, and it isn't Neovim's.** Measured on
|
|
169
|
+
this project's own dev machine: `map pop` runs in ~60-85ms end to end —
|
|
170
|
+
bare `python3 -c pass` alone is ~30ms of that, this package's own imports
|
|
171
|
+
add maybe another ~20ms, and the rest is the file read/archive/write. If
|
|
172
|
+
`/prompt` still feels slow, that latency lives in the *agent host's* own
|
|
173
|
+
tool-call round trip (spawning/sandboxing the shell command), not in
|
|
174
|
+
anything this package does — there's no nvim process in this path at all.
|
|
175
|
+
The one thing the `prompt` skill itself controls: it runs `map pop`
|
|
176
|
+
directly rather than checking `which map` first, since that check is a
|
|
177
|
+
second subprocess spawn that's a wasted round trip almost every time.
|
|
178
|
+
|
|
179
|
+
## Agent integration
|
|
180
|
+
|
|
181
|
+
`skills/prompt/` is a portable [Agent Skill](https://code.claude.com/docs/en/skills)
|
|
182
|
+
that teaches a host to read `.ma/prompt/current.md` and treat its content as
|
|
183
|
+
the user's message when they type `/prompt`. See
|
|
184
|
+
[`skills/README.md`](./skills/README.md) for install paths.
|
|
185
|
+
|
|
186
|
+
## Why not tmux `send-keys` / auto-injection?
|
|
187
|
+
|
|
188
|
+
That's the obvious next step — save, close the pane, and have the tool type
|
|
189
|
+
`/prompt` into the neighboring agent pane for you — but it needs a reliable
|
|
190
|
+
way to identify *which* pane is running the agent, which varies by terminal
|
|
191
|
+
(tmux panes, Windows Terminal panes, and OS-level input injection like
|
|
192
|
+
`xdotool`/`wtype` all solve a different half of that problem). The manual
|
|
193
|
+
handoff above works everywhere today; auto-injection is being explored as a
|
|
194
|
+
follow-up, potentially building on [`multi-agent-registry`](https://github.com/InTEGr8or/multi-agent-registry)'s
|
|
195
|
+
chat discovery to identify a live agent session (not just a recent one) in
|
|
196
|
+
the same directory.
|
|
197
|
+
|
|
198
|
+
If/when that lands: prefer sending a widely-supported "submit" keystroke —
|
|
199
|
+
`Ctrl+Enter` is the closest thing to a universal convention across chat
|
|
200
|
+
input boxes — into the target pane after typing `/prompt`, over anything
|
|
201
|
+
input-method-specific, so the same injection code has a chance of working
|
|
202
|
+
across agents rather than being re-tuned per host.
|
|
203
|
+
|
|
204
|
+
## Design notes
|
|
205
|
+
|
|
206
|
+
- **The prompt file is per-project, not global.** `.ma/prompt/current.md`
|
|
207
|
+
resolves against the nearest `.git` root, so working on two projects in two
|
|
208
|
+
terminal tabs never mixes up their scratch files.
|
|
209
|
+
- **`.ma/` is a shared root, not this tool's alone.** It's the umbrella
|
|
210
|
+
directory for the whole multi-agent-* line (`map`'s CLI alias is the shared
|
|
211
|
+
`ma` prefix of `map`/`mar`/`maa`) — one dotfolder instead of one per
|
|
212
|
+
product. Only `.ma/prompt/` is this tool's; a sibling product could use
|
|
213
|
+
`.ma/registry/`, etc., without colliding. Note that `task-agent`'s existing
|
|
214
|
+
`.task-agent/` convention predates this and stays as-is — migrating an
|
|
215
|
+
already-shipped tool's config layout is a separate, larger decision.
|
|
216
|
+
- **`.gitignore` is managed for you.** The first time `map edit` runs in a
|
|
217
|
+
git repo, it appends `.ma/prompt/` to `.gitignore` if it isn't already
|
|
218
|
+
covered — including by a broader pre-existing `.ma/` entry.
|
|
219
|
+
- **Reading is non-destructive; handing off isn't, but it never discards
|
|
220
|
+
either.** `map show` and `@prompt.md` (file mention) just print/read — the
|
|
221
|
+
file is untouched either way. `/prompt` (`map pop`) clears it, because the
|
|
222
|
+
whole point is not needing a separate "now go clear it" step — but it
|
|
223
|
+
archives first, so "cleared" never means "gone". `--no-archive` is the
|
|
224
|
+
explicit opt-out for content that shouldn't be kept anywhere at all.
|
|
225
|
+
- **The editor notices when the file changes out from under it.** `/prompt`
|
|
226
|
+
runs `map pop` from the *agent's* process, not from inside the running
|
|
227
|
+
nvim session — so if you're still looking at that buffer when you hand a
|
|
228
|
+
draft off, it auto-reloads (`autoread` + `checktime` on
|
|
229
|
+
`FocusGained`/`BufEnter`) the next time you focus it, rather than keep
|
|
230
|
+
showing the text you already submitted. It won't discard anything you've
|
|
231
|
+
since typed there unsaved.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Multi-Agent Prompt ✍️
|
|
2
|
+
|
|
3
|
+
A crash-proof, autosaving prompt editor for AI coding agent CLIs.
|
|
4
|
+
|
|
5
|
+
Chat text boxes lose long, carefully-composed prompts to a single wrong
|
|
6
|
+
keystroke, and give you almost no editing power while you write. `multi-agent-prompt`
|
|
7
|
+
moves composition out of the chat box entirely: write in a real Neovim, in a
|
|
8
|
+
split pane next to your agent session, autosaved to disk every couple of
|
|
9
|
+
seconds so a crash or a fat-fingered shortcut can never cost you more than a
|
|
10
|
+
moment of typing. When you're ready, hand it to the agent with `/prompt` —
|
|
11
|
+
which reads it, archives it, and clears the file in one step, so there's no
|
|
12
|
+
separate "now go clear it" to remember.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install multi-agent-prompt
|
|
20
|
+
# or
|
|
21
|
+
uv tool install multi-agent-prompt
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires [Neovim](https://neovim.io/) — the autosave hook is Lua, so plain
|
|
25
|
+
Vim isn't supported.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Open a split pane next to your agent CLI (Windows Terminal: `Alt+Shift+-`;
|
|
30
|
+
tmux: `<prefix> "`) in the **same working directory**, then run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
map
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This opens `.ma/prompt/current.md` — resolved to the current project's git
|
|
37
|
+
root, so every pane in the same repo shares one scratch file — in your actual
|
|
38
|
+
`nvim`, your actual `init.lua`, your actual keymaps and plugins. No config
|
|
39
|
+
import step, no reimplemented Vim subset: it's just Neovim. You land in
|
|
40
|
+
**insert mode already**, cursor at the end of the buffer, so there's no `i`
|
|
41
|
+
to press before you start typing (`--no-insert` if you'd rather open in
|
|
42
|
+
normal mode). A buffer-local autocmd (scoped only to this one buffer — it
|
|
43
|
+
never touches how you edit anything else) autosaves ~2 seconds after you stop
|
|
44
|
+
typing, and immediately when you switch away from the pane
|
|
45
|
+
(`FocusLost`/`BufLeave`), so the save always beats you to the chat window.
|
|
46
|
+
|
|
47
|
+
When you're done, switch back to the agent pane and either:
|
|
48
|
+
|
|
49
|
+
- Type `/prompt` (if the agent's install includes the skill — see below) —
|
|
50
|
+
reads the draft, archives it, and clears the file, all in one step, or
|
|
51
|
+
- Reference `@prompt.md` / `@.ma/prompt/current.md` directly, if your agent
|
|
52
|
+
supports file mentions and can see gitignored files — this is a plain
|
|
53
|
+
read, so unlike `/prompt` it doesn't archive or clear anything.
|
|
54
|
+
|
|
55
|
+
Pasting a long CLI transcript or diff in? Anything **6 lines or more**
|
|
56
|
+
(bracketed paste, `"+p`, `"*p` — any paste source) auto-folds, closed, so it
|
|
57
|
+
doesn't bury the rest of what you're writing — `za`/`zo`/`zc` (standard Vim)
|
|
58
|
+
toggle it open.
|
|
59
|
+
|
|
60
|
+
Also want to browse past drafts without leaving the editor? A buffer-local
|
|
61
|
+
keymap — `<leader>ph` by default — lists archived drafts (newest first) in a
|
|
62
|
+
split; `<CR>` opens one, read-only. Forgot what any of this is bound to?
|
|
63
|
+
Press `g?` — it shows exactly the keymaps active for *this* session
|
|
64
|
+
(respecting any `--clear-key`/`--history-key` overrides, and omitting
|
|
65
|
+
whichever ones you disabled):
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
map # open the scratch file (same as `map edit`)
|
|
69
|
+
map where # print the resolved file path
|
|
70
|
+
map show # print its current content to stdout (does not clear it)
|
|
71
|
+
map pop # print it, archive it, and clear it — what /prompt uses
|
|
72
|
+
map clear # archive the current draft, then empty it, without printing it
|
|
73
|
+
map --clean # skip your init.lua entirely (-u NONE) for faster startup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
map edit --clear-key '<F5>' # rebind the in-editor clear keymap
|
|
78
|
+
map edit --no-clear-key # don't register it at all
|
|
79
|
+
map edit --history-key '<F6>' # rebind the in-editor history-browse keymap
|
|
80
|
+
map edit --no-history-key # don't register it at all
|
|
81
|
+
map edit --help-key '<F1>' # rebind the in-editor g? cheatsheet keymap
|
|
82
|
+
map edit --no-help-key # don't register it at all
|
|
83
|
+
map edit --fold-threshold 3 # auto-fold pastes of 3+ lines instead of 6
|
|
84
|
+
map edit --no-fold-paste # don't auto-fold pastes at all
|
|
85
|
+
map edit --no-insert # open in normal mode instead of insert mode
|
|
86
|
+
map clear --keep 20 # override how many archived drafts to retain
|
|
87
|
+
map clear --no-archive # discard instead of archiving (e.g. it had a secret in it)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`map pop` accepts the same `--keep`/`--no-archive` as `map clear`.
|
|
91
|
+
|
|
92
|
+
### The archive
|
|
93
|
+
|
|
94
|
+
`map pop`/`map clear` — from the CLI or the in-editor keymap, which shells
|
|
95
|
+
out to `map clear` — never actually discard a non-empty draft; they move it
|
|
96
|
+
to `.ma/prompt/archive/<timestamp>-<seq>.md` first, then prune that archive
|
|
97
|
+
down to the most recent entries. Pruning is by **count**, not age (`--keep` /
|
|
98
|
+
`MAP_ARCHIVE_KEEP` env var, default **10**) — simpler to reason about than a
|
|
99
|
+
retention window, and it doesn't depend on the clock. Use `--no-archive` for
|
|
100
|
+
the one case that shouldn't be kept anywhere: you pasted a secret and want it
|
|
101
|
+
actually gone.
|
|
102
|
+
|
|
103
|
+
## Performance
|
|
104
|
+
|
|
105
|
+
A `map` session opens your *entire* Neovim config, which is exactly the
|
|
106
|
+
point — but if that config is large, startup isn't free. Two things worth
|
|
107
|
+
knowing, from actually measuring it (not guessing):
|
|
108
|
+
|
|
109
|
+
- **It's not a one-time thing.** Whatever makes your first `map` slow will
|
|
110
|
+
make every subsequent one about equally slow, unless something changes
|
|
111
|
+
(lazy.nvim itself finishes installing/compiling once; LSP servers and
|
|
112
|
+
large plugin trees generally don't get meaningfully faster after that).
|
|
113
|
+
- **On WSL specifically, check for `vim.opt.clipboard = 'unnamedplus'` (or
|
|
114
|
+
similar) in your own init.lua.** Setting that option makes Neovim probe for
|
|
115
|
+
a clipboard provider immediately, and on WSL that probe walks every `PATH`
|
|
116
|
+
entry checking `executable()` — including everything under `/mnt/c/...`,
|
|
117
|
+
which is slow cross-filesystem interop. Measured on this project's own dev
|
|
118
|
+
machine: ~900ms startup with that line in play, ~160ms with the Windows
|
|
119
|
+
`PATH` entries stripped, ~4ms with `-u NONE`. That's not multi-agent-prompt
|
|
120
|
+
overhead; it's one line in a personal init.lua interacting badly with WSL,
|
|
121
|
+
and fixing it (or guarding it — see `MAP_SESSION` below) speeds up *every*
|
|
122
|
+
Neovim session, not just `map`.
|
|
123
|
+
- **`map --clean` (`-u NONE`)** sidesteps all of it by skipping your config
|
|
124
|
+
entirely — the built-in fast path when you don't need your plugins for a
|
|
125
|
+
quick edit.
|
|
126
|
+
- **`MAP_SESSION=1`** is set in the environment of every nvim `map` launches.
|
|
127
|
+
Nothing in this package reads it — it's there so you can guard an
|
|
128
|
+
expensive line in your *own* init.lua behind
|
|
129
|
+
`if not vim.env.MAP_SESSION then ... end`, if you want full-config speed
|
|
130
|
+
back without giving up faster `map` startup. That's your config to edit,
|
|
131
|
+
not something this tool does for you. This isn't just for the clipboard
|
|
132
|
+
line above — a scratch prompt buffer has no use for a file-tree sidebar or
|
|
133
|
+
git tooling either, and with [lazy.nvim](https://github.com/folke/lazy.nvim)
|
|
134
|
+
you can skip those specific plugins the same way, with `cond` on the spec:
|
|
135
|
+
|
|
136
|
+
```lua
|
|
137
|
+
return {
|
|
138
|
+
'nvim-neo-tree/neo-tree.nvim',
|
|
139
|
+
cond = not vim.env.MAP_SESSION,
|
|
140
|
+
-- ...
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Confirmed on this project's own dev config: with `neo-tree.nvim` and
|
|
145
|
+
`gitsigns.nvim` guarded this way, `<leader>e` (NeoTree's toggle) becomes a
|
|
146
|
+
no-op in a `map` session, and their entire module trees (dozens of
|
|
147
|
+
individual `require()` calls each) simply don't load — real startup
|
|
148
|
+
savings, not just fewer keymaps to trip over. `cond` is evaluated on every
|
|
149
|
+
start, so this takes effect immediately; no `:Lazy sync`/restart needed.
|
|
150
|
+
- **It's specifically a startup cost, not a per-save one.** The clipboard
|
|
151
|
+
probe runs once, while your init.lua is sourced, not again on autosave, on
|
|
152
|
+
`FocusLost`, or when you switch panes and type `/prompt` — that last step
|
|
153
|
+
never touches nvim at all (`map pop`/reading the file is a separate,
|
|
154
|
+
plain filesystem read and write). If a session still feels slow at the
|
|
155
|
+
hand-off moment specifically, that's terminal pane-switching or the
|
|
156
|
+
agent's own slash-command overhead, not this tool or Neovim.
|
|
157
|
+
- **`/prompt`'s own overhead is small, and it isn't Neovim's.** Measured on
|
|
158
|
+
this project's own dev machine: `map pop` runs in ~60-85ms end to end —
|
|
159
|
+
bare `python3 -c pass` alone is ~30ms of that, this package's own imports
|
|
160
|
+
add maybe another ~20ms, and the rest is the file read/archive/write. If
|
|
161
|
+
`/prompt` still feels slow, that latency lives in the *agent host's* own
|
|
162
|
+
tool-call round trip (spawning/sandboxing the shell command), not in
|
|
163
|
+
anything this package does — there's no nvim process in this path at all.
|
|
164
|
+
The one thing the `prompt` skill itself controls: it runs `map pop`
|
|
165
|
+
directly rather than checking `which map` first, since that check is a
|
|
166
|
+
second subprocess spawn that's a wasted round trip almost every time.
|
|
167
|
+
|
|
168
|
+
## Agent integration
|
|
169
|
+
|
|
170
|
+
`skills/prompt/` is a portable [Agent Skill](https://code.claude.com/docs/en/skills)
|
|
171
|
+
that teaches a host to read `.ma/prompt/current.md` and treat its content as
|
|
172
|
+
the user's message when they type `/prompt`. See
|
|
173
|
+
[`skills/README.md`](./skills/README.md) for install paths.
|
|
174
|
+
|
|
175
|
+
## Why not tmux `send-keys` / auto-injection?
|
|
176
|
+
|
|
177
|
+
That's the obvious next step — save, close the pane, and have the tool type
|
|
178
|
+
`/prompt` into the neighboring agent pane for you — but it needs a reliable
|
|
179
|
+
way to identify *which* pane is running the agent, which varies by terminal
|
|
180
|
+
(tmux panes, Windows Terminal panes, and OS-level input injection like
|
|
181
|
+
`xdotool`/`wtype` all solve a different half of that problem). The manual
|
|
182
|
+
handoff above works everywhere today; auto-injection is being explored as a
|
|
183
|
+
follow-up, potentially building on [`multi-agent-registry`](https://github.com/InTEGr8or/multi-agent-registry)'s
|
|
184
|
+
chat discovery to identify a live agent session (not just a recent one) in
|
|
185
|
+
the same directory.
|
|
186
|
+
|
|
187
|
+
If/when that lands: prefer sending a widely-supported "submit" keystroke —
|
|
188
|
+
`Ctrl+Enter` is the closest thing to a universal convention across chat
|
|
189
|
+
input boxes — into the target pane after typing `/prompt`, over anything
|
|
190
|
+
input-method-specific, so the same injection code has a chance of working
|
|
191
|
+
across agents rather than being re-tuned per host.
|
|
192
|
+
|
|
193
|
+
## Design notes
|
|
194
|
+
|
|
195
|
+
- **The prompt file is per-project, not global.** `.ma/prompt/current.md`
|
|
196
|
+
resolves against the nearest `.git` root, so working on two projects in two
|
|
197
|
+
terminal tabs never mixes up their scratch files.
|
|
198
|
+
- **`.ma/` is a shared root, not this tool's alone.** It's the umbrella
|
|
199
|
+
directory for the whole multi-agent-* line (`map`'s CLI alias is the shared
|
|
200
|
+
`ma` prefix of `map`/`mar`/`maa`) — one dotfolder instead of one per
|
|
201
|
+
product. Only `.ma/prompt/` is this tool's; a sibling product could use
|
|
202
|
+
`.ma/registry/`, etc., without colliding. Note that `task-agent`'s existing
|
|
203
|
+
`.task-agent/` convention predates this and stays as-is — migrating an
|
|
204
|
+
already-shipped tool's config layout is a separate, larger decision.
|
|
205
|
+
- **`.gitignore` is managed for you.** The first time `map edit` runs in a
|
|
206
|
+
git repo, it appends `.ma/prompt/` to `.gitignore` if it isn't already
|
|
207
|
+
covered — including by a broader pre-existing `.ma/` entry.
|
|
208
|
+
- **Reading is non-destructive; handing off isn't, but it never discards
|
|
209
|
+
either.** `map show` and `@prompt.md` (file mention) just print/read — the
|
|
210
|
+
file is untouched either way. `/prompt` (`map pop`) clears it, because the
|
|
211
|
+
whole point is not needing a separate "now go clear it" step — but it
|
|
212
|
+
archives first, so "cleared" never means "gone". `--no-archive` is the
|
|
213
|
+
explicit opt-out for content that shouldn't be kept anywhere at all.
|
|
214
|
+
- **The editor notices when the file changes out from under it.** `/prompt`
|
|
215
|
+
runs `map pop` from the *agent's* process, not from inside the running
|
|
216
|
+
nvim session — so if you're still looking at that buffer when you hand a
|
|
217
|
+
draft off, it auto-reloads (`autoread` + `checktime` on
|
|
218
|
+
`FocusGained`/`BufEnter`) the next time you focus it, rather than keep
|
|
219
|
+
showing the text you already submitted. It won't discard anything you've
|
|
220
|
+
since typed there unsaved.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "multi-agent-prompt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A crash-proof, autosaving prompt editor for AI coding agent CLIs — compose in your real editor, hand off with a slash command"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Mark Stouffer", email = "1802850+InTEGr8or@users.noreply.github.com" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"verkit>=0.1.4",
|
|
17
|
+
"rich>=13.9.4",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
multi-agent-prompt = "multi_agent_prompt.cli:main"
|
|
22
|
+
map = "multi_agent_prompt.cli:main"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/multi_agent_prompt"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.metadata]
|
|
28
|
+
allow-direct-references = true
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
32
|
+
pythonpath = ["src"]
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0.0",
|
|
37
|
+
"ruff>=0.8.0",
|
|
38
|
+
"mypy>=1.13.0",
|
|
39
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Multi-Agent Prompt portable skill
|
|
2
|
+
|
|
3
|
+
One skill (`prompt`) that teaches a host to read the user's draft from
|
|
4
|
+
`.ma/prompt/current.md` — the scratch file `map` edits — treat it as their
|
|
5
|
+
next message, and clear it for the next one (via `map pop`, which archives
|
|
6
|
+
before it clears) when they type `/prompt`.
|
|
7
|
+
|
|
8
|
+
## Manual install
|
|
9
|
+
|
|
10
|
+
Skills are a plain directory. Copy or symlink it into the host's skills path.
|
|
11
|
+
|
|
12
|
+
### Claude Code
|
|
13
|
+
|
|
14
|
+
Project-local (recommended):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mkdir -p .claude/skills
|
|
18
|
+
ln -sfn ../../skills/prompt .claude/skills/prompt
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
User-global:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mkdir -p ~/.claude/skills
|
|
25
|
+
cp -a skills/prompt ~/.claude/skills/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Antigravity CLI (`agy`)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
mkdir -p .agents/skills
|
|
32
|
+
cp -a skills/prompt .agents/skills/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Other hosts (Cursor, Copilot, Grok, OpenCode)
|
|
36
|
+
|
|
37
|
+
Copy `skills/prompt` into that host's skills location. If the host doesn't
|
|
38
|
+
support skills at all, `@prompt.md` / `@.ma/prompt/current.md` (file mention)
|
|
39
|
+
works anywhere the host can see the file — including gitignored files, in
|
|
40
|
+
most hosts. Note that a file mention just reads the content; it doesn't run
|
|
41
|
+
`map pop`, so unlike `/prompt` it won't archive or clear the draft.
|