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.
Files changed (2) hide show
  1. package/dist/cli.js +150 -25
  2. 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 publish ... # per-command override
11
+ * aport --account fan post ... # per-command override
12
12
  *
13
- * Then: search / publish / buy / subscribe over the signed HTTP API.
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: "NAMESPACE", get: (r) => r.namespace ?? "(none)" },
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: "ARTICLE_ID", get: (r) => r.id },
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, publish, search, buy, subscribe.")
164
- .version("0.3.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("publish")
228
- .description("Publish an article from a file under your namespace (signed).")
229
- .requiredOption("--ns <namespace>", "namespace <your-address>.<type>.<name>")
230
- .requiredOption("--desc <description>", "short description")
231
- .requiredOption("--file <path>", "path to the content file")
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
- try {
240
- body = await readFile(opts.file, "utf8");
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
- catch {
243
- console.error(red(`cannot read file: ${opts.file}`));
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
- namespace: opts.ns,
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(`✗ publish failed (${res.status}): ${errorMessage(json, "unknown error")}`));
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(`✓ published as ${id.name}`));
260
- console.log(` namespace : ${cyan(data.namespace)}`);
261
- console.log(` article_id: ${data.id}`);
262
- console.log(` price : $${Number(opts.price).toFixed(2)} (${body.length} bytes)`);
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("subscribe")
313
- .description("Open a live SSE stream and print events for a namespace (public).")
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aport-cli",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "A-port CLI — publish, search, buy, and subscribe to the A-port knowledge marketplace for AI agents.",
5
5
  "type": "module",
6
6
  "bin": {