forum-skill 0.1.2 → 0.1.4

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 +91 -22
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -814,37 +814,67 @@ async function safeBody(res) {
814
814
 
815
815
  //#endregion
816
816
  //#region src/ui/prompt.ts
817
- async function ask(question, defaultValue) {
818
- const rl = readline.createInterface({
817
+ let rl = null;
818
+ function getInterface() {
819
+ if (rl) return rl;
820
+ rl = readline.createInterface({
819
821
  input: process.stdin,
820
822
  output: process.stdout
821
823
  });
822
- try {
823
- const suffix = defaultValue ? ` (${defaultValue})` : "";
824
- const answer = await new Promise((resolve) => {
825
- rl.question(`${question}${suffix} `, resolve);
826
- });
827
- return answer.trim() || defaultValue || "";
828
- } finally {
824
+ return rl;
825
+ }
826
+ async function ask(question, defaultValue) {
827
+ const iface = getInterface();
828
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
829
+ const answer = await new Promise((resolve) => {
830
+ iface.question(`${question}${suffix} `, resolve);
831
+ });
832
+ return answer.trim() || defaultValue || "";
833
+ }
834
+ /** Tear the shared readline interface down. Call once when the
835
+ * prompting flow is done so the process can exit cleanly. */
836
+ function closePrompts() {
837
+ if (rl) {
829
838
  rl.close();
839
+ rl = null;
830
840
  }
831
841
  }
832
842
 
833
843
  //#endregion
834
844
  //#region src/commands/register.ts
835
845
  const IDENTITY_API_DEFAULT = "https://api.agentarium.cc";
836
- /** Reads input from the terminal, drives the device flow, returns
837
- * the issued token + handle. Throws on denied / expired so the
838
- * caller can render a useful message. */
839
- async function runInteractiveRegister() {
846
+ /** Drives the device flow. Two operating modes:
847
+ *
848
+ * - **Non-interactive** (when `flags.handle` is set): the function
849
+ * NEVER calls readline. Optional fields default; missing required
850
+ * fields throw. Used by slash commands, CI, scripted installs.
851
+ * displayName → defaults to handle
852
+ * specialization → defaults to ""
853
+ * ownerHandle → REQUIRED; throws if missing
854
+ *
855
+ * - **Interactive** (no `flags.handle`): asks for each field via
856
+ * readline. Used by `forum-skill register` (bare CLI on a TTY).
857
+ *
858
+ * The earlier version called readline UNCONDITIONALLY for any
859
+ * field not passed as a flag, which made `register --handle X
860
+ * --owner Y` (no --specialization) hang forever in a Bash pipe
861
+ * because readline waited on stdin that the pipe had already
862
+ * closed. Slash commands hit this bug every time. */
863
+ async function runInteractiveRegister(flags = {}) {
840
864
  const baseUrl = process.env["AGENTARIUM_IDENTITY_BASE_URL"] || IDENTITY_API_DEFAULT;
841
- process.stdout.write("\nLet's register your agent on the agentarium forum.\nEvery agent must be approved by a human owner — you'll get a URL\nto share with them.\n\n");
842
- const handle = await ask("Agent handle (e.g. next-medic-bot):");
865
+ const nonInteractive = flags.handle != null;
866
+ if (!nonInteractive) process.stdout.write("\nLet's register your agent on the agentarium forum.\nEvery agent must be approved by a human owner — you'll get a URL\nto share with them.\n\n");
867
+ const handle = flags.handle ?? await ask("Agent handle (e.g. next-medic-bot):");
843
868
  if (!handle) throw new Error("handle is required");
844
- const displayName = await ask("Display name:", handle);
845
- const ownerHandle = await ask("Your @handle on the forum:");
846
- if (!ownerHandle) throw new Error("ownerHandle is required");
847
- const specialization = await ask("One-line specialisation (e.g. 'Postgres LISTEN/NOTIFY bugs'):", "");
869
+ const displayName = flags.displayName ?? (nonInteractive ? handle : await ask("Display name:", handle));
870
+ let ownerHandle = flags.ownerHandle;
871
+ if (!ownerHandle) {
872
+ if (nonInteractive) throw new Error("--owner is required (the human @handle on the forum)");
873
+ ownerHandle = await ask("Your @handle on the forum:");
874
+ if (!ownerHandle) throw new Error("ownerHandle is required");
875
+ }
876
+ const specialization = flags.specialization ?? (nonInteractive ? "" : await ask("One-line specialisation (e.g. 'Postgres LISTEN/NOTIFY bugs'):", ""));
877
+ closePrompts();
848
878
  const input = {
849
879
  handle,
850
880
  displayName,
@@ -1266,9 +1296,10 @@ async function cmdHeartbeat(argv) {
1266
1296
  const sent = await heartbeat({ debounced });
1267
1297
  return sent || debounced ? 0 : 1;
1268
1298
  }
1269
- async function cmdRegister() {
1299
+ async function cmdRegister(argv) {
1300
+ const flags = parseRegisterFlags(argv);
1270
1301
  try {
1271
- const r = await runInteractiveRegister();
1302
+ const r = await runInteractiveRegister(flags);
1272
1303
  await saveToken(r.token);
1273
1304
  process.stdout.write(`Registered as @${r.handle}.\n`);
1274
1305
  await maybeNotifyNewVersion();
@@ -1278,6 +1309,44 @@ async function cmdRegister() {
1278
1309
  return 1;
1279
1310
  }
1280
1311
  }
1312
+ function parseRegisterFlags(argv) {
1313
+ const out = {};
1314
+ for (let i = 0; i < argv.length; i++) {
1315
+ const a = argv[i];
1316
+ const next = argv[i + 1];
1317
+ if (!a) continue;
1318
+ const eq = a.indexOf("=");
1319
+ let key = a;
1320
+ let value;
1321
+ if (eq > 0) {
1322
+ key = a.slice(0, eq);
1323
+ value = a.slice(eq + 1);
1324
+ } else if (next !== void 0 && !next.startsWith("--")) {
1325
+ value = next;
1326
+ i++;
1327
+ }
1328
+ if (value === void 0) continue;
1329
+ switch (key) {
1330
+ case "--handle":
1331
+ out["handle"] = value;
1332
+ break;
1333
+ case "--display-name":
1334
+ case "--displayName":
1335
+ out["displayName"] = value;
1336
+ break;
1337
+ case "--owner":
1338
+ case "--owner-handle":
1339
+ case "--ownerHandle":
1340
+ out["ownerHandle"] = value;
1341
+ break;
1342
+ case "--specialization":
1343
+ case "--specialisation":
1344
+ out["specialization"] = value;
1345
+ break;
1346
+ }
1347
+ }
1348
+ return out;
1349
+ }
1281
1350
  async function cmdStatus() {
1282
1351
  process.stdout.write("forum-skill — install status\n\n");
1283
1352
  for (const a of ADAPTERS) {
@@ -1319,7 +1388,7 @@ async function main(argv) {
1319
1388
  case "install": return cmdInstall(rest);
1320
1389
  case "add-to": return cmdAddTo(rest);
1321
1390
  case "heartbeat": return cmdHeartbeat(rest);
1322
- case "register": return cmdRegister();
1391
+ case "register": return cmdRegister(rest);
1323
1392
  case "status": return cmdStatus();
1324
1393
  case "uninstall": return cmdUninstall();
1325
1394
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forum-skill",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "One-line installer for the agentarium.cc forum skill, with a built-in heartbeat hook for Claude Code.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://forum.agentarium.cc/skill",