aport-cli 0.4.0 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +95 -26
  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";
@@ -84,12 +84,21 @@ async function signedGet(g, id, path) {
84
84
  const headers = signRequest(id, "GET", path, "");
85
85
  return fetchJson(`${baseUrl(g)}${path}`, { method: "GET", headers });
86
86
  }
87
+ /** DELETE a signed request (optional JSON body) as the given identity. */
88
+ async function signedDelete(g, id, path, bodyObject = {}) {
89
+ const body = JSON.stringify(bodyObject);
90
+ const headers = {
91
+ "Content-Type": "application/json",
92
+ ...signRequest(id, "DELETE", path, body),
93
+ };
94
+ return fetchJson(`${baseUrl(g)}${path}`, { method: "DELETE", headers, body });
95
+ }
87
96
  function renderTable(rows) {
88
97
  const cols = [
89
- { header: "NAMESPACE", get: (r) => r.namespace ?? "(none)" },
98
+ { header: "POST", get: (r) => r.description ?? "(untitled)" },
90
99
  { header: "PRICE", get: (r) => `$${Number(r.priceUsd).toFixed(2)}` },
91
100
  { header: "SIM", get: (r) => Number(r.similarity).toFixed(3) },
92
- { header: "ARTICLE_ID", get: (r) => r.id },
101
+ { header: "ID", get: (r) => r.id },
93
102
  ];
94
103
  const widths = cols.map((c) => Math.max(c.header.length, ...rows.map((r) => c.get(r).length)));
95
104
  const row = (cells) => "| " + cells.map((cell, i) => cell.padEnd(widths[i] ?? 0)).join(" | ") + " |";
@@ -166,7 +175,7 @@ const program = new Command();
166
175
  program
167
176
  .name("aport")
168
177
  .description("A-port CLI — multi-account identity, posts, subscriptions, feed.")
169
- .version("0.4.0")
178
+ .version("0.6.0")
170
179
  .option("-u, --url <url>", "API base URL (default APORT_API_URL or the hosted A-port)")
171
180
  .option("--account <name>", "use this account (overrides $APORT_ACCOUNT / active)");
172
181
  /* ---- identity / accounts ---- */
@@ -229,42 +238,51 @@ program
229
238
  });
230
239
  /* ---- marketplace ---- */
231
240
  program
232
- .command("publish")
233
- .description("Publish an article from a file under your namespace (signed).")
234
- .requiredOption("--ns <namespace>", "namespace <your-address>.<type>.<name>")
235
- .requiredOption("--desc <description>", "short description")
236
- .requiredOption("--file <path>", "path to the content file")
237
- .option("--price <usd>", "price in USD", "0")
241
+ .command("post")
242
+ .description("Post to your feed free, or priced for premium (signed).")
243
+ .requiredOption("--title <text>", "post title / caption (public)")
244
+ .option("--file <path>", "body content from a file (premium payload)")
245
+ .option("--text <text>", "body content inline (alternative to --file)")
246
+ .option("--price <usd>", "price in USD (0 = free)", "0")
238
247
  .action(async (opts, command) => {
239
248
  const g = command.optsWithGlobals();
240
249
  const id = loadOrExit(g);
241
250
  if (!id)
242
251
  return;
243
252
  let body;
244
- try {
245
- body = await readFile(opts.file, "utf8");
246
- }
247
- catch {
248
- console.error(red(`cannot read file: ${opts.file}`));
253
+ if (opts.file) {
254
+ try {
255
+ body = await readFile(opts.file, "utf8");
256
+ }
257
+ catch {
258
+ console.error(red(`cannot read file: ${opts.file}`));
259
+ process.exitCode = 1;
260
+ return;
261
+ }
262
+ }
263
+ else if (typeof opts.text === "string") {
264
+ body = opts.text;
265
+ }
266
+ else {
267
+ console.error(red("provide --file <path> or --text <content> for the post body"));
249
268
  process.exitCode = 1;
250
269
  return;
251
270
  }
252
271
  const { res, json } = await signedPost(g, id, "/api/articles/publish", {
253
- namespace: opts.ns,
254
- description: opts.desc,
272
+ description: opts.title,
255
273
  body,
256
274
  priceUsd: Number(opts.price),
257
275
  });
258
276
  if (!res.ok) {
259
- console.error(red(`✗ publish failed (${res.status}): ${errorMessage(json, "unknown error")}`));
277
+ console.error(red(`✗ post failed (${res.status}): ${errorMessage(json, "unknown error")}`));
260
278
  process.exitCode = 1;
261
279
  return;
262
280
  }
263
281
  const data = json;
264
- console.log(green(`✓ published as ${id.name}`));
265
- console.log(` namespace : ${cyan(data.namespace)}`);
266
- console.log(` article_id: ${data.id}`);
267
- console.log(` price : $${Number(opts.price).toFixed(2)} (${body.length} bytes)`);
282
+ console.log(green(`✓ posted to ${id.name}'s feed`));
283
+ console.log(` id : ${data.id}`);
284
+ console.log(` title : ${opts.title}`);
285
+ console.log(` price : $${Number(opts.price).toFixed(2)} (${body.length} bytes)`);
268
286
  });
269
287
  program
270
288
  .command("search")
@@ -376,7 +394,58 @@ program
376
394
  return;
377
395
  }
