@rolino/cli 0.6.0 → 0.7.1

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/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-3WBZCTM5.js";
4
+ } from "./chunk-KSPZKKBG.js";
5
5
 
6
6
  // src/bin.ts
7
7
  void runCli().then(
@@ -123,12 +123,18 @@ function removeExistingCodexEntry(source, newline) {
123
123
  return kept.join(newline).trimEnd();
124
124
  }
125
125
  function codexBlock(server, newline) {
126
+ const configuration = server.transport === "http" ? [
127
+ `url = ${tomlString(server.url)}`,
128
+ `auth = ${tomlString(server.auth)}`
129
+ ] : [
130
+ `command = ${tomlString(server.command)}`,
131
+ `args = [${server.args.map(tomlString).join(", ")}]`,
132
+ `env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`
133
+ ];
126
134
  return [
127
135
  CODEX_BEGIN,
128
136
  "[mcp_servers.rolino]",
129
- `command = ${tomlString(server.command)}`,
130
- `args = [${server.args.map(tomlString).join(", ")}]`,
131
- `env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`,
137
+ ...configuration,
132
138
  CODEX_END
133
139
  ].join(newline);
134
140
  }
@@ -137,13 +143,12 @@ function updateCodexConfig(source, server) {
137
143
  const block = codexBlock(server, newline);
138
144
  const begin = source.indexOf(CODEX_BEGIN);
139
145
  const end = source.indexOf(CODEX_END);
140
- if (begin === -1 !== (end === -1) || begin !== -1 && end < begin) {
141
- throw new TypeError("Codex config contains an incomplete Rolino-managed block.");
142
- }
143
146
  let unmanagedSource = source;
144
- if (begin !== -1) {
147
+ if (begin !== -1 && end !== -1 && end > begin) {
145
148
  const after = end + CODEX_END.length;
146
149
  unmanagedSource = `${source.slice(0, begin)}${source.slice(after)}`;
150
+ } else if (begin !== -1 || end !== -1) {
151
+ unmanagedSource = source.split(/\r?\n/).filter((line) => line.trim() !== CODEX_BEGIN && line.trim() !== CODEX_END).join(newline);
147
152
  }
148
153
  const withoutRolino = removeExistingCodexEntry(unmanagedSource, newline);
149
154
  return `${withoutRolino ? `${withoutRolino}${newline}${newline}` : ""}${block}${newline}`;
@@ -168,7 +173,12 @@ function updateClaudeProjectConfig(source, server) {
168
173
  ...parsed,
169
174
  mcpServers: {
170
175
  ...currentServers,
171
- rolino: { type: "stdio", ...server }
176
+ rolino: server.transport === "http" ? { type: "http", url: server.url } : {
177
+ type: "stdio",
178
+ command: server.command,
179
+ args: server.args,
180
+ env: server.env
181
+ }
172
182
  }
173
183
  }, null, 2)}
174
184
  `;
@@ -200,6 +210,7 @@ async function setupCodex(options, server) {
200
210
  backupPath,
201
211
  changed,
202
212
  dryRun: options.dryRun ?? false,
213
+ transport: server.transport,
203
214
  server
204
215
  };
205
216
  }
@@ -220,6 +231,7 @@ async function setupClaudeCode(options, server) {
220
231
  backupPath,
221
232
  changed,
222
233
  dryRun: options.dryRun ?? false,
234
+ transport: server.transport,
223
235
  server
224
236
  };
225
237
  }
@@ -232,6 +244,7 @@ async function setupClaudeCode(options, server) {
232
244
  backupPath: null,
233
245
  changed: true,
234
246
  dryRun: true,
247
+ transport: server.transport,
235
248
  server
236
249
  };
237
250
  }
@@ -245,7 +258,12 @@ async function setupClaudeCode(options, server) {
245
258
  "mcp",
246
259
  "add-json",
247
260
  "rolino",
248
- JSON.stringify({ type: "stdio", ...server }),
261
+ JSON.stringify(server.transport === "http" ? { type: "http", url: server.url } : {
262
+ type: "stdio",
263
+ command: server.command,
264
+ args: server.args,
265
+ env: server.env
266
+ }),
249
267
  "--scope",
250
268
  "user"
251
269
  ], options);
@@ -261,14 +279,51 @@ async function setupClaudeCode(options, server) {
261
279
  backupPath: null,
262
280
  changed: true,
263
281
  dryRun: false,
282
+ transport: server.transport,
264
283
  server
265
284
  };
266
285
  }
286
+ function remoteMcpUrl(baseUrl) {
287
+ return new URL("mcp", `${baseUrl.replace(/\/$/, "")}/`).toString();
288
+ }
289
+ async function advertisedRemoteMcp(options) {
290
+ const fetchImplementation = options.fetch ?? globalThis.fetch;
291
+ try {
292
+ const response = await fetchImplementation(
293
+ new URL("api/v1/meta", `${options.baseUrl.replace(/\/$/, "")}/`),
294
+ {
295
+ headers: { accept: "application/json" },
296
+ signal: AbortSignal.timeout(5e3)
297
+ }
298
+ );
299
+ if (!response.ok) return false;
300
+ const payload = await response.json();
301
+ return payload.data?.mcp?.streamableHttp === true;
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+ async function resolveTransport(options) {
307
+ const requested = options.transport ?? "stdio";
308
+ if (requested === "stdio") return "stdio";
309
+ if (await advertisedRemoteMcp(options)) return "http";
310
+ if (requested === "http") {
311
+ throw new TypeError(
312
+ "This Rolino instance does not advertise Streamable HTTP MCP. Use --transport stdio, or enable and verify remote MCP on the server."
313
+ );
314
+ }
315
+ return "stdio";
316
+ }
267
317
  async function setupMcp(options) {
268
- const serverPath = await resolveServerPath(options);
269
- const server = {
318
+ const transport = await resolveTransport(options);
319
+ const server = transport === "http" ? {
320
+ transport: "http",
321
+ url: remoteMcpUrl(options.baseUrl),
322
+ auth: "oauth"
323
+ } : {
324
+ transport: "stdio",
270
325
  command: options.nodePath,
271
- args: [serverPath],
326
+ args: [await resolveServerPath(options)],
272
327
  env: { ROLINO_URL: options.baseUrl }
273
328
  };
274
329
  return options.client === "codex" ? setupCodex(options, server) : setupClaudeCode(options, server);
@@ -285,7 +340,7 @@ import { Command, CommanderError, InvalidArgumentError } from "commander";
285
340
  // package.json
286
341
  var package_default = {
287
342
  name: "@rolino/cli",
288
- version: "0.6.0",
343
+ version: "0.7.1",
289
344
  description: "Agent-friendly command-line interface for Rolino",
290
345
  type: "module",
291
346
  license: "MIT",
@@ -342,9 +397,9 @@ var package_default = {
342
397
  dev: "tsx src/bin.ts"
343
398
  },
344
399
  dependencies: {
345
- "@rolino/contracts": "0.6.0",
346
- "@rolino/local-auth": "0.6.0",
347
- "@rolino/sdk": "0.6.0",
400
+ "@rolino/contracts": "0.7.1",
401
+ "@rolino/local-auth": "0.7.1",
402
+ "@rolino/sdk": "0.7.1",
348
403
  commander: "^15.0.0",
349
404
  open: "^11.0.0"
350
405
  },
@@ -360,6 +415,7 @@ var package_default = {
360
415
  import {
361
416
  PostStatusSchema,
362
417
  AgentBlogDraftUpdateSchema,
418
+ BacklinkProspectStageSchema,
363
419
  ProjectCreateInputSchema,
364
420
  ProjectTypeSchema,
365
421
  ProviderDeliveryOptionsProviderSchema,
@@ -381,6 +437,7 @@ import {
381
437
  // src/browser-login.ts
382
438
  import { createHash, randomBytes, timingSafeEqual } from "crypto";
383
439
  import { createServer } from "http";
440
+ import { AGENT_CAPABILITIES } from "@rolino/contracts";
384
441
  import {
385
442
  oauthConfiguration
386
443
  } from "@rolino/local-auth";
@@ -388,13 +445,9 @@ import open from "open";
388
445
  var OAUTH_CALLBACK_PORT = 48391;
389
446
  var OAUTH_CALLBACK_PATH = "/oauth/callback";
390
447
  var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
391
- var DEFAULT_SCOPES = [
448
+ var AUTHORIZATION_REQUEST_SCOPES = [
392
449
  "offline_access",
393
- "identity:read",
394
- "projects:read",
395
- "posts:read",
396
- "integrations:read",
397
- "calendar:read"
450
+ ...AGENT_CAPABILITIES
398
451
  ];
399
452
  function escapeHtml(value) {
400
453
  return value.replace(/[&<>"']/g, (character) => ({
@@ -434,7 +487,7 @@ function createBrowserAuthorizationRequest(baseUrl) {
434
487
  url.searchParams.set("response_type", "code");
435
488
  url.searchParams.set("client_id", configuration.clientId);
436
489
  url.searchParams.set("redirect_uri", redirectUri);
437
- url.searchParams.set("scope", DEFAULT_SCOPES.join(" "));
490
+ url.searchParams.set("scope", AUTHORIZATION_REQUEST_SCOPES.join(" "));
438
491
  url.searchParams.set("resource", configuration.resource);
439
492
  url.searchParams.set("state", state);
440
493
  url.searchParams.set("code_challenge", codeChallenge);
@@ -473,7 +526,7 @@ async function exchangeAuthorizationCode(options) {
473
526
  accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString(),
474
527
  refreshToken: payload.refresh_token,
475
528
  refreshTokenExpiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(),
476
- scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : [...DEFAULT_SCOPES]
529
+ scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : [...AUTHORIZATION_REQUEST_SCOPES]
477
530
  };
478
531
  }
479
532
  async function loginWithBrowser(options) {
@@ -943,6 +996,10 @@ function mcpScope(value) {
943
996
  if (value === "user" || value === "project") return value;
944
997
  throw new InvalidArgumentError("MCP setup scope must be user or project.");
945
998
  }
999
+ function mcpTransport(value) {
1000
+ if (value === "auto" || value === "http" || value === "stdio") return value;
1001
+ throw new InvalidArgumentError("MCP transport must be auto, http, or stdio.");
1002
+ }
946
1003
  function isoDateTime(value) {
947
1004
  const date = new Date(value);
948
1005
  if (Number.isNaN(date.getTime())) {
@@ -1003,13 +1060,21 @@ function requireBlogExecutionConsent(options) {
1003
1060
  throw new TypeError("This Blog execute command requires explicit consent. Review the confirmed operation and pass --yes.");
1004
1061
  }
1005
1062
  function formatMcpSetupPreview(result) {
1063
+ const connection = result.server.transport === "http" ? [
1064
+ `Transport: Streamable HTTP`,
1065
+ `Endpoint: ${result.server.url}`,
1066
+ "Authentication: OAuth in the MCP client"
1067
+ ] : [
1068
+ "Transport: local STDIO",
1069
+ `Command: ${result.server.command}`,
1070
+ `Arguments: ${result.server.args.join(" ")}`,
1071
+ `Rolino URL: ${result.server.env.ROLINO_URL}`
1072
+ ];
1006
1073
  return [
1007
1074
  `Client: ${result.client === "codex" ? "Codex" : "Claude Code"}`,
1008
1075
  `Scope: ${result.scope}`,
1009
1076
  `Target: ${result.target}`,
1010
- `Command: ${result.server.command}`,
1011
- `Arguments: ${result.server.args.join(" ")}`,
1012
- `Rolino URL: ${result.server.env.ROLINO_URL}`,
1077
+ ...connection,
1013
1078
  "No token will be written to MCP configuration."
1014
1079
  ].join("\n");
1015
1080
  }
@@ -1472,7 +1537,7 @@ async function runCli(argv = process.argv, overrides = {}) {
1472
1537
  });
1473
1538
  });
1474
1539
  const setup = program.command("setup").description("Configure local agent tools for Rolino");
1475
- 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) => {
1540
+ 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) => {
1476
1541
  const global = program.opts();
1477
1542
  commandExitCode = await execute({
1478
1543
  command: "setup mcp",
@@ -1485,7 +1550,8 @@ async function runCli(argv = process.argv, overrides = {}) {
1485
1550
  cwd: runtime.cwd,
1486
1551
  env: runtime.env,
1487
1552
  nodePath: runtime.nodePath,
1488
- cliEntryPath: runtime.cliEntryPath
1553
+ cliEntryPath: runtime.cliEntryPath,
1554
+ fetch: runtime.fetch
1489
1555
  };
1490
1556
  const preview = await setupMcp({ ...setupOptions, dryRun: true });
1491
1557
  let result = preview;
@@ -1512,11 +1578,12 @@ async function runCli(argv = process.argv, overrides = {}) {
1512
1578
  `${state} Rolino MCP for ${clientLabel}.`,
1513
1579
  `Scope: ${result.scope}`,
1514
1580
  `Target: ${result.target}`,
1581
+ `Transport: ${result.transport === "http" ? "Streamable HTTP" : "local STDIO"}`,
1515
1582
  ...result.backupPath && !result.dryRun ? [`Backup: ${result.backupPath}`] : [],
1516
1583
  `Rolino URL: ${client.baseUrl}`,
1517
1584
  "No token was written to MCP configuration."
1518
1585
  ].join("\n"),
1519
- result.client === "codex" ? ["codex mcp list", "rolino auth login"] : ["claude mcp get rolino", "rolino auth login"]
1586
+ 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"]
1520
1587
  );
1521
1588
  }
1522
1589
  });
@@ -2255,6 +2322,106 @@ Revision: ${local.revision}` });
2255
2322
  writeSuccess(context, data, JSON.stringify(data, null, 2));
