aweshare 0.4.7 → 0.4.9

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.ai.md ADDED
@@ -0,0 +1,346 @@
1
+ # aweshare Bootstrap Protocol
2
+
3
+ This document is for AI coding agents. Help the user install and configure `aweshare`, a local-first AI capability relay: producers share local Ollama/vLLM or authorized OpenAI/Anthropic backends through a self-hosted hub; consumers point standard OpenAI/Anthropic SDKs at the hub and call models by `namespace/alias`.
4
+
5
+ ## Long-Running Services and Secrets
6
+
7
+ Unlike ordinary CLIs, aweshare's two core processes are **long-running services**:
8
+
9
+ - Never launch them from this agent — not `aweshare hub serve`, not `aweshare producer start` (foreground blocks the session; even `--background` starts a shared service, which is the user's call). The same applies to deploying the hub as a container (`docker run` / `docker compose up` of `ghcr.io/wehuman01/aweshare`). Give the user the commands and let them run them in their own terminal.
10
+ - Upstream API keys live only in `~/.aweshare/secrets.json` (0600) on the producer's machine. Never print them, never echo them into logs or chat. `aweshare producer config show` redacts them.
11
+ - Some tokens are printed exactly once (the hub admin token from `hub init`, an `asc_` consumer token from `consumer join`, invite codes from `hub invite`). Prefer having the user run those commands so the secret lands in their terminal; if you must run one, tell the user to save the value immediately.
12
+ - Before helping share an OpenAI/Anthropic backend, state the compliance boundary: relaying a personal-subscription key (coding plans included) to third parties likely violates the upstream's terms; self-hosted open models have no such issue. When in doubt, don't share.
13
+
14
+ ## Language Behavior
15
+
16
+ - Reply in the user's language when possible.
17
+ - If the user asks in Chinese, continue in Chinese.
18
+ - If the user asks in English, continue in English.
19
+
20
+ ## Step 0: Identify the user's role
21
+
22
+ aweshare has three roles with different setups. Ask which one applies (more than one is possible):
23
+
24
+ - **Hub operator** — runs the public hub on an always-on server; mints invite codes.
25
+ - **Producer** — shares models from their own machine through a hub.
26
+ - **Consumer** — calls shared models with a standard SDK from any machine.
27
+
28
+ Then follow Steps 1–2 (identical for everyone) and the matching path in Step 4.
29
+
30
+ ## Step 1: Install aweshare CLI
31
+
32
+ ### Prerequisites
33
+
34
+ - Node >= 22 (`node --version`)
35
+
36
+ If Node.js is missing, tell the user to install it from https://nodejs.org/.
37
+
38
+ ### Steps
39
+
40
+ ```bash
41
+ npm install -g aweshare
42
+ aweshare -v
43
+ ```
44
+
45
+ Expected output: a version number (e.g. `0.4.8`).
46
+
47
+ ---
48
+
49
+ ## Step 2: Install the aweshare skill
50
+
51
+ Install the skill so the agent can operate aweshare in this and future sessions. Choose one of the following options.
52
+
53
+ ### Option A: Via aweskill (recommended if aweskill is available)
54
+
55
+ Use this option if the user already has aweskill installed, or is willing to install it. This gives full skill management — install, update, projection, backup.
56
+
57
+ #### Prerequisites
58
+
59
+ - Node.js >= 20 (`node --version`) — required by aweskill
60
+ - npm available (`npm --version`)
61
+
62
+ #### Steps
63
+
64
+ ##### A1. Install aweskill (if not already installed)
65
+
66
+ ```bash
67
+ npm install -g aweskill
68
+ ```
69
+
70
+ ##### A2. Initialize the aweskill central store (if not already done)
71
+
72
+ ```bash
73
+ aweskill store init
74
+ ```
75
+
76
+ ##### A3. Install the aweshare skill from GitHub
77
+
78
+ ```bash
79
+ aweskill install wehuman01/aweshare
80
+ ```
81
+
82
+ ##### A4. Identify the current agent
83
+
84
+ ```bash
85
+ aweskill agent supported
86
+ ```
87
+
88
+ Look for lines marked with `✓`. Common agent ids: `claude-code`, `cursor`, `codex`, `gemini-cli`, `windsurf`, `opencode`, `qwen-code`.
89
+
90
+ If you cannot determine the agent id, ask the user.
91
+
92
+ ##### A5. Project the aweshare skill to this agent
93
+
94
+ ```bash
95
+ aweskill agent add skill aweshare --global --agent <agent-id>
96
+ ```
97
+
98
+ ##### A6. Verify
99
+
100
+ ```bash
101
+ aweskill agent list --global --agent <agent-id>
102
+ ```
103
+
104
+ Expected: `aweshare` shows as `linked`.
105
+
106
+ ---
107
+
108
+ ### Option B: Direct copy (no aweskill needed)
109
+
110
+ Use this option if the user does not have aweskill. This copies the SKILL.md file directly into the agent's skill directory.
111
+
112
+ #### Prerequisites
113
+
114
+ - `curl` or `wget` available
115
+
116
+ #### Steps
117
+
118
+ ##### B1. Identify the current agent's skill directory
119
+
120
+ Determine which agent is running and its global skill directory:
121
+
122
+ | Agent | Skill directory |
123
+ |---|---|
124
+ | Claude Code | `~/.claude/skills/aweshare/` |
125
+ | Codex | `~/.codex/skills/aweshare/` |
126
+ | Cursor | `.cursor/skills/aweshare/` (project-level) |
127
+ | Gemini CLI | `~/.gemini/skills/aweshare/` |
128
+ | Windsurf | `~/.windsurf/skills/aweshare/` |
129
+ | OpenCode | `~/.opencode/skills/aweshare/` |
130
+ | Qwen Code | `~/.qwen/skills/aweshare/` |
131
+
132
+ If the agent is not in this list, ask the user where to place the skill file.
133
+
134
+ ##### B2. Download and place SKILL.md
135
+
136
+ ```bash
137
+ mkdir -p <skill-directory>
138
+ curl -fsSL https://raw.githubusercontent.com/wehuman01/aweshare/main/resources/skills/aweshare/SKILL.md -o <skill-directory>/SKILL.md
139
+ ```
140
+
141
+ Replace `<skill-directory>` with the path from step B1.
142
+
143
+ ---
144
+
145
+ ## Step 3: Detect the current setup (read-only, safe to run)
146
+
147
+ ```bash
148
+ aweshare producer config path # where config.toml / secrets.json live
149
+ aweshare producer config show # config with token + secrets redacted
150
+ aweshare producer doctor --status # background producer state + recent log, instant, no network
151
+ ```
152
+
153
+ Report the findings to the user:
154
+
155
+ - No config at `~/.aweshare/config.toml` — fresh machine, continue with Step 4.
156
+ - Config present — a producer is (or was) set up here; `doctor --status` tells whether a background instance is running.
157
+ - Full `aweshare producer doctor` (without `--status`) adds network probes of the configured backends and the hub plus the recent log. Diagnostic traffic only — run it freely after setup, fix the first FAIL, re-run until green.
158
+
159
+ ---
160
+
161
+ ## Step 4: Role-specific setup
162
+
163
+ Interactive or service-starting commands stay in the user's terminal. Everything else you can do.
164
+
165
+ ### Producer — shares models from this machine
166
+
167
+ 1. Join the hub (either works):
168
+ - The user redeems their invite code — you may run it, it is non-interactive and writes `~/.aweshare/config.toml` + empty `secrets.json`:
169
+ ```bash
170
+ aweshare producer join --hub https://hub.example.com --code asi_...
171
+ ```
172
+ - Or the operator handed over a token directly:
173
+ ```bash
174
+ aweshare producer init --hub https://hub.example.com --token asp_...
175
+ ```
176
+ 2. Edit `config.toml` yourself: set `hubUrl`/`token`, define `[[backends]]` (protocol + baseUrl convention: `openai` baseUrl includes `/v1`; `anthropic` excludes `/v1`; `responses` includes the version path) and `[[offerings]]` (alias namespace = producer name, one upstream model each).
177
+ 3. Upstream keys go into `secrets.json` under the backend's `keyRef` name. **Do not ask the user to paste keys into chat** — ask them to edit `secrets.json` themselves, then continue.
178
+ 4. Verify:
179
+ ```bash
180
+ aweshare producer doctor # instance, config, backend probes, hub — fix the first FAIL
181
+ ```
182
+ 5. Tell the user (do not run):
183
+ > Run `aweshare producer start` in your terminal — or `aweshare producer start --background` to detach it (logs to `~/.aweshare/producer.log`). Stopping later: `aweshare producer stop`.
184
+
185
+ After config edits on a running producer, `aweshare producer reload` applies the new catalog without a restart (both processes also stat-poll their config every 2s and hot-apply valid edits; `hubUrl`/`token` still need a restart).
186
+
187
+ ### Consumer — calls shared models
188
+
189
+ 1. Have the user redeem their invite in their own terminal — it prints the `asc_` token exactly once:
190
+ ```bash
191
+ aweshare consumer join --hub https://hub.example.com --code asi_...
192
+ ```
193
+ (No aweshare installed? One curl works: `POST https://hub.example.com/invites/v1/redeem` with `{"code":"asi_..."}`.)
194
+ 2. Configure the tool with the printed env vars:
195
+ ```bash
196
+ # Claude Code / Anthropic SDK — the key is the asc_ consumer key, NOT any upstream x-api-key
197
+ export ANTHROPIC_BASE_URL=https://hub.example.com
198
+ export ANTHROPIC_AUTH_TOKEN=asc_...
199
+ claude --model peng/sonnet
200
+
201
+ # OpenAI SDK / Codex
202
+ export OPENAI_BASE_URL=https://hub.example.com/v1
203
+ export OPENAI_API_KEY=asc_...
204
+ ```
205
+ 3. Discovery (read-only, safe to run once you have the token):
206
+ ```bash
207
+ aweshare consumer list --hub https://hub.example.com --token asc_...
208
+ ```
209
+ Every producer, alias, protocol, status, caps, live occupancy and remaining daily tokens.
210
+ 4. A first smoke-test request (`curl ... /v1/chat/completions` with `"ping"`) is a real model call — only run it when the user asks.
211
+
212
+ Remind the consumer: prompts and responses transit the hub in plaintext — only use a hub they trust. If Claude Code ignores env config, a stale OAuth login is overriding it — switch with `/login`.
213
+
214
+ ### Hub operator — runs the hub on a server
215
+
216
+ 1. Choose npm or Docker (Docker is the better default on a VPS). Both `hub serve` and container deployment run in the **user's** terminal:
217
+ ```bash
218
+ # npm
219
+ npm install -g aweshare
220
+ aweshare hub init # data in ~/.aweshare-hub; prints the admin token ONCE — save it
221
+ aweshare hub serve # listens on :8787; put Caddy/nginx TLS in front
222
+
223
+ # docker
224
+ docker run -d --name aweshare-hub --restart unless-stopped \
225
+ -p 127.0.0.1:8787:8787 -v "$PWD/data:/data" ghcr.io/wehuman01/aweshare:latest
226
+ docker exec aweshare-hub aweshare hub init
227
+ ```
228
+ 2. After `hub init` (wherever it ran), mint invites — you may run these:
229
+ ```bash
230
+ aweshare hub invite --name peng # bound producer code → asi_...
231
+ aweshare hub invite --count 10 # unbound batch, name+email at redeem
232
+ aweshare hub invite --role consumer --name alice # bound consumer code → asi_...
233
+ ```
234
+ Consumers redeem codes themselves (`aweshare consumer join`); producers via `aweshare producer join`.
235
+ 3. Administering a remote hub: run admin commands on the server (`ssh` + CLI, or `docker exec aweshare-hub aweshare hub ...`), or locally with `AWESHARE_HUB_URL=https://<hub-host>` plus the admin-token file in a local data dir — otherwise they fail with "no admin token" / connection refused.
236
+ 4. Guardrails you can tune on request: per-consumer `aweshare hub limits NAME [--rps N] [--tpm N] [--max-total-tokens N] ...`, suspension `aweshare hub revoke --id N` / `restore --id N` (reversible, invite-keyed).
237
+ 5. Security notes for the operator: keep :8787 off the public internet behind TLS; a redeemed consumer key can call **every** offering on the hub; tokens/codes stored plaintext-recoverable (`hub list --reveal` / `--token`) means a data-dir leak exposes identities — guard it.
238
+
239
+ ---
240
+
241
+ ## Step 5: Verify and tune (safe to run)
242
+
243
+ After setup:
244
+
245
+ ```bash
246
+ aweshare producer doctor # full diagnosis, ordered to find the first failing link
247
+ aweshare producer list # registered offerings, live status, caps, occupancy, config drift
248
+ aweshare hub list producers # roster + last seen (admin context only)
249
+ aweshare hub usage # who used how much, aggregated per consumer × model
250
+ aweshare self-update --check # installed vs npm latest
251
+ ```
252
+
253
+ On the user's request you may also:
254
+
255
+ ```bash
256
+ aweshare hub limits alice --tpm 60000 --max-total-tokens 5000000 # cap a consumer
257
+ aweshare hub usage --details --since 24h # per-request log
258
+ aweshare producer reload # apply config edits without restart
259
+ ```
260
+
261
+ ## Useful commands
262
+
263
+ Read-only commands (safe to run in agent):
264
+
265
+ ```bash
266
+ aweshare -v # installed version
267
+ aweshare producer config path # config/secrets locations
268
+ aweshare producer config show # config, secrets redacted
269
+ aweshare producer doctor [--status] # diagnosis (with --status: instant, no network)
270
+ aweshare producer list [--json] # this producer's offerings + drift
271
+ aweshare producer usage [...] # who used this producer's models
272
+ aweshare consumer list --hub URL --token asc_... # hub catalog view
273
+ aweshare hub list [invites|producers|consumers] [--json] # rosters / invite lifecycle
274
+ aweshare hub limits NAME # bare call views current overrides
275
+ aweshare hub usage [--details] [--group-by consumer|alias] [--since 7d]
276
+ aweshare self-update --check # versions only
277
+ ```
278
+
279
+ Local changes (run on user request):
280
+
281
+ ```bash
282
+ aweshare producer init [--hub URL] [--token asp_...] # write templates; no-op if they exist
283
+ aweshare producer join --hub URL --code asi_... # redeem a producer invite into the config
284
+ aweshare producer config edit # open config.toml in $EDITOR
285
+ aweshare producer reload # re-read config + re-register offerings
286
+ aweshare hub init # create data dir + admin token (printed once!)
287
+ aweshare hub invite [--role producer|consumer] ... # mint one-time codes (printed once)
288
+ aweshare hub limits NAME [--rps N] [--burst N] [--max-concurrent N] [--tpm N] [--max-total-tokens N] [--clear]
289
+ aweshare hub revoke --id N / restore --id N # reversible suspension, by invite handle
290
+ ```
291
+
292
+ User-only commands (long-running, TTY-confirming, or one-time-secret-printing):
293
+
294
+ ```bash
295
+ aweshare producer start [--background] / stop # the relay service
296
+ aweshare hub serve # the hub
297
+ aweshare consumer join --hub URL --code asi_... # prints the asc_ token once
298
+ aweshare self-update # y/n confirm; non-interactive: npm install -g aweshare
299
+ docker run / docker compose up # hub deployment
300
+ ```
301
+
302
+ ## Safety Rules
303
+
304
+ - Never start or stop the long-running services (`hub serve`, `producer start`/`stop`, hub containers) inside the agent — deployment is the user's call. Inspect a detached producer with `producer doctor --status`.
305
+ - Never read or print `secrets.json` values; use `producer config show` (redacted) when showing config. Never ask the user to paste an upstream API key into chat — point them at the file instead.
306
+ - Commands that print a secret exactly once (`hub init`, `consumer join`, `hub invite`) are best run by the user; if you run one, flag the value as save-now.
307
+ - State the trust boundary before anyone joins a hub: consumer traffic transits the hub in plaintext; a redeemed consumer key can call every offering. State the compliance boundary before anyone shares a key: personal-subscription keys forwarded to third parties likely violate upstream terms — the producer bears the consequences.
308
+ - Read state through the CLI (`config show`, `list`, `doctor`); do not hand-edit SQLite or pidfiles. Config TOML edits are fine — they hot-reload.
309
+ - Offering aliases must be `namespace/name`, lowercase, namespace matching the producer token's name. One offering = one upstream model; the old `maxConcurrency` key fails validation with a rename hint (use `maxConcurrencyPerUser`).
310
+ - If any command fails, report the exact command and error message (codes like `403 HUB_FULL`, `409 INVITE_ROLE_MISMATCH`, `401 TOKEN_REVOKED` carry meaning — pass them through). Do not silently retry.
311
+
312
+ ## Final Step
313
+
314
+ After setup, tell the user to invoke skills (`/` in Claude Code, `$` in Codex, or the equivalent in other agents) and check if `aweshare` appears in the list. If it does, the skill is ready to use immediately. If not, the user should restart the agent.
315
+
316
+ > aweshare is installed and configured. Invoke skills (type `/` or `$` depending on your agent) and look for `aweshare` — if it appears, you're good to go. If not, restart the agent. Then you can ask me things like:
317
+ >
318
+ > - "Share my local Ollama qwen2.5:7b as peng/qwen2.5.7b."
319
+ > - "Why is my producer offline?" (I'll run `producer doctor` and fix the first FAIL.)
320
+ > - "Cap alice at 60k tokens per minute."
321
+ > - "Who used my models this week?"
322
+
323
+ If the user is speaking Chinese, use this version instead:
324
+
325
+ > aweshare 已安装并配置完成。请调用 skills(输入 `/` 或 `$`,取决于你的 agent),看看列表中是否出现了 `aweshare`。如果出现了,说明已就绪可以直接使用。如果没有,请重启 agent 后再试。然后你可以继续问我,例如:
326
+ >
327
+ > - “把我本地的 Ollama qwen2.5:7b 以 peng/qwen2.5.7b 共享出去。”
328
+ > - “我的 producer 为什么掉线了?”(我会跑 `producer doctor` 并修复第一个 FAIL。)
329
+ > - “给 alice 限流到每分钟 6 万 token。”
330
+ > - “这周谁用了我的模型?”
331
+
332
+ ---
333
+
334
+ ## Next Steps
335
+
336
+ ### Community hub — no server of your own
337
+
338
+ The project's developer runs an invite-based community hub at https://aweshare.wehuman.top. If the user wants to share or consume without operating a hub, guide them through the tutorial: https://github.com/wehuman01/aweshare/blob/main/docs/community-hub/README.md — requesting an invite at peng@wehuman.top, joining as producer or consumer, and the first connection. State the trust rule before they join: their traffic transits that hub in plaintext.
339
+
340
+ ### awewarm — keep shared subscription windows warm
341
+
342
+ If the user shares a coding-plan-backed endpoint through aweshare, pair it with [awewarm](https://github.com/wehuman01/awewarm): it schedules one minimal request per window so the underlying subscription quota never cools down mid-day. Its agent doc lives at https://github.com/wehuman01/awewarm/blob/main/README.ai.md.
343
+
344
+ ### aweswitch — agent profile switching
345
+
346
+ Consumers who juggle several providers can use [aweswitch](https://github.com/Webioinfo01/aweswitch) to switch Claude Code / Codex / OpenCode profiles; pointing one of those profiles at an aweshare hub is just another provider entry.
package/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  <a href="https://ko-fi.com/mugpeng"><img src="https://img.shields.io/badge/Ko--fi-Buy%20me%20a%20coffee-FF5E5B?style=flat-square&logo=ko-fi&logoColor=white" alt="Ko-fi"></a>
15
15
  </p>
16
16
  <p>
17
- <a href="https://github.com/wehuman01/aweshare-source/releases"><img src="https://img.shields.io/badge/version-0.4.7-7C3AED?style=flat-square" alt="Version"></a>
17
+ <a href="https://github.com/wehuman01/aweshare-source/releases"><img src="https://img.shields.io/badge/version-0.4.9-7C3AED?style=flat-square" alt="Version"></a>
18
18
  <a href="https://github.com/wehuman01/aweshare"><img src="https://img.shields.io/badge/node-%E2%89%A522-0EA5E9?style=flat-square" alt="Node"></a>
19
19
  <a href="https://github.com/wehuman01/aweshare/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-proprietary-E34F26?style=flat-square" alt="License"></a>
20
20
  <a href="https://www.npmjs.com/package/aweshare"><img src="https://img.shields.io/badge/npm-aweshare-7C3AED?style=flat-square" alt="npm package"></a>
@@ -69,7 +69,19 @@ Consumer (standard SDK, zero changes) Producer side
69
69
 
70
70
  Published as [`aweshare`](https://www.npmjs.com/package/aweshare) on npm (requires Node ≥ 22) and as a Docker image (`ghcr.io/wehuman01/aweshare`). No clone needed.
71
71
 
72
- ### 1. Start the hub (operator, one VPS)
72
+ ### Let an AI agent set it up
73
+
74
+ Working in Claude Code, Codex, or another coding agent? Tell it:
75
+
76
+ ```text
77
+ Read https://github.com/wehuman01/aweshare/blob/main/README.ai.md and follow it to install and configure aweshare.
78
+ ```
79
+
80
+ The agent installs the CLI and the skill, asks whether you are a hub operator, producer, or consumer, and does everything that is safe to automate — editing configs, minting invites, running `producer doctor`. Steps that print one-time tokens (`hub init`, `consumer join`) or start long-running services (`hub serve`, `producer start`) stay in your terminal. After setup you can ask things like "share my local Ollama model as peng/qwen2.5.7b", "why is my producer offline?", or "who used my models this week?".
81
+
82
+ ### Manual setup
83
+
84
+ #### 1. Start the hub (operator, one VPS)
73
85
 
74
86
  **npm** (simplest):
75
87
 
@@ -79,7 +91,7 @@ aweshare hub init # data in ~/.aweshare-hub; prints the admin token — s
79
91
  aweshare hub serve # listens on :8787 (put Caddy/nginx TLS in front)
80
92
  ```
81
93
 
82
- The hub can also host models itself — no producer machine needed. Add `[[backends]]`/`[[offerings]]` sections to `~/.aweshare-hub/config.toml` (same format as a producer's config, alias namespace `hub/…`), put the upstream keys in `~/.aweshare-hub/secrets.json`, and run:
94
+ The hub can also host models itself — no producer machine needed. Add `[[backends]]`/`[[offerings]]` sections to `~/.aweshare-hub/config.produce.toml` (same format as a producer's config, alias namespace `hub/…`), put the upstream keys in `~/.aweshare-hub/secrets.json`, and run:
83
95
 
84
96
  ```bash
85
97
  aweshare hub produce # serve + the hub's own models; consumers dial hub/<name> like any offering
@@ -132,7 +144,7 @@ The producer's `name` becomes their alias namespace (the `peng/` in `peng/gpt-4o
132
144
 
133
145
  The operator owns admission: one-time invite codes for both roles, the only admission path (every identity carries its invite handle for its whole lifecycle). A redeemed consumer key may call **every** offering on the hub — if you let someone in, they can use what is shared. Guardrails: per-consumer `hub limits` (rate, concurrency, token budgets), per-offering caps (`maxConcurrentUsers`, `dailyTokens`) and `hub revoke` suspension, all enforced by the hub.
134
146
 
135
- ### 2. Producer first run (in this order)
147
+ #### 2. Producer first run (in this order)
136
148
 
137
149
  ```bash
138
150
  npm install -g aweshare # ⓪ once, on the producer machine (Node ≥ 22)
@@ -153,7 +165,7 @@ aweshare producer start # foreground; add --background to detach it
153
165
  # and stopped with 'aweshare producer stop'
154
166
  ```
155
167
 
156
- ### 3. Consumer first run (in this order)
168
+ #### 3. Consumer first run (in this order)
157
169
 
158
170
  ```bash
159
171
  # ⓪ redeem your invite code (skip if the operator handed you an asc_ key directly)
@@ -278,11 +290,11 @@ Honest limits: token-based caps count what upstreams report — Ollama streams r
278
290
  | `GET /v1/catalog` | every offering on the hub — producer, alias, protocol, status, the per-offering caps, live in-flight occupancy (`activeUsers`/`activeRequests`) and today's used/remaining tokens (discovery view for `aweshare consumer list`) |
279
291
  | `GET /healthz` | liveness |
280
292
  | `GET /admin/v1/offerings` | registered offerings with live status, caps, in-flight occupancy and today's used tokens — admin sees everything, a producer token only its own slice (`aweshare producer list`) |
281
- | `/admin/v1/*` | token/limit/usage management (admin or producer token) · usage: `GET /admin/v1/usage` (newest-first log) and `GET /admin/v1/usage/summary` (`group=consumer\|alias`, `since=30m\|12h\|7d`, `consumer`/`producer`/`alias` filters; every role sees its own slice) · consumer limit overrides: `GET`/`PUT`/`DELETE /admin/v1/consumers/{name}/limits` (admin only) |
293
+ | `/admin/v1/*` | token/limit/usage management (admin or producer token) · usage: `GET /admin/v1/usage` (newest-first log) and `GET /admin/v1/usage/summary` (`group=consumer-alias\|consumer\|alias`, `since=30m\|12h\|7d\|all`, default 7d, `consumer`/`producer`/`alias` filters; every role sees its own slice) · consumer limit overrides: `GET`/`PUT`/`DELETE /admin/v1/consumers/{name}/limits` (admin only) |
282
294
 
283
295
  Error semantics: `401` invalid key · `401 TOKEN_REVOKED` suspended token (ask the operator to restore it) · `403 HUB_FULL` producer capacity reached · `404` unknown alias · `400 PROTOCOL_MISMATCH` protocol/alias mismatch · `429` rate limit, TPM or producer concurrency cap (`PRODUCER_MAX_USERS` = distinct-consumer cap; `QUOTA_EXCEEDED` = lifetime or daily token budget hit) · `502` upstream/tunnel failure (upstream 4xx/5xx passes through verbatim) · `503` producer offline / backend degraded · `504` timeout. Errors carry `{error:{code,message,requestId}}`; the requestId spans both sides' logs.
284
296
 
285
- Usage metering: one row per request (alias, real model, status, duration, byte counts, best-effort token counts), **zero content stored**. The log is queried with `aweshare hub usage` or `GET /admin/v1/usage` (admin sees everything, producers and consumers their own slice; rows carry the consumer/producer names). For "who used how much", `aweshare hub usage summary --since 7d` (or `GET /admin/v1/usage/summary`) aggregates server-side on the hub's SQLite: one row per producer × consumer (`--group-by consumer`, the default) or per alias (`--group-by alias`) with request/error counts, best-effort token totals, an explicit unknown-token count (streaming backends that report no counts) and mean duration busiest first. A producer sees its own slice the same way with `aweshare producer usage [summary]` on its own machine.
297
+ Usage metering: one row per request (alias, real model, status, duration, byte counts, best-effort token counts), **zero content stored**. `aweshare hub usage` (and `aweshare producer usage` on a producer's machine, scoped to its own models) answers "who used how much" by default: server-side aggregation on the hub's SQLite, one row per consumer × model a person's rows stay together, busiest person and busiest model first — with request/error counts, best-effort token totals, an explicit unknown-token count (streaming backends that report no counts) and mean duration. The window defaults to 7 days and is printed with the table (`--since 30m\|12h\|7d\|…\|all`); `--group-by consumer` rolls up to per-person totals, `--group-by alias` to per-model totals. `--details` switches to the per-request log (`GET /admin/v1/usage`; admin sees everything, producers and consumers their own slice, rows carry the consumer/producer names).
286
298
 
287
299
  ## Command reference
288
300
 
@@ -294,11 +306,11 @@ Both sides at a glance — details in the sections above.
294
306
  |---|---|
295
307
  | `aweshare hub init` | create data dir + admin token (printed once) |
296
308
  | `aweshare hub serve [--host H] [--port N]` | run the hub |
297
- | `aweshare hub produce [--host H] [--port N]` | run the hub with its own models attached — same as `serve`; the `[[backends]]`/`[[offerings]]` sections of the hub's config.toml become `hub/…` offerings served in-process (keys in the data dir's secrets.json; edits hot-reload) |
309
+ | `aweshare hub produce [--host H] [--port N]` | run the hub with its own models attached — same as `serve`; the `[[backends]]`/`[[offerings]]` sections of `config.produce.toml` become `hub/…` offerings served in-process (keys in the data dir's secrets.json; edits hot-reload) |
298
310
  | `aweshare hub invite [--role producer\|consumer] [--name NAME] [--count N] [--expires-in D]` | mint one-time invite codes (`asi_…`, printed once, expire after 7 d by default; re-view with `list --reveal`); producer codes: bound (`--name`) or unbound (name + email at redeem, `--count` batches); consumer codes: always bound to one name |
299
311
  | `aweshare hub list [invites\|producers\|consumers] [--reveal] [--token] [--json]` | read hub state: invite lifecycle (ROLE + who redeemed, `--reveal` codes, `--token` the minted tokens), or the producer/consumer rosters with status and last seen |
300
312
  | `aweshare hub limits NAME [--rps N] [--burst N] [--max-concurrent N] [--tpm N] [--max-total-tokens N] [--clear] [--json]` | show, merge or clear one consumer's limit overrides (unset keys keep the hub-wide defaults) |
301
- | `aweshare hub usage [summary] [--consumer NAME] [--producer NAME] [--alias ns/model] [--limit N] [--group-by consumer\|alias] [--since 7d] [--json]` | request log (default): newest first, one row per request, zero content stored, says which consumer made each call · `summary`: server-side aggregate per producer × consumer (default) or per alias — requests, errors, rate, best-effort token totals, unknown-token count, mean duration; `--since` (30m, 12h, 7d, …) required |
313
+ | `aweshare hub usage [--details] [--consumer NAME] [--producer NAME] [--alias ns/model] [--group-by consumer-alias\|consumer\|alias] [--since 7d\|all] [--limit N] [--json]` | who used how much (default): aggregate per consumer × model, a person's rows together, busiest first — requests, errors, rate, best-effort token totals, unknown-token count, mean duration; window defaults to 7d and is printed with the table · `--details`: per-request log, newest first, zero content stored, each row naming its consumer |
302
314
  | `aweshare hub revoke --id N` · `aweshare hub restore --id N` | kill an invite / undo — a redeemed code suspends the producer it minted, restore revives both |
303
315
 
304
316
  Token issuance runs through invites (both roles). `limits` and `usage` are thin wrappers over the admin REST API (`/admin/v1/*`, see Endpoints and errors) — curl works too.
@@ -314,7 +326,7 @@ Token issuance runs through invites (both roles). `limits` and `usage` are thin
314
326
  | `aweshare producer config path` · `config show` · `config edit` | locate / inspect (secrets redacted) / edit the config |
315
327
  | `aweshare producer doctor [--status]` | diagnose end to end: background instance, config, backend probes, hub (including how many of your offerings are registered), recent log (`--status` skips the network probes for an instant answer) |
316
328
  | `aweshare producer list [--json]` | what this producer has registered on the hub — alias, protocol, live status, caps, live occupancy (`IN USE`, distinct consumers in flight right now), today's token use — plus the local background instance state and drift against config.toml (hubUrl/token come from config.toml) |
317
- | `aweshare producer usage [summary] [--consumer NAME] [--alias ns/model] [--limit N] [--group-by consumer\|alias] [--since 7d] [--json]` | who used this producer's models (the producer token scopes the hub's metering to its own slice): request log (default), newest first with the consumer named per call, or `summary` aggregated per consumer (default) or per alias — requests, errors, rate, best-effort token totals, unknown-token count, mean duration; `--since` required |
329
+ | `aweshare producer usage [--details] [--consumer NAME] [--alias ns/model] [--group-by consumer-alias\|consumer\|alias] [--since 7d\|all] [--limit N] [--json]` | who used this producer's models (the producer token scopes the hub's metering to its own slice): aggregate per consumer × model by default a person's rows together, busiest first, window defaults to 7d · `--details`: per-request log, newest first, each row naming its consumer |
318
330
  | `aweshare producer start [--background]` | connect and relay (long-running; `--background` detaches it — logs to `~/.aweshare/producer.log`, pid to `producer.pid`) |
319
331
  | `aweshare producer reload` | signal the background producer (SIGHUP) to re-read `config.toml` + `secrets.json` and re-register its offerings on the open tunnel — no disconnect; a broken config keeps the previous values |
320
332
  | `aweshare producer stop` | stop the background producer (SIGTERM, SIGKILL after 10s) and clean up its pidfile |
@@ -336,7 +348,7 @@ The hub reads `config.toml` from its data dir (`~/.aweshare-hub/config.toml`; Do
336
348
 
337
349
  **Hot reload:** every tunable in the table except host/port applies live on `SIGHUP` (`kill -HUP <pid>`; Docker: `docker kill -s HUP aweshare-hub`) — the new file is validated first, and a broken edit is logged while the previous values keep serving. Env vars are fixed at process start, so keys pinned by `AWESHARE_*` ignore the reloaded file (same precedence as startup); host/port need a restart. Producer-side offerings and caps reload via `aweshare producer reload`.
338
350
 
339
- **Hub-hosted models (`hub produce`):** the same config.toml may carry `[[backends]]` and `[[offerings]]` sections (producer format; alias namespace `hub/…` — bare names are auto-prefixed) with upstream keys in `secrets.json` next to it (chmod 600). Those offerings appear in the catalog under producer `hub` and are served by the hub process directly — no tunnel, and they never count against `AWESHARE_MAX_PRODUCERS`. Caps (`maxConcurrencyPerUser`, `maxConcurrentUsers`, `dailyTokens`), usage metering and consumer limits apply exactly as for remote producers. The built-in `hub` producer is not an identity (no token, no invite, cannot be revoked); it appears in `hub list producers` — status `built-in` — only while it carries offerings. Catalog and key edits hot-reload like the tunables; a broken catalog keeps the previous one and is logged.
351
+ **Hub-hosted models (`hub produce`):** `config.produce.toml` carries `[[backends]]` and `[[offerings]]` sections (producer format; alias namespace `hub/…` — bare names are auto-prefixed) with upstream keys in `secrets.json` next to it (chmod 600). `config.toml` remains exclusively for Hub runtime settings. Those offerings appear in the catalog under producer `hub` and are served by the hub process directly — no tunnel, and they never count against `AWESHARE_MAX_PRODUCERS`. Caps (`maxConcurrencyPerUser`, `maxConcurrentUsers`, `dailyTokens`), usage metering and consumer limits apply exactly as for remote producers. The built-in `hub` producer is not an identity (no token, no invite, cannot be revoked); it appears in `hub list producers` — status `built-in` — only while it carries offerings. Catalog and key edits hot-reload like the tunables; a broken catalog keeps the previous one and is logged.
340
352
 
341
353
  | Env var | Default | Purpose |
342
354
  |---|---|---|
@@ -31,7 +31,7 @@ Commands:
31
31
  doctor Diagnose: instance, config, backends, hub (--status for the quick view).
32
32
  list Show what this producer has registered on the hub.
33
33
  status Summarize this producer's config, process and registered models.
34
- usage Who used this producer's models: request log or aggregated summary.
34
+ usage Who used this producer's models: summary (default) or --details.
35
35
  start Connect, register offerings, relay (--background to daemonize).
36
36
  reload Force the background producer to re-read its config now.
37
37
  stop Stop the background producer and clean up its pidfile.`;
@@ -94,25 +94,25 @@ Options:
94
94
 
95
95
  Options:
96
96
  -h, --help Show this message and exit.`,
97
- usage: `Usage: aweshare producer usage [summary] [OPTIONS]
97
+ usage: `Usage: aweshare producer usage [OPTIONS]
98
98
 
99
- Detail (default): recent requests against this producer's models, newest
100
- first one row per request, zero content stored. The producer token scopes
101
- everything to this producer's slice; rows say which consumer made each call.
99
+ Who used this producer's models: aggregated on the hub, one row per
100
+ consumer × model (default) a person's rows stay together, busiest person
101
+ and busiest model first. The producer token scopes everything to this
102
+ producer's slice; narrow further with --consumer/--alias.
102
103
 
103
- Summary: consumption aggregated on the hub one row per consumer (default)
104
- or per alias, busiest first, with request/error counts, best-effort token
105
- totals and mean duration. --since is required there: aggregates answer
106
- "how much", not "when".
104
+ --details switches to the request log instead: newest first, one row per
105
+ request, zero content stored, each row naming the consumer that made it.
107
106
 
108
107
  Options:
109
- --consumer NAME Only rows for this consumer.
110
- --alias ALIAS Only rows for this alias (one of your offerings).
111
- --limit N Detail only: rows to show, 1..1000 (default 100).
112
- --group-by KEY Summary only: consumer (default) or alias.
113
- --since DUR Summary only: window, like 30m, 12h or 7d (required).
114
- --json Print the raw API response instead of a table.
115
- -h, --help Show this message and exit.`,
108
+ --details Show the per-request log instead of the summary.
109
+ --consumer NAME Only rows for this consumer.
110
+ --alias ALIAS Only rows for this alias (one of your offerings).
111
+ --group-by KEY consumer × model (default), consumer or alias.
112
+ --since DUR Window: 30m, 12h, 7d (default), 30d… or all.
113
+ --limit N --details only: rows to show, 1..1000 (default 100).
114
+ --json Print the raw API response instead of a table.
115
+ -h, --help Show this message and exit.`,
116
116
  start: `Usage: aweshare producer start [OPTIONS]
117
117
 
118
118
  Connect to the hub, register offerings, relay requests.
@@ -153,7 +153,7 @@ const COMMAND_FLAGS = {
153
153
  doctor: ['status'],
154
154
  list: ['json'],
155
155
  status: [],
156
- usage: ['consumer', 'alias', 'limit', 'group-by', 'since', 'json'],
156
+ usage: ['consumer', 'alias', 'limit', 'group-by', 'since', 'details', 'json'],
157
157
  start: ['background'],
158
158
  reload: [],
159
159
  };
@@ -374,30 +374,37 @@ async function runStatus(dir) {
374
374
  /** Usage views: detail log (default) or `summary` aggregate. The producer
375
375
  * token scopes both to this producer's models on the hub side. */
376
376
  async function runUsage(dir, flags, positional) {
377
- const sub = positional[0];
378
- if (positional[1])
379
- fail(`unexpected argument '${positional[1]}' (see: aweshare producer usage -h)`);
380
- if (sub !== undefined && sub !== 'summary')
381
- fail(`unknown usage view '${sub}' — expected 'summary' or nothing (see: aweshare producer usage -h)`);
377
+ if (positional[0])
378
+ fail(`unexpected argument '${positional[0]}' — summaries are the default view now; the request log is --details (see: aweshare producer usage -h)`);
382
379
  const { config } = loadAgentConfig(dir);
383
380
  const consumer = flags.get('consumer');
384
381
  const alias = flags.get('alias');
385
- if (sub === 'summary') {
386
- const since = flags.get('since');
387
- if (!since)
388
- fail('--since is required for summaries (e.g. --since 7d) — aggregates need an explicit window');
382
+ if (flags.get('details') !== 'true') {
389
383
  if (flags.get('limit') !== undefined)
390
- fail('--limit applies to the detail view, not summaries');
391
- const group = flags.get('group-by') ?? 'consumer';
384
+ fail('--limit applies to --details, not the summary');
385
+ const group = flags.get('group-by') ?? 'consumer-alias';
386
+ if (group !== 'consumer-alias' && group !== 'consumer' && group !== 'alias')
387
+ fail(`--group-by must be 'consumer', 'alias' or 'consumer-alias' (default), got '${group}'`);
388
+ const since = flags.get('since') ?? '7d';
389
+ if (since !== 'all' && !/^\d+[smhd]$/.test(since))
390
+ fail(`--since must look like 30m, 12h, 7d or all, got '${since}'`);
391
+ const usageGroup = group;
392
392
  const rows = await fetchUsageSummary(config.hubUrl, config.token, {
393
- group: group === 'alias' ? 'alias' : 'consumer',
393
+ group: usageGroup,
394
394
  since,
395
395
  consumer,
396
396
  alias,
397
397
  });
398
- console.log(flags.get('json') === 'true'
399
- ? JSON.stringify(rows, null, 2)
400
- : renderUsageSummary(rows, group === 'alias' ? 'alias' : 'consumer'));
398
+ if (flags.get('json') === 'true') {
399
+ console.log(JSON.stringify(rows, null, 2));
400
+ return;
401
+ }
402
+ const window = since === 'all'
403
+ ? 'all time'
404
+ : `since ${new Date(Date.now() - sinceMs(since)).toISOString().slice(0, 10)} (${since})`;
405
+ const grouping = group === 'alias' ? 'alias' : group === 'consumer' ? 'consumer' : 'consumer × model';
406
+ console.log(`usage ${window} — group: ${grouping}`);
407
+ console.log(renderUsageSummary(rows, usageGroup));
401
408
  return;
402
409
  }
403
410
  let limit;
@@ -419,6 +426,12 @@ async function runUsage(dir, flags, positional) {
419
426
  names.set(r.consumer_id, r.consumer_name ?? `#${r.consumer_id}`);
420
427
  console.log(renderUsage(rows, (id) => names.get(id) ?? `#${id}`));
421
428
  }
429
+ /** '30m' / '12h' / '7d' → milliseconds (the window header's date math). */
430
+ function sinceMs(raw) {
431
+ const m = /^(\d+)([smhd])$/.exec(raw);
432
+ const unit = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m?.[2]];
433
+ return Number(m?.[1]) * unit;
434
+ }
422
435
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
423
436
  async function runStart(dir, flags) {
424
437
  // Validate before daemonizing so config errors hit the terminal, not the log file.