aweshare 0.5.9 → 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.ai.md +12 -10
- package/README.md +23 -24
- package/apps/consumer/dist/cli.js +16 -0
- package/apps/consumer/dist/cli.js.map +1 -1
- package/apps/consumer/dist/table.d.ts +1 -1
- package/apps/consumer/dist/table.js +1 -1
- package/apps/hub/dist/admin.js +9 -8
- package/apps/hub/dist/admin.js.map +1 -1
- package/apps/hub/dist/auth.js +1 -1
- package/apps/hub/dist/auth.js.map +1 -1
- package/apps/hub/dist/cli.js +298 -206
- package/apps/hub/dist/cli.js.map +1 -1
- package/apps/hub/dist/config.d.ts +5 -5
- package/apps/hub/dist/config.js +9 -3
- package/apps/hub/dist/config.js.map +1 -1
- package/apps/hub/dist/consumer.js +7 -5
- package/apps/hub/dist/consumer.js.map +1 -1
- package/apps/hub/dist/db.d.ts +26 -15
- package/apps/hub/dist/db.js +64 -35
- package/apps/hub/dist/db.js.map +1 -1
- package/apps/hub/dist/invites.js +2 -2
- package/apps/hub/dist/invites.js.map +1 -1
- package/apps/hub/dist/local.js +21 -2
- package/apps/hub/dist/local.js.map +1 -1
- package/apps/hub/dist/table.d.ts +11 -8
- package/apps/hub/dist/table.js +10 -14
- package/apps/hub/dist/table.js.map +1 -1
- package/apps/hub/dist/tunnel.js +1 -1
- package/apps/hub/dist/tunnel.js.map +1 -1
- package/bin/aweshare.mjs +1 -1
- package/package.json +1 -1
package/apps/hub/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
3
3
|
import { fmtDate, fmtTime } from '../../../packages/protocol/dist/index.js';
|
|
4
4
|
import { ensureDataDir, HUB_CONFIG_TEMPLATE, initHubProduceFiles, loadHubConfig, readAdminToken, resolvePaths, } from './config.js';
|
|
5
5
|
import { createHubServer, installConfigReloader } from './server.js';
|
|
6
|
-
import { refreshTargets, renderHubStatus, renderInvites, renderUsage, renderUsageSummary, } from './table.js';
|
|
6
|
+
import { refreshTargets, renderConsumers, renderHubStatus, renderInvites, renderOfferings, renderProducers, renderUsage, renderUsageSummary, } from './table.js';
|
|
7
7
|
import { loadOrCreateAdminToken, loadOrCreatePepper } from './tokens.js';
|
|
8
8
|
const VERSION = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8')).version;
|
|
9
9
|
// One-liners only; flags per command live in their own -h, everything else in README.
|
|
@@ -17,14 +17,12 @@ Options:
|
|
|
17
17
|
|
|
18
18
|
Commands:
|
|
19
19
|
init Create the data dir, pepper, and admin token.
|
|
20
|
-
serve Run the hub server.
|
|
21
|
-
produce
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
limits Show, set or clear one consumer's limit overrides
|
|
26
|
-
status Live state: capacity, offering health; rosters print with --all.
|
|
27
|
-
offering Block or restore one offering alias (the per-alias scalpel).`;
|
|
20
|
+
serve Run the hub server (the only runner; config.produce.toml mounts itself).
|
|
21
|
+
produce Hub-local models: init scaffolds, refresh re-anchors today's budgets.
|
|
22
|
+
admin Govern: invite mint|revoke|restore, offering revoke|restore.
|
|
23
|
+
list Read hub state: invites (default), producers, consumers, offerings, usage.
|
|
24
|
+
status Live dashboard: capacity, 5m health, admission pressure, defaults.
|
|
25
|
+
limits Show, set or clear one consumer's limit overrides.`;
|
|
28
26
|
const COMMAND_HELP = {
|
|
29
27
|
init: `Usage: aweshare hub init
|
|
30
28
|
|
|
@@ -34,7 +32,9 @@ Options:
|
|
|
34
32
|
-h, --help Show this message and exit.`,
|
|
35
33
|
serve: `Usage: aweshare hub serve [OPTIONS]
|
|
36
34
|
|
|
37
|
-
Run the hub server.
|
|
35
|
+
Run the hub server — the only runner. A config.produce.toml in the
|
|
36
|
+
data dir mounts automatically: its [[backends]]/[[offerings]] are
|
|
37
|
+
served in-process as hub/... (scaffold one with: aweshare hub produce init).
|
|
38
38
|
|
|
39
39
|
Tunables in config.toml (rate limits, timeouts, maxProducers) hot-reload:
|
|
40
40
|
edits are picked up within a couple of seconds (or send SIGHUP at once);
|
|
@@ -44,14 +44,15 @@ Options:
|
|
|
44
44
|
--host ADDR Address to listen on (default from AWESHARE_HUB_HOST, else 0.0.0.0).
|
|
45
45
|
--port N Port to listen on, 1..65535 (default from AWESHARE_HUB_PORT, else 8787).
|
|
46
46
|
-h, --help Show this message and exit.`,
|
|
47
|
-
produce: `Usage: aweshare hub produce
|
|
47
|
+
produce: `Usage: aweshare hub produce init|refresh [OPTIONS]
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
next to it, and consumers reach those models as
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
Hub-local models — the hub serving its own catalog, no producer agent.
|
|
50
|
+
Define [[backends]] and [[offerings]] in the data dir's
|
|
51
|
+
config.produce.toml (namespace hub/...), put upstream keys in
|
|
52
|
+
secrets.json next to it, and consumers reach those models as
|
|
53
|
+
hub/<name>. Running the server is 'aweshare hub serve' — the same
|
|
54
|
+
runner mounts the catalog automatically; this group only scaffolds
|
|
55
|
+
and re-anchors budgets.
|
|
55
56
|
|
|
56
57
|
` +
|
|
57
58
|
'`init`' +
|
|
@@ -78,14 +79,34 @@ Options:
|
|
|
78
79
|
aweshare hub produce refresh glm-5.3 --clear # drop both markers
|
|
79
80
|
|
|
80
81
|
Options:
|
|
81
|
-
--host ADDR Address to listen on (default from AWESHARE_HUB_HOST, else 0.0.0.0).
|
|
82
|
-
--port N Port to listen on, 1..65535 (default from AWESHARE_HUB_PORT, else 8787).
|
|
83
82
|
--add N refresh only: raise today's cap by N tokens until Beijing midnight.
|
|
84
83
|
--clear refresh only: drop the refresh marker and any bonus.
|
|
85
84
|
--all refresh only: bare-refresh every hub/... model (no ALIAS, --add or --clear).
|
|
86
85
|
--json refresh only: print the raw API response(s).
|
|
87
86
|
-h, --help Show this message and exit.`,
|
|
88
|
-
|
|
87
|
+
admin: `Usage: aweshare hub admin [invite|offering] ACTION [ARGS] [OPTIONS]
|
|
88
|
+
|
|
89
|
+
The operator's write operations, one gate. Reads are 'list', the live
|
|
90
|
+
dashboard is 'status', tuning is 'limits' — everything that changes
|
|
91
|
+
hub state lives here (thin wrappers over the admin REST API).
|
|
92
|
+
|
|
93
|
+
Commands:
|
|
94
|
+
invite mint [...] Mint one-time invite codes.
|
|
95
|
+
invite revoke N Kill invite N — the sledgehammer: a pending
|
|
96
|
+
code stops pairing, a redeemed one suspends
|
|
97
|
+
the whole producer or consumer it minted.
|
|
98
|
+
invite restore N Undo an invite revoke.
|
|
99
|
+
offering revoke ALIAS The scalpel: 503 OFFERING_BLOCKED for new
|
|
100
|
+
requests to one alias while the producer's
|
|
101
|
+
other offerings keep serving.
|
|
102
|
+
offering restore ALIAS Undo an offering revoke.
|
|
103
|
+
|
|
104
|
+
Run 'aweshare hub admin invite -h' or 'aweshare hub admin offering -h'
|
|
105
|
+
for the full semantics of each group.
|
|
106
|
+
|
|
107
|
+
Options:
|
|
108
|
+
-h, --help Show this message and exit.`,
|
|
109
|
+
'admin invite': `Usage: aweshare hub admin invite [mint|revoke|restore] [ARGS] [OPTIONS]
|
|
89
110
|
|
|
90
111
|
The invite lifecycle. One code, one identity — and the invite is the
|
|
91
112
|
operator's handle from mint to suspension: revoking a redeemed code
|
|
@@ -93,7 +114,7 @@ Options:
|
|
|
93
114
|
a consumer's key stops working); restoring brings both back.
|
|
94
115
|
|
|
95
116
|
Commands:
|
|
96
|
-
mint Mint one-time invite codes
|
|
117
|
+
mint Mint one-time invite codes. Printed once; recover
|
|
97
118
|
with: aweshare hub list invites --reveal. Listing lives under
|
|
98
119
|
'list invites' on purpose — minting sends real codes, reading
|
|
99
120
|
the ledger never should.
|
|
@@ -114,19 +135,38 @@ Options:
|
|
|
114
135
|
token's lifetime; 'none' mints a code (and identity) that
|
|
115
136
|
never expires.
|
|
116
137
|
-h, --help Show this message and exit.`,
|
|
117
|
-
|
|
138
|
+
'admin offering': `Usage: aweshare hub admin offering revoke|restore ALIAS
|
|
139
|
+
|
|
140
|
+
Revoke one offering alias (every protocol row of it) or bring it back —
|
|
141
|
+
the scalpel between doing nothing and revoking the whole producer
|
|
142
|
+
('admin invite revoke'). A revoke 503s new requests with
|
|
143
|
+
OFFERING_BLOCKED while the producer's other offerings keep serving;
|
|
144
|
+
'list offerings' shows 'blocked'. Manual revokes survive producer
|
|
145
|
+
re-registers; auto revokes (repeated model mismatch, only when
|
|
146
|
+
autoBlockModelMismatch is enabled) clear once the producer re-declares
|
|
147
|
+
a different upstreamModel.
|
|
148
|
+
|
|
149
|
+
Options:
|
|
150
|
+
-h, --help Show this message and exit.`,
|
|
151
|
+
list: `Usage: aweshare hub list [invites|producers|consumers|offerings|usage] [OPTIONS]
|
|
118
152
|
|
|
119
|
-
Read hub state (default: invites). The
|
|
120
|
-
'aweshare hub status'
|
|
153
|
+
Read hub state, one table per noun (default: invites). The live
|
|
154
|
+
dashboard is 'aweshare hub status'.
|
|
121
155
|
|
|
122
156
|
Commands:
|
|
123
|
-
invites
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
157
|
+
invites The invite ledger: every code, the identity it minted and its
|
|
158
|
+
lifecycle (pending/used/suspended/revoked/expired).
|
|
159
|
+
producers The producer roster: name, status, online state, last seen.
|
|
160
|
+
consumers The consumer roster: name, status, last seen.
|
|
161
|
+
offerings The catalog: one row per alias, worst status first — the same
|
|
162
|
+
columns as 'consumer list' ('aweshare consumer list' from the
|
|
163
|
+
other side), with observed model, caps, live occupancy and
|
|
164
|
+
today's remaining tokens.
|
|
165
|
+
usage Who used how much: aggregated on the hub's SQLite, one row per
|
|
166
|
+
consumer × model (default) — a person's rows stay together,
|
|
167
|
+
busiest person and busiest model first. --details switches to the
|
|
168
|
+
request log instead: newest first, one row per request, zero
|
|
169
|
+
content stored, each row naming the consumer that made it.
|
|
130
170
|
|
|
131
171
|
Options:
|
|
132
172
|
--reveal Invites only: show the full invite codes (asi_...).
|
|
@@ -139,7 +179,7 @@ Options:
|
|
|
139
179
|
--group-by KEY Usage only: consumer × model (default), consumer or alias.
|
|
140
180
|
--since DUR Usage only: window: 30m, 12h, 7d (default), 30d… or all.
|
|
141
181
|
--limit N Usage --details only: rows to show, 1..1000 (default 100).
|
|
142
|
-
--json Print the raw API
|
|
182
|
+
--json Print the raw API rows instead of a table.
|
|
143
183
|
-h, --help Show this message and exit.`,
|
|
144
184
|
limits: `Usage: aweshare hub limits NAME [OPTIONS]
|
|
145
185
|
|
|
@@ -156,31 +196,16 @@ Options:
|
|
|
156
196
|
--clear Drop every override (back to hub-wide defaults).
|
|
157
197
|
--json Print the raw API response instead of text.
|
|
158
198
|
-h, --help Show this message and exit.`,
|
|
159
|
-
status: `Usage: aweshare hub status
|
|
199
|
+
status: `Usage: aweshare hub status
|
|
160
200
|
|
|
161
|
-
The hub's live
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
--all to print them in full.
|
|
170
|
-
|
|
171
|
-
Options:
|
|
172
|
-
--all Also print the producer and consumer rosters (name, status,
|
|
173
|
-
online state, last seen).
|
|
174
|
-
-h, --help Show this message and exit.`,
|
|
175
|
-
offering: `Usage: aweshare hub offering block|restore ALIAS
|
|
176
|
-
|
|
177
|
-
Block one offering alias (every protocol row of it) or bring it back — the
|
|
178
|
-
scalpel between doing nothing and revoking the whole producer. A block 503s
|
|
179
|
-
new requests with OFFERING_BLOCKED while the producer's other offerings
|
|
180
|
-
keep serving; status shows 'blocked'. Manual blocks survive producer
|
|
181
|
-
re-registers; auto blocks (repeated model mismatch, only when
|
|
182
|
-
autoBlockModelMismatch is enabled) clear once the producer re-declares a
|
|
183
|
-
different upstreamModel.
|
|
201
|
+
The hub's live dashboard: capacity, the last-5m health, admission
|
|
202
|
+
(rate-limit) pressure and the effective hub-wide consumer defaults.
|
|
203
|
+
No tokens or request contents appear, and no tables — every table
|
|
204
|
+
this summarizes lives under 'aweshare hub list' (producers, consumers,
|
|
205
|
+
offerings, usage). The last-5m health line comes from the usage
|
|
206
|
+
summary — admission 429s are never metered, so it reflects relayed
|
|
207
|
+
outcomes only; the rejection lines are the throttling that summary
|
|
208
|
+
cannot see, the tuning evidence for 'limits' and per-offering caps.
|
|
184
209
|
|
|
185
210
|
Options:
|
|
186
211
|
-h, --help Show this message and exit.`,
|
|
@@ -212,6 +237,26 @@ function fail(message) {
|
|
|
212
237
|
console.error(`error: ${message}`);
|
|
213
238
|
process.exit(1);
|
|
214
239
|
}
|
|
240
|
+
/** Flags each command accepts (beyond -h/--help); anything else is a typo
|
|
241
|
+
* and must fail loudly. Commands whose subcommands take different flags
|
|
242
|
+
* (produce, admin, list) resolve the allowed list first, then call this. */
|
|
243
|
+
function checkFlags(cmd, flags, allowed) {
|
|
244
|
+
for (const name of flags.keys()) {
|
|
245
|
+
if (name !== 'help' && !allowed.includes(name))
|
|
246
|
+
fail(`unknown option '--${name}' (see: aweshare hub ${cmd} -h)`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Deepest matching help entry: 'admin invite mint -h' shows the invite
|
|
250
|
+
* help, 'hub produce refresh -h' the produce help. */
|
|
251
|
+
function helpFor(cmd, positional) {
|
|
252
|
+
const words = [cmd, ...positional.filter((a) => !a.startsWith('-'))];
|
|
253
|
+
for (let n = words.length; n >= 1; n--) {
|
|
254
|
+
const help = COMMAND_HELP[words.slice(0, n).join(' ')];
|
|
255
|
+
if (help)
|
|
256
|
+
return help;
|
|
257
|
+
}
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
215
260
|
/** Listen, turning bind errors (EADDRINUSE, unbindable host) into the CLI's
|
|
216
261
|
* one-line error shape instead of an uncaught 'error' event stack trace. */
|
|
217
262
|
function listenOrFail(hub, host, port) {
|
|
@@ -339,6 +384,17 @@ function fmtLimits(name, overrides) {
|
|
|
339
384
|
? `${name}: ${set.join(', ')}`
|
|
340
385
|
: `${name}: no overrides — hub-wide defaults apply (AWESHARE_CONSUMER_* env vars)`;
|
|
341
386
|
}
|
|
387
|
+
/** The five readable tables; bare `list` defaults to the ledger (a read,
|
|
388
|
+
* and the one operators reach for first). */
|
|
389
|
+
const LIST_TARGETS = ['invites', 'producers', 'consumers', 'offerings', 'usage'];
|
|
390
|
+
/** Flags each list target accepts — a flag from another target is a typo. */
|
|
391
|
+
const LIST_FLAGS = {
|
|
392
|
+
invites: ['reveal', 'token', 'json'],
|
|
393
|
+
producers: ['json'],
|
|
394
|
+
consumers: ['json'],
|
|
395
|
+
offerings: ['json'],
|
|
396
|
+
usage: ['details', 'consumer', 'producer', 'alias', 'group-by', 'since', 'limit', 'json'],
|
|
397
|
+
};
|
|
342
398
|
async function main() {
|
|
343
399
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
344
400
|
const { positional, flags } = parseArgs(rest);
|
|
@@ -350,7 +406,7 @@ async function main() {
|
|
|
350
406
|
console.log(USAGE);
|
|
351
407
|
return;
|
|
352
408
|
}
|
|
353
|
-
const help =
|
|
409
|
+
const help = helpFor(cmd, positional);
|
|
354
410
|
if (help && (positional.includes('-h') || flags.get('help') === 'true')) {
|
|
355
411
|
console.log(help);
|
|
356
412
|
return;
|
|
@@ -358,6 +414,8 @@ async function main() {
|
|
|
358
414
|
const paths = resolvePaths();
|
|
359
415
|
switch (cmd) {
|
|
360
416
|
case 'init': {
|
|
417
|
+
checkFlags('init', flags, []);
|
|
418
|
+
noPositionals('init', positional);
|
|
361
419
|
ensureDataDir(paths);
|
|
362
420
|
loadOrCreatePepper(paths);
|
|
363
421
|
const tokenExisted = existsSync(paths.adminTokenFile);
|
|
@@ -376,8 +434,7 @@ async function main() {
|
|
|
376
434
|
return;
|
|
377
435
|
}
|
|
378
436
|
case 'serve': {
|
|
379
|
-
|
|
380
|
-
// config-driven and loads either way
|
|
437
|
+
checkFlags('serve', flags, ['host', 'port']);
|
|
381
438
|
noPositionals('serve', positional);
|
|
382
439
|
const hub = createHubServer({ paths });
|
|
383
440
|
installConfigReloader(hub);
|
|
@@ -390,11 +447,20 @@ async function main() {
|
|
|
390
447
|
return;
|
|
391
448
|
}
|
|
392
449
|
case 'produce': {
|
|
393
|
-
|
|
450
|
+
const sub = positional[0];
|
|
451
|
+
if (!sub) {
|
|
452
|
+
// serve absorbed the runner: the local catalog is config-driven and
|
|
453
|
+
// mounts either way, so a second runner only invited drift
|
|
454
|
+
fail("'hub produce' no longer runs the server — 'aweshare hub serve' is the only runner and mounts config.produce.toml by itself;\n" +
|
|
455
|
+
"this group is produce init (scaffold) and produce refresh (re-anchor today's budgets)");
|
|
456
|
+
}
|
|
457
|
+
if (sub !== 'init' && sub !== 'refresh') {
|
|
458
|
+
fail(`unknown produce action '${sub}' — expected init or refresh (the runner is: aweshare hub serve)`);
|
|
459
|
+
}
|
|
460
|
+
if (sub === 'init') {
|
|
461
|
+
checkFlags('produce', flags, []);
|
|
394
462
|
if (positional[1])
|
|
395
463
|
fail(`unexpected argument '${positional[1]}' (see: aweshare hub produce -h)`);
|
|
396
|
-
if (flags.size > 0)
|
|
397
|
-
fail(`'aweshare hub produce init' takes no options`);
|
|
398
464
|
const files = initHubProduceFiles(paths);
|
|
399
465
|
loadOrCreatePepper(paths);
|
|
400
466
|
const adminToken = loadOrCreateAdminToken(paths);
|
|
@@ -404,80 +470,127 @@ async function main() {
|
|
|
404
470
|
console.log(`secrets file: ${paths.secretsFile} (${files.secretsCreated ? 'created' : 'already exists; kept'})`);
|
|
405
471
|
console.log(`data dir: ${paths.dataDir}`);
|
|
406
472
|
console.log(`admin token: ${adminToken}`);
|
|
407
|
-
console.log('next: add [[backends]] / [[offerings]] to config.produce.toml, fill secrets.json, then run: aweshare hub
|
|
473
|
+
console.log('next: add [[backends]] / [[offerings]] to config.produce.toml, fill secrets.json, then run: aweshare hub serve');
|
|
408
474
|
return;
|
|
409
475
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const api = adminApi();
|
|
421
|
-
await runProduceRefreshAll(api, flags.get('json') === 'true');
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (!raw)
|
|
425
|
-
fail('produce refresh: alias is required (hub/model — the hub/ prefix is optional), or --all for every hub/ model');
|
|
426
|
-
// hub-hosted offerings live in the reserved hub/ namespace — accept
|
|
427
|
-
// the bare name too; anything else the hub rejects as not its own
|
|
428
|
-
const alias = raw.includes('/') ? raw : `hub/${raw}`;
|
|
429
|
-
const params = new URLSearchParams({ alias });
|
|
430
|
-
const add = flags.get('add');
|
|
431
|
-
if (add !== undefined) {
|
|
432
|
-
const n = Number(add);
|
|
433
|
-
if (!Number.isSafeInteger(n) || n <= 0)
|
|
434
|
-
fail(`--add must be a positive integer, got '${add}'`);
|
|
435
|
-
params.set('add', String(n));
|
|
436
|
-
}
|
|
437
|
-
if (flags.get('clear') === 'true') {
|
|
438
|
-
if (add !== undefined)
|
|
439
|
-
fail('--clear drops both markers — drop --add too');
|
|
440
|
-
params.set('clear', 'true');
|
|
441
|
-
}
|
|
476
|
+
checkFlags('produce', flags, ['add', 'clear', 'all', 'json']);
|
|
477
|
+
const raw = positional[1];
|
|
478
|
+
const all = flags.get('all') === 'true';
|
|
479
|
+
if (positional[2])
|
|
480
|
+
fail(`unexpected argument '${positional[2]}' (see: aweshare hub produce -h)`);
|
|
481
|
+
if (all) {
|
|
482
|
+
if (raw)
|
|
483
|
+
fail(`--all refreshes every hub/ model — drop the alias '${raw}'`);
|
|
484
|
+
if (flags.get('add') !== undefined || flags.get('clear') === 'true')
|
|
485
|
+
fail('--all is a bare refresh of every hub/ model — --add/--clear stay single-alias');
|
|
442
486
|
const api = adminApi();
|
|
443
|
-
|
|
444
|
-
if (flags.get('json') === 'true') {
|
|
445
|
-
console.log(JSON.stringify(out, null, 2));
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
printRefreshResult(out);
|
|
449
|
-
if (out.reset)
|
|
450
|
-
console.log(` undo: aweshare hub produce refresh ${out.alias} --clear`);
|
|
487
|
+
await runProduceRefreshAll(api, flags.get('json') === 'true');
|
|
451
488
|
return;
|
|
452
489
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
|
|
490
|
+
if (!raw)
|
|
491
|
+
fail('produce refresh: alias is required (hub/model — the hub/ prefix is optional), or --all for every hub/ model');
|
|
492
|
+
// hub-hosted offerings live in the reserved hub/ namespace — accept
|
|
493
|
+
// the bare name too; anything else the hub rejects as not its own
|
|
494
|
+
const alias = raw.includes('/') ? raw : `hub/${raw}`;
|
|
495
|
+
const params = new URLSearchParams({ alias });
|
|
496
|
+
const add = flags.get('add');
|
|
497
|
+
if (add !== undefined) {
|
|
498
|
+
const n = Number(add);
|
|
499
|
+
if (!Number.isSafeInteger(n) || n <= 0)
|
|
500
|
+
fail(`--add must be a positive integer, got '${add}'`);
|
|
501
|
+
params.set('add', String(n));
|
|
462
502
|
}
|
|
463
|
-
|
|
503
|
+
if (flags.get('clear') === 'true') {
|
|
504
|
+
if (add !== undefined)
|
|
505
|
+
fail('--clear drops both markers — drop --add too');
|
|
506
|
+
params.set('clear', 'true');
|
|
507
|
+
}
|
|
508
|
+
const api = adminApi();
|
|
509
|
+
const out = (await api('POST', `/admin/v1/offerings/refresh?${params}`));
|
|
510
|
+
if (flags.get('json') === 'true') {
|
|
511
|
+
console.log(JSON.stringify(out, null, 2));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
printRefreshResult(out);
|
|
515
|
+
if (out.reset)
|
|
516
|
+
console.log(` undo: aweshare hub produce refresh ${out.alias} --clear`);
|
|
464
517
|
return;
|
|
465
518
|
}
|
|
466
|
-
case '
|
|
467
|
-
// the
|
|
468
|
-
//
|
|
469
|
-
const
|
|
470
|
-
if (
|
|
471
|
-
|
|
519
|
+
case 'admin': {
|
|
520
|
+
// the write gate: every state-changing verb lives here — bare calls
|
|
521
|
+
// print help (the safest action), nothing mutates by accident
|
|
522
|
+
const group = positional[0];
|
|
523
|
+
if (!group) {
|
|
524
|
+
console.log(COMMAND_HELP.admin);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (group !== 'invite' && group !== 'offering') {
|
|
528
|
+
fail(`unknown admin target '${group}' — expected invite or offering (see: aweshare hub admin -h)`);
|
|
529
|
+
}
|
|
530
|
+
const action = positional[1];
|
|
531
|
+
if (!action) {
|
|
532
|
+
console.log(COMMAND_HELP[`admin ${group}`]);
|
|
533
|
+
return;
|
|
472
534
|
}
|
|
473
535
|
const api = adminApi();
|
|
474
|
-
if (
|
|
475
|
-
|
|
536
|
+
if (group === 'invite') {
|
|
537
|
+
if (action !== 'mint' && action !== 'revoke' && action !== 'restore') {
|
|
538
|
+
fail(`unknown invite action '${action}' — expected mint, revoke or restore`);
|
|
539
|
+
}
|
|
540
|
+
if (action === 'mint') {
|
|
541
|
+
checkFlags('admin invite', flags, ['role', 'name', 'count', 'expires-in']);
|
|
542
|
+
if (positional[2])
|
|
543
|
+
fail(`unexpected argument '${positional[2]}' (see: aweshare hub admin invite -h)`);
|
|
544
|
+
const body = {};
|
|
545
|
+
const role = flags.get('role') ?? 'producer';
|
|
546
|
+
if (role !== 'producer' && role !== 'consumer')
|
|
547
|
+
fail("--role must be 'producer' or 'consumer'");
|
|
548
|
+
body.role = role;
|
|
549
|
+
const name = flags.get('name');
|
|
550
|
+
if (name)
|
|
551
|
+
body.name = name;
|
|
552
|
+
if (role === 'consumer' && !name)
|
|
553
|
+
fail('consumer invites need --name (limits, usage and suspension reference consumers by name)');
|
|
554
|
+
const count = flags.get('count');
|
|
555
|
+
if (role === 'consumer' && count && count !== '1')
|
|
556
|
+
fail('consumer invites are one per name — drop --count and mint one per consumer');
|
|
557
|
+
// absent flag still mints a 7d code — an unexpiring secret should be a
|
|
558
|
+
// deliberate choice (`none` here; API callers may omit expiresAt). Since
|
|
559
|
+
// v12 the expiry also bounds the minted identity's lifetime.
|
|
560
|
+
const expiresIn = flags.get('expires-in') ?? '7d';
|
|
561
|
+
if (expiresIn !== 'none') {
|
|
562
|
+
body.expiresAt = new Date(Date.now() + parseDuration(expiresIn)).toISOString();
|
|
563
|
+
}
|
|
564
|
+
if (count) {
|
|
565
|
+
const n = Number(count);
|
|
566
|
+
if (!Number.isInteger(n) || n < 1 || n > 100) {
|
|
567
|
+
fail(`--count must be an integer between 1 and 100, got '${count}'`);
|
|
568
|
+
}
|
|
569
|
+
body.count = n;
|
|
570
|
+
}
|
|
571
|
+
const out = (await api('POST', '/admin/v1/invites', body));
|
|
572
|
+
for (const invite of out.invites)
|
|
573
|
+
console.log(invite.code);
|
|
574
|
+
if (role === 'consumer') {
|
|
575
|
+
console.error('(save this now; codes print only here — re-view later with: aweshare hub list invites --reveal.\n' +
|
|
576
|
+
`hand it to '${name}' — they redeem it and keep the returned asc_ token:\n` +
|
|
577
|
+
' aweshare consumer join --hub https://YOUR-HUB --code CODE\n' +
|
|
578
|
+
'no aweshare installed? curl -s -X POST https://YOUR-HUB/invites/v1/redeem -H \'content-type: application/json\' -d \'{"code":"asi_..."}\')');
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
console.error(name
|
|
582
|
+
? '(save these now; codes print only here — re-view later with: aweshare hub list invites --reveal. redeem: aweshare producer join --hub URL --code CODE)'
|
|
583
|
+
: '(save these now; codes print only here — re-view later with: aweshare hub list invites --reveal. redeem: aweshare producer join --hub URL --code CODE --name NAME --email YOU@EXAMPLE.COM)');
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
checkFlags('admin invite', flags, []);
|
|
588
|
+
const id = positional[2];
|
|
476
589
|
if (!id)
|
|
477
|
-
fail(`invite ${
|
|
478
|
-
if (positional[
|
|
479
|
-
fail(`unexpected argument '${positional[
|
|
480
|
-
if (
|
|
590
|
+
fail(`invite ${action}: id is required (find ids with: aweshare hub list invites)`);
|
|
591
|
+
if (positional[3])
|
|
592
|
+
fail(`unexpected argument '${positional[3]}' (see: aweshare hub admin invite -h)`);
|
|
593
|
+
if (action === 'revoke') {
|
|
481
594
|
const out = (await api('DELETE', `/admin/v1/invites/${id}`));
|
|
482
595
|
if (!out.revoked && !out.suspended) {
|
|
483
596
|
console.log(`invite ${id} was already revoked — nothing to do`);
|
|
@@ -489,7 +602,7 @@ async function main() {
|
|
|
489
602
|
else {
|
|
490
603
|
console.log(`✓ invite ${id} revoked — the code no longer pairs`);
|
|
491
604
|
}
|
|
492
|
-
console.log(` undo: aweshare hub invite restore ${id}`);
|
|
605
|
+
console.log(` undo: aweshare hub admin invite restore ${id}`);
|
|
493
606
|
return;
|
|
494
607
|
}
|
|
495
608
|
const out = (await api('POST', `/admin/v1/invites/${id}/restore`));
|
|
@@ -508,65 +621,39 @@ async function main() {
|
|
|
508
621
|
}
|
|
509
622
|
return;
|
|
510
623
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
if (flags.has('list')) {
|
|
516
|
-
fail("'hub invite --list' became its own command — the ledger is: aweshare hub list invites");
|
|
517
|
-
}
|
|
518
|
-
const body = {};
|
|
519
|
-
const role = flags.get('role') ?? 'producer';
|
|
520
|
-
if (role !== 'producer' && role !== 'consumer')
|
|
521
|
-
fail("--role must be 'producer' or 'consumer'");
|
|
522
|
-
body.role = role;
|
|
523
|
-
const name = flags.get('name');
|
|
524
|
-
if (name)
|
|
525
|
-
body.name = name;
|
|
526
|
-
if (role === 'consumer' && !name)
|
|
527
|
-
fail('consumer invites need --name (limits, usage and suspension reference consumers by name)');
|
|
528
|
-
const count = flags.get('count');
|
|
529
|
-
if (role === 'consumer' && count && count !== '1')
|
|
530
|
-
fail('consumer invites are one per name — drop --count and mint one per consumer');
|
|
531
|
-
// absent flag still mints a 7d code — an unexpiring secret should be a
|
|
532
|
-
// deliberate choice (`none` here; API callers may omit expiresAt). Since
|
|
533
|
-
// v12 the expiry also bounds the minted identity's lifetime.
|
|
534
|
-
const expiresIn = flags.get('expires-in') ?? '7d';
|
|
535
|
-
if (expiresIn !== 'none') {
|
|
536
|
-
body.expiresAt = new Date(Date.now() + parseDuration(expiresIn)).toISOString();
|
|
537
|
-
}
|
|
538
|
-
if (count) {
|
|
539
|
-
const n = Number(count);
|
|
540
|
-
if (!Number.isInteger(n) || n < 1 || n > 100) {
|
|
541
|
-
fail(`--count must be an integer between 1 and 100, got '${count}'`);
|
|
542
|
-
}
|
|
543
|
-
body.count = n;
|
|
624
|
+
// offering: the per-alias scalpel ('block' renamed to 'revoke' — the
|
|
625
|
+
// hub error code stays OFFERING_BLOCKED, the API path stays /block)
|
|
626
|
+
if (action === 'block') {
|
|
627
|
+
fail("'block' became 'revoke' — it is 'aweshare hub admin offering revoke ALIAS' now");
|
|
544
628
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
console.log(invite.code);
|
|
548
|
-
if (role === 'consumer') {
|
|
549
|
-
console.error('(save this now; codes print only here — re-view later with: aweshare hub list invites --reveal.\n' +
|
|
550
|
-
`hand it to '${name}' — they redeem it and keep the returned asc_ token:\n` +
|
|
551
|
-
' aweshare consumer join --hub https://YOUR-HUB --code CODE\n' +
|
|
552
|
-
'no aweshare installed? curl -s -X POST https://YOUR-HUB/invites/v1/redeem -H \'content-type: application/json\' -d \'{"code":"asi_..."}\')');
|
|
629
|
+
if (action !== 'revoke' && action !== 'restore') {
|
|
630
|
+
fail(`unknown offering action '${action}' — expected revoke or restore`);
|
|
553
631
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
632
|
+
checkFlags('admin offering', flags, []);
|
|
633
|
+
const alias = positional[2];
|
|
634
|
+
if (!alias)
|
|
635
|
+
fail(`offering ${action}: alias is required (ns/model)`);
|
|
636
|
+
if (positional[3])
|
|
637
|
+
fail(`unexpected argument '${positional[3]}' (see: aweshare hub admin offering -h)`);
|
|
638
|
+
if (action === 'revoke') {
|
|
639
|
+
const out = (await api('POST', `/admin/v1/offerings/block?alias=${encodeURIComponent(alias)}`));
|
|
640
|
+
console.log(`✓ ${alias} revoked (${out.offerings} protocol row(s)) — new requests get 503 OFFERING_BLOCKED`);
|
|
641
|
+
console.log(` undo: aweshare hub admin offering restore ${alias}`);
|
|
642
|
+
return;
|
|
558
643
|
}
|
|
644
|
+
const out = (await api('POST', `/admin/v1/offerings/restore?alias=${encodeURIComponent(alias)}`));
|
|
645
|
+
console.log(`✓ ${alias} restored (${out.offerings} protocol row(s)) — requests flow again`);
|
|
559
646
|
return;
|
|
560
647
|
}
|
|
561
648
|
case 'list': {
|
|
562
|
-
// the read verbs
|
|
563
|
-
|
|
564
|
-
const sub = positional[0] ?? 'invites';
|
|
649
|
+
// the read verbs, one table per noun; bare `list` defaults to the ledger
|
|
650
|
+
const sub = (positional[0] ?? 'invites');
|
|
565
651
|
if (positional[1])
|
|
566
652
|
fail(`unexpected argument '${positional[1]}' (see: aweshare hub list -h)`);
|
|
567
|
-
if (sub
|
|
568
|
-
fail(`unknown list target '${sub}' — expected
|
|
653
|
+
if (!LIST_TARGETS.includes(sub)) {
|
|
654
|
+
fail(`unknown list target '${sub}' — expected ${new Intl.ListFormat('en', { type: 'disjunction' }).format(LIST_TARGETS)}`);
|
|
569
655
|
}
|
|
656
|
+
checkFlags('list', flags, LIST_FLAGS[sub]);
|
|
570
657
|
const api = adminApi();
|
|
571
658
|
if (sub === 'invites') {
|
|
572
659
|
const reveal = flags.get('reveal') === 'true';
|
|
@@ -588,6 +675,26 @@ async function main() {
|
|
|
588
675
|
}
|
|
589
676
|
return;
|
|
590
677
|
}
|
|
678
|
+
if (sub === 'producers' || sub === 'consumers') {
|
|
679
|
+
const roster = (await api('GET', '/admin/v1/tokens'));
|
|
680
|
+
if (flags.get('json') === 'true') {
|
|
681
|
+
console.log(JSON.stringify(sub === 'producers' ? roster.producers : roster.consumers));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
console.log(sub === 'producers'
|
|
685
|
+
? renderProducers(roster.producers)
|
|
686
|
+
: renderConsumers(roster.consumers));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (sub === 'offerings') {
|
|
690
|
+
const data = (await api('GET', '/admin/v1/offerings'));
|
|
691
|
+
if (flags.get('json') === 'true') {
|
|
692
|
+
console.log(JSON.stringify(data.offerings, null, 2));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
console.log(renderOfferings(data.offerings));
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
591
698
|
const params = new URLSearchParams();
|
|
592
699
|
const consumer = flags.get('consumer');
|
|
593
700
|
if (consumer)
|
|
@@ -658,10 +765,8 @@ async function main() {
|
|
|
658
765
|
console.log(renderUsage(rows, (id) => names.get(id) ?? `#${id}`));
|
|
659
766
|
return;
|
|
660
767
|
}
|
|
661
|
-
case 'usage':
|
|
662
|
-
// usage became a `list` subcommand — the read surface is list + status
|
|
663
|
-
return fail("'hub usage' moved — it is 'aweshare hub list usage' now");
|
|
664
768
|
case 'limits': {
|
|
769
|
+
checkFlags('limits', flags, [...LIMIT_FLAGS.map(([flag]) => flag), 'clear', 'json']);
|
|
665
770
|
const name = positional[0];
|
|
666
771
|
if (!name)
|
|
667
772
|
fail('consumer name is required (see: aweshare hub limits -h)');
|
|
@@ -700,7 +805,8 @@ async function main() {
|
|
|
700
805
|
return;
|
|
701
806
|
}
|
|
702
807
|
case 'status': {
|
|
703
|
-
|
|
808
|
+
checkFlags('status', flags, []);
|
|
809
|
+
noPositionals('status', positional);
|
|
704
810
|
const api = adminApi();
|
|
705
811
|
const [roster, offeringsRes, usageRes, rejectionsRes] = await Promise.all([
|
|
706
812
|
api('GET', '/admin/v1/tokens'),
|
|
@@ -736,37 +842,23 @@ async function main() {
|
|
|
736
842
|
offerings: offeringsRes.offerings,
|
|
737
843
|
recent,
|
|
738
844
|
rejections,
|
|
739
|
-
all: flags.get('all') === 'true',
|
|
740
845
|
}));
|
|
741
846
|
return;
|
|
742
847
|
}
|
|
743
|
-
case '
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
if (positional[2])
|
|
752
|
-
fail(`unexpected argument '${positional[2]}' (see: aweshare hub offering -h)`);
|
|
753
|
-
const api = adminApi();
|
|
754
|
-
if (action === 'block') {
|
|
755
|
-
const out = (await api('POST', `/admin/v1/offerings/block?alias=${encodeURIComponent(alias)}`));
|
|
756
|
-
console.log(`✓ ${alias} blocked (${out.offerings} protocol row(s)) — new requests get 503 OFFERING_BLOCKED`);
|
|
757
|
-
console.log(` undo: aweshare hub offering restore ${alias}`);
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
const out = (await api('POST', `/admin/v1/offerings/restore?alias=${encodeURIComponent(alias)}`));
|
|
761
|
-
console.log(`✓ ${alias} restored (${out.offerings} protocol row(s)) — requests flow again`);
|
|
762
|
-
return;
|
|
763
|
-
}
|
|
848
|
+
case 'invite':
|
|
849
|
+
// invite mint/revoke/restore moved under the admin gate
|
|
850
|
+
return fail("'hub invite' moved — it is 'aweshare hub admin invite mint|revoke|restore' now");
|
|
851
|
+
case 'offering':
|
|
852
|
+
return fail("'hub offering' moved — block/restore live under 'aweshare hub admin offering revoke|restore' now");
|
|
853
|
+
case 'usage':
|
|
854
|
+
// usage became a `list` subcommand — the read surface is list + status
|
|
855
|
+
return fail("'hub usage' moved — it is 'aweshare hub list usage' now");
|
|
764
856
|
case 'revoke':
|
|
765
|
-
// revoke/restore became `invite` subcommands — the invite is the
|
|
766
|
-
// lifecycle handle, same shape as `offering
|
|
767
|
-
return fail("'hub revoke --id N' moved — it is 'aweshare hub invite revoke N' now");
|
|
857
|
+
// revoke/restore became `admin invite` subcommands — the invite is the
|
|
858
|
+
// lifecycle handle, same shape as `admin offering revoke|restore`
|
|
859
|
+
return fail("'hub revoke --id N' moved — it is 'aweshare hub admin invite revoke N' now");
|
|
768
860
|
case 'restore':
|
|
769
|
-
return fail("'hub restore --id N' moved — it is 'aweshare hub invite restore N' now");
|
|
861
|
+
return fail("'hub restore --id N' moved — it is 'aweshare hub admin invite restore N' now");
|
|
770
862
|
default:
|
|
771
863
|
fail(`unknown command: ${cmd}\n\n${USAGE}`);
|
|
772
864
|
}
|