@kodiak-finance/orderly-devkit 1.0.2

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.
@@ -0,0 +1,147 @@
1
+ const crypto = require("crypto");
2
+ const { exec } = require("child_process");
3
+ const { heading, info, success, warn, error } = require("../shared");
4
+ const { saveOAuthToken, isLoggedIn } = require("../internal/auth");
5
+ const { startCallbackServer } = require("../internal/login-server");
6
+ const {
7
+ CLI_CALLBACK_PORT,
8
+ CLI_LOGIN_TIMEOUT_MS,
9
+ MARKETPLACE_WEB_LOGIN_URL,
10
+ } = require("../internal/constants");
11
+
12
+ function openBrowser(url) {
13
+ const platform = process.platform;
14
+ let command;
15
+ if (platform === "darwin") {
16
+ command = `open "${url}"`;
17
+ } else if (platform === "win32") {
18
+ command = `start "" "${url}"`;
19
+ } else {
20
+ command = `xdg-open "${url}"`;
21
+ }
22
+ return new Promise((resolve, reject) => {
23
+ exec(command, (err) => {
24
+ if (err) reject(err);
25
+ else resolve();
26
+ });
27
+ });
28
+ }
29
+
30
+ module.exports = {
31
+ command: "login",
32
+ describe: "Login to Orderly Marketplace via GitHub",
33
+ builder: (yargs) => {
34
+ return yargs
35
+ .option("port", {
36
+ alias: "p",
37
+ type: "number",
38
+ describe:
39
+ "number; local callback server port used for the OAuth redirect (must be available). Default is the CLI_CALLBACK_PORT constant",
40
+ default: CLI_CALLBACK_PORT,
41
+ })
42
+ .option("force", {
43
+ alias: "f",
44
+ type: "boolean",
45
+ describe:
46
+ "boolean; if true, force re-login even if you're already logged in",
47
+ default: false,
48
+ })
49
+ .example("orderly login", "Open the browser and complete GitHub OAuth")
50
+ .example(
51
+ "orderly login --force --port 9877",
52
+ "Re-authenticate and use a custom callback port",
53
+ );
54
+ },
55
+ handler: async (argv) => {
56
+ heading("Login to Orderly Marketplace");
57
+ info("This will open your browser to authenticate via GitHub.\n");
58
+
59
+ if (isLoggedIn() && !argv.force) {
60
+ warn("You are already logged in.");
61
+ info("Use 'orderly login --force' to re-authenticate.");
62
+ return;
63
+ }
64
+
65
+ // Generate CSRF state
66
+ const state = crypto.randomBytes(16).toString("hex");
67
+ const port = argv.port;
68
+ const browserUrl = `${MARKETPLACE_WEB_LOGIN_URL}?port=${port}&state=${state}`;
69
+
70
+ // Start local callback server
71
+ const { server, waitForToken } = startCallbackServer({ port, state });
72
+
73
+ // Handle server errors (e.g. port in use)
74
+ const serverReady = new Promise((resolve, reject) => {
75
+ server.on("error", (err) => {
76
+ if (err.code === "EADDRINUSE") {
77
+ reject(
78
+ new Error(
79
+ `Port ${port} is already in use. Use --port to specify a different port.`,
80
+ ),
81
+ );
82
+ } else {
83
+ reject(err);
84
+ }
85
+ });
86
+ server.on("listening", resolve);
87
+ });
88
+
89
+ try {
90
+ server.listen(port);
91
+ await serverReady;
92
+ } catch (err) {
93
+ error(err.message);
94
+ server.close();
95
+ return;
96
+ }
97
+
98
+ // Open browser
99
+ info("Opening browser...");
100
+ info(` ${browserUrl}\n`);
101
+ try {
102
+ await openBrowser(browserUrl);
103
+ } catch (e) {
104
+ info("Could not open browser automatically.");
105
+ info("Please open the URL above in your browser manually.\n");
106
+ }
107
+
108
+ info("Waiting for authentication...");
109
+
110
+ // Timeout
111
+ let timedOut = false;
112
+ const timeout = setTimeout(() => {
113
+ timedOut = true;
114
+ error("Login timed out after 3 minutes.");
115
+ }, CLI_LOGIN_TIMEOUT_MS);
116
+
117
+ // Handle Ctrl+C during login
118
+ const onSigInt = () => {
119
+ info("\nLogin cancelled.");
120
+ clearTimeout(timeout);
121
+ server.close();
122
+ process.exit(1);
123
+ };
124
+ process.on("SIGINT", onSigInt);
125
+
126
+ try {
127
+ const tokenData = await waitForToken;
128
+ clearTimeout(timeout);
129
+ process.removeListener("SIGINT", onSigInt);
130
+
131
+ saveOAuthToken(tokenData);
132
+
133
+ success("Login successful!");
134
+ const displayName = tokenData.email || tokenData.username;
135
+ if (displayName) {
136
+ info(`Logged in as: ${displayName}`);
137
+ }
138
+ info("Use 'orderly-devkit whoami' to verify your account.");
139
+ } catch (err) {
140
+ if (!timedOut) {
141
+ error(`Login failed: ${err.message}`);
142
+ }
143
+ } finally {
144
+ server.close();
145
+ }
146
+ },
147
+ };
@@ -0,0 +1,22 @@
1
+ const { heading, info, success, warn } = require("../shared");
2
+ const { isLoggedIn, logout } = require("../internal/auth");
3
+
4
+ module.exports = {
5
+ command: "logout",
6
+ describe: "Logout from Orderly Marketplace",
7
+ handler: async () => {
8
+ heading("Logout from Orderly Marketplace");
9
+
10
+ if (!isLoggedIn()) {
11
+ warn("You are not logged in.");
12
+ return;
13
+ }
14
+
15
+ info("This command will clear your stored authentication.\n");
16
+
17
+ logout();
18
+
19
+ success("Logout successful!");
20
+ console.log("Your session has been cleared.");
21
+ },
22
+ };
@@ -0,0 +1,128 @@
1
+ const chalk = require("chalk");
2
+ const { heading, info, dim } = require("../../shared");
3
+ const {
4
+ ALL_CLIENTS,
5
+ getOrderlySdkDocsMcpReport,
6
+ } = require("../../internal/orderlySdkDocsMcpDetect");
7
+
8
+ /**
9
+ * @param {string} clientArg
10
+ * @returns {string[]}
11
+ */
12
+ function parseClients(clientArg) {
13
+ const raw = String(clientArg || "all").trim();
14
+ if (raw === "all") {
15
+ return [...ALL_CLIENTS];
16
+ }
17
+ const parts = raw
18
+ .split(",")
19
+ .map((x) => x.trim())
20
+ .filter(Boolean);
21
+ for (const p of parts) {
22
+ if (!ALL_CLIENTS.includes(p)) {
23
+ throw new Error(
24
+ `Invalid --client value: ${p}. Use: ${ALL_CLIENTS.join(", ")}, or all (comma-separated).`,
25
+ );
26
+ }
27
+ }
28
+ return parts;
29
+ }
30
+
31
+ /**
32
+ * @param {{ cwd: string; defaultServerName: string; clients: Record<string, { user: { configured: boolean }; project: { configured: boolean } }> }} report
33
+ */
34
+ function printHumanReport(report) {
35
+ heading("Orderly SDK Docs MCP — detect");
36
+ info(`Working directory: ${report.cwd}`);
37
+ info(`Default server name: ${report.defaultServerName}\n`);
38
+
39
+ for (const client of Object.keys(report.clients)) {
40
+ const row = report.clients[client];
41
+ console.log(chalk.bold.cyan(client));
42
+ for (const scope of /** @type {const} */ (["user", "project"])) {
43
+ const cell = row[scope];
44
+ const scopeLabel = scope === "user" ? "user" : "proj";
45
+ const pathLine = chalk.dim(cell.configPath);
46
+ if (cell.error) {
47
+ console.log(
48
+ ` [${scopeLabel}] ${chalk.red("invalid JSON")} — ${cell.error}`,
49
+ );
50
+ console.log(` ${pathLine}`);
51
+ continue;
52
+ }
53
+ if (!cell.exists) {
54
+ console.log(` [${scopeLabel}] ${chalk.yellow("no file")}`);
55
+ console.log(` ${pathLine}`);
56
+ continue;
57
+ }
58
+ if (cell.configured) {
59
+ const key = cell.serverKey ? ` (${cell.serverKey})` : "";
60
+ console.log(` [${scopeLabel}] ${chalk.green("configured")}${key}`);
61
+ } else {
62
+ console.log(` [${scopeLabel}] ${chalk.yellow("not detected")}`);
63
+ }
64
+ console.log(` ${pathLine}`);
65
+ }
66
+ console.log();
67
+ }
68
+
69
+ const anyConfigured = Object.values(report.clients).some(
70
+ (row) => row.user.configured || row.project.configured,
71
+ );
72
+ if (!anyConfigured) {
73
+ dim("Orderly SDK Docs MCP was not found in any checked config.");
74
+ info("Install MCP for your agent(s):");
75
+ info(" orderly-devkit mcp install");
76
+ info(" orderly-devkit mcp install --client cursor --scope project");
77
+ info("Install plugin workflow skills for your coding agent:");
78
+ info(" orderly-devkit skills install");
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ command: "detect",
84
+ describe:
85
+ "Detect whether Orderly SDK Docs MCP is present in agent config files (user + project scope)",
86
+ builder: (yargs) => {
87
+ return yargs
88
+ .option("client", {
89
+ type: "string",
90
+ describe:
91
+ "claude|codex|cursor|opencode|all, or comma-separated list (default: all)",
92
+ default: "all",
93
+ })
94
+ .option("json", {
95
+ type: "boolean",
96
+ default: false,
97
+ describe: "Print machine-readable JSON to stdout",
98
+ })
99
+ .example(
100
+ "orderly-devkit mcp detect",
101
+ "Scan all supported agents (user + project configs for cwd)",
102
+ )
103
+ .example(
104
+ "orderly-devkit mcp detect --client cursor",
105
+ "Only inspect Cursor mcp.json paths",
106
+ )
107
+ .example("orderly-devkit mcp detect --json", "JSON report for scripting");
108
+ },
109
+ handler: async (argv) => {
110
+ let clients;
111
+ try {
112
+ clients = parseClients(argv.client);
113
+ } catch (e) {
114
+ console.error(chalk.red(e instanceof Error ? e.message : String(e)));
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+
119
+ const report = getOrderlySdkDocsMcpReport(process.cwd(), { clients });
120
+
121
+ if (argv.json) {
122
+ console.log(JSON.stringify(report, null, 2));
123
+ return;
124
+ }
125
+
126
+ printHumanReport(report);
127
+ },
128
+ };
@@ -0,0 +1,122 @@
1
+ const { spawnSync } = require("child_process");
2
+ const chalk = require("chalk");
3
+
4
+ /** stderr-only; set ORDERLY_MCP_INSTALL_DEBUG=1 to trace spawn failures / hangs */
5
+ function debugMcpInstall(...parts) {
6
+ if (
7
+ process.env.ORDERLY_MCP_INSTALL_DEBUG !== "1" &&
8
+ process.env.ORDERLY_MCP_INSTALL_DEBUG !== "true"
9
+ ) {
10
+ return;
11
+ }
12
+ console.error(chalk.dim("[orderly-devkit mcp install:debug]"), ...parts);
13
+ }
14
+
15
+ /**
16
+ * Forward MCP install to sdk-docs so install ownership stays centralized.
17
+ * Uses an explicit bin invocation to avoid npx argument parsing ambiguity
18
+ * across npm versions and caches.
19
+ */
20
+ function forwardToSdkDocs(argv) {
21
+ console.log(
22
+ chalk.cyan("orderly-devkit:") +
23
+ " Installing Orderly SDK Docs MCP config via npx…",
24
+ );
25
+ console.log(
26
+ chalk.dim("First run may download @orderly.network/sdk-docs; please wait."),
27
+ );
28
+
29
+ /** Pinned package spec for npx -y <spec> … (must match sdk-docs install merge). */
30
+ const rawSdkVer = argv.sdkDocsVersion ?? argv["sdk-docs-version"];
31
+ const ver =
32
+ rawSdkVer && String(rawSdkVer).trim() ? String(rawSdkVer).trim() : "";
33
+ const pkgSpec = ver
34
+ ? `@orderly.network/sdk-docs@${ver}`
35
+ : "@orderly.network/sdk-docs";
36
+ if (ver) {
37
+ console.log(chalk.dim(`Using package: ${pkgSpec}`));
38
+ }
39
+
40
+ const command = "npx";
41
+ const args = ["-y", pkgSpec, "orderly-sdk-docs-mcp", "install"];
42
+
43
+ if (argv.client) args.push("--client", String(argv.client));
44
+ if (argv.scope) args.push("--scope", String(argv.scope));
45
+ if (argv.name) args.push("--name", String(argv.name));
46
+ if (ver) args.push("--sdk-docs-version", ver);
47
+ if (argv["dry-run"]) args.push("--dry-run");
48
+ if (argv.force) args.push("--force");
49
+
50
+ debugMcpInstall("cwd", process.cwd());
51
+ debugMcpInstall("spawn", command, args);
52
+
53
+ const result = spawnSync(command, args, {
54
+ stdio: "inherit",
55
+ shell: process.platform === "win32",
56
+ });
57
+
58
+ debugMcpInstall(
59
+ "spawnSync done",
60
+ "status",
61
+ result.status,
62
+ "signal",
63
+ result.signal,
64
+ "error",
65
+ result.error,
66
+ );
67
+
68
+ if (result.error) {
69
+ throw result.error;
70
+ }
71
+ return result.status ?? 1;
72
+ }
73
+
74
+ module.exports = {
75
+ command: "install",
76
+ describe:
77
+ "Install Orderly SDK Docs MCP config for Claude/Codex/Cursor/OpenCode",
78
+ builder: (yargs) => {
79
+ return yargs
80
+ .option("client", {
81
+ type: "string",
82
+ describe:
83
+ "Target client(s): claude|codex|cursor|opencode|all (comma-separated supported)",
84
+ default: "all",
85
+ })
86
+ .option("scope", {
87
+ type: "string",
88
+ choices: ["user", "project"],
89
+ default: "user",
90
+ })
91
+ .option("name", {
92
+ type: "string",
93
+ describe: "MCP server name in config mcpServers map",
94
+ default: "orderly-sdk-docs",
95
+ })
96
+ .option("dry-run", {
97
+ type: "boolean",
98
+ default: false,
99
+ })
100
+ .option("force", {
101
+ type: "boolean",
102
+ default: false,
103
+ })
104
+ .option("sdk-docs-version", {
105
+ type: "string",
106
+ describe:
107
+ "Pin @orderly.network/sdk-docs (semver or dist-tag, e.g. 0.1.0 or beta); applies to this run and merged MCP config",
108
+ })
109
+ .example(
110
+ "orderly-devkit mcp install --client cursor --scope project",
111
+ "Install MCP entry for Cursor in project scope",
112
+ )
113
+ .example(
114
+ "orderly-devkit mcp install --sdk-docs-version 0.1.0 --scope user",
115
+ "Pin a specific sdk-docs release for npx and editor MCP configs",
116
+ );
117
+ },
118
+ handler: async (argv) => {
119
+ const status = forwardToSdkDocs(argv);
120
+ process.exitCode = status;
121
+ },
122
+ };
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ command: "mcp <subcommand>",
3
+ describe: "MCP related commands",
4
+ builder: (yargs) => {
5
+ return yargs
6
+ .commandDir("mcp")
7
+ .demandCommand(1, "Please provide an MCP subcommand.");
8
+ },
9
+ };
@@ -0,0 +1,211 @@
1
+ const { spawnSync } = require("child_process");
2
+ const chalk = require("chalk");
3
+ const {
4
+ ORDERLY_SKILLS_REPO,
5
+ ORDERLY_PLUGIN_SKILL_NAMES,
6
+ } = require("../../internal/constants");
7
+
8
+ /**
9
+ * Extra argv after `--` is forwarded verbatim to `skills add` (upstream flags).
10
+ * @returns {string[]}
11
+ */
12
+ function getPassthroughArgs() {
13
+ const idx = process.argv.indexOf("--");
14
+ if (idx === -1) {
15
+ return [];
16
+ }
17
+ return process.argv.slice(idx + 1);
18
+ }
19
+
20
+ /**
21
+ * @param {unknown} value
22
+ * @returns {string[]}
23
+ */
24
+ function asStringArray(value) {
25
+ if (value == null) {
26
+ return [];
27
+ }
28
+ if (Array.isArray(value)) {
29
+ return value.map(String).filter(Boolean);
30
+ }
31
+ return [String(value)];
32
+ }
33
+
34
+ /**
35
+ * Build argv for: npx -y skills add …
36
+ * @param {Record<string, unknown>} argv
37
+ * @returns {string[]}
38
+ */
39
+ function buildSkillsAddArgs(argv) {
40
+ const source =
41
+ typeof argv.source === "string" && argv.source.trim() !== ""
42
+ ? argv.source.trim()
43
+ : ORDERLY_SKILLS_REPO;
44
+
45
+ const args = [source];
46
+ const passthrough = getPassthroughArgs();
47
+
48
+ if (argv.list) {
49
+ args.push("--list");
50
+ if (argv.yes === true) {
51
+ args.push("-y");
52
+ }
53
+ appendSharedTail(args, argv);
54
+ args.push(...passthrough);
55
+ return args;
56
+ }
57
+
58
+ if (argv.all === true) {
59
+ args.push("--all");
60
+ appendSharedTail(args, argv);
61
+ args.push(...passthrough);
62
+ return args;
63
+ }
64
+
65
+ const userSkills = asStringArray(argv.skill);
66
+ if (userSkills.length > 0) {
67
+ for (const name of userSkills) {
68
+ args.push("--skill", name);
69
+ }
70
+ } else {
71
+ for (const name of ORDERLY_PLUGIN_SKILL_NAMES) {
72
+ args.push("--skill", name);
73
+ }
74
+ }
75
+ args.push("-y");
76
+
77
+ appendSharedTail(args, argv);
78
+ args.push(...passthrough);
79
+ return args;
80
+ }
81
+
82
+ /**
83
+ * @param {string[]} args
84
+ * @param {Record<string, unknown>} argv
85
+ */
86
+ function appendSharedTail(args, argv) {
87
+ if (argv.global === true) {
88
+ args.push("-g");
89
+ }
90
+ const agents = asStringArray(argv.agent);
91
+ for (const a of agents) {
92
+ args.push("-a", a);
93
+ }
94
+ if (argv.copy === true) {
95
+ args.push("--copy");
96
+ }
97
+ }
98
+
99
+ /**
100
+ * @param {string[]} skillsAddArgs
101
+ * @returns {string[]}
102
+ */
103
+ function buildNpxArgv(skillsAddArgs) {
104
+ return ["-y", "skills", "add", ...skillsAddArgs];
105
+ }
106
+
107
+ /**
108
+ * @param {string[]} argv
109
+ */
110
+ function printDryRun(argv) {
111
+ const line = [
112
+ "npx",
113
+ ...argv.map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)),
114
+ ].join(" ");
115
+ console.log(chalk.cyan("Dry run — would execute:"));
116
+ console.log(line);
117
+ }
118
+
119
+ module.exports = {
120
+ command: "install [source]",
121
+ describe:
122
+ "Install Orderly plugin workflow agent skills (defaults: official repo + four skills + -y)",
123
+ builder: (yargs) => {
124
+ return yargs
125
+ .positional("source", {
126
+ type: "string",
127
+ describe:
128
+ "GitHub owner/repo, URL, or local path (default: OrderlyNetwork/orderly-skills)",
129
+ })
130
+ .option("list", {
131
+ type: "boolean",
132
+ default: false,
133
+ describe:
134
+ "List skills in the source repo without installing (no default --skill/-y)",
135
+ })
136
+ .option("skill", {
137
+ alias: "s",
138
+ type: "array",
139
+ describe:
140
+ "Install only these skill names (replaces default four when set; not used with --list)",
141
+ })
142
+ .option("all", {
143
+ type: "boolean",
144
+ default: false,
145
+ describe:
146
+ "Forward --all to skills CLI (install all skills/agents; skips default four + auto -y)",
147
+ })
148
+ .option("global", {
149
+ alias: "g",
150
+ type: "boolean",
151
+ default: false,
152
+ describe: "Install globally (forward -g to skills CLI)",
153
+ })
154
+ .option("agent", {
155
+ alias: "a",
156
+ type: "array",
157
+ describe: "Target agent(s), e.g. -a cursor -a claude-code",
158
+ })
159
+ .option("copy", {
160
+ type: "boolean",
161
+ default: false,
162
+ describe: "Copy files instead of symlinks (--copy)",
163
+ })
164
+ .option("yes", {
165
+ alias: "y",
166
+ type: "boolean",
167
+ describe:
168
+ "With --list only: also pass -y to skills CLI (default install already uses -y)",
169
+ })
170
+ .option("dry-run", {
171
+ type: "boolean",
172
+ default: false,
173
+ describe: "Print the npx command without running it",
174
+ })
175
+ .example(
176
+ "orderly-devkit skills install",
177
+ "Install all four Orderly plugin skills from the official repo (-y, non-interactive)",
178
+ )
179
+ .example(
180
+ "orderly-devkit skills install --list",
181
+ "List available skills in the official repo",
182
+ )
183
+ .example(
184
+ "orderly-devkit skills install other/repo --skill my-skill -y",
185
+ "Advanced: different repo and explicit skills (default four skipped)",
186
+ )
187
+ .example(
188
+ "orderly-devkit skills install -- --some-new-skills-flag",
189
+ "Forward extra flags after -- to the skills CLI",
190
+ );
191
+ },
192
+ handler: async (argv) => {
193
+ const skillsAddArgs = buildSkillsAddArgs(argv);
194
+ const npxArgs = buildNpxArgv(skillsAddArgs);
195
+
196
+ if (argv["dry-run"]) {
197
+ printDryRun(npxArgs);
198
+ return;
199
+ }
200
+
201
+ const result = spawnSync("npx", npxArgs, {
202
+ stdio: "inherit",
203
+ shell: process.platform === "win32",
204
+ });
205
+
206
+ if (result.error) {
207
+ throw result.error;
208
+ }
209
+ process.exitCode = result.status ?? 1;
210
+ },
211
+ };
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ command: "skills <subcommand>",
3
+ describe:
4
+ "Install Orderly agent skills for plugin workflows (create, write, add, submit)",
5
+ builder: (yargs) => {
6
+ return yargs
7
+ .commandDir("skills")
8
+ .demandCommand(1, "Please provide a skills subcommand.");
9
+ },
10
+ };