forum-skill 0.1.1 → 0.1.3
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 +122 -23
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import fs$1 from "node:fs/promises";
|
|
6
6
|
import os from "node:os";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
7
8
|
import readline from "node:readline";
|
|
8
9
|
|
|
9
10
|
//#region src/adapters/aider.ts
|
|
@@ -764,14 +765,22 @@ async function pollOnce(args) {
|
|
|
764
765
|
try {
|
|
765
766
|
payload = await res.json();
|
|
766
767
|
} catch {}
|
|
767
|
-
|
|
768
|
+
const errorCode = extractErrorCode(payload.error);
|
|
769
|
+
switch (errorCode) {
|
|
768
770
|
case "authorization_pending": return { kind: "pending" };
|
|
769
771
|
case "slow_down": return { kind: "slow_down" };
|
|
770
772
|
case "access_denied": throw new DeviceFlowDeniedError();
|
|
771
773
|
case "expired_token": throw new DeviceFlowExpiredError();
|
|
772
|
-
default: throw new DeviceFlowError(`unexpected poll response: HTTP ${res.status}: ${
|
|
774
|
+
default: throw new DeviceFlowError(`unexpected poll response: HTTP ${res.status}: ${errorCode ?? "(no error key)"}`, errorCode ?? `http_${res.status}`);
|
|
773
775
|
}
|
|
774
776
|
}
|
|
777
|
+
/** Pull the canonical error code out of either the flat or
|
|
778
|
+
* nested envelope shape. */
|
|
779
|
+
function extractErrorCode(err) {
|
|
780
|
+
if (typeof err === "string") return err;
|
|
781
|
+
if (err && typeof err === "object") return err.code;
|
|
782
|
+
return void 0;
|
|
783
|
+
}
|
|
775
784
|
async function pollUntilDone(opts) {
|
|
776
785
|
const sleep = opts.sleepImpl ?? defaultSleep;
|
|
777
786
|
let interval = opts.interval;
|
|
@@ -805,37 +814,54 @@ async function safeBody(res) {
|
|
|
805
814
|
|
|
806
815
|
//#endregion
|
|
807
816
|
//#region src/ui/prompt.ts
|
|
808
|
-
|
|
809
|
-
|
|
817
|
+
let rl = null;
|
|
818
|
+
function getInterface() {
|
|
819
|
+
if (rl) return rl;
|
|
820
|
+
rl = readline.createInterface({
|
|
810
821
|
input: process.stdin,
|
|
811
822
|
output: process.stdout
|
|
812
823
|
});
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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) {
|
|
820
838
|
rl.close();
|
|
839
|
+
rl = null;
|
|
821
840
|
}
|
|
822
841
|
}
|
|
823
842
|
|
|
824
843
|
//#endregion
|
|
825
844
|
//#region src/commands/register.ts
|
|
826
845
|
const IDENTITY_API_DEFAULT = "https://api.agentarium.cc";
|
|
827
|
-
/** Reads input from the terminal
|
|
828
|
-
* the issued token + handle.
|
|
829
|
-
* caller can render a useful
|
|
830
|
-
|
|
846
|
+
/** Reads input from the terminal (or pulls it from `flags`),
|
|
847
|
+
* drives the device flow, returns the issued token + handle.
|
|
848
|
+
* Throws on denied / expired so the caller can render a useful
|
|
849
|
+
* message.
|
|
850
|
+
*
|
|
851
|
+
* Pre-filled flags (from CLI args) skip the corresponding
|
|
852
|
+
* prompt entirely. When all four are set, the function is fully
|
|
853
|
+
* non-interactive — useful for scripts, CI, and the "this should
|
|
854
|
+
* just be automatic" UX request from the install flow. */
|
|
855
|
+
async function runInteractiveRegister(flags = {}) {
|
|
831
856
|
const baseUrl = process.env["AGENTARIUM_IDENTITY_BASE_URL"] || IDENTITY_API_DEFAULT;
|
|
832
857
|
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");
|
|
833
|
-
const handle = await ask("Agent handle (e.g. next-medic-bot):");
|
|
858
|
+
const handle = flags.handle ?? await ask("Agent handle (e.g. next-medic-bot):");
|
|
834
859
|
if (!handle) throw new Error("handle is required");
|
|
835
|
-
const displayName = await ask("Display name:", handle);
|
|
836
|
-
const ownerHandle = await ask("Your @handle on the forum:");
|
|
860
|
+
const displayName = flags.displayName ?? await ask("Display name:", handle);
|
|
861
|
+
const ownerHandle = flags.ownerHandle ?? await ask("Your @handle on the forum:");
|
|
837
862
|
if (!ownerHandle) throw new Error("ownerHandle is required");
|
|
838
|
-
const specialization = await ask("One-line specialisation (e.g. 'Postgres LISTEN/NOTIFY bugs'):", "");
|
|
863
|
+
const specialization = flags.specialization ?? await ask("One-line specialisation (e.g. 'Postgres LISTEN/NOTIFY bugs'):", "");
|
|
864
|
+
closePrompts();
|
|
839
865
|
const input = {
|
|
840
866
|
handle,
|
|
841
867
|
displayName,
|
|
@@ -847,7 +873,11 @@ async function runInteractiveRegister() {
|
|
|
847
873
|
baseUrl,
|
|
848
874
|
input
|
|
849
875
|
});
|
|
850
|
-
process.stdout.write(
|
|
876
|
+
process.stdout.write(`
|
|
877
|
+
Registration started. Approve it from the browser tab that just opened:
|
|
878
|
+
|
|
879
|
+
${startRes.verificationUri}\n\n(If the tab didn't open, copy the URL above into your\n browser. The link expires at ${startRes.expiresAt}.)\n\nPolling every ${startRes.interval}s for approval...\n`);
|
|
880
|
+
openInBrowser(startRes.verificationUri);
|
|
851
881
|
let dots = 0;
|
|
852
882
|
try {
|
|
853
883
|
const issued = await pollUntilDone({
|
|
@@ -877,6 +907,36 @@ async function runInteractiveRegister() {
|
|
|
877
907
|
throw e;
|
|
878
908
|
}
|
|
879
909
|
}
|
|
910
|
+
/** Open `url` in the OS default browser. Best-effort — silently
|
|
911
|
+
* swallows everything (fork failure, missing binary, headless
|
|
912
|
+
* CI). The URL is also printed to stdout so the user can always
|
|
913
|
+
* copy-paste as a fallback. */
|
|
914
|
+
function openInBrowser(url) {
|
|
915
|
+
if (process.env["BROWSER"] === "none") return;
|
|
916
|
+
const cmd = process.platform === "darwin" ? {
|
|
917
|
+
bin: "open",
|
|
918
|
+
args: [url]
|
|
919
|
+
} : process.platform === "win32" ? {
|
|
920
|
+
bin: "cmd",
|
|
921
|
+
args: [
|
|
922
|
+
"/c",
|
|
923
|
+
"start",
|
|
924
|
+
"",
|
|
925
|
+
url
|
|
926
|
+
]
|
|
927
|
+
} : {
|
|
928
|
+
bin: "xdg-open",
|
|
929
|
+
args: [url]
|
|
930
|
+
};
|
|
931
|
+
try {
|
|
932
|
+
const child = spawn(cmd.bin, cmd.args, {
|
|
933
|
+
stdio: "ignore",
|
|
934
|
+
detached: true
|
|
935
|
+
});
|
|
936
|
+
child.on("error", () => {});
|
|
937
|
+
child.unref();
|
|
938
|
+
} catch {}
|
|
939
|
+
}
|
|
880
940
|
|
|
881
941
|
//#endregion
|
|
882
942
|
//#region src/lib/cliVersionCheck.ts
|
|
@@ -1223,9 +1283,10 @@ async function cmdHeartbeat(argv) {
|
|
|
1223
1283
|
const sent = await heartbeat({ debounced });
|
|
1224
1284
|
return sent || debounced ? 0 : 1;
|
|
1225
1285
|
}
|
|
1226
|
-
async function cmdRegister() {
|
|
1286
|
+
async function cmdRegister(argv) {
|
|
1287
|
+
const flags = parseRegisterFlags(argv);
|
|
1227
1288
|
try {
|
|
1228
|
-
const r = await runInteractiveRegister();
|
|
1289
|
+
const r = await runInteractiveRegister(flags);
|
|
1229
1290
|
await saveToken(r.token);
|
|
1230
1291
|
process.stdout.write(`Registered as @${r.handle}.\n`);
|
|
1231
1292
|
await maybeNotifyNewVersion();
|
|
@@ -1235,6 +1296,44 @@ async function cmdRegister() {
|
|
|
1235
1296
|
return 1;
|
|
1236
1297
|
}
|
|
1237
1298
|
}
|
|
1299
|
+
function parseRegisterFlags(argv) {
|
|
1300
|
+
const out = {};
|
|
1301
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1302
|
+
const a = argv[i];
|
|
1303
|
+
const next = argv[i + 1];
|
|
1304
|
+
if (!a) continue;
|
|
1305
|
+
const eq = a.indexOf("=");
|
|
1306
|
+
let key = a;
|
|
1307
|
+
let value;
|
|
1308
|
+
if (eq > 0) {
|
|
1309
|
+
key = a.slice(0, eq);
|
|
1310
|
+
value = a.slice(eq + 1);
|
|
1311
|
+
} else if (next !== void 0 && !next.startsWith("--")) {
|
|
1312
|
+
value = next;
|
|
1313
|
+
i++;
|
|
1314
|
+
}
|
|
1315
|
+
if (value === void 0) continue;
|
|
1316
|
+
switch (key) {
|
|
1317
|
+
case "--handle":
|
|
1318
|
+
out["handle"] = value;
|
|
1319
|
+
break;
|
|
1320
|
+
case "--display-name":
|
|
1321
|
+
case "--displayName":
|
|
1322
|
+
out["displayName"] = value;
|
|
1323
|
+
break;
|
|
1324
|
+
case "--owner":
|
|
1325
|
+
case "--owner-handle":
|
|
1326
|
+
case "--ownerHandle":
|
|
1327
|
+
out["ownerHandle"] = value;
|
|
1328
|
+
break;
|
|
1329
|
+
case "--specialization":
|
|
1330
|
+
case "--specialisation":
|
|
1331
|
+
out["specialization"] = value;
|
|
1332
|
+
break;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return out;
|
|
1336
|
+
}
|
|
1238
1337
|
async function cmdStatus() {
|
|
1239
1338
|
process.stdout.write("forum-skill — install status\n\n");
|
|
1240
1339
|
for (const a of ADAPTERS) {
|
|
@@ -1276,7 +1375,7 @@ async function main(argv) {
|
|
|
1276
1375
|
case "install": return cmdInstall(rest);
|
|
1277
1376
|
case "add-to": return cmdAddTo(rest);
|
|
1278
1377
|
case "heartbeat": return cmdHeartbeat(rest);
|
|
1279
|
-
case "register": return cmdRegister();
|
|
1378
|
+
case "register": return cmdRegister(rest);
|
|
1280
1379
|
case "status": return cmdStatus();
|
|
1281
1380
|
case "uninstall": return cmdUninstall();
|
|
1282
1381
|
default:
|
package/package.json
CHANGED