arbiter-skill 0.1.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 +142 -0
- package/SKILL.md +291 -0
- package/dist/get.d.ts +13 -0
- package/dist/get.js +84 -0
- package/dist/push.d.ts +24 -0
- package/dist/push.js +143 -0
- package/dist/status.d.ts +16 -0
- package/dist/status.js +98 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.js +4 -0
- package/dist/utils.d.ts +39 -0
- package/dist/utils.js +114 -0
- package/package.json +36 -0
- package/scripts/get.sh +13 -0
- package/scripts/push.sh +13 -0
- package/scripts/status.sh +13 -0
- package/src/get.ts +99 -0
- package/src/push.ts +161 -0
- package/src/status.ts +111 -0
- package/src/types.ts +87 -0
- package/src/utils.ts +132 -0
- package/templates/decision.md +77 -0
- package/tsconfig.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Arbiter Skill
|
|
2
|
+
|
|
3
|
+
Agent-side CLI for pushing decisions to [Arbiter Zebu](https://github.com/5hanth/arbiter-zebu). Works with Clawdbot/OpenClaw agents or standalone.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
**Via ClawHub (for Clawdbot/OpenClaw):**
|
|
8
|
+
```bash
|
|
9
|
+
clawhub install arbiter
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Via npm/bun (standalone CLI):**
|
|
13
|
+
```bash
|
|
14
|
+
bun add -g arbiter-skill
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- [Arbiter Zebu](https://github.com/5hanth/arbiter-zebu) bot running (`bunx arbiter-zebu`)
|
|
20
|
+
- `~/.arbiter/queue/` directory (created automatically by the bot)
|
|
21
|
+
|
|
22
|
+
## CLI Commands
|
|
23
|
+
|
|
24
|
+
### arbiter-push
|
|
25
|
+
|
|
26
|
+
Push a decision plan for human review:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
arbiter-push '{
|
|
30
|
+
"title": "API Design Decisions",
|
|
31
|
+
"tag": "my-project",
|
|
32
|
+
"priority": "high",
|
|
33
|
+
"notify": "agent:swe1:main",
|
|
34
|
+
"decisions": [
|
|
35
|
+
{
|
|
36
|
+
"id": "auth",
|
|
37
|
+
"title": "Auth Method",
|
|
38
|
+
"context": "How to authenticate users",
|
|
39
|
+
"options": [
|
|
40
|
+
{"key": "jwt", "label": "JWT tokens"},
|
|
41
|
+
{"key": "session", "label": "Server sessions"},
|
|
42
|
+
{"key": "oauth", "label": "OAuth provider"}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "database",
|
|
47
|
+
"title": "Database Choice",
|
|
48
|
+
"context": "Primary datastore",
|
|
49
|
+
"options": [
|
|
50
|
+
{"key": "pg", "label": "PostgreSQL"},
|
|
51
|
+
{"key": "mongo", "label": "MongoDB"}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"planId": "abc123",
|
|
62
|
+
"file": "~/.arbiter/queue/pending/ceo-api-design-abc123.md",
|
|
63
|
+
"total": 2,
|
|
64
|
+
"status": "pending"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### arbiter-status
|
|
69
|
+
|
|
70
|
+
Check plan status:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
arbiter-status '{"planId": "abc123"}'
|
|
74
|
+
# or by tag
|
|
75
|
+
arbiter-status '{"tag": "my-project"}'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### arbiter-get
|
|
79
|
+
|
|
80
|
+
Get answers from a completed plan:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
arbiter-get '{"planId": "abc123"}'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"planId": "abc123",
|
|
90
|
+
"status": "completed",
|
|
91
|
+
"answers": {
|
|
92
|
+
"auth": "jwt",
|
|
93
|
+
"database": "pg"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How It Works
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
arbiter-push writes markdown → ~/.arbiter/queue/pending/
|
|
102
|
+
↓
|
|
103
|
+
Arbiter Zebu bot detects new file
|
|
104
|
+
↓
|
|
105
|
+
Human reviews & answers in Telegram
|
|
106
|
+
↓
|
|
107
|
+
On completion, notification written to
|
|
108
|
+
~/.arbiter/queue/notify/
|
|
109
|
+
↓
|
|
110
|
+
Agent picks up answers (heartbeat or poll)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## JSON Fields
|
|
114
|
+
|
|
115
|
+
### Push args
|
|
116
|
+
|
|
117
|
+
| Field | Required | Description |
|
|
118
|
+
|-------|----------|-------------|
|
|
119
|
+
| `title` | Yes | Plan title |
|
|
120
|
+
| `tag` | No | Project tag for filtering |
|
|
121
|
+
| `context` | No | Background for the reviewer |
|
|
122
|
+
| `priority` | No | `low` / `normal` / `high` / `urgent` |
|
|
123
|
+
| `notify` | No | Session key to notify on completion |
|
|
124
|
+
| `decisions` | Yes | Array of decision objects |
|
|
125
|
+
|
|
126
|
+
### Decision object
|
|
127
|
+
|
|
128
|
+
| Field | Required | Description |
|
|
129
|
+
|-------|----------|-------------|
|
|
130
|
+
| `id` | Yes | Unique ID within the plan |
|
|
131
|
+
| `title` | Yes | Human-readable title |
|
|
132
|
+
| `context` | No | Explanation for the reviewer |
|
|
133
|
+
| `options` | Yes | Array of `{key, label}` |
|
|
134
|
+
| `allowCustom` | No | Allow free-text answers |
|
|
135
|
+
|
|
136
|
+
## Usage with Clawdbot
|
|
137
|
+
|
|
138
|
+
See [SKILL.md](./SKILL.md) for full agent integration docs.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arbiter
|
|
3
|
+
description: Push decisions to Arbiter Zebu for async human review. Use when you need human input on plans, architectural choices, or approval before proceeding.
|
|
4
|
+
metadata: {"openclaw":{"requires":{"bins":["arbiter-push"]}}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Arbiter Skill
|
|
8
|
+
|
|
9
|
+
Push decisions to Arbiter Zebu for async human review. Use when you need human input on plans, architectural choices, or approval before proceeding.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
**Quick install via ClawHub:**
|
|
14
|
+
```bash
|
|
15
|
+
clawhub install arbiter
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Or via bun (makes CLI commands available globally):**
|
|
19
|
+
```bash
|
|
20
|
+
bun add -g arbiter-skill
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Or manual:**
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/5hanth/arbiter-skill.git
|
|
26
|
+
cd arbiter-skill && npm install && npm run build
|
|
27
|
+
ln -s $(pwd) ~/.clawdbot/skills/arbiter
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Prerequisites
|
|
31
|
+
|
|
32
|
+
- [Arbiter Zebu](https://github.com/5hanth/arbiter-zebu) bot running (or just `bunx arbiter-zebu`)
|
|
33
|
+
- `~/.arbiter/queue/` directory (created automatically by the bot)
|
|
34
|
+
|
|
35
|
+
## Environment Variables
|
|
36
|
+
|
|
37
|
+
Set these in your agent's environment for automatic agent/session detection:
|
|
38
|
+
|
|
39
|
+
| Variable | Description | Example |
|
|
40
|
+
|----------|-------------|---------|
|
|
41
|
+
| `CLAWDBOT_AGENT` | Agent ID | `ceo`, `swe1` |
|
|
42
|
+
| `CLAWDBOT_SESSION` | Session key | `agent:ceo:main` |
|
|
43
|
+
|
|
44
|
+
## When to Use
|
|
45
|
+
|
|
46
|
+
- Plan review before implementation
|
|
47
|
+
- Architectural decisions with tradeoffs
|
|
48
|
+
- Anything blocking that needs human judgment
|
|
49
|
+
- Multiple related decisions as a batch
|
|
50
|
+
|
|
51
|
+
**Do NOT use for:**
|
|
52
|
+
- Simple yes/no that doesn't need explanation
|
|
53
|
+
- Urgent real-time decisions (use direct message instead)
|
|
54
|
+
- Technical questions you can research yourself
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
### arbiter_push
|
|
59
|
+
|
|
60
|
+
Create a decision plan for human review.
|
|
61
|
+
|
|
62
|
+
**CLI:** `arbiter-push '<json>'` — takes a single JSON argument containing all fields.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
arbiter-push '{
|
|
66
|
+
"title": "API Design Decisions",
|
|
67
|
+
"tag": "nft-marketplace",
|
|
68
|
+
"context": "SWE2 needs these decided before API work",
|
|
69
|
+
"priority": "normal",
|
|
70
|
+
"notify": "agent:swe2:main",
|
|
71
|
+
"decisions": [
|
|
72
|
+
{
|
|
73
|
+
"id": "auth-strategy",
|
|
74
|
+
"title": "Auth Strategy",
|
|
75
|
+
"context": "How to authenticate admin users",
|
|
76
|
+
"options": [
|
|
77
|
+
{"key": "jwt", "label": "JWT tokens", "note": "Stateless"},
|
|
78
|
+
{"key": "session", "label": "Sessions", "note": "More control"},
|
|
79
|
+
{"key": "oauth", "label": "OAuth", "note": "External provider"}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "database",
|
|
84
|
+
"title": "Database Choice",
|
|
85
|
+
"context": "Primary datastore",
|
|
86
|
+
"options": [
|
|
87
|
+
{"key": "postgresql", "label": "PostgreSQL + JSONB"},
|
|
88
|
+
{"key": "mongodb", "label": "MongoDB"}
|
|
89
|
+
],
|
|
90
|
+
"allowCustom": true
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**JSON Fields:**
|
|
97
|
+
|
|
98
|
+
| Field | Required | Description |
|
|
99
|
+
|-------|----------|-------------|
|
|
100
|
+
| `title` | Yes | Plan title |
|
|
101
|
+
| `tag` | No | Tag for filtering (e.g., project name) |
|
|
102
|
+
| `context` | No | Background for reviewer |
|
|
103
|
+
| `priority` | No | `low`, `normal`, `high`, `urgent` (default: normal) |
|
|
104
|
+
| `notify` | No | Session to notify when complete |
|
|
105
|
+
| `agent` | No | Agent ID (auto-detected from `CLAWDBOT_AGENT` env) |
|
|
106
|
+
| `session` | No | Session key (auto-detected from `CLAWDBOT_SESSION` env) |
|
|
107
|
+
| `decisions` | Yes | Array of decisions |
|
|
108
|
+
|
|
109
|
+
**Decision object:**
|
|
110
|
+
|
|
111
|
+
| Field | Required | Description |
|
|
112
|
+
|-------|----------|-------------|
|
|
113
|
+
| `id` | Yes | Unique ID within plan |
|
|
114
|
+
| `title` | Yes | Decision title |
|
|
115
|
+
| `context` | No | Explanation for reviewer |
|
|
116
|
+
| `options` | Yes | Array of `{key, label, note?}` |
|
|
117
|
+
| `allowCustom` | No | Allow free-text answer (default: false) |
|
|
118
|
+
| `default` | No | Suggested option key |
|
|
119
|
+
|
|
120
|
+
**Returns:**
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"planId": "abc123",
|
|
125
|
+
"file": "~/.arbiter/queue/pending/ceo-api-design-abc123.md",
|
|
126
|
+
"total": 2,
|
|
127
|
+
"status": "pending"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### arbiter_status
|
|
132
|
+
|
|
133
|
+
Check the status of a decision plan.
|
|
134
|
+
|
|
135
|
+
**CLI:** `arbiter-status <plan-id>` or `arbiter-status --tag <tag>`
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
arbiter-status abc12345
|
|
139
|
+
# or
|
|
140
|
+
arbiter-status --tag nft-marketplace
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Returns:**
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"planId": "abc123",
|
|
148
|
+
"title": "API Design Decisions",
|
|
149
|
+
"status": "in_progress",
|
|
150
|
+
"total": 3,
|
|
151
|
+
"answered": 1,
|
|
152
|
+
"remaining": 2,
|
|
153
|
+
"decisions": {
|
|
154
|
+
"auth-strategy": {"status": "answered", "answer": "jwt"},
|
|
155
|
+
"database": {"status": "pending", "answer": null},
|
|
156
|
+
"caching": {"status": "pending", "answer": null}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### arbiter_get
|
|
162
|
+
|
|
163
|
+
Get answers from a completed plan.
|
|
164
|
+
|
|
165
|
+
**CLI:** `arbiter-get <plan-id>` or `arbiter-get --tag <tag>`
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
arbiter-get abc12345
|
|
169
|
+
# or
|
|
170
|
+
arbiter-get --tag nft-marketplace
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Returns:**
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"planId": "abc123",
|
|
178
|
+
"status": "completed",
|
|
179
|
+
"completedAt": "2026-01-30T01:45:00Z",
|
|
180
|
+
"answers": {
|
|
181
|
+
"auth-strategy": "jwt",
|
|
182
|
+
"database": "postgresql",
|
|
183
|
+
"caching": "redis"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Error if not complete:**
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"error": "Plan not complete",
|
|
193
|
+
"status": "in_progress",
|
|
194
|
+
"remaining": 2
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### arbiter_await
|
|
199
|
+
|
|
200
|
+
Block until plan is complete (with timeout).
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
arbiter-await abc12345 --timeout 3600
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Polls every 30 seconds until complete or timeout.
|
|
207
|
+
|
|
208
|
+
**Returns:** Same as `arbiter_get` on completion.
|
|
209
|
+
|
|
210
|
+
## Usage Examples
|
|
211
|
+
|
|
212
|
+
### Example 1: Plan Review
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
# Push plan decisions (single JSON argument)
|
|
216
|
+
RESULT=$(arbiter-push '{"title":"Clean IT i18n Plan","tag":"clean-it","priority":"high","notify":"agent:swe3:main","decisions":[{"id":"library","title":"i18n Library","options":[{"key":"i18next","label":"i18next"},{"key":"formatjs","label":"FormatJS"}]},{"id":"keys","title":"Key Structure","options":[{"key":"flat","label":"Flat (login.button)"},{"key":"nested","label":"Nested ({login:{button}})"}]}]}')
|
|
217
|
+
|
|
218
|
+
PLAN_ID=$(echo $RESULT | jq -r '.planId')
|
|
219
|
+
echo "Pushed plan $PLAN_ID — waiting for human review"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Example 2: Check and Proceed
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# Check if decisions are ready
|
|
226
|
+
STATUS=$(arbiter-status --tag nft-marketplace)
|
|
227
|
+
|
|
228
|
+
if [ "$(echo $STATUS | jq -r '.status')" == "completed" ]; then
|
|
229
|
+
ANSWERS=$(arbiter-get --tag nft-marketplace)
|
|
230
|
+
AUTH=$(echo $ANSWERS | jq -r '.answers["auth-strategy"]')
|
|
231
|
+
echo "Using auth strategy: $AUTH"
|
|
232
|
+
# Proceed with implementation
|
|
233
|
+
else
|
|
234
|
+
echo "Still waiting for $(echo $STATUS | jq -r '.remaining') decisions"
|
|
235
|
+
fi
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Example 3: Blocking Wait
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Wait up to 1 hour for decisions
|
|
242
|
+
ANSWERS=$(arbiter-await abc12345 --timeout 3600)
|
|
243
|
+
|
|
244
|
+
if [ $? -eq 0 ]; then
|
|
245
|
+
# Got answers, proceed
|
|
246
|
+
echo "Decisions ready: $ANSWERS"
|
|
247
|
+
else
|
|
248
|
+
echo "Timeout waiting for decisions"
|
|
249
|
+
fi
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Best Practices
|
|
253
|
+
|
|
254
|
+
1. **Batch related decisions** — Don't push one at a time
|
|
255
|
+
2. **Provide context** — Human needs to understand tradeoffs
|
|
256
|
+
3. **Use tags** — Makes filtering easy (`--tag project-name`)
|
|
257
|
+
4. **Set notify** — So blocked agents get woken up
|
|
258
|
+
5. **Use priority sparingly** — Reserve `urgent` for true blockers
|
|
259
|
+
|
|
260
|
+
## File Locations
|
|
261
|
+
|
|
262
|
+
| Path | Purpose |
|
|
263
|
+
|------|---------|
|
|
264
|
+
| `~/.arbiter/queue/pending/` | Plans awaiting review |
|
|
265
|
+
| `~/.arbiter/queue/completed/` | Answered plans (archive) |
|
|
266
|
+
| `~/.arbiter/queue/notify/` | Agent notifications |
|
|
267
|
+
|
|
268
|
+
## Checking Notifications (Agent Heartbeat)
|
|
269
|
+
|
|
270
|
+
In your HEARTBEAT.md, add:
|
|
271
|
+
|
|
272
|
+
```markdown
|
|
273
|
+
## Check Arbiter Notifications
|
|
274
|
+
|
|
275
|
+
1. Check if `~/.arbiter/queue/notify/` has files for my session
|
|
276
|
+
2. If yes, read answers and proceed with blocked work
|
|
277
|
+
3. Delete notification file after processing
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Troubleshooting
|
|
281
|
+
|
|
282
|
+
| Issue | Solution |
|
|
283
|
+
|-------|----------|
|
|
284
|
+
| Plan not showing in Arbiter | Check file is valid YAML frontmatter |
|
|
285
|
+
| Answers not appearing | Check `arbiter_status`, may be incomplete |
|
|
286
|
+
| Notification not received | Ensure `--notify` was set correctly |
|
|
287
|
+
|
|
288
|
+
## See Also
|
|
289
|
+
|
|
290
|
+
- [Arbiter Zebu Architecture](https://github.com/5hanth/arbiter-zebu/blob/main/ARCHITECTURE.md)
|
|
291
|
+
- [Arbiter Zebu Bot](https://github.com/5hanth/arbiter-zebu)
|
package/dist/get.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* arbiter get - Retrieve answers from a completed decision plan
|
|
4
|
+
*
|
|
5
|
+
* Usage: arbiter-get <plan-id> [--tag <tag>]
|
|
6
|
+
*
|
|
7
|
+
* Returns JSON with:
|
|
8
|
+
* - planId: Plan ID
|
|
9
|
+
* - status: completed (or error if not complete)
|
|
10
|
+
* - completedAt: ISO timestamp
|
|
11
|
+
* - answers: Map of decision ID -> answer
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/dist/get.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* arbiter get - Retrieve answers from a completed decision plan
|
|
4
|
+
*
|
|
5
|
+
* Usage: arbiter-get <plan-id> [--tag <tag>]
|
|
6
|
+
*
|
|
7
|
+
* Returns JSON with:
|
|
8
|
+
* - planId: Plan ID
|
|
9
|
+
* - status: completed (or error if not complete)
|
|
10
|
+
* - completedAt: ISO timestamp
|
|
11
|
+
* - answers: Map of decision ID -> answer
|
|
12
|
+
*/
|
|
13
|
+
import { findPlanFile, parsePlanFile, parseDecisions } from './utils.js';
|
|
14
|
+
/**
|
|
15
|
+
* Get answers from a completed decision plan
|
|
16
|
+
*/
|
|
17
|
+
function get(planId, tag) {
|
|
18
|
+
if (!planId && !tag) {
|
|
19
|
+
return { error: 'Either planId or tag is required' };
|
|
20
|
+
}
|
|
21
|
+
const file = findPlanFile(planId, tag);
|
|
22
|
+
if (!file) {
|
|
23
|
+
return { error: 'Plan not found' };
|
|
24
|
+
}
|
|
25
|
+
const { frontmatter, content } = parsePlanFile(file);
|
|
26
|
+
if (frontmatter.status !== 'completed') {
|
|
27
|
+
return {
|
|
28
|
+
error: `Plan not complete (status: ${frontmatter.status})`,
|
|
29
|
+
planId: frontmatter.id,
|
|
30
|
+
status: frontmatter.status,
|
|
31
|
+
completedAt: '',
|
|
32
|
+
answers: {}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const decisions = parseDecisions(content);
|
|
36
|
+
const answers = {};
|
|
37
|
+
for (const d of decisions) {
|
|
38
|
+
if (d.answer !== null) {
|
|
39
|
+
answers[d.id] = d.answer;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
planId: frontmatter.id,
|
|
44
|
+
status: 'completed',
|
|
45
|
+
completedAt: frontmatter.completed_at || '',
|
|
46
|
+
answers
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// CLI entry point
|
|
50
|
+
function main() {
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
53
|
+
console.log(`
|
|
54
|
+
arbiter get - Retrieve answers from a completed decision plan
|
|
55
|
+
|
|
56
|
+
Usage: arbiter-get <plan-id>
|
|
57
|
+
arbiter-get --tag <tag>
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
arbiter-get abc12345
|
|
61
|
+
arbiter-get --tag nft-marketplace
|
|
62
|
+
|
|
63
|
+
Note: Returns error if plan is not yet completed.
|
|
64
|
+
`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
let planId;
|
|
68
|
+
let tag;
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
if (args[i] === '--tag' && args[i + 1]) {
|
|
71
|
+
tag = args[i + 1];
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
else if (!args[i].startsWith('-')) {
|
|
75
|
+
planId = args[i];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const result = get(planId, tag);
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
if ('error' in result) {
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
main();
|
package/dist/push.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* arbiter push - Create a decision file in the queue
|
|
4
|
+
*
|
|
5
|
+
* Usage: arbiter-push <json-args>
|
|
6
|
+
*
|
|
7
|
+
* The JSON should contain:
|
|
8
|
+
* - title: Plan title (required)
|
|
9
|
+
* - tag: Project tag (optional)
|
|
10
|
+
* - context: Context for reviewer (optional)
|
|
11
|
+
* - priority: low|normal|high|urgent (optional, default: normal)
|
|
12
|
+
* - notify: Session to notify on completion (optional)
|
|
13
|
+
* - agent: Agent ID (optional, uses CLAWDBOT_AGENT env)
|
|
14
|
+
* - session: Session key (optional, uses CLAWDBOT_SESSION env)
|
|
15
|
+
* - decisions: Array of decisions (required)
|
|
16
|
+
*
|
|
17
|
+
* Each decision:
|
|
18
|
+
* - id: Unique ID within the plan
|
|
19
|
+
* - title: Decision title
|
|
20
|
+
* - context: Context for this decision
|
|
21
|
+
* - options: Array of { key, label, note? }
|
|
22
|
+
* - allowCustom: Allow custom text answer (optional)
|
|
23
|
+
*/
|
|
24
|
+
export {};
|
package/dist/push.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* arbiter push - Create a decision file in the queue
|
|
4
|
+
*
|
|
5
|
+
* Usage: arbiter-push <json-args>
|
|
6
|
+
*
|
|
7
|
+
* The JSON should contain:
|
|
8
|
+
* - title: Plan title (required)
|
|
9
|
+
* - tag: Project tag (optional)
|
|
10
|
+
* - context: Context for reviewer (optional)
|
|
11
|
+
* - priority: low|normal|high|urgent (optional, default: normal)
|
|
12
|
+
* - notify: Session to notify on completion (optional)
|
|
13
|
+
* - agent: Agent ID (optional, uses CLAWDBOT_AGENT env)
|
|
14
|
+
* - session: Session key (optional, uses CLAWDBOT_SESSION env)
|
|
15
|
+
* - decisions: Array of decisions (required)
|
|
16
|
+
*
|
|
17
|
+
* Each decision:
|
|
18
|
+
* - id: Unique ID within the plan
|
|
19
|
+
* - title: Decision title
|
|
20
|
+
* - context: Context for this decision
|
|
21
|
+
* - options: Array of { key, label, note? }
|
|
22
|
+
* - allowCustom: Allow custom text answer (optional)
|
|
23
|
+
*/
|
|
24
|
+
import { writeFileSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { nanoid } from 'nanoid';
|
|
27
|
+
import { getQueueDir, ensureQueueDirs, slugify, nowISO } from './utils.js';
|
|
28
|
+
/**
|
|
29
|
+
* Generate the markdown content for a decision plan
|
|
30
|
+
*/
|
|
31
|
+
function generateMarkdown(args) {
|
|
32
|
+
const now = nowISO();
|
|
33
|
+
const total = args.decisions.length;
|
|
34
|
+
// Build frontmatter
|
|
35
|
+
const frontmatter = `---
|
|
36
|
+
id: ${args.id}
|
|
37
|
+
version: 1
|
|
38
|
+
agent: ${args.agent}
|
|
39
|
+
session: ${args.session}
|
|
40
|
+
tag: ${args.tag || 'general'}
|
|
41
|
+
title: "${args.title}"
|
|
42
|
+
priority: ${args.priority || 'normal'}
|
|
43
|
+
status: pending
|
|
44
|
+
created_at: ${now}
|
|
45
|
+
updated_at: ${now}
|
|
46
|
+
completed_at: null
|
|
47
|
+
total: ${total}
|
|
48
|
+
answered: 0
|
|
49
|
+
remaining: ${total}
|
|
50
|
+
${args.notify ? `notify_session: ${args.notify}` : ''}
|
|
51
|
+
---`;
|
|
52
|
+
// Build context section
|
|
53
|
+
const contextSection = `
|
|
54
|
+
# ${args.title}
|
|
55
|
+
|
|
56
|
+
${args.context || 'Please review and answer the following decisions.'}
|
|
57
|
+
`;
|
|
58
|
+
// Build decision sections
|
|
59
|
+
const decisionSections = args.decisions.map((d, i) => {
|
|
60
|
+
const optionsMarkdown = d.options
|
|
61
|
+
.map(o => `- \`${o.key}\` — ${o.label}${o.note ? ` (${o.note})` : ''}`)
|
|
62
|
+
.join('\n');
|
|
63
|
+
return `
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Decision ${i + 1}: ${d.title}
|
|
67
|
+
|
|
68
|
+
id: ${d.id}
|
|
69
|
+
status: pending
|
|
70
|
+
answer: null
|
|
71
|
+
answered_at: null
|
|
72
|
+
${d.allowCustom ? 'allow_custom: true' : ''}
|
|
73
|
+
|
|
74
|
+
**Context:** ${d.context}
|
|
75
|
+
|
|
76
|
+
**Options:**
|
|
77
|
+
${optionsMarkdown}
|
|
78
|
+
`;
|
|
79
|
+
}).join('\n');
|
|
80
|
+
return frontmatter + contextSection + decisionSections;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Push a decision plan to the queue
|
|
84
|
+
*/
|
|
85
|
+
function push(args) {
|
|
86
|
+
// Validate required fields
|
|
87
|
+
if (!args.title) {
|
|
88
|
+
throw new Error('title is required');
|
|
89
|
+
}
|
|
90
|
+
if (!args.decisions || args.decisions.length === 0) {
|
|
91
|
+
throw new Error('decisions array is required and must not be empty');
|
|
92
|
+
}
|
|
93
|
+
// Validate each decision
|
|
94
|
+
for (const d of args.decisions) {
|
|
95
|
+
if (!d.id || !d.title || !d.options || d.options.length === 0) {
|
|
96
|
+
throw new Error(`Invalid decision: ${JSON.stringify(d)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const id = nanoid(8);
|
|
100
|
+
const agent = args.agent || process.env.CLAWDBOT_AGENT || 'unknown';
|
|
101
|
+
const session = args.session || process.env.CLAWDBOT_SESSION || 'unknown';
|
|
102
|
+
const filename = `${agent}-${slugify(args.title)}-${id}.md`;
|
|
103
|
+
const filepath = join(getQueueDir('pending'), filename);
|
|
104
|
+
const content = generateMarkdown({
|
|
105
|
+
...args,
|
|
106
|
+
id,
|
|
107
|
+
agent,
|
|
108
|
+
session
|
|
109
|
+
});
|
|
110
|
+
ensureQueueDirs();
|
|
111
|
+
writeFileSync(filepath, content, 'utf-8');
|
|
112
|
+
return {
|
|
113
|
+
planId: id,
|
|
114
|
+
file: filepath,
|
|
115
|
+
total: args.decisions.length,
|
|
116
|
+
status: 'pending'
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// CLI entry point
|
|
120
|
+
function main() {
|
|
121
|
+
const args = process.argv.slice(2);
|
|
122
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
123
|
+
console.log(`
|
|
124
|
+
arbiter push - Create a decision file in the queue
|
|
125
|
+
|
|
126
|
+
Usage: arbiter-push '<json>'
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
arbiter-push '{"title":"API Decisions","decisions":[{"id":"auth","title":"Auth Method","context":"Choose auth","options":[{"key":"jwt","label":"JWT tokens"},{"key":"session","label":"Sessions"}]}]}'
|
|
130
|
+
`);
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const input = JSON.parse(args.join(' '));
|
|
135
|
+
const result = push(input);
|
|
136
|
+
console.log(JSON.stringify(result, null, 2));
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error('Error:', err instanceof Error ? err.message : err);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
main();
|