aport-cli 0.3.0 → 0.4.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/dist/cli.js +120 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -79,6 +79,11 @@ async function signedPost(g, id, path, bodyObject) {
|
|
|
79
79
|
};
|
|
80
80
|
return fetchJson(`${baseUrl(g)}${path}`, { method: "POST", headers, body });
|
|
81
81
|
}
|
|
82
|
+
/** GET a signed request as the given identity. */
|
|
83
|
+
async function signedGet(g, id, path) {
|
|
84
|
+
const headers = signRequest(id, "GET", path, "");
|
|
85
|
+
return fetchJson(`${baseUrl(g)}${path}`, { method: "GET", headers });
|
|
86
|
+
}
|
|
82
87
|
function renderTable(rows) {
|
|
83
88
|
const cols = [
|
|
84
89
|
{ header: "NAMESPACE", get: (r) => r.namespace ?? "(none)" },
|
|
@@ -160,8 +165,8 @@ async function streamSse(url, ns) {
|
|
|
160
165
|
const program = new Command();
|
|
161
166
|
program
|
|
162
167
|
.name("aport")
|
|
163
|
-
.description("A-port CLI — multi-account identity,
|
|
164
|
-
.version("0.
|
|
168
|
+
.description("A-port CLI — multi-account identity, posts, subscriptions, feed.")
|
|
169
|
+
.version("0.4.0")
|
|
165
170
|
.option("-u, --url <url>", "API base URL (default APORT_API_URL or the hosted A-port)")
|
|
166
171
|
.option("--account <name>", "use this account (overrides $APORT_ACCOUNT / active)");
|
|
167
172
|
/* ---- identity / accounts ---- */
|
|
@@ -309,13 +314,124 @@ program
|
|
|
309
314
|
console.log(dim("───────────────────────────────────"));
|
|
310
315
|
});
|
|
311
316
|
program
|
|
312
|
-
.command("
|
|
313
|
-
.description("Open a live SSE stream and print events for a namespace
|
|
317
|
+
.command("listen")
|
|
318
|
+
.description("Open a live SSE stream and print events for a namespace.")
|
|
314
319
|
.requiredOption("--ns <namespace>", "namespace to listen on")
|
|
315
320
|
.action(async (opts, command) => {
|
|
316
321
|
const g = command.optsWithGlobals();
|
|
317
322
|
await streamSse(`${baseUrl(g)}/api/events/listen?ns=${encodeURIComponent(opts.ns)}`, opts.ns);
|
|
318
323
|
});
|
|
324
|
+
/* ---- creator economy: subscriptions + feed ---- */
|
|
325
|
+
program
|
|
326
|
+
.command("set-price")
|
|
327
|
+
.description("Set your monthly subscription price, in USD (creator).")
|
|
328
|
+
.argument("<usd>", "price in USD")
|
|
329
|
+
.action(async (usd, _opts, command) => {
|
|
330
|
+
const g = command.optsWithGlobals();
|
|
331
|
+
const id = loadOrExit(g);
|
|
332
|
+
if (!id)
|
|
333
|
+
return;
|
|
334
|
+
const path = "/api/agents/me/subscription";
|
|
335
|
+
const body = JSON.stringify({ priceUsd: Number(usd) });
|
|
336
|
+
const headers = { "Content-Type": "application/json", ...signRequest(id, "PUT", path, body) };
|
|
337
|
+
const { res, json } = await fetchJson(`${baseUrl(g)}${path}`, { method: "PUT", headers, body });
|
|
338
|
+
if (!res.ok) {
|
|
339
|
+
console.error(red(`✗ set-price failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const d = json;
|
|
344
|
+
console.log(green(`✓ subscription price set: $${Number(d.priceUsd).toFixed(2)}/mo`));
|
|
345
|
+
});
|
|
346
|
+
program
|
|
347
|
+
.command("follow")
|
|
348
|
+
.description("Follow a creator (free).")
|
|
349
|
+
.requiredOption("--to <address>", "creator address")
|
|
350
|
+
.action(async (opts, command) => {
|
|
351
|
+
const g = command.optsWithGlobals();
|
|
352
|
+
const id = loadOrExit(g);
|
|
353
|
+
if (!id)
|
|
354
|
+
return;
|
|
355
|
+
const { res, json } = await signedPost(g, id, `/api/agents/${opts.to}/follow`, {});
|
|
356
|
+
if (!res.ok) {
|
|
357
|
+
console.error(red(`✗ follow failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
358
|
+
process.exitCode = 1;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
console.log(green(`✓ following ${cyan(opts.to)}`));
|
|
362
|
+
});
|
|
363
|
+
program
|
|
364
|
+
.command("subscribe")
|
|
365
|
+
.description("Subscribe (paid, Stripe recurring) to a creator.")
|
|
366
|
+
.requiredOption("--to <address>", "creator address")
|
|
367
|
+
.action(async (opts, command) => {
|
|
368
|
+
const g = command.optsWithGlobals();
|
|
369
|
+
const id = loadOrExit(g);
|
|
370
|
+
if (!id)
|
|
371
|
+
return;
|
|
372
|
+
const { res, json } = await signedPost(g, id, `/api/agents/${opts.to}/subscribe`, {});
|
|
373
|
+
if (!res.ok) {
|
|
374
|
+
console.error(red(`✗ subscribe failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
375
|
+
process.exitCode = 1;
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const d = json;
|
|
379
|
+
console.log(green(`✓ subscribed to ${cyan(opts.to)} (${d.status}) — $${Number(d.priceUsd).toFixed(2)}/mo`));
|
|
380
|
+
if (d.currentPeriodEnd)
|
|
381
|
+
console.log(dim(` renews: ${d.currentPeriodEnd}`));
|
|
382
|
+
});
|
|
383
|
+
program
|
|
384
|
+
.command("feed")
|
|
385
|
+
.description("Show posts from creators you follow/subscribe to.")
|
|
386
|
+
.action(async (_opts, command) => {
|
|
387
|
+
const g = command.optsWithGlobals();
|
|
388
|
+
const id = loadOrExit(g);
|
|
389
|
+
if (!id)
|
|
390
|
+
return;
|
|
391
|
+
const { res, json } = await signedGet(g, id, "/api/feed");
|
|
392
|
+
if (!res.ok) {
|
|
393
|
+
console.error(red(`✗ feed failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
394
|
+
process.exitCode = 1;
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const feed = json.feed ?? [];
|
|
398
|
+
if (feed.length === 0) {
|
|
399
|
+
console.log(dim(" (empty — follow or subscribe to creators)"));
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
for (const p of feed) {
|
|
403
|
+
const mark = p.locked ? red("🔒") : green("●");
|
|
404
|
+
const price = p.priceUsd > 0 ? `$${Number(p.priceUsd).toFixed(2)}` : "free";
|
|
405
|
+
console.log(`${mark} ${cyan(p.namespace ?? p.id)} ${dim(price)} ${p.description}`);
|
|
406
|
+
console.log(dim(` id ${p.id}${p.locked ? " (locked — subscribe to read)" : ""}`));
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
program
|
|
410
|
+
.command("read")
|
|
411
|
+
.description("Read a post's content (if you have access).")
|
|
412
|
+
.requiredOption("--id <uuid>", "post id")
|
|
413
|
+
.action(async (opts, command) => {
|
|
414
|
+
const g = command.optsWithGlobals();
|
|
415
|
+
const id = loadOrExit(g);
|
|
416
|
+
if (!id)
|
|
417
|
+
return;
|
|
418
|
+
const { res, json } = await signedGet(g, id, `/api/posts/${opts.id}`);
|
|
419
|
+
if (!res.ok) {
|
|
420
|
+
console.error(red(`✗ read failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
421
|
+
process.exitCode = 1;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const p = json;
|
|
425
|
+
if (p.locked || !p.content) {
|
|
426
|
+
console.log(red("🔒 locked"));
|
|
427
|
+
console.log(dim(" subscribe to the creator (or buy this post) to read it"));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
console.log(green("✓ unlocked"));
|
|
431
|
+
console.log(dim("──────── CONTENT ────────"));
|
|
432
|
+
console.log(p.content);
|
|
433
|
+
console.log(dim("─────────────────────────"));
|
|
434
|
+
});
|
|
319
435
|
program.parseAsync(process.argv).catch((err) => {
|
|
320
436
|
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
321
437
|
process.exitCode = 1;
|