@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/CHANGELOG.md +29 -0
- package/README.md +43 -21
- package/dist/bin.cjs +213 -47
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-3WBZCTM5.js → chunk-KSPZKKBG.js} +198 -31
- package/dist/chunk-KSPZKKBG.js.map +1 -0
- package/dist/index.cjs +213 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -10
- package/dist/index.d.ts +21 -10
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-3WBZCTM5.js.map +0 -1
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.
|
|
53
|
+
version: "0.7.1",
|
|
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.
|
|
111
|
-
"@rolino/local-auth": "0.
|
|
112
|
-
"@rolino/sdk": "0.
|
|
110
|
+
"@rolino/contracts": "0.7.1",
|
|
111
|
+
"@rolino/local-auth": "0.7.1",
|
|
112
|
+
"@rolino/sdk": "0.7.1",
|
|
113
113
|
commander: "^15.0.0",
|
|
114
114
|
open: "^11.0.0"
|
|
115
115
|
},
|
|
@@ -122,24 +122,21 @@ var package_default = {
|
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
// src/cli.ts
|
|
125
|
-
var
|
|
125
|
+
var import_contracts2 = require("@rolino/contracts");
|
|
126
126
|
var import_sdk = require("@rolino/sdk");
|
|
127
127
|
|
|
128
128
|
// src/browser-login.ts
|
|
129
129
|
var import_node_crypto = require("crypto");
|
|
130
130
|
var import_node_http = require("http");
|
|
131
|
+
var import_contracts = require("@rolino/contracts");
|
|
131
132
|
var import_local_auth = require("@rolino/local-auth");
|
|
132
133
|
var import_open = __toESM(require("open"), 1);
|
|
133
134
|
var OAUTH_CALLBACK_PORT = 48391;
|
|
134
135
|
var OAUTH_CALLBACK_PATH = "/oauth/callback";
|
|
135
136
|
var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
136
|
-
var
|
|
137
|
+
var AUTHORIZATION_REQUEST_SCOPES = [
|
|
137
138
|
"offline_access",
|
|
138
|
-
|
|
139
|
-
"projects:read",
|
|
140
|
-
"posts:read",
|
|
141
|
-
"integrations:read",
|
|
142
|
-
"calendar:read"
|
|
139
|
+
...import_contracts.AGENT_CAPABILITIES
|
|
143
140
|
];
|
|
144
141
|
function escapeHtml(value) {
|
|
145
142
|
return value.replace(/[&<>"']/g, (character) => ({
|
|
@@ -179,7 +176,7 @@ function createBrowserAuthorizationRequest(baseUrl) {
|
|
|
179
176
|
url.searchParams.set("response_type", "code");
|
|
180
177
|
url.searchParams.set("client_id", configuration.clientId);
|
|
181
178
|
url.searchParams.set("redirect_uri", redirectUri);
|
|
182
|
-
url.searchParams.set("scope",
|
|
179
|
+
url.searchParams.set("scope", AUTHORIZATION_REQUEST_SCOPES.join(" "));
|
|
183
180
|
url.searchParams.set("resource", configuration.resource);
|
|
184
181
|
url.searchParams.set("state", state);
|
|
185
182
|
url.searchParams.set("code_challenge", codeChallenge);
|
|
@@ -218,7 +215,7 @@ async function exchangeAuthorizationCode(options) {
|
|
|
218
215
|
accessTokenExpiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString(),
|
|
219
216
|
refreshToken: payload.refresh_token,
|
|
220
217
|
refreshTokenExpiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1e3).toISOString(),
|
|
221
|
-
scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : [...
|
|
218
|
+
scopes: typeof payload.scope === "string" ? payload.scope.split(/\s+/).filter(Boolean) : [...AUTHORIZATION_REQUEST_SCOPES]
|
|
222
219
|
};
|
|
223
220
|
}
|
|
224
221
|
async function loginWithBrowser(options) {
|
|
@@ -665,12 +662,18 @@ function removeExistingCodexEntry(source, newline) {
|
|
|
665
662
|
return kept.join(newline).trimEnd();
|
|
666
663
|
}
|
|
667
664
|
function codexBlock(server, newline) {
|
|
665
|
+
const configuration = server.transport === "http" ? [
|
|
666
|
+
`url = ${tomlString(server.url)}`,
|
|
667
|
+
`auth = ${tomlString(server.auth)}`
|
|
668
|
+
] : [
|
|
669
|
+
`command = ${tomlString(server.command)}`,
|
|
670
|
+
`args = [${server.args.map(tomlString).join(", ")}]`,
|
|
671
|
+
`env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`
|
|
672
|
+
];
|
|
668
673
|
return [
|
|
669
674
|
CODEX_BEGIN,
|
|
670
675
|
"[mcp_servers.rolino]",
|
|
671
|
-
|
|
672
|
-
`args = [${server.args.map(tomlString).join(", ")}]`,
|
|
673
|
-
`env = { ROLINO_URL = ${tomlString(server.env.ROLINO_URL)} }`,
|
|
676
|
+
...configuration,
|
|
674
677
|
CODEX_END
|
|
675
678
|
].join(newline);
|
|
676
679
|
}
|
|
@@ -679,13 +682,12 @@ function updateCodexConfig(source, server) {
|
|
|
679
682
|
const block = codexBlock(server, newline);
|
|
680
683
|
const begin = source.indexOf(CODEX_BEGIN);
|
|
681
684
|
const end = source.indexOf(CODEX_END);
|
|
682
|
-
if (begin === -1 !== (end === -1) || begin !== -1 && end < begin) {
|
|
683
|
-
throw new TypeError("Codex config contains an incomplete Rolino-managed block.");
|
|
684
|
-
}
|
|
685
685
|
let unmanagedSource = source;
|
|
686
|
-
if (begin !== -1) {
|
|
686
|
+
if (begin !== -1 && end !== -1 && end > begin) {
|
|
687
687
|
const after = end + CODEX_END.length;
|
|
688
688
|
unmanagedSource = `${source.slice(0, begin)}${source.slice(after)}`;
|
|
689
|
+
} else if (begin !== -1 || end !== -1) {
|
|
690
|
+
unmanagedSource = source.split(/\r?\n/).filter((line) => line.trim() !== CODEX_BEGIN && line.trim() !== CODEX_END).join(newline);
|
|
689
691
|
}
|
|
690
692
|
const withoutRolino = removeExistingCodexEntry(unmanagedSource, newline);
|
|
691
693
|
return `${withoutRolino ? `${withoutRolino}${newline}${newline}` : ""}${block}${newline}`;
|
|
@@ -710,7 +712,12 @@ function updateClaudeProjectConfig(source, server) {
|
|
|
710
712
|
...parsed,
|
|
711
713
|
mcpServers: {
|
|
712
714
|
...currentServers,
|
|
713
|
-
rolino: { type: "
|
|
715
|
+
rolino: server.transport === "http" ? { type: "http", url: server.url } : {
|
|
716
|
+
type: "stdio",
|
|
717
|
+
command: server.command,
|
|
718
|
+
args: server.args,
|
|
719
|
+
env: server.env
|
|
720
|
+
}
|
|
714
721
|
}
|
|
715
722
|
}, null, 2)}
|
|
716
723
|
`;
|
|
@@ -742,6 +749,7 @@ async function setupCodex(options, server) {
|
|
|
742
749
|
backupPath,
|
|
743
750
|
changed,
|
|
744
751
|
dryRun: options.dryRun ?? false,
|
|
752
|
+
transport: server.transport,
|
|
745
753
|
server
|
|
746
754
|
};
|
|
747
755
|
}
|
|
@@ -762,6 +770,7 @@ async function setupClaudeCode(options, server) {
|
|
|
762
770
|
backupPath,
|
|
763
771
|
changed,
|
|
764
772
|
dryRun: options.dryRun ?? false,
|
|
773
|
+
transport: server.transport,
|
|
765
774
|
server
|
|
766
775
|
};
|
|
767
776
|
}
|
|
@@ -774,6 +783,7 @@ async function setupClaudeCode(options, server) {
|
|
|
774
783
|
backupPath: null,
|
|
775
784
|
changed: true,
|
|
776
785
|
dryRun: true,
|
|
786
|
+
transport: server.transport,
|
|
777
787
|
server
|
|
778
788
|
};
|
|
779
789
|
}
|
|
@@ -787,7 +797,12 @@ async function setupClaudeCode(options, server) {
|
|
|
787
797
|
"mcp",
|
|
788
798
|
"add-json",
|
|
789
799
|
"rolino",
|
|
790
|
-
JSON.stringify({ type: "
|
|
800
|
+
JSON.stringify(server.transport === "http" ? { type: "http", url: server.url } : {
|
|
801
|
+
type: "stdio",
|
|
802
|
+
command: server.command,
|
|
803
|
+
args: server.args,
|
|
804
|
+
env: server.env
|
|
805
|
+
}),
|
|
791
806
|
"--scope",
|
|
792
807
|
"user"
|
|
793
808
|
], options);
|
|
@@ -803,14 +818,51 @@ async function setupClaudeCode(options, server) {
|
|
|
803
818
|
backupPath: null,
|
|
804
819
|
changed: true,
|
|
805
820
|
dryRun: false,
|
|
821
|
+
transport: server.transport,
|
|
806
822
|
server
|
|
807
823
|
};
|
|
808
824
|
}
|
|
825
|
+
function remoteMcpUrl(baseUrl) {
|
|
826
|
+
return new URL("mcp", `${baseUrl.replace(/\/$/, "")}/`).toString();
|
|
827
|
+
}
|
|
828
|
+
async function advertisedRemoteMcp(options) {
|
|
829
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
830
|
+
try {
|
|
831
|
+
const response = await fetchImplementation(
|
|
832
|
+
new URL("api/v1/meta", `${options.baseUrl.replace(/\/$/, "")}/`),
|
|
833
|
+
{
|
|
834
|
+
headers: { accept: "application/json" },
|
|
835
|
+
signal: AbortSignal.timeout(5e3)
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
if (!response.ok) return false;
|
|
839
|
+
const payload = await response.json();
|
|
840
|
+
return payload.data?.mcp?.streamableHttp === true;
|
|
841
|
+
} catch {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
async function resolveTransport(options) {
|
|
846
|
+
const requested = options.transport ?? "stdio";
|
|
847
|
+
if (requested === "stdio") return "stdio";
|
|
848
|
+
if (await advertisedRemoteMcp(options)) return "http";
|
|
849
|
+
if (requested === "http") {
|
|
850
|
+
throw new TypeError(
|
|
851
|
+
"This Rolino instance does not advertise Streamable HTTP MCP. Use --transport stdio, or enable and verify remote MCP on the server."
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
return "stdio";
|
|
855
|
+
}
|
|
809
856
|
async function setupMcp(options) {
|
|
810
|
-
const
|
|
811
|
-
const server = {
|
|
857
|
+
const transport = await resolveTransport(options);
|
|
858
|
+
const server = transport === "http" ? {
|
|
859
|
+
transport: "http",
|
|
860
|
+
url: remoteMcpUrl(options.baseUrl),
|
|
861
|
+
auth: "oauth"
|
|
862
|
+
} : {
|
|
863
|
+
transport: "stdio",
|
|
812
864
|
command: options.nodePath,
|
|
813
|
-
args: [
|
|
865
|
+
args: [await resolveServerPath(options)],
|
|
814
866
|
env: { ROLINO_URL: options.baseUrl }
|
|
815
867
|
};
|
|
816
868
|
return options.client === "codex" ? setupCodex(options, server) : setupClaudeCode(options, server);
|
|
@@ -841,40 +893,40 @@ function outputFormat(value) {
|
|
|
841
893
|
throw new import_commander.InvalidArgumentError("Output must be human, json, or jsonl.");
|
|
842
894
|
}
|
|
843
895
|
function postStatus(value) {
|
|
844
|
-
const parsed =
|
|
896
|
+
const parsed = import_contracts2.PostStatusSchema.safeParse(value.toUpperCase());
|
|
845
897
|
if (parsed.success) return parsed.data;
|
|
846
898
|
throw new import_commander.InvalidArgumentError(
|
|
847
|
-
`Status must be one of ${
|
|
899
|
+
`Status must be one of ${import_contracts2.PostStatusSchema.options.join(", ")}.`
|
|
848
900
|
);
|
|
849
901
|
}
|
|
850
902
|
function seoOpportunityKind(value) {
|
|
851
|
-
const parsed =
|
|
903
|
+
const parsed = import_contracts2.SeoOpportunityKindSchema.safeParse(value.toUpperCase());
|
|
852
904
|
if (parsed.success) return parsed.data;
|
|
853
|
-
throw new import_commander.InvalidArgumentError(`SEO kind must be one of ${
|
|
905
|
+
throw new import_commander.InvalidArgumentError(`SEO kind must be one of ${import_contracts2.SeoOpportunityKindSchema.options.join(", ")}.`);
|
|
854
906
|
}
|
|
855
907
|
function seoExpectedImpact(value) {
|
|
856
|
-
const parsed =
|
|
908
|
+
const parsed = import_contracts2.SeoExpectedImpactSchema.safeParse(value.toUpperCase());
|
|
857
909
|
if (parsed.success) return parsed.data;
|
|
858
910
|
throw new import_commander.InvalidArgumentError("SEO impact must be HIGH, MEDIUM, or LOW.");
|
|
859
911
|
}
|
|
860
912
|
function seoReportCompleteness(value) {
|
|
861
|
-
const parsed =
|
|
913
|
+
const parsed = import_contracts2.SeoReportCompletenessSchema.safeParse(value.toUpperCase());
|
|
862
914
|
if (parsed.success) return parsed.data;
|
|
863
915
|
throw new import_commander.InvalidArgumentError("Report status must be COMPLETE or PARTIAL.");
|
|
864
916
|
}
|
|
865
917
|
function projectType(value) {
|
|
866
|
-
const parsed =
|
|
918
|
+
const parsed = import_contracts2.ProjectTypeSchema.safeParse(value.toUpperCase());
|
|
867
919
|
if (parsed.success) return parsed.data;
|
|
868
920
|
throw new import_commander.InvalidArgumentError(
|
|
869
|
-
`Type must be one of ${
|
|
921
|
+
`Type must be one of ${import_contracts2.ProjectTypeSchema.options.join(", ")}.`
|
|
870
922
|
);
|
|
871
923
|
}
|
|
872
924
|
function publishingProvider(value) {
|
|
873
925
|
const normalized = value.toUpperCase();
|
|
874
|
-
const parsed =
|
|
926
|
+
const parsed = import_contracts2.PublishingProviderSchema.safeParse(normalized);
|
|
875
927
|
if (parsed.success) return parsed.data;
|
|
876
928
|
throw new import_commander.InvalidArgumentError(
|
|
877
|
-
`Platform must be one of ${
|
|
929
|
+
`Platform must be one of ${import_contracts2.PublishingProviderSchema.options.join(", ")}.`
|
|
878
930
|
);
|
|
879
931
|
}
|
|
880
932
|
function collectPublishingProvider(value, previous) {
|
|
@@ -885,10 +937,10 @@ function collectString(value, previous) {
|
|
|
885
937
|
}
|
|
886
938
|
function deliveryOptionsProvider(value) {
|
|
887
939
|
const normalized = value.toUpperCase();
|
|
888
|
-
const parsed =
|
|
940
|
+
const parsed = import_contracts2.ProviderDeliveryOptionsProviderSchema.safeParse(normalized);
|
|
889
941
|
if (parsed.success) return parsed.data;
|
|
890
942
|
throw new import_commander.InvalidArgumentError(
|
|
891
|
-
`Provider must be one of ${
|
|
943
|
+
`Provider must be one of ${import_contracts2.ProviderDeliveryOptionsProviderSchema.options.join(", ")}.`
|
|
892
944
|
);
|
|
893
945
|
}
|
|
894
946
|
function tiktokPostMode(value) {
|
|
@@ -926,7 +978,7 @@ function yesOrNo(value) {
|
|
|
926
978
|
function youtubeSettings(options) {
|
|
927
979
|
const hasYouTubeOptions = options.youtubeTitle !== void 0 || options.youtubeCategoryId !== void 0 || options.youtubePrivacy !== void 0 || options.youtubeMadeForKids !== void 0 || options.youtubeSyntheticMedia === true || options.youtubeNotifySubscribers === false || (options.youtubeTag?.length ?? 0) > 0;
|
|
928
980
|
if (!hasYouTubeOptions) return null;
|
|
929
|
-
return
|
|
981
|
+
return import_contracts2.YouTubePostSettingsSchema.parse({
|
|
930
982
|
title: options.youtubeTitle,
|
|
931
983
|
categoryId: options.youtubeCategoryId,
|
|
932
984
|
privacyStatus: options.youtubePrivacy,
|
|
@@ -951,6 +1003,10 @@ function mcpScope(value) {
|
|
|
951
1003
|
if (value === "user" || value === "project") return value;
|
|
952
1004
|
throw new import_commander.InvalidArgumentError("MCP setup scope must be user or project.");
|
|
953
1005
|
}
|
|
1006
|
+
function mcpTransport(value) {
|
|
1007
|
+
if (value === "auto" || value === "http" || value === "stdio") return value;
|
|
1008
|
+
throw new import_commander.InvalidArgumentError("MCP transport must be auto, http, or stdio.");
|
|
1009
|
+
}
|
|
954
1010
|
function isoDateTime(value) {
|
|
955
1011
|
const date = new Date(value);
|
|
956
1012
|
if (Number.isNaN(date.getTime())) {
|
|
@@ -1011,13 +1067,21 @@ function requireBlogExecutionConsent(options) {
|
|
|
1011
1067
|
throw new TypeError("This Blog execute command requires explicit consent. Review the confirmed operation and pass --yes.");
|
|
1012
1068
|
}
|
|
1013
1069
|
function formatMcpSetupPreview(result) {
|
|
1070
|
+
const connection = result.server.transport === "http" ? [
|
|
1071
|
+
`Transport: Streamable HTTP`,
|
|
1072
|
+
`Endpoint: ${result.server.url}`,
|
|
1073
|
+
"Authentication: OAuth in the MCP client"
|
|
1074
|
+
] : [
|
|
1075
|
+
"Transport: local STDIO",
|
|
1076
|
+
`Command: ${result.server.command}`,
|
|
1077
|
+
`Arguments: ${result.server.args.join(" ")}`,
|
|
1078
|
+
`Rolino URL: ${result.server.env.ROLINO_URL}`
|
|
1079
|
+
];
|
|
1014
1080
|
return [
|
|
1015
1081
|
`Client: ${result.client === "codex" ? "Codex" : "Claude Code"}`,
|
|
1016
1082
|
`Scope: ${result.scope}`,
|
|
1017
1083
|
`Target: ${result.target}`,
|
|
1018
|
-
|
|
1019
|
-
`Arguments: ${result.server.args.join(" ")}`,
|
|
1020
|
-
`Rolino URL: ${result.server.env.ROLINO_URL}`,
|
|
1084
|
+
...connection,
|
|
1021
1085
|
"No token will be written to MCP configuration."
|
|
1022
1086
|
].join("\n");
|
|
1023
1087
|
}
|
|
@@ -1103,7 +1167,7 @@ function errorForOutput(error) {
|
|
|
1103
1167
|
function tiktokSettings(options) {
|
|
1104
1168
|
const hasTikTokOptions = options.tiktokMode !== void 0 || options.tiktokVisibility !== void 0 || options.tiktokComments !== void 0 || options.tiktokDuet !== void 0 || options.tiktokStitch !== void 0 || options.tiktokCommercialContent !== void 0 || options.tiktokPromotesOwnBrand !== void 0 || options.tiktokPromotesThirdParty !== void 0 || options.tiktokAiGenerated !== void 0 || options.tiktokCoverTimestampMs !== void 0 || options.tiktokSettingsReviewed === true;
|
|
1105
1169
|
if (!hasTikTokOptions) return void 0;
|
|
1106
|
-
return
|
|
1170
|
+
return import_contracts2.TikTokDraftSettingsSchema.parse({
|
|
1107
1171
|
postMode: options.tiktokMode,
|
|
1108
1172
|
privacyLevel: options.tiktokVisibility,
|
|
1109
1173
|
allowComment: options.tiktokComments,
|
|
@@ -1247,7 +1311,7 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1247
1311
|
global,
|
|
1248
1312
|
runtime,
|
|
1249
1313
|
async action(context, client) {
|
|
1250
|
-
const parsed =
|
|
1314
|
+
const parsed = import_contracts2.ProjectCreateInputSchema.safeParse({
|
|
1251
1315
|
name: local.name,
|
|
1252
1316
|
type: local.type,
|
|
1253
1317
|
websiteUrl: local.website,
|
|
@@ -1480,7 +1544,7 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1480
1544
|
});
|
|
1481
1545
|
});
|
|
1482
1546
|
const setup = program.command("setup").description("Configure local agent tools for Rolino");
|
|
1483
|
-
setup.command("mcp").description("Configure
|
|
1547
|
+
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
1548
|
const global = program.opts();
|
|
1485
1549
|
commandExitCode = await execute({
|
|
1486
1550
|
command: "setup mcp",
|
|
@@ -1493,7 +1557,8 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1493
1557
|
cwd: runtime.cwd,
|
|
1494
1558
|
env: runtime.env,
|
|
1495
1559
|
nodePath: runtime.nodePath,
|
|
1496
|
-
cliEntryPath: runtime.cliEntryPath
|
|
1560
|
+
cliEntryPath: runtime.cliEntryPath,
|
|
1561
|
+
fetch: runtime.fetch
|
|
1497
1562
|
};
|
|
1498
1563
|
const preview = await setupMcp({ ...setupOptions, dryRun: true });
|
|
1499
1564
|
let result = preview;
|
|
@@ -1520,11 +1585,12 @@ async function runCli(argv = process.argv, overrides = {}) {
|
|
|
1520
1585
|
`${state} Rolino MCP for ${clientLabel}.`,
|
|
1521
1586
|
`Scope: ${result.scope}`,
|
|
1522
1587
|
`Target: ${result.target}`,
|
|
1588
|
+
`Transport: ${result.transport === "http" ? "Streamable HTTP" : "local STDIO"}`,
|
|
1523
1589
|
...result.backupPath && !result.dryRun ? [`Backup: ${result.backupPath}`] : [],
|
|
1524
1590
|
`Rolino URL: ${client.baseUrl}`,
|
|
1525
1591
|
"No token was written to MCP configuration."
|
|
1526
1592
|
].join("\n"),
|
|
1527
|
-
result.client === "codex" ? ["codex mcp
|
|
1593
|
+
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
1594
|
);
|
|
1529
1595
|
}
|
|
1530
1596
|
});
|
|
@@ -2121,7 +2187,7 @@ This moves eligible editorial dates only. It does not schedule, publish, or unpu
|
|
|
2121
2187
|
blogArticles.command("update").argument("<article-id>").requiredOption("--project <project-id>", "exact Rolino project ID").requiredOption("--input <json-file>", "complete Blog draft JSON file").option("--yes", "confirm the draft save").action(async (articleId, local) => {
|
|
2122
2188
|
const global = program.opts();
|
|
2123
2189
|
commandExitCode = await execute({ command: "blog articles update", global, runtime, async action(context, client) {
|
|
2124
|
-
const draft =
|
|
2190
|
+
const draft = import_contracts2.AgentBlogDraftUpdateSchema.parse(JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.resolve)(runtime.cwd, local.input), "utf8")));
|
|
2125
2191
|
await requireWriteConsent({ yes: local.yes, global, runtime, preview: `Save a new immutable Blog draft revision?
|
|
2126
2192
|
Project: ${local.project}
|
|
2127
2193
|
Article: ${articleId}
|
|
@@ -2263,6 +2329,106 @@ Revision: ${local.revision}` });
|
|
|
2263
2329
|
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2264
2330
|
} });
|
|
2265
2331
|
});
|
|
2332
|
+
const backlinks = program.command("backlinks").description("Review backlink prospects and public contact drafts. Rolino never sends email.");
|
|
2333
|
+
const backlinkTargets = backlinks.command("targets");
|
|
2334
|
+
backlinkTargets.command("list").requiredOption("--project <project-id>").action(async (local) => {
|
|
2335
|
+
const global = program.opts();
|
|
2336
|
+
commandExitCode = await execute({ command: "backlinks targets list", global, runtime, async action(context, client) {
|
|
2337
|
+
const data = await client.backlinks.targets.list(local.project, { requestId: context.requestId });
|
|
2338
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2339
|
+
} });
|
|
2340
|
+
});
|
|
2341
|
+
backlinkTargets.command("add").requiredOption("--project <project-id>").requiredOption("--url <url>").requiredOption("--label <label>").action(async (local) => {
|
|
2342
|
+
const global = program.opts();
|
|
2343
|
+
commandExitCode = await execute({ command: "backlinks targets add", global, runtime, async action(context, client) {
|
|
2344
|
+
const data = await client.backlinks.targets.add(local.project, { url: local.url, label: local.label }, context.requestId, { requestId: context.requestId });
|
|
2345
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2346
|
+
} });
|
|
2347
|
+
});
|
|
2348
|
+
backlinks.command("discover").requiredOption("--project <project-id>").option("--limit <number>", "maximum saved prospects", Number, 20).option("--idempotency-key <key>").action(async (local) => {
|
|
2349
|
+
const global = program.opts();
|
|
2350
|
+
commandExitCode = await execute({ command: "backlinks discover", global, runtime, async action(context, client) {
|
|
2351
|
+
const data = await client.backlinks.discoveries.start(local.project, { limit: local.limit }, local.idempotencyKey ?? context.requestId, { requestId: context.requestId });
|
|
2352
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2353
|
+
} });
|
|
2354
|
+
});
|
|
2355
|
+
const backlinkRuns = backlinks.command("runs");
|
|
2356
|
+
backlinkRuns.command("get").requiredOption("--project <project-id>").requiredOption("--run <run-id>").action(async (local) => {
|
|
2357
|
+
const global = program.opts();
|
|
2358
|
+
commandExitCode = await execute({ command: "backlinks runs get", global, runtime, async action(context, client) {
|
|
2359
|
+
const data = await client.backlinks.discoveries.get(local.project, local.run, { requestId: context.requestId });
|
|
2360
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2361
|
+
} });
|
|
2362
|
+
});
|
|
2363
|
+
const backlinkProspects = backlinks.command("prospects");
|
|
2364
|
+
backlinkProspects.command("list").requiredOption("--project <project-id>").option("--stage <stage>").action(async (local) => {
|
|
2365
|
+
const global = program.opts();
|
|
2366
|
+
commandExitCode = await execute({ command: "backlinks prospects list", global, runtime, async action(context, client) {
|
|
2367
|
+
const stage = local.stage ? import_contracts2.BacklinkProspectStageSchema.parse(local.stage.toUpperCase()) : void 0;
|
|
2368
|
+
const data = await client.backlinks.prospects.list(local.project, { stage }, { requestId: context.requestId });
|
|
2369
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2370
|
+
} });
|
|
2371
|
+
});
|
|
2372
|
+
backlinkProspects.command("get").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
|
|
2373
|
+
const global = program.opts();
|
|
2374
|
+
commandExitCode = await execute({ command: "backlinks prospects get", global, runtime, async action(context, client) {
|
|
2375
|
+
const data = await client.backlinks.prospects.get(local.project, local.prospect, { requestId: context.requestId });
|
|
2376
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2377
|
+
} });
|
|
2378
|
+
});
|
|
2379
|
+
backlinkProspects.command("approve").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").requiredOption("--expected-version <number>", "current optimistic version", Number).action(async (local) => {
|
|
2380
|
+
const global = program.opts();
|
|
2381
|
+
commandExitCode = await execute({ command: "backlinks prospects approve", global, runtime, async action(context, client) {
|
|
2382
|
+
const data = await client.backlinks.prospects.updateStage(local.project, local.prospect, { stage: "APPROVED", expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
|
|
2383
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2384
|
+
} });
|
|
2385
|
+
});
|
|
2386
|
+
const backlinkContacts = backlinks.command("contacts");
|
|
2387
|
+
backlinkContacts.command("research").requiredOption("--project <project-id>").requiredOption("--prospect <prospect-id>").action(async (local) => {
|
|
2388
|
+
const global = program.opts();
|
|
2389
|
+
commandExitCode = await execute({ command: "backlinks contacts research", global, runtime, async action(context, client) {
|
|
2390
|
+
const data = await client.backlinks.prospects.researchContact(local.project, local.prospect, context.requestId, { requestId: context.requestId });
|
|
2391
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2392
|
+
} });
|
|
2393
|
+
});
|
|
2394
|
+
backlinkContacts.command("list").requiredOption("--project <project-id>").action(async (local) => {
|
|
2395
|
+
const global = program.opts();
|
|
2396
|
+
commandExitCode = await execute({ command: "backlinks contacts list", global, runtime, async action(context, client) {
|
|
2397
|
+
const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
|
|
2398
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2399
|
+
} });
|
|
2400
|
+
});
|
|
2401
|
+
backlinkContacts.command("export").requiredOption("--project <project-id>").option("--format <format>", "json or csv", "csv").action(async (local) => {
|
|
2402
|
+
const global = program.opts();
|
|
2403
|
+
commandExitCode = await execute({ command: "backlinks contacts export", global, runtime, async action(context, client) {
|
|
2404
|
+
const data = await client.backlinks.contacts.list(local.project, { limit: 50 }, { requestId: context.requestId });
|
|
2405
|
+
if (local.format !== "csv") return writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2406
|
+
const cell = (value) => {
|
|
2407
|
+
let text = String(value ?? "").replace(/[\r\n]+/g, " ");
|
|
2408
|
+
if (/^[=+\-@\t]/.test(text)) text = `'${text}`;
|
|
2409
|
+
return `"${text.replaceAll('"', '""')}"`;
|
|
2410
|
+
};
|
|
2411
|
+
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");
|
|
2412
|
+
context.stdout.write(`${csv}
|
|
2413
|
+
`);
|
|
2414
|
+
} });
|
|
2415
|
+
});
|
|
2416
|
+
const backlinkOutreach = backlinks.command("outreach");
|
|
2417
|
+
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) => {
|
|
2418
|
+
const global = program.opts();
|
|
2419
|
+
commandExitCode = await execute({ command: "backlinks outreach update", global, runtime, async action(context, client) {
|
|
2420
|
+
const payload = JSON.parse(await (0, import_promises2.readFile)((0, import_node_path2.resolve)(runtime.cwd, local.file), "utf8"));
|
|
2421
|
+
const data = await client.backlinks.prospects.updateOutreach(local.project, local.prospect, { ...payload, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
|
|
2422
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2423
|
+
} });
|
|
2424
|
+
});
|
|
2425
|
+
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) => {
|
|
2426
|
+
const global = program.opts();
|
|
2427
|
+
commandExitCode = await execute({ command: "backlinks verify", global, runtime, async action(context, client) {
|
|
2428
|
+
const data = await client.backlinks.prospects.verify(local.project, local.prospect, { url: local.url, expectedVersion: local.expectedVersion }, context.requestId, { requestId: context.requestId });
|
|
2429
|
+
writeSuccess(context, data, JSON.stringify(data, null, 2));
|
|
2430
|
+
} });
|
|
2431
|
+
});
|
|
2266
2432
|
const seo = program.command("seo").description("Read authorized SEO opportunities and weekly reports");
|
|
2267
2433
|
const seoOpportunities = seo.command("opportunities").description("Read accepted SEO opportunities");
|
|
2268
2434
|
seoOpportunities.command("list").description("List bounded SEO opportunities").requiredOption("--project <project-id>", "exact Rolino project ID").option("--limit <number>", "maximum opportunities to return", (value) => {
|