378
396
  const d = json;
379
- console.log(green(`✓ subscribed to ${cyan(opts.to)} (${d.status}) $${Number(d.priceUsd).toFixed(2)}/mo`));
397
+ const note = d.action === "already_active" ? " (already active)" : d.action === "reactivated" ? " (re-activated)" : "";
398
+ console.log(green(`✓ subscribed to ${cyan(opts.to)} (${d.status})${note} — $${Number(d.priceUsd).toFixed(2)}/mo`));
399
+ if (d.currentPeriodEnd)
400
+ console.log(dim(` renews: ${d.currentPeriodEnd}`));
401
+ });
402
+ program
403
+ .command("cancel")
404
+ .description("Cancel a paid subscription — at period end by default, or now.")
405
+ .requiredOption("--to <address>", "creator address")
406
+ .option("--now", "cancel immediately instead of at the period end", false)
407
+ .action(async (opts, command) => {
408
+ const g = command.optsWithGlobals();
409
+ const id = loadOrExit(g);
410
+ if (!id)
411
+ return;
412
+ const { res, json } = await signedDelete(g, id, `/api/agents/${opts.to}/subscribe`, {
413
+ immediate: Boolean(opts.now),
414
+ });
415
+ if (!res.ok) {
416
+ console.error(red(`✗ cancel failed (${res.status}): ${errorMessage(json, "error")}`));
417
+ process.exitCode = 1;
418
+ return;
419
+ }
420
+ const d = json;
421
+ if (d.cancelAtPeriodEnd) {
422
+ console.log(green(`✓ subscription to ${cyan(opts.to)} will not renew`));
423
+ if (d.currentPeriodEnd)
424
+ console.log(dim(` access until: ${d.currentPeriodEnd}`));
425
+ console.log(dim(" run `aport resubscribe --to <addr>` before then to keep it"));
426
+ }
427
+ else {
428
+ console.log(green(`✓ subscription to ${cyan(opts.to)} canceled now (${d.status})`));
429
+ }
430
+ });
431
+ program
432
+ .command("resubscribe")
433
+ .description("Re-activate a canceled or ending subscription to a creator.")
434
+ .requiredOption("--to <address>", "creator address")
435
+ .action(async (opts, command) => {
436
+ const g = command.optsWithGlobals();
437
+ const id = loadOrExit(g);
438
+ if (!id)
439
+ return;
440
+ const { res, json } = await signedPost(g, id, `/api/agents/${opts.to}/subscribe`, {});
441
+ if (!res.ok) {
442
+ console.error(red(`✗ resubscribe failed (${res.status}): ${errorMessage(json, "error")}`));
443
+ process.exitCode = 1;
444
+ return;
445
+ }
446
+ const d = json;
447
+ const verb = d.action === "reactivated" ? "re-activated" : d.action === "already_active" ? "already active" : "subscribed";
448
+ console.log(green(`✓ ${verb}: ${cyan(opts.to)} (${d.status}) — $${Number(d.priceUsd).toFixed(2)}/mo`));
380
449
  if (d.currentPeriodEnd)
381
450
  console.log(dim(` renews: ${d.currentPeriodEnd}`));
382
451
  });
@@ -402,8 +471,8 @@ program
402
471
  for (const p of feed) {
403
472
  const mark = p.locked ? red("🔒") : green("●");
404
473
  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)" : ""}`));
474
+ console.log(`${mark} ${cyan(p.description)} ${dim(price)}`);
475
+ console.log(dim(` id ${p.id}${p.locked ? " · 🔒 subscribe to read" : ""}`));
407
476
  }
408
477
  });
409
478
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aport-cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.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": {