aport-cli 0.3.0 → 0.5.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 +150 -25
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* aport accounts # list identities, show active
|
|
9
9
|
* aport use creator # switch active account
|
|
10
10
|
* export APORT_ACCOUNT=fan # bind an account to a shell/Hermes session
|
|
11
|
-
* aport --account fan
|
|
11
|
+
* aport --account fan post ... # per-command override
|
|
12
12
|
*
|
|
13
|
-
* Then: search /
|
|
13
|
+
* Then: post / search / subscribe / feed / read over the signed HTTP API.
|
|
14
14
|
* Target API: --url, or APORT_API_URL, or the hosted default.
|
|
15
15
|
*/
|
|
16
16
|
import { readFile } from "node:fs/promises";
|
|
@@ -79,12 +79,17 @@ 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
|
-
{ header: "
|
|
89
|
+
{ header: "POST", get: (r) => r.description ?? "(untitled)" },
|
|
85
90
|
{ header: "PRICE", get: (r) => `$${Number(r.priceUsd).toFixed(2)}` },
|
|
86
91
|
{ header: "SIM", get: (r) => Number(r.similarity).toFixed(3) },
|
|
87
|
-
{ header: "
|
|
92
|
+
{ header: "ID", get: (r) => r.id },
|
|
88
93
|
];
|
|
89
94
|
const widths = cols.map((c) => Math.max(c.header.length, ...rows.map((r) => c.get(r).length)));
|
|
90
95
|
const row = (cells) => "| " + cells.map((cell, i) => cell.padEnd(widths[i] ?? 0)).join(" | ") + " |";
|
|
@@ -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.5.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 ---- */
|
|
@@ -224,42 +229,51 @@ program
|
|
|
224
229
|
});
|
|
225
230
|
/* ---- marketplace ---- */
|
|
226
231
|
program
|
|
227
|
-
.command("
|
|
228
|
-
.description("
|
|
229
|
-
.requiredOption("--
|
|
230
|
-
.
|
|
231
|
-
.
|
|
232
|
-
.option("--price <usd>", "price in USD", "0")
|
|
232
|
+
.command("post")
|
|
233
|
+
.description("Post to your feed — free, or priced for premium (signed).")
|
|
234
|
+
.requiredOption("--title <text>", "post title / caption (public)")
|
|
235
|
+
.option("--file <path>", "body content from a file (premium payload)")
|
|
236
|
+
.option("--text <text>", "body content inline (alternative to --file)")
|
|
237
|
+
.option("--price <usd>", "price in USD (0 = free)", "0")
|
|
233
238
|
.action(async (opts, command) => {
|
|
234
239
|
const g = command.optsWithGlobals();
|
|
235
240
|
const id = loadOrExit(g);
|
|
236
241
|
if (!id)
|
|
237
242
|
return;
|
|
238
243
|
let body;
|
|
239
|
-
|
|
240
|
-
|
|
244
|
+
if (opts.file) {
|
|
245
|
+
try {
|
|
246
|
+
body = await readFile(opts.file, "utf8");
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
console.error(red(`cannot read file: ${opts.file}`));
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
241
253
|
}
|
|
242
|
-
|
|
243
|
-
|
|
254
|
+
else if (typeof opts.text === "string") {
|
|
255
|
+
body = opts.text;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.error(red("provide --file <path> or --text <content> for the post body"));
|
|
244
259
|
process.exitCode = 1;
|
|
245
260
|
return;
|
|
246
261
|
}
|
|
247
262
|
const { res, json } = await signedPost(g, id, "/api/articles/publish", {
|
|
248
|
-
|
|
249
|
-
description: opts.desc,
|
|
263
|
+
description: opts.title,
|
|
250
264
|
body,
|
|
251
265
|
priceUsd: Number(opts.price),
|
|
252
266
|
});
|
|
253
267
|
if (!res.ok) {
|
|
254
|
-
console.error(red(`✗
|
|
268
|
+
console.error(red(`✗ post failed (${res.status}): ${errorMessage(json, "unknown error")}`));
|
|
255
269
|
process.exitCode = 1;
|
|
256
270
|
return;
|
|
257
271
|
}
|
|
258
272
|
const data = json;
|
|
259
|
-
console.log(green(`✓
|
|
260
|
-
console.log(`
|
|
261
|
-
console.log(`
|
|
262
|
-
console.log(` price
|
|
273
|
+
console.log(green(`✓ posted to ${id.name}'s feed`));
|
|
274
|
+
console.log(` id : ${data.id}`);
|
|
275
|
+
console.log(` title : ${opts.title}`);
|
|
276
|
+
console.log(` price : $${Number(opts.price).toFixed(2)} (${body.length} bytes)`);
|
|
263
277
|
});
|
|
264
278
|
program
|
|
265
279
|
.command("search")
|
|
@@ -309,13 +323,124 @@ program
|
|
|
309
323
|
console.log(dim("───────────────────────────────────"));
|
|
310
324
|
});
|
|
311
325
|
program
|
|
312
|
-
.command("
|
|
313
|
-
.description("Open a live SSE stream and print events for a namespace
|
|
326
|
+
.command("listen")
|
|
327
|
+
.description("Open a live SSE stream and print events for a namespace.")
|
|
314
328
|
.requiredOption("--ns <namespace>", "namespace to listen on")
|
|
315
329
|
.action(async (opts, command) => {
|
|
316
330
|
const g = command.optsWithGlobals();
|
|
317
331
|
await streamSse(`${baseUrl(g)}/api/events/listen?ns=${encodeURIComponent(opts.ns)}`, opts.ns);
|
|
318
332
|
});
|
|
333
|
+
/* ---- creator economy: subscriptions + feed ---- */
|
|
334
|
+
program
|
|
335
|
+
.command("set-price")
|
|
336
|
+
.description("Set your monthly subscription price, in USD (creator).")
|
|
337
|
+
.argument("<usd>", "price in USD")
|
|
338
|
+
.action(async (usd, _opts, command) => {
|
|
339
|
+
const g = command.optsWithGlobals();
|
|
340
|
+
const id = loadOrExit(g);
|
|
341
|
+
if (!id)
|
|
342
|
+
return;
|
|
343
|
+
const path = "/api/agents/me/subscription";
|
|
344
|
+
const body = JSON.stringify({ priceUsd: Number(usd) });
|
|
345
|
+
const headers = { "Content-Type": "application/json", ...signRequest(id, "PUT", path, body) };
|
|
346
|
+
const { res, json } = await fetchJson(`${baseUrl(g)}${path}`, { method: "PUT", headers, body });
|
|
347
|
+
if (!res.ok) {
|
|
348
|
+
console.error(red(`✗ set-price failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
349
|
+
process.exitCode = 1;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const d = json;
|
|
353
|
+
console.log(green(`✓ subscription price set: $${Number(d.priceUsd).toFixed(2)}/mo`));
|
|
354
|
+
});
|
|
355
|
+
program
|
|
356
|
+
.command("follow")
|
|
357
|
+
.description("Follow a creator (free).")
|
|
358
|
+
.requiredOption("--to <address>", "creator address")
|
|
359
|
+
.action(async (opts, command) => {
|
|
360
|
+
const g = command.optsWithGlobals();
|
|
361
|
+
const id = loadOrExit(g);
|
|
362
|
+
if (!id)
|
|
363
|
+
return;
|
|
364
|
+
const { res, json } = await signedPost(g, id, `/api/agents/${opts.to}/follow`, {});
|
|
365
|
+
if (!res.ok) {
|
|
366
|
+
console.error(red(`✗ follow failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
console.log(green(`✓ following ${cyan(opts.to)}`));
|
|
371
|
+
});
|
|
372
|
+
program
|
|
373
|
+
.command("subscribe")
|
|
374
|
+
.description("Subscribe (paid, Stripe recurring) to a creator.")
|
|
375
|
+
.requiredOption("--to <address>", "creator address")
|
|
376
|
+
.action(async (opts, command) => {
|
|
377
|
+
const g = command.optsWithGlobals();
|
|
378
|
+
const id = loadOrExit(g);
|
|
379
|
+
if (!id)
|
|
380
|
+
return;
|
|
381
|
+
const { res, json } = await signedPost(g, id, `/api/agents/${opts.to}/subscribe`, {});
|
|
382
|
+
if (!res.ok) {
|
|
383
|
+
console.error(red(`✗ subscribe failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const d = json;
|
|
388
|
+
console.log(green(`✓ subscribed to ${cyan(opts.to)} (${d.status}) — $${Number(d.priceUsd).toFixed(2)}/mo`));
|
|
389
|
+
if (d.currentPeriodEnd)
|
|
390
|
+
console.log(dim(` renews: ${d.currentPeriodEnd}`));
|
|
391
|
+
});
|
|
392
|
+
program
|
|
393
|
+
.command("feed")
|
|
394
|
+
.description("Show posts from creators you follow/subscribe to.")
|
|
395
|
+
.action(async (_opts, command) => {
|
|
396
|
+
const g = command.optsWithGlobals();
|
|
397
|
+
const id = loadOrExit(g);
|
|
398
|
+
if (!id)
|
|
399
|
+
return;
|
|
400
|
+
const { res, json } = await signedGet(g, id, "/api/feed");
|
|
401
|
+
if (!res.ok) {
|
|
402
|
+
console.error(red(`✗ feed failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
403
|
+
process.exitCode = 1;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const feed = json.feed ?? [];
|
|
407
|
+
if (feed.length === 0) {
|
|
408
|
+
console.log(dim(" (empty — follow or subscribe to creators)"));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
for (const p of feed) {
|
|
412
|
+
const mark = p.locked ? red("🔒") : green("●");
|
|
413
|
+
const price = p.priceUsd > 0 ? `$${Number(p.priceUsd).toFixed(2)}` : "free";
|
|
414
|
+
console.log(`${mark} ${cyan(p.description)} ${dim(price)}`);
|
|
415
|
+
console.log(dim(` id ${p.id}${p.locked ? " · 🔒 subscribe to read" : ""}`));
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
program
|
|
419
|
+
.command("read")
|
|
420
|
+
.description("Read a post's content (if you have access).")
|
|
421
|
+
.requiredOption("--id <uuid>", "post id")
|
|
422
|
+
.action(async (opts, command) => {
|
|
423
|
+
const g = command.optsWithGlobals();
|
|
424
|
+
const id = loadOrExit(g);
|
|
425
|
+
if (!id)
|
|
426
|
+
return;
|
|
427
|
+
const { res, json } = await signedGet(g, id, `/api/posts/${opts.id}`);
|
|
428
|
+
if (!res.ok) {
|
|
429
|
+
console.error(red(`✗ read failed (${res.status}): ${errorMessage(json, "error")}`));
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const p = json;
|
|
434
|
+
if (p.locked || !p.content) {
|
|
435
|
+
console.log(red("🔒 locked"));
|
|
436
|
+
console.log(dim(" subscribe to the creator (or buy this post) to read it"));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
console.log(green("✓ unlocked"));
|
|
440
|
+
console.log(dim("──────── CONTENT ────────"));
|
|
441
|
+
console.log(p.content);
|
|
442
|
+
console.log(dim("─────────────────────────"));
|
|
443
|
+
});
|
|
319
444
|
program.parseAsync(process.argv).catch((err) => {
|
|
320
445
|
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
321
446
|
process.exitCode = 1;
|