@rolino/cli 0.6.0 → 0.7.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/index.cjs CHANGED
@@ -50,7 +50,7 @@ var import_commander = require("commander");
50
50
  // package.json
51
51
  var package_default = {
52
52
  name: "@rolino/cli",
53
- version: "0.6.0",
53
+ version: "0.7.0",
54
54
  description: "Agent-friendly command-line interface for Rolino",
55
55
  type: "module",
56
56
  license: "MIT",
@@ -107,9 +107,9 @@ var package_default = {
107
107
  dev: "tsx src/bin.ts"
108
108
  },
109
109
  dependencies: {
110
- "@rolino/contracts": "0.6.0",
111
- "@rolino/local-auth": "0.6.0",
112
- "@rolino/sdk": "0.6.0",
110
+ "@rolino/contracts": "0.7.0",
111
+ "@rolino/local-auth": "0.7.0",
112
+ "@rolino/sdk": "0.7.0",
113
113
  commander: "^15.0.0",
114
114
  open: "^11.0.0"
115
115
  },
@@ -665,12 +665,18 @@ function removeExistingCodexEntry(source, newline) {
665
665
  return kept.join(newline).trimEnd();
666
666
  }
667
667
  function codexBlock(server, newline) {
668
+ const configuration = server.transport === "http" ? [
669
+ `url = ${tomlString(server.url)}`,
670
+ `auth = ${tomlString(server.auth)}`
671
+ ] : [
672
+ `command = ${tomlString(server.command)}`,
673
+ `args = [${server.args.map(tomlString).join(", ")}]`,
674
+ `env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`
675
+ ];
668
676
  return [
669
677
  CODEX_BEGIN,
670
678
  "[mcp_servers.rolino]",
671
- `command = ${tomlString(server.command)}`,
672
- `args = [${server.args.map(tomlString).join(", ")}]`,
673
- `env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`,
679
+ ...configuration,
674
680
  CODEX_END
675
681
  ].join(newline);
676
682
  }
@@ -710,7 +716,12 @@ function updateClaudeProjectConfig(source, server) {
710
716
  ...parsed,
711
717
  mcpServers: {
712
718
  ...currentServers,
713
- rolino: { type: "stdio", ...server }
719
+ rolino: server.transport === "http" ? { type: "http", url: server.url } : {
720
+ type: "stdio",
721
+ command: server.command,
722
+ args: server.args,
723
+ env: server.env
724
+ }
714
725
  }
715
726
  }, null, 2)}
