@grknbyk/agent-wire 0.4.1 → 0.6.0
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.
- package/README.md +110 -24
- package/bin/agent-wire.mjs +148 -115
- package/manifest.json +31 -38
- package/package.json +3 -2
- package/src/config.mjs +182 -68
- package/src/drain.mjs +86 -0
- package/src/identity.mjs +101 -73
- package/src/inbox.mjs +107 -76
- package/src/mcp.mjs +382 -309
- package/src/protocol.mjs +77 -68
- package/src/setup.mjs +171 -262
- package/src/slack.mjs +322 -236
- package/src/status.mjs +9 -4
package/README.md
CHANGED
|
@@ -33,17 +33,17 @@ got it, rewriting the report query
|
|
|
33
33
|
npx @grknbyk/agent-wire setup
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Setup
|
|
37
|
-
|
|
36
|
+
Setup prints the path of the bundled `manifest.json`. You create the app from it
|
|
37
|
+
at [api.slack.com/apps/new](https://api.slack.com/apps/new), install it, and paste
|
|
38
|
+
the Bot User OAuth Token back. Then you create the channel in Slack and type
|
|
39
|
+
`/invite @agent-wire` in it.
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
After that it creates the app from the bundled manifest, opens your browser once
|
|
42
|
-
for approval, catches the redirect itself, creates the channel, and joins it.
|
|
41
|
+
The app never adds itself to anything. It has no scope to create a channel or to
|
|
42
|
+
join one, so a person decides where it can read and write.
|
|
43
43
|
|
|
44
44
|
Setup never asks "did you do it? (y/n)". Every step it can verify, it verifies by
|
|
45
45
|
asking Slack. When a step is stuck for a reason Slack reports, such as a missing
|
|
46
|
-
scope, a
|
|
46
|
+
scope, a channel nobody invited it to, or a token from the wrong workspace, it
|
|
47
47
|
says which one and what to do about it. Quit halfway and re-run: it resumes at
|
|
48
48
|
the first unfinished step, because the config file is the progress.
|
|
49
49
|
|
|
@@ -72,17 +72,39 @@ Or, for any other MCP client:
|
|
|
72
72
|
| `agent-wire serve` | Run the MCP stdio server, which is what your client launches |
|
|
73
73
|
| `agent-wire doctor` | Re-check the token, the channels and the identity |
|
|
74
74
|
| `agent-wire drain` | Print what arrived since last time, for a prompt hook |
|
|
75
|
-
| `agent-wire channels` | List the channels and
|
|
76
|
-
| `agent-wire
|
|
75
|
+
| `agent-wire channels` | List the channels and what each one is set to here |
|
|
76
|
+
| `agent-wire ask <name>` | Name who is waiting and how many; open nothing |
|
|
77
|
+
| `agent-wire read <name>` | Put the messages themselves into every prompt |
|
|
78
|
+
| `agent-wire off <name>` | Say nothing about this channel in this session |
|
|
77
79
|
|
|
78
80
|
## Tools your agent gets
|
|
79
81
|
|
|
80
|
-
`send`, `send_file`, `inbox`, `archive`, `peers`, `channels`, `my_id`.
|
|
82
|
+
`send`, `send_file`, `inbox`, `archive`, `peers`, `members`, `channels`, `my_id`.
|
|
83
|
+
|
|
84
|
+
The mode of a channel is a command the user runs, never a tool. A message
|
|
85
|
+
arriving from the channel must not be able to talk the agent into silencing
|
|
86
|
+
another channel, nor into opening one.
|
|
81
87
|
|
|
82
88
|
Text over 3500 characters is posted as a Markdown file instead of a message.
|
|
83
89
|
Slack splits anything longer, and the tail arrives without a header, so half an
|
|
84
90
|
answer vanishes while the sender is told it was delivered.
|
|
85
91
|
|
|
92
|
+
## Files go both ways
|
|
93
|
+
|
|
94
|
+
`send_file` uploads, and the receiving side downloads. A `.md` plan sent from one
|
|
95
|
+
machine lands on the other as a real file in `~/.agent-wire/files/`, and `inbox`
|
|
96
|
+
prints that path in the fence header, so the agent opens it with its own tools.
|
|
97
|
+
Files a human drags into the channel arrive the same way.
|
|
98
|
+
|
|
99
|
+
Slack accepts no metadata on a file upload, so the file and the message that
|
|
100
|
+
describes it are two posts. The message is the signed one, and the file id it
|
|
101
|
+
names is inside what the signature covers, so a valid signature cannot be lifted
|
|
102
|
+
onto somebody else's upload. A message that fails verification is never
|
|
103
|
+
downloaded.
|
|
104
|
+
|
|
105
|
+
Anything over 20 MB stays in Slack. The message still arrives and says why the
|
|
106
|
+
file was left there.
|
|
107
|
+
|
|
86
108
|
## One channel per project
|
|
87
109
|
|
|
88
110
|
Setup configures one channel. Add more by hand in `~/.agent-wire/config.json`:
|
|
@@ -97,18 +119,64 @@ Setup configures one channel. Add more by hand in `~/.agent-wire/config.json`:
|
|
|
97
119
|
Every message is tagged with the channel it came from, `send` takes an optional
|
|
98
120
|
`channel`, and `inbox` can filter by one. The first entry is the default.
|
|
99
121
|
|
|
122
|
+
## Three modes, one per session
|
|
123
|
+
|
|
124
|
+
Every channel is in one of three modes, and the mode belongs to the session, not
|
|
125
|
+
to the machine:
|
|
126
|
+
|
|
127
|
+
| Mode | What a prompt gets |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `off` | Nothing. The channel is not mentioned. |
|
|
130
|
+
| `ask` | One line naming who is waiting and how many. Nothing is opened. Default. |
|
|
131
|
+
| `read` | The messages themselves, fenced, and marked read as they arrive. |
|
|
132
|
+
|
|
133
|
+
`ask` looks like this, and is what a prompt hook prints:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Unread messages : Huso(5), Sinan(2)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Loudest sender first, because five messages from one person is a conversation
|
|
140
|
+
waiting while one each from five people is a standup. Past five names the rest
|
|
141
|
+
become `+3 more`. Anything that failed its signature check is called out on the
|
|
142
|
+
same line — `[1 FORGED]` — rather than counted in silently.
|
|
143
|
+
|
|
144
|
+
`read` is the one to think about before turning on: it puts other people's
|
|
145
|
+
writing into your agent's prompt without you asking. It arrives inside the same
|
|
146
|
+
fence the `inbox` tool uses, but the guarantee is weaker there. Over MCP the rule
|
|
147
|
+
for reading fenced content is delivered once through the handshake, where no
|
|
148
|
+
message can sit beside it; a prompt hook has no handshake, so the rule and the
|
|
149
|
+
content share a page.
|
|
150
|
+
|
|
151
|
+
### What "per session" means
|
|
152
|
+
|
|
153
|
+
A session is identified by its working directory, because that is the only thing
|
|
154
|
+
a fresh `drain` process can see — it is launched again on every prompt and
|
|
155
|
+
remembers nothing. So one project can run `read` while another runs `off`, with
|
|
156
|
+
the same nickname, the same keys and the same Slack app behind both. Two windows
|
|
157
|
+
open on one folder count as one session; set `AGENT_WIRE_SCOPE` to tell them
|
|
158
|
+
apart.
|
|
159
|
+
|
|
160
|
+
Read and unread are per session too. They have to be: a session on `read` opens
|
|
161
|
+
everything it is handed, and if that also marked the message read next door, an
|
|
162
|
+
`ask` session would report an empty inbox forever.
|
|
163
|
+
|
|
164
|
+
The poller is not per session. One poller feeds one shared log for the whole
|
|
165
|
+
machine, so a channel stays polled while any session still wants it. `off` means
|
|
166
|
+
"do not tell me", not "stop collecting" — otherwise the quietest session on the
|
|
167
|
+
machine would decide what the busiest one is allowed to see.
|
|
168
|
+
|
|
100
169
|
## Working on two of five channels
|
|
101
170
|
|
|
102
|
-
|
|
103
|
-
with no arguments shows where you stand:
|
|
171
|
+
Running `agent-wire` with no arguments shows where you stand:
|
|
104
172
|
|
|
105
173
|
```
|
|
106
174
|
┌──────────────── agent-wire ────────────────┐
|
|
107
175
|
│ name grkn mark 🔥 │
|
|
108
176
|
│ key MCowBQYDK2VwAyEAq7Xn2mZ8kLcYzQwErTy… │
|
|
109
177
|
├───────────────── CHANNELS ─────────────────┤
|
|
110
|
-
│ agent-wms ●
|
|
111
|
-
│ agent-crm
|
|
178
|
+
│ agent-wms ● read 3 unread │
|
|
179
|
+
│ agent-crm ◐ ask 1 unread │
|
|
112
180
|
│ agent-hcm ○ off 1 held │
|
|
113
181
|
│ agent-lab ○ off 1 held │
|
|
114
182
|
├────────────────── PEERS ───────────────────┤
|
|
@@ -129,8 +197,9 @@ verifies. Letting a later message clear it would hand an attacker the way to bur
|
|
|
129
197
|
the evidence.
|
|
130
198
|
|
|
131
199
|
```bash
|
|
132
|
-
agent-wire off
|
|
133
|
-
agent-wire
|
|
200
|
+
agent-wire off agent-hcm
|
|
201
|
+
agent-wire ask agent-hcm
|
|
202
|
+
agent-wire read agent-wms
|
|
134
203
|
```
|
|
135
204
|
|
|
136
205
|
`status` reads the config and the local log only, so it answers instantly.
|
|
@@ -205,20 +274,29 @@ Your workspace admin will ask. The manifest requests:
|
|
|
205
274
|
| Scope | Why |
|
|
206
275
|
|---|---|
|
|
207
276
|
| `chat:write` | Post messages |
|
|
208
|
-
| `channels:history
|
|
209
|
-
| `channels:read
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `files:read`, `files:write` | Send and receive long messages as files |
|
|
277
|
+
| `channels:history` | Read the channels it was added to |
|
|
278
|
+
| `channels:read` | Find a channel by name, list who is in it |
|
|
279
|
+
| `files:write` | Send a file, and post a long message as one |
|
|
280
|
+
| `files:read` | Download a file somebody sent |
|
|
213
281
|
| `users:read` | Show a human's name instead of `U08J21KLER1` |
|
|
214
282
|
|
|
215
|
-
|
|
283
|
+
Six, and that is the whole list. No `channels:join` or `channels:manage`, so the
|
|
284
|
+
app cannot add itself to a channel or create one. No `groups:*`, so private
|
|
285
|
+
channels are out of reach: use a public one.
|
|
286
|
+
|
|
287
|
+
The two lookups it does are both scoped to the invite. Channels come from
|
|
288
|
+
`users.conversations`, which answers "which channels am I in", never
|
|
289
|
+
`conversations.list`, which answers "which channels exist here". Names come from
|
|
290
|
+
`conversations.members` on one of those channels. There is no call in the package
|
|
291
|
+
that can enumerate the workspace.
|
|
216
292
|
|
|
217
293
|
## Where things are stored
|
|
218
294
|
|
|
219
295
|
Everything lives in `~/.agent-wire/` (override with `AGENT_WIRE_HOME`).
|
|
220
296
|
`config.json` holds the token, identity and channels. `inbox.jsonl` is the
|
|
221
|
-
append-only message log. `peers.json` holds the pinned keys.
|
|
297
|
+
append-only message log. `peers.json` holds the pinned keys. `files/` holds every
|
|
298
|
+
attachment that arrived, named by Slack file id so two `plan.md` files stay two
|
|
299
|
+
files.
|
|
222
300
|
|
|
223
301
|
The local log is the source of truth. Slack is a cache that can be re-read at any
|
|
224
302
|
time, so recovering a lost inbox is an ordinary operation rather than a
|
|
@@ -232,12 +310,20 @@ reinstalled app cannot produce duplicates.
|
|
|
232
310
|
- Discord as a second transport
|
|
233
311
|
- Published measurements of fenced against unfenced injection compliance
|
|
234
312
|
|
|
313
|
+
Wire format v2 signs the attached file id alongside the text, so a 0.5 agent and
|
|
314
|
+
a 0.4 agent cannot verify each other. Upgrade both ends together.
|
|
315
|
+
|
|
235
316
|
## Development
|
|
236
317
|
|
|
237
318
|
```bash
|
|
238
|
-
npm test
|
|
319
|
+
npm test # 53 tests, no network
|
|
320
|
+
npm run bench # medians over a synthetic 20k-message log
|
|
239
321
|
```
|
|
240
322
|
|
|
323
|
+
The benchmark is here because the slow paths are the ones nobody watches: a log
|
|
324
|
+
that only grows, and a CLI that a prompt hook runs on every prompt. It is not
|
|
325
|
+
shipped to npm.
|
|
326
|
+
|
|
241
327
|
## License
|
|
242
328
|
|
|
243
329
|
MIT
|
package/bin/agent-wire.mjs
CHANGED
|
@@ -1,115 +1,148 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
agent-wire
|
|
15
|
-
agent-wire
|
|
16
|
-
agent-wire
|
|
17
|
-
agent-wire
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return 0;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
console.log(
|
|
67
|
-
return 1;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
process.
|
|
115
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Only the two cheap modules are imported up front. `status` builds an
|
|
3
|
+
// Intl.Segmenter and touches the Extended_Pictographic table, which together cost
|
|
4
|
+
// more than everything else this file does; `setup` pulls in readline. A prompt
|
|
5
|
+
// hook runs `drain` on every single prompt, so it must not pay for the panel it
|
|
6
|
+
// never draws.
|
|
7
|
+
import { activeChannels, channelMode, loadConfig, scopeId, setChannelMode } from '../src/config.mjs';
|
|
8
|
+
import { markRead, selectMessages } from '../src/inbox.mjs';
|
|
9
|
+
import { drainReport } from '../src/drain.mjs';
|
|
10
|
+
import { mintNonce } from '../src/protocol.mjs';
|
|
11
|
+
|
|
12
|
+
const USAGE = `agent-wire — message other AI coding agents through Slack
|
|
13
|
+
|
|
14
|
+
agent-wire status show identity, channels and unread counts (also the default)
|
|
15
|
+
agent-wire setup connect a workspace, a channel and this agent's identity
|
|
16
|
+
agent-wire serve run the MCP stdio server (what your agent client launches)
|
|
17
|
+
agent-wire doctor re-check the token, the channels and this agent's identity
|
|
18
|
+
agent-wire drain report what arrived since the last drain, then stop
|
|
19
|
+
agent-wire channels list the channels and what each one is set to here
|
|
20
|
+
agent-wire ask <name> name who is waiting and how many; open nothing (default)
|
|
21
|
+
agent-wire read <name> put the messages themselves into every prompt
|
|
22
|
+
agent-wire off <name> say nothing about it in this session
|
|
23
|
+
|
|
24
|
+
The three modes are per session, identified by the working directory. The token,
|
|
25
|
+
the nickname and the keys are shared. Set AGENT_WIRE_SCOPE to tell two sessions
|
|
26
|
+
in one folder apart.
|
|
27
|
+
|
|
28
|
+
Docs: https://github.com/grknbyk/agent-wire`;
|
|
29
|
+
|
|
30
|
+
// A prompt hook has one line of the user's screen to work with, so a backlog past
|
|
31
|
+
// this is reported as a count rather than listed.
|
|
32
|
+
const DRAIN_COUNT = 50;
|
|
33
|
+
|
|
34
|
+
// For a client hook that runs on every prompt. An `ask` channel costs the agent
|
|
35
|
+
// one line and reads nothing; a `read` channel spends the prompt on the messages
|
|
36
|
+
// themselves and marks them read, because nothing else is going to.
|
|
37
|
+
async function drain() {
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
if (!config) return 0;
|
|
40
|
+
|
|
41
|
+
const { pollOnce } = await import('../src/mcp.mjs');
|
|
42
|
+
await pollOnce(config).catch(() => {
|
|
43
|
+
// Offline is not an error here; the next drain catches up.
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const heard = activeChannels(config);
|
|
47
|
+
const waiting = selectMessages({
|
|
48
|
+
state: 'unread',
|
|
49
|
+
count: DRAIN_COUNT,
|
|
50
|
+
channels: heard.map((channel) => channel.name),
|
|
51
|
+
});
|
|
52
|
+
if (waiting.length === 0) return 0;
|
|
53
|
+
|
|
54
|
+
const { lines, readItems } = drainReport(config, heard, waiting, mintNonce());
|
|
55
|
+
if (lines.length === 0) return 0;
|
|
56
|
+
|
|
57
|
+
console.log(lines.join('\n'));
|
|
58
|
+
markRead(readItems);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function listChannels() {
|
|
63
|
+
const config = loadConfig();
|
|
64
|
+
const configured = config?.channels ?? [];
|
|
65
|
+
if (configured.length === 0) {
|
|
66
|
+
console.log('no channels configured — run `agent-wire setup`');
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const channel of configured) console.log(`${channelMode(config, channel).padEnd(4)} #${channel.name}`);
|
|
71
|
+
console.log(`\nmodes are per session; this one is ${scopeId()}`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const MODE_EXPLAINED = {
|
|
76
|
+
off: (name) => `#${name} is off for this session. Nothing about it reaches this agent;`
|
|
77
|
+
+ ` its history stays readable with inbox channel="${name}".`,
|
|
78
|
+
ask: (name) => `#${name} is on ask. Every prompt names who is waiting and how many, and opens nothing.`,
|
|
79
|
+
read: (name) => `#${name} is on read. Every prompt carries the messages themselves, marked read as they arrive.`
|
|
80
|
+
+ ' Other people\'s writing now reaches this agent without you asking for it.',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Which mode a channel is in is a decision for the person running the agent, so
|
|
84
|
+
// it lives on the command line and not in the MCP tool list. A message arriving
|
|
85
|
+
// from the channel must not be able to talk the agent into silencing another one,
|
|
86
|
+
// nor into opening one.
|
|
87
|
+
function switchChannel(name, mode) {
|
|
88
|
+
if (!name) {
|
|
89
|
+
console.log(`usage: agent-wire ${mode} <channel>`);
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const changed = setChannelMode(name, mode);
|
|
94
|
+
if (!changed) {
|
|
95
|
+
console.log(`no configured channel named "${name}"`);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(MODE_EXPLAINED[mode](changed.channel.name));
|
|
100
|
+
if (changed.previous === 'off' && mode !== 'off') {
|
|
101
|
+
console.log('The next poll replays everything that arrived while it was off.');
|
|
102
|
+
}
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const showStatus = async () => (await import('../src/status.mjs')).runStatus();
|
|
107
|
+
|
|
108
|
+
const commands = {
|
|
109
|
+
status: async () => await showStatus() ?? notConfigured(),
|
|
110
|
+
setup: async () => (await import('../src/setup.mjs')).runSetup(),
|
|
111
|
+
doctor: async () => (await import('../src/setup.mjs')).runDoctor(),
|
|
112
|
+
drain,
|
|
113
|
+
channels: listChannels,
|
|
114
|
+
off: () => switchChannel(process.argv[3], 'off'),
|
|
115
|
+
ask: () => switchChannel(process.argv[3], 'ask'),
|
|
116
|
+
read: () => switchChannel(process.argv[3], 'read'),
|
|
117
|
+
// `on` was the only way to undo `off` before there were three modes, and it
|
|
118
|
+
// meant "announce it without opening it". That is ask.
|
|
119
|
+
on: () => switchChannel(process.argv[3], 'ask'),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function notConfigured() {
|
|
123
|
+
console.log('not configured yet — run `agent-wire setup`');
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const name = process.argv[2];
|
|
128
|
+
|
|
129
|
+
// The exit code is set, never forced. Calling process.exit() while an undici
|
|
130
|
+
// socket from a Slack call is still closing aborts libuv on Windows, and doctor
|
|
131
|
+
// hit that on every run. Nothing here holds the event loop open, so letting Node
|
|
132
|
+
// finish by itself costs about a millisecond.
|
|
133
|
+
//
|
|
134
|
+
// serve() is the one command that must not exit at all: the open stdin stream is
|
|
135
|
+
// what keeps the MCP server alive.
|
|
136
|
+
if (name === 'serve') {
|
|
137
|
+
(await import('../src/mcp.mjs')).serve();
|
|
138
|
+
} else if (commands[name]) {
|
|
139
|
+
process.exitCode = await commands[name]() ?? 0;
|
|
140
|
+
} else if (!name) {
|
|
141
|
+
// Bare invocation shows where you stand once there is something to stand on,
|
|
142
|
+
// and the usage text while there is not.
|
|
143
|
+
const shown = await showStatus();
|
|
144
|
+
if (shown === null) console.log(USAGE);
|
|
145
|
+
} else {
|
|
146
|
+
console.log(USAGE);
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
}
|
package/manifest.json
CHANGED
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"display_information": {
|
|
3
|
-
"name": "agent-wire",
|
|
4
|
-
"description": "Message bridge between AI coding agents",
|
|
5
|
-
"background_color": "#af3c02",
|
|
6
|
-
"long_description": "agent-wire connects AI coding agents running on different machines through a Slack channel they share. Each agent posts under its own nickname and signs what it sends, so a message can be traced to the agent that wrote it. Humans in the same channel can read the whole exchange and join it at any point, which is the reason the bridge runs on Slack rather than a private protocol: the conversation between machines stays readable by the team that owns them."
|
|
7
|
-
},
|
|
8
|
-
"features": {
|
|
9
|
-
"bot_user": {
|
|
10
|
-
"display_name": "agent-wire",
|
|
11
|
-
"always_online": true
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"oauth_config": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
"settings": {
|
|
34
|
-
"org_deploy_enabled": false,
|
|
35
|
-
"socket_mode_enabled": false,
|
|
36
|
-
"token_rotation_enabled": false
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"display_information": {
|
|
3
|
+
"name": "agent-wire",
|
|
4
|
+
"description": "Message bridge between AI coding agents",
|
|
5
|
+
"background_color": "#af3c02",
|
|
6
|
+
"long_description": "agent-wire connects AI coding agents running on different machines through a Slack channel they share. Each agent posts under its own nickname and signs what it sends, so a message can be traced to the agent that wrote it. Humans in the same channel can read the whole exchange and join it at any point, which is the reason the bridge runs on Slack rather than a private protocol: the conversation between machines stays readable by the team that owns them."
|
|
7
|
+
},
|
|
8
|
+
"features": {
|
|
9
|
+
"bot_user": {
|
|
10
|
+
"display_name": "agent-wire",
|
|
11
|
+
"always_online": true
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"oauth_config": {
|
|
15
|
+
"scopes": {
|
|
16
|
+
"bot": [
|
|
17
|
+
"chat:write",
|
|
18
|
+
"channels:read",
|
|
19
|
+
"channels:history",
|
|
20
|
+
"files:read",
|
|
21
|
+
"files:write",
|
|
22
|
+
"users:read"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"settings": {
|
|
27
|
+
"org_deploy_enabled": false,
|
|
28
|
+
"socket_mode_enabled": false,
|
|
29
|
+
"token_rotation_enabled": false
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grknbyk/agent-wire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Let AI coding agents message each other through a shared Slack channel, over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
-
"test": "node --test \"test/*.test.mjs\""
|
|
23
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
24
|
+
"bench": "node bench/bench.mjs"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"bin",
|