2256
2323
  } });
2257
2324
  });
2325
+ const backlinks = program.command("backlinks").description("Review backlink prospects and public contact drafts. Rolino never sends email.");
2326
+ const backlinkTargets = backlinks.command("targets");
2327
+ backlinkTargets.command("list").requiredOption("--project <project-id>").action(async (local) => {
2328
+ const global = program.opts();
2329
+ commandExitCode = await execute({ command: "backlinks targets list", global, runtime, async action(context, client) {
2330
+ const data = await client.backlinks.targets.list(local.project, { requestId: context.requestId });
2331
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2332
+ } });
2333
+ });
2334
+ backlinkTargets.command("add").requiredOption("--project <project-id>").requiredOption("--url <url>").requiredOption("--label <label>").action(async (local) => {
2335
+ const global = program.opts();
2336
+ commandExitCode = await execute({ command: "backlinks targets add", global, runtime, async action(context, client) {
2337
+ const data = await client.backlinks.targets.add(local.project, { url: local.url, label: local.label }, context.requestId, { requestId: context.requestId });
2338
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2339
+ } });
2340
+ });
2341
+ backlinks.command("discover").requiredOption("--project <project-id>").option("--limit <number>", "maximum saved prospects", Number, 20).option("--idempotency-key <key>").action(async (local) => {
2342
+ const global = program.opts();
2343
+ commandExitCode = await execute({ command: "backlinks discover", global, runtime, async action(context, client) {
2344
+ const data = await client.backlinks.discoveries.start(local.project, { limit: local.limit }, local.idempotencyKey ?? context.requestId, { requestId: context.requestId });
2345
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2346
+ } });
2347
+ });
2348
+ const backlinkRuns = backlinks.command("runs");
2349
+ backlinkRuns.command("get").requiredOption("--project <project-id>").requiredOption("--run <run-id>").action(async (local) => {
2350
+ const global = program.opts();
2351
+ commandExitCode = await execute({ command: "backlinks runs get", global, runtime, async action(context, client) {
2352
+ const data = await client.backlinks.discoveries.get(local.project, local.run, { requestId: context.requestId });
2353
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2354
+ } });
2355
+ });
2356
+ const backlinkProspects = backlinks.command("prospects");
2357
+ backlinkProspects.command("list").requiredOption("--project <project-id>").option("--stage <stage>").action(async (local) => {
2358
+ const global = program.opts();
2359
+ commandExitCode = await execute({ command: "backlinks prospects list", global, runtime, async action(context, client) {
2360
+ const stage = local.stage ? BacklinkProspectStageSchema.parse(local.stage.toUpperCase()) : void 0;
2361
+ const data = await client.backlinks.prospects.list(local.project, { stage }, { requestId: context.requestId });
2362
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2363
+ } });
2364
+ });
2365
+ backlinkProspects.command("get").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
2366
+ const global = program.opts();
2367
+ commandExitCode = await execute({ command: "backlinks prospects get", global, runtime, async action(context, client) {
2368
+ const data = await client.backlinks.prospects.get(local.project, local.prospect, { requestId: context.requestId });
2369
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2370
+ } });
2371
+ });
2372
+ backlinkProspects.command("approve").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").requiredOption("--expected-version <number>", "current optimistic version", Number).action(async (local) => {
2373
+ const global = program.opts();
2374
+ commandExitCode = await execute({ command: "backlinks prospects approve", global, runtime, async action(context, client) {
2375
+ const data = await client.backlinks.prospects.updateStage(local.project, local.prospect, { stage: "APPROVED", expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2376
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2377
+ } });
2378
+ });
2379
+ const backlinkContacts = backlinks.command("contacts");
2380
+ backlinkContacts.command("research").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
2381
+ const global = program.opts();
2382
+ commandExitCode = await execute({ command: "backlinks contacts research", global, runtime, async action(context, client) {
2383
+ const data = await client.backlinks.prospects.researchContact(local.project, local.prospect, context.requestId, { requestId: context.requestId });
2384
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2385
+ } });
2386
+ });
2387
+ backlinkContacts.command("list").requiredOption("--project <project-id>").action(async (local) => {
2388
+ const global = program.opts();
2389
+ commandExitCode = await execute({ command: "backlinks contacts list", global, runtime, async action(context, client) {
2390
+ const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
2391
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2392
+ } });
2393
+ });
2394
+ backlinkContacts.command("export").requiredOption("--project <project-id>").option("--format <format>", "json or csv", "csv").action(async (local) => {
2395
+ const global = program.opts();
2396
+ commandExitCode = await execute({ command: "backlinks contacts export", global, runtime, async action(context, client) {
2397
+ const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
2398
+ if (local.format !== "csv") return writeSuccess(context, data, JSON.stringify(data, null, 2));
2399
+ const cell = (value) => {
2400
+ let text = String(value ?? "").replace(/[\r\n]+/g, " ");
2401
+ if (/^[=+\-@\t]/.test(text)) text = `'${text}`;
2402
+ return `"${text.replaceAll('"', '""')}"`;
2403
+ };
2404
+ 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");
2405
+ context.stdout.write(`${csv}
2406
+ `);
2407
+ } });
2408
+ });
2409
+ const backlinkOutreach = backlinks.command("outreach");
2410
+ 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) => {
2411
+ const global = program.opts();
2412
+ commandExitCode = await execute({ command: "backlinks outreach update", global, runtime, async action(context, client) {
2413
+ const payload = JSON.parse(await readFile2(resolve2(runtime.cwd, local.file), "utf8"));
2414
+ const data = await client.backlinks.prospects.updateOutreach(local.project, local.prospect, { ...payload, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2415
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2416
+ } });
2417
+ });
2418
+ 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) => {
2419
+ const global = program.opts();
2420
+ commandExitCode = await execute({ command: "backlinks verify", global, runtime, async action(context, client) {
2421
+ const data = await client.backlinks.prospects.verify(local.project, local.prospect, { url: local.url, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
2422
+ writeSuccess(context, data, JSON.stringify(data, null, 2));
2423
+ } });
2424
+ });
2258
2425
  const seo = program.command("seo").description("Read authorized SEO opportunities and weekly reports");
2259
2426
  const seoOpportunities = seo.command("opportunities").description("Read accepted SEO opportunities");
2260
2427
  seoOpportunities.command("list").description("List bounded SEO opportunities").requiredOption("--project <project-id>", "exact Rolino project ID").option("--limit <number>", "maximum opportunities to return", (value) => {
@@ -2420,4 +2587,4 @@ export {
2420
2587
  ROLINO_CLI_VERSION,
2421
2588
  runCli
2422
2589
  };
2423
- //# sourceMappingURL=chunk-3WBZCTM5.js.map
2590
+ //# sourceMappingURL=chunk-KSPZKKBG.js.map