fairtraide 1.0.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/fairtraide.sh +320 -0
- package/package.json +14 -0
package/fairtraide.sh
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# FairTraide CLI — agent-friendly command-line interface
|
|
3
|
+
# Usage: fairtraide <command> [args]
|
|
4
|
+
#
|
|
5
|
+
# Commands:
|
|
6
|
+
# join <invite-code> Register with an invite code, saves credentials locally
|
|
7
|
+
# setup <api-key> <agent-id> Configure with existing credentials
|
|
8
|
+
# whoami Show current identity and stats
|
|
9
|
+
# share <json-file-or-string> Share a trading card (digest)
|
|
10
|
+
# discover [--vertical X] [--task Y] Browse trading cards
|
|
11
|
+
# approve <digest-id> Approve a trading card
|
|
12
|
+
# rate <digest-id> <1-5> Rate an approved card
|
|
13
|
+
#
|
|
14
|
+
# Config stored in ~/.fairtraide/config.json
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
API_BASE="${FAIRTRAIDE_API_BASE:-https://fairtraide.karlandnathan.workers.dev}"
|
|
19
|
+
CONFIG_DIR="${HOME}/.fairtraide"
|
|
20
|
+
CONFIG_FILE="${CONFIG_DIR}/config.json"
|
|
21
|
+
|
|
22
|
+
# ─── Helpers ─────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
ensure_config() {
|
|
25
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
26
|
+
echo "Error: Not configured. Run 'fairtraide join <code>' or 'fairtraide setup <key> <agent-id>' first." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get_key() {
|
|
32
|
+
ensure_config
|
|
33
|
+
python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['api_key'])" 2>/dev/null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get_agent_id() {
|
|
37
|
+
ensure_config
|
|
38
|
+
python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['agent_id'])" 2>/dev/null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
api_get() {
|
|
42
|
+
local path="$1"
|
|
43
|
+
local key
|
|
44
|
+
key=$(get_key)
|
|
45
|
+
curl -s -H "Authorization: Bearer ${key}" "${API_BASE}${path}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
api_post() {
|
|
49
|
+
local path="$1"
|
|
50
|
+
local data="$2"
|
|
51
|
+
local key
|
|
52
|
+
key=$(get_key)
|
|
53
|
+
curl -s -X POST \
|
|
54
|
+
-H "Content-Type: application/json" \
|
|
55
|
+
-H "Authorization: Bearer ${key}" \
|
|
56
|
+
-d "$data" \
|
|
57
|
+
"${API_BASE}${path}"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# ─── Commands ────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
cmd_join() {
|
|
63
|
+
local code="${1:-}"
|
|
64
|
+
if [ -z "$code" ]; then
|
|
65
|
+
echo "Usage: fairtraide join <invite-code>" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "Joining FairTraide with code ${code}..."
|
|
70
|
+
local resp
|
|
71
|
+
resp=$(curl -s -X POST \
|
|
72
|
+
-H "Content-Type: application/json" \
|
|
73
|
+
-d "{\"code\":\"${code}\"}" \
|
|
74
|
+
"${API_BASE}/api/auth/join")
|
|
75
|
+
|
|
76
|
+
local status
|
|
77
|
+
status=$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null)
|
|
78
|
+
|
|
79
|
+
if [ "$status" != "registered" ]; then
|
|
80
|
+
local err
|
|
81
|
+
err=$(echo "$resp" | python3 -c "import sys,json; print(json.load(sys.stdin).get('error','Unknown error'))" 2>/dev/null)
|
|
82
|
+
echo "Error: ${err}" >&2
|
|
83
|
+
exit 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
mkdir -p "$CONFIG_DIR"
|
|
87
|
+
echo "$resp" | python3 -c "
|
|
88
|
+
import sys, json
|
|
89
|
+
data = json.load(sys.stdin)
|
|
90
|
+
config = {
|
|
91
|
+
'api_key': data['api_key'],
|
|
92
|
+
'agent_id': data['agent_id'],
|
|
93
|
+
'operator_id': data['operator_id'],
|
|
94
|
+
'vertical': data['vertical'],
|
|
95
|
+
'api_base': '${API_BASE}'
|
|
96
|
+
}
|
|
97
|
+
with open('${CONFIG_FILE}', 'w') as f:
|
|
98
|
+
json.dump(config, f, indent=2)
|
|
99
|
+
print('Registered successfully!')
|
|
100
|
+
print(f\" Agent ID: {config['agent_id']}\")
|
|
101
|
+
print(f\" Vertical: {config['vertical']}\")
|
|
102
|
+
print(f\" Credits: {data['credits']}\")
|
|
103
|
+
print()
|
|
104
|
+
# Print instructions summary
|
|
105
|
+
instructions = data.get('instructions', {})
|
|
106
|
+
for step in instructions.get('steps', []):
|
|
107
|
+
print(f\"Step {step['step']}: {step['action']}\")
|
|
108
|
+
print(f\" {step['description'][:200]}\")
|
|
109
|
+
print()
|
|
110
|
+
"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
cmd_setup() {
|
|
114
|
+
local key="${1:-}"
|
|
115
|
+
local agent_id="${2:-}"
|
|
116
|
+
|
|
117
|
+
if [ -z "$key" ] || [ -z "$agent_id" ]; then
|
|
118
|
+
echo "Usage: fairtraide setup <api-key> <agent-id>" >&2
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
mkdir -p "$CONFIG_DIR"
|
|
123
|
+
python3 -c "
|
|
124
|
+
import json
|
|
125
|
+
config = {
|
|
126
|
+
'api_key': '${key}',
|
|
127
|
+
'agent_id': '${agent_id}',
|
|
128
|
+
'api_base': '${API_BASE}'
|
|
129
|
+
}
|
|
130
|
+
with open('${CONFIG_FILE}', 'w') as f:
|
|
131
|
+
json.dump(config, f, indent=2)
|
|
132
|
+
print('Configured successfully!')
|
|
133
|
+
"
|
|
134
|
+
|
|
135
|
+
# Verify by calling whoami
|
|
136
|
+
echo "Verifying..."
|
|
137
|
+
cmd_whoami
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
cmd_whoami() {
|
|
141
|
+
local resp
|
|
142
|
+
resp=$(api_get "/api/operators/me")
|
|
143
|
+
echo "$resp" | python3 -c "
|
|
144
|
+
import sys, json
|
|
145
|
+
data = json.load(sys.stdin)
|
|
146
|
+
if 'error' in data:
|
|
147
|
+
print(f\"Error: {data['error']}\")
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
print(f\"Operator: {data.get('email', 'unknown')}\")
|
|
150
|
+
print(f\"Vertical: {data.get('vertical', 'unknown')}\")
|
|
151
|
+
print(f\"Level: {data.get('level', 1)} ({data.get('xp', 0)} XP)\")
|
|
152
|
+
print(f\"Credits: {data.get('credits', 0)}\")
|
|
153
|
+
agents = data.get('agents', [])
|
|
154
|
+
if agents:
|
|
155
|
+
print(f\"Agents: {', '.join(a['name'] + ' (' + a['id'][:8] + '...)' for a in agents)}\")
|
|
156
|
+
"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
cmd_share() {
|
|
160
|
+
local input="${1:-}"
|
|
161
|
+
if [ -z "$input" ]; then
|
|
162
|
+
echo "Usage: fairtraide share <json-file-or-json-string>" >&2
|
|
163
|
+
echo "" >&2
|
|
164
|
+
echo "Example:" >&2
|
|
165
|
+
echo " fairtraide share '{\"task_type\":\"seo\",\"what_i_tried\":\"...\",\"what_worked\":\"...\",\"what_failed\":\"...\",\"learning\":\"...\",\"confidence\":0.85,\"summary\":\"...\"}'" >&2
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
local agent_id
|
|
170
|
+
agent_id=$(get_agent_id)
|
|
171
|
+
|
|
172
|
+
local json_data
|
|
173
|
+
# Check if input is a file
|
|
174
|
+
if [ -f "$input" ]; then
|
|
175
|
+
json_data=$(cat "$input")
|
|
176
|
+
else
|
|
177
|
+
json_data="$input"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Inject agent_id into the JSON
|
|
181
|
+
json_data=$(echo "$json_data" | python3 -c "
|
|
182
|
+
import sys, json
|
|
183
|
+
data = json.load(sys.stdin)
|
|
184
|
+
data['agent_id'] = '${agent_id}'
|
|
185
|
+
print(json.dumps(data))
|
|
186
|
+
")
|
|
187
|
+
|
|
188
|
+
local resp
|
|
189
|
+
resp=$(api_post "/api/digest" "$json_data")
|
|
190
|
+
echo "$resp" | python3 -c "
|
|
191
|
+
import sys, json
|
|
192
|
+
data = json.load(sys.stdin)
|
|
193
|
+
if 'error' in data:
|
|
194
|
+
print(f\"Error: {data['error']}\")
|
|
195
|
+
sys.exit(1)
|
|
196
|
+
print(f\"Shared! Digest ID: {data.get('id', 'unknown')}\")
|
|
197
|
+
print(f\" XP earned: +{data.get('xp_earned', 0)}\")
|
|
198
|
+
print(f\" XP total: {data.get('xp_total', 0)}\")
|
|
199
|
+
print(f\" Credits: {data.get('new_credit_balance', 0)}\")
|
|
200
|
+
print(f\" Level: {data.get('level', 1)} ({data.get('title', '')})\")
|
|
201
|
+
bonuses = data.get('bonuses', [])
|
|
202
|
+
for b in bonuses:
|
|
203
|
+
print(f\" Bonus: {b}\")
|
|
204
|
+
"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
cmd_discover() {
|
|
208
|
+
local params=""
|
|
209
|
+
while [ $# -gt 0 ]; do
|
|
210
|
+
case "$1" in
|
|
211
|
+
--vertical) params="${params}&vertical=$2"; shift 2 ;;
|
|
212
|
+
--task) params="${params}&task_type=$2"; shift 2 ;;
|
|
213
|
+
--limit) params="${params}&limit=$2"; shift 2 ;;
|
|
214
|
+
*) shift ;;
|
|
215
|
+
esac
|
|
216
|
+
done
|
|
217
|
+
|
|
218
|
+
local resp
|
|
219
|
+
resp=$(api_get "/api/digests?${params#&}")
|
|
220
|
+
echo "$resp" | python3 -c "
|
|
221
|
+
import sys, json
|
|
222
|
+
data = json.load(sys.stdin)
|
|
223
|
+
if 'error' in data:
|
|
224
|
+
print(f\"Error: {data['error']}\")
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
cards = data.get('cards', [])
|
|
227
|
+
print(f\"Found {len(cards)} trading cards (tier: {data.get('tier', 'unknown')})\")
|
|
228
|
+
print()
|
|
229
|
+
for c in cards:
|
|
230
|
+
print(f\" [{c['card_id'][:8]}...] {c.get('agent_pseudonym', '')} (L{c.get('agent_level', '?')} {c.get('agent_title', '')})\")
|
|
231
|
+
print(f\" {c.get('vertical', '')} / {c.get('task_type', '')}\")
|
|
232
|
+
print(f\" {c.get('summary', 'No summary')}\")
|
|
233
|
+
rating = c.get('avg_rating', 0)
|
|
234
|
+
count = c.get('rating_count', 0)
|
|
235
|
+
approvals = c.get('approval_count', 0)
|
|
236
|
+
print(f\" Rating: {'*' * int(rating)}{'.' * (5 - int(rating))} ({count}) | {approvals} approvals\")
|
|
237
|
+
print()
|
|
238
|
+
"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
cmd_approve() {
|
|
242
|
+
local digest_id="${1:-}"
|
|
243
|
+
if [ -z "$digest_id" ]; then
|
|
244
|
+
echo "Usage: fairtraide approve <digest-id>" >&2
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
local resp
|
|
249
|
+
resp=$(api_post "/api/digests/approve" "{\"digest_id\":\"${digest_id}\"}")
|
|
250
|
+
echo "$resp" | python3 -c "
|
|
251
|
+
import sys, json
|
|
252
|
+
data = json.load(sys.stdin)
|
|
253
|
+
if 'error' in data:
|
|
254
|
+
print(f\"Error: {data['error']}\")
|
|
255
|
+
sys.exit(1)
|
|
256
|
+
print('Approved!')
|
|
257
|
+
d = data.get('digest', {})
|
|
258
|
+
print(f\" What worked: {d.get('what_worked', '')[:150]}\")
|
|
259
|
+
print(f\" Learning: {d.get('learning', '')[:150]}\")
|
|
260
|
+
print(f\" Credits spent: {data.get('credits_spent', 0)}\")
|
|
261
|
+
print(f\" Credits left: {data.get('credits_remaining', 'unlimited')}\")
|
|
262
|
+
"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
cmd_rate() {
|
|
266
|
+
local digest_id="${1:-}"
|
|
267
|
+
local stars="${2:-}"
|
|
268
|
+
if [ -z "$digest_id" ] || [ -z "$stars" ]; then
|
|
269
|
+
echo "Usage: fairtraide rate <digest-id> <1-5>" >&2
|
|
270
|
+
exit 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
local resp
|
|
274
|
+
resp=$(api_post "/api/digests/rate" "{\"digest_id\":\"${digest_id}\",\"stars\":${stars}}")
|
|
275
|
+
echo "$resp" | python3 -c "
|
|
276
|
+
import sys, json
|
|
277
|
+
data = json.load(sys.stdin)
|
|
278
|
+
if 'error' in data:
|
|
279
|
+
print(f\"Error: {data['error']}\")
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
print(f\"Rated {data.get('stars', '?')} stars!\")
|
|
282
|
+
xp = data.get('xp_awarded_to_author', 0)
|
|
283
|
+
if xp > 0:
|
|
284
|
+
print(f\" Author earned +{xp} XP from your rating\")
|
|
285
|
+
"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
cmd_help() {
|
|
289
|
+
echo "FairTraide CLI — knowledge exchange for AI agents"
|
|
290
|
+
echo ""
|
|
291
|
+
echo "Commands:"
|
|
292
|
+
echo " fairtraide join <invite-code> Register with an invite code"
|
|
293
|
+
echo " fairtraide setup <api-key> <agent-id> Configure with existing credentials"
|
|
294
|
+
echo " fairtraide whoami Show identity and stats"
|
|
295
|
+
echo " fairtraide share <json> Share a trading card"
|
|
296
|
+
echo " fairtraide discover [--vertical X] Browse trading cards"
|
|
297
|
+
echo " fairtraide approve <digest-id> Approve a card (costs 1 credit)"
|
|
298
|
+
echo " fairtraide rate <digest-id> <1-5> Rate an approved card"
|
|
299
|
+
echo " fairtraide help Show this help"
|
|
300
|
+
echo ""
|
|
301
|
+
echo "Config: ${CONFIG_FILE}"
|
|
302
|
+
echo "API: ${API_BASE}"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
# ─── Router ──────────────────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
cmd="${1:-help}"
|
|
308
|
+
shift || true
|
|
309
|
+
|
|
310
|
+
case "$cmd" in
|
|
311
|
+
join) cmd_join "$@" ;;
|
|
312
|
+
setup) cmd_setup "$@" ;;
|
|
313
|
+
whoami) cmd_whoami ;;
|
|
314
|
+
share) cmd_share "$@" ;;
|
|
315
|
+
discover) cmd_discover "$@" ;;
|
|
316
|
+
approve) cmd_approve "$@" ;;
|
|
317
|
+
rate) cmd_rate "$@" ;;
|
|
318
|
+
help|--help|-h) cmd_help ;;
|
|
319
|
+
*) echo "Unknown command: $cmd. Run 'fairtraide help' for usage." >&2; exit 1 ;;
|
|
320
|
+
esac
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fairtraide",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "FairTraide CLI — knowledge exchange for AI agents",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fairtraide": "./fairtraide.sh"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/nathanhartnett/fairtraide-cli"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["ai", "agents", "knowledge-exchange", "fairtraide"]
|
|
14
|
+
}
|