716
727
  `;
@@ -742,6 +753,7 @@ async function setupCodex(options, server) {
742
753
  backupPath,
743
754
  changed,
744
755
  dryRun: options.dryRun ?? false,
756
+ transport: server.transport,
745
757
  server
746
758
  };
747
759
  }
@@ -762,6 +774,7 @@ async function setupClaudeCode(options, server) {
762
774
  backupPath,
763
775
  changed,
764
776
  dryRun: options.dryRun ?? false,
777
+ transport: server.transport,
765
778
  server
766
779
  };
767
780
  }
@@ -774,6 +787,7 @@ async function setupClaudeCode(options, server) {
774
787
  backupPath: null,
775
788
  changed: true,
776
789
  dryRun: true,
790
+ transport: server.transport,
777
791
  server
778
792
  };
779
793
  }
@@ -787,7 +801,12 @@ async function setupClaudeCode(options, server) {
787
801
  "mcp",
788
802
  "add-json",
789
803
  "rolino",
790
- JSON.stringify({ type: "stdio", ...server }),
804
+ JSON.stringify(server.transport === "http" ? { type: "http", url: server.url } : {
805
+ type: "stdio",
806
+ command: server.command,
807
+ args: server.args,
808
+ env: server.env
809
+ }),
791
810
  "--scope",
792
811
  "user"
793
812
  ], options);
@@ -803,14 +822,51 @@ async function setupClaudeCode(options, server) {
803
822
  backupPath: null,
804
823
  changed: true,
805
824
  dryRun: false,
825
+ transport: server.transport,
806
826
  server
807
827
  };
808
828
  }
829
+ function remoteMcpUrl(baseUrl) {
830
+ return new URL("mcp", `${baseUrl.replace(/\/$/, "")}/`).toString();
831
+ }
832
+ async function advertisedRemoteMcp(options) {
833
+ const fetchImplementation = options.fetch ?? globalThis.fetch;
834
+ try {
835
+ const response = await fetchImplementation(
836
+ new URL("api/v1/meta", `${options.baseUrl.replace(/\/$/, "")}/`),
837
+ {
838
+ headers: { accept: "application/json" },
839
+ signal: AbortSignal.timeout(5e3)
840
+ }
841
+ );
842
+ if (!response.ok) return false;
843
+ const payload = await response.json();
844
+ return payload.data?.mcp?.streamableHttp === true;
845
+ } catch {
846
+ return false;
847
+ }
848
+ }
849
+ async function resolveTransport(options) {
850
+ const requested = options.transport ?? "stdio";
851
+ if (requested === "stdio") return "stdio";
852
+ if (await advertisedRemoteMcp(options)) return "http";
853
+ if (requested === "http") {
854
+ throw new TypeError(
855
+ "This Rolino instance does not advertise Streamable HTTP MCP. Use --transport stdio, or enable and verify remote MCP on the server."
856
+ );
857
+ }
858
+ return "stdio";
859
+ }
809
860
  async function setupMcp(options) {
810
- const serverPath = await resolveServerPath(options);
811
- const server = {
861
+ const transport = await resolveTransport(options);
862
+ const server = transport === "http" ? {
863
+ transport: "http",
864
+ url: remoteMcpUrl(options.baseUrl),
865
+ auth: "oauth"
866
+ } : {
867
+ transport: "stdio",
812
868
  command: options.nodePath,
813
- args: [serverPath],
869
+ args: [await resolveServerPath(options)],
814
870
  env: { ROLINO_URL: options.baseUrl }
815
871
  };
816
872
  return options.client === "codex" ? setupCodex(options, server) : setupClaudeCode(options, server);
@@ -951,6 +1007,10 @@ function mcpScope(value) {
951
1007
  if (value === "user" || value === "project") return value;
952
1008
  throw new import_commander.InvalidArgumentError("MCP setup scope must be user or project.");
953
1009
  }
1010
+ function mcpTransport(value) {
1011
+ if (value === "auto" || value === "http" || value === "stdio") return value;
1012
+ throw new import_commander.InvalidArgumentError("MCP transport must be auto, http, or stdio.");
1013
+ }
954
1014
  function isoDateTime(value) {
955
1015
  const date = new Date(value);
956
1016
  if (Number.isNaN(date.getTime())) {
@@ -1011,13 +1071,21 @@ function requireBlogExecutionConsent(options) {
1011
1071
  throw new TypeError("This Blog execute command requires explicit consent. Review the confirmed operation and pass --yes.");
1012
1072
  }
1013
1073
  function formatMcpSetupPreview(result) {
1074
+ const connection = result.server.transport === "http" ? [
1075
+ `Transport: Streamable HTTP`,
1076
+ `Endpoint: ${result.server.url}`,
1077
+ "Authentication: OAuth in the MCP client"
1078
+ ] : [
1079
+ "Transport: local STDIO",
1080
+ `Command: ${result.server.command}`,
1081
+ `Arguments: ${result.server.args.join(" ")}`,
1082
+ `Rolino URL: ${result.server.env.ROLINO_URL}`
1083
+ ];
1014
1084
  return [
1015
1085
  `Client: ${result.client === "codex" ? "Codex" : "Claude Code"}`,
1016
1086
  `Scope: ${result.scope}`,
1017
1087
  `Target: ${result.target}`,
1018
- `Command: ${result.server.command}`,
1019
- `Arguments: ${result.server.args.join(" ")}`,
1020
- `Rolino URL: ${result.server.env.ROLINO_URL}`,
1088
+ ...connection,
1021
1089
  "No token will be written to MCP configuration."
1022
1090
  ].join("\n");
1023
1091
  }
@@ -1480,7 +1548,7 @@ async function runCli(argv = process.argv, overrides = {}) {
1480
1548
  });
1481
1549
  });
1482
1550
  const setup = program.command("setup").description("Configure local agent tools for Rolino");
1483
- setup.command("mcp").description("Configure the Rolino stdio MCP server for a supported client").requiredOption("--client <client>", "codex or claude-code", mcpClient).option("--scope <scope>", "user or project", mcpScope, "user").option("--server-path <path>", "absolute or working-directory-relative MCP server path").option("--dry-run", "show the intended configuration without writing it").option("--yes", "apply without an interactive confirmation").option("--force", "replace an existing Claude Code user-scoped Rolino server").action(async (local) => {
1551
+ setup.command("mcp").description("Configure Rolino MCP with Streamable HTTP or local STDIO").requiredOption("--client <client>", "codex or claude-code", mcpClient).option("--scope <scope>", "user or project", mcpScope, "user").option("--transport <transport>", "auto, http, or stdio", mcpTransport, "auto").option("--server-path <path>", "absolute or working-directory-relative MCP server path").option("--dry-run", "show the intended configuration without writing it").option("--yes", "apply without an interactive confirmation").option("--force", "replace an existing Claude Code user-scoped Rolino server").action(async (local) => {
1484
1552
  const global = program.opts();
1485
1553
  commandExitCode = await execute({
1486
1554
  command: "setup mcp",
@@ -1493,7 +1561,8 @@ async function runCli(argv = process.argv, overrides = {}) {
1493
1561
  cwd: runtime.cwd,
1494
1562
  env: runtime.env,
1495
1563
  nodePath: runtime.nodePath,
1496
- cliEntryPath: runtime.cliEntryPath
1564
+ cliEntryPath: runtime.cliEntryPath,
1565
+ fetch: runtime.fetch
1497
1566
  };
1498
1567
  const preview = await setupMcp({ ...setupOptions, dryRun: true });
1499
1568
  let result = preview;
@@ -1520,11 +1589,12 @@ async function runCli(argv = process.argv, overrides = {}) {
1520
1589
  `${state} Rolino MCP for ${clientLabel}.`,
1521
1590
  `Scope: ${result.scope}`,
1522
1591
  `Target: ${result.target}`,
1592
+ `Transport: ${result.transport === "http" ? "Streamable HTTP" : "local STDIO"}`,
1523
1593
  ...result.backupPath && !result.dryRun ? [`Backup: ${result.backupPath}`] : [],
1524
1594
  `Rolino URL: ${client.baseUrl}`,
1525
1595
  "No token was written to MCP configuration."
1526
1596
  ].join("\n"),
1527
- result.client === "codex" ? ["codex mcp list", "rolino auth login"] : ["claude mcp get rolino", "rolino auth login"]
1597
+ result.client === "codex" ? result.transport === "http" ? ["codex mcp login rolino", "codex mcp list"] : ["rolino auth login", "codex mcp list"] : result.transport === "http" ? ["claude mcp get rolino", "Complete OAuth when Claude prompts you"] : ["rolino auth login", "claude mcp get rolino"]
1528
1598
  );
1529
1599
  }
1530
1600
  });
@@ -2263,6 +2333,106 @@ Revision: ${local.revision}` });
2263
2333
  writeSuccess(context, data, JSON.stringify(data, null, 2));
2264
2334
  } });
2265
2335
  });
2336
+ const backlinks = program.command("backlinks").description("Review backlink prospects and public contact drafts. Rolino never sends email.");
2337
+ const backlinkTargets = backlinks.command("targets");
2338
+ backlinkTargets.command("list").requiredOption("--project <project-id>").action(async (local) => {
2339
+ const global = program.opts();
2340
+ commandExitCode = await execute({ command: "backlinks targets list", global, runtime, async action(context, client) {
2341
+ const data = await client.backlinks.targets.list(local.project, { requestId: context.requestId });
2342
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2343
+ } });
2344
+ });
2345
+ backlinkTargets.command("add").requiredOption("--project <project-id>").requiredOption("--url <url>").requiredOption("--label <label>").action(async (local) => {
2346
+ const global = program.opts();
2347
+ commandExitCode = await execute({ command: "backlinks targets add", global, runtime, async action(context, client) {
2348
+ const data = await client.backlinks.targets.add(local.project, { url: local.url, label: local.label }, context.requestId, { requestId: context.requestId });
2349
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2350
+ } });
2351
+ });
2352
+ backlinks.command("discover").requiredOption("--project <project-id>").option("--limit <number>", "maximum saved prospects", Number, 20).option("--idempotency-key <key>").action(async (local) => {
2353
+ const global = program.opts();
2354
+ commandExitCode = await execute({ command: "backlinks discover", global, runtime, async action(context, client) {
2355
+ const data = await client.backlinks.discoveries.start(local.project, { limit: local.limit }, local.idempotencyKey ?? context.requestId, { requestId: context.requestId });
2356
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2357
+ } });
2358
+ });
2359
+ const backlinkRuns = backlinks.command("runs");
2360
+ backlinkRuns.command("get").requiredOption("--project <project-id>").requiredOption("--run <run-id>").action(async (local) => {
2361
+ const global = program.opts();
2362
+ commandExitCode = await execute({ command: "backlinks runs get", global, runtime, async action(context, client) {
2363
+ const data = await client.backlinks.discoveries.get(local.project, local.run, { requestId: context.requestId });
2364
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2365
+ } });
2366
+ });
2367
+ const backlinkProspects = backlinks.command("prospects");
2368
+ backlinkProspects.command("list").requiredOption("--project <project-id>").option("--stage <stage>").action(async (local) => {
2369
+ const global = program.opts();
2370
+ commandExitCode = await execute({ command: "backlinks prospects list", global, runtime, async action(context, client) {
2371
+ const stage = local.stage ? import_contracts.BacklinkProspectStageSchema.parse(local.stage.toUpperCase()) : void 0;
2372
+ const data = await client.backlinks.prospects.list(local.project, { stage }, { requestId: context.requestId });
2373
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2374
+ } });
2375
+ });
2376
+ backlinkProspects.command("get").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
2377
+ const global = program.opts();
2378
+ commandExitCode = await execute({ command: "backlinks prospects get", global, runtime, async action(context, client) {
2379
+ const data = await client.backlinks.prospects.get(local.project, local.prospect, { requestId: context.requestId });
2380
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2381
+ } });
2382
+ });
2383
+ backlinkProspects.command("approve").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").requiredOption("--expected-version <number>", "current optimistic version", Number).action(async (local) => {
2384
+ const global = program.opts();
2385
+ commandExitCode = await execute({ command: "backlinks prospects approve", global, runtime, async action(context, client) {
2386
+ const data = await client.backlinks.prospects.updateStage(local.project, local.prospect, { stage: "APPROVED", expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2387
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2388
+ } });
2389
+ });
2390
+ const backlinkContacts = backlinks.command("contacts");
2391
+ backlinkContacts.command("research").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
2392
+ const global = program.opts();
2393
+ commandExitCode = await execute({ command: "backlinks contacts research", global, runtime, async action(context, client) {
2394
+ const data = await client.backlinks.prospects.researchContact(local.project, local.prospect, context.requestId, { requestId: context.requestId });
2395
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2396
+ } });
2397
+ });
2398
+ backlinkContacts.command("list").requiredOption("--project <project-id>").action(async (local) => {
2399
+ const global = program.opts();
2400
+ commandExitCode = await execute({ command: "backlinks contacts list", global, runtime, async action(context, client) {
2401
+ const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
2402
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2403
+ } });
2404
+ });
2405
+ backlinkContacts.command("export").requiredOption("--project <project-id>").option("--format <format>", "json or csv", "csv").action(async (local) => {
2406
+ const global = program.opts();
2407
+ commandExitCode = await execute({ command: "backlinks contacts export", global, runtime, async action(context, client) {
2408
+ const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
2409
+ if (local.format !== "csv") return writeSuccess(context, data, JSON.stringify(data, null, 2));
2410
+ const cell = (value) => {
2411
+ let text = String(value ?? "").replace(/[\r\n]+/g, " ");
2412
+ if (/^[=+\-@\t]/.test(text)) text = `'${text}`;
2413
+ return `"${text.replaceAll('"', '""')}"`;
2414
+ };
2415
+ const csv = ["prospectId,name,role,email,sourceUrl,checkedAt", ...data.items.map((item) => [item.prospectId, item.name, item.role, item.email, item.sourceUrl, item.checkedAt].map(cell).join(","))].join("\n");
2416
+ context.stdout.write(`${csv}
2417
+ `);
2418
+ } });
2419
+ });
2420
+ const backlinkOutreach = backlinks.command("outreach");
2421
+ backlinkOutreach.command("update").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").requiredOption("--file <file>").requiredOption("--expected-version <number>", "current draft version", Number).action(async (local) => {
2422
+ const global = program.opts();
2423
+ commandExitCode = await execute({ command: "backlinks outreach update", global, runtime, async action(context, client) {
2424
+ const payload = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.resolve)(runtime.cwd, local.file), "utf8"));
2425
+ const data = await client.backlinks.prospects.updateOutreach(local.project, local.prospect, { ...payload, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2426
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2427
+ } });
2428
+ });
2429
+ backlinks.command("verify").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").requiredOption("--url <url>").requiredOption("--expected-version <number>", "current prospect version", Number).action(async (local) => {
2430
+ const global = program.opts();
2431
+ commandExitCode = await execute({ command: "backlinks verify", global, runtime, async action(context, client) {
2432
+ const data = await client.backlinks.prospects.verify(local.project, local.prospect, { url: local.url, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2433
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2434
+ } });
2435
+ });
2266
2436
  const seo = program.command("seo").description("Read authorized SEO opportunities and weekly reports");
2267
2437
  const seoOpportunities = seo.command("opportunities").description("Read accepted SEO opportunities");
2268
2438
  seoOpportunities.command("list").description("List bounded SEO opportunities").requiredOption("--project <project-id>", "exact Rolino project ID").option("--limit <number>", "maximum opportunities to return", (value) => {