create-nudge-ui 0.2.0 → 0.2.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.
- package/README.md +44 -1
- package/dist/agent-setup.d.ts +39 -0
- package/dist/agent-setup.js +124 -0
- package/dist/agent-verification.d.ts +3 -0
- package/dist/agent-verification.js +21 -0
- package/dist/arguments.d.ts +6 -1
- package/dist/arguments.js +42 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +177 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# `create-nudge-ui`
|
|
2
2
|
|
|
3
3
|
`create-nudge-ui` detects an application's host framework, installs the
|
|
4
|
-
corresponding Nudge UI adapter, and updates the host configuration.
|
|
4
|
+
corresponding Nudge UI adapter, and updates the host configuration. The guided
|
|
5
|
+
setup can also connect a coding agent to Nudge.
|
|
5
6
|
|
|
6
7
|
Run the initializer from the application root:
|
|
7
8
|
|
|
@@ -13,6 +14,16 @@ The initializer supports Next.js, Astro, Vite with React, and static HTML. Host
|
|
|
13
14
|
frameworks take precedence over their underlying tools, so an Astro project
|
|
14
15
|
with React islands receives `nudge-ui/astro`, not `nudge-ui/vite`.
|
|
15
16
|
|
|
17
|
+
In an interactive terminal, the initializer asks whether to connect a coding
|
|
18
|
+
agent. It detects installed agents, installs the matching `@nudge-ui/mcp`
|
|
19
|
+
version in the project, and installs a versioned reusable adapter under
|
|
20
|
+
`~/.nudge-ui/adapters/`. The adapter is independent of the application and
|
|
21
|
+
does not contain a project path, so different Git worktrees can use the same
|
|
22
|
+
global MCP registration. Existing project-scoped Nudge entries are backed up
|
|
23
|
+
and removed when the host supports that migration.
|
|
24
|
+
|
|
25
|
+
Fully restart the agent host, then ask it to “listen to Nudge.”
|
|
26
|
+
|
|
16
27
|
Use an explicit framework when automatic detection is ambiguous:
|
|
17
28
|
|
|
18
29
|
```sh
|
|
@@ -22,3 +33,35 @@ npm create nudge-ui@latest -- --framework astro
|
|
|
22
33
|
Use `--dry-run` to print the package installation and configuration change
|
|
23
34
|
without modifying the project. Run `npm create nudge-ui@latest -- --help` for
|
|
24
35
|
the complete command reference.
|
|
36
|
+
|
|
37
|
+
For scripts, select the MCP behavior explicitly:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
# Configure detected agents without prompts.
|
|
41
|
+
npm create nudge-ui@latest -- --mcp --yes
|
|
42
|
+
|
|
43
|
+
# Configure one or more supported add-mcp agent IDs.
|
|
44
|
+
npm create nudge-ui@latest -- --mcp --agent codex --agent cursor --yes
|
|
45
|
+
|
|
46
|
+
# Skip agent setup.
|
|
47
|
+
npm create nudge-ui@latest -- --no-mcp
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use agent-only mode to add or repair the integration without changing the host
|
|
51
|
+
framework configuration:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
npm create nudge-ui@latest -- --agent-only --agent codex --yes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For local MCP package testing, pass a tarball with `--mcp-package`. Setup uses
|
|
58
|
+
that artifact for both the project bridge and reusable adapter:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npx nudge-ui agent setup --mcp-package /absolute/path/to/nudge-ui-mcp.tgz
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Agent configuration is best-effort. If an agent is not supported or its native
|
|
65
|
+
configuration cannot be updated, the initializer prints the complete stdio
|
|
66
|
+
configuration to add to that agent's project settings. Other MCP servers and
|
|
67
|
+
agent settings are preserved on every run.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type AgentType, type InstallResult, type McpServerConfig } from "add-mcp";
|
|
2
|
+
import { type InstallCommand } from "./installer.js";
|
|
3
|
+
import type { PackageManager } from "./types.js";
|
|
4
|
+
export declare const nudgeServerName = "nudge_ui";
|
|
5
|
+
export interface AgentSetupPlan {
|
|
6
|
+
readonly projectRoot: string;
|
|
7
|
+
readonly packageSpecifier: string;
|
|
8
|
+
readonly installCommand: InstallCommand;
|
|
9
|
+
readonly adapterRoot: string;
|
|
10
|
+
readonly adapterInstallCommand: InstallCommand;
|
|
11
|
+
readonly serverConfig: McpServerConfig;
|
|
12
|
+
readonly serverEntry: string;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentInstallOutcome {
|
|
15
|
+
readonly agent: string;
|
|
16
|
+
readonly displayName: string;
|
|
17
|
+
readonly result?: InstallResult;
|
|
18
|
+
readonly unsupported: boolean;
|
|
19
|
+
readonly migrationError?: string;
|
|
20
|
+
readonly backups?: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
/** Plans the project bridge dependency and an independent, versioned adapter installation. */
|
|
23
|
+
export declare function planAgentSetup(projectRoot: string, packageManager: PackageManager, nudgeUiVersion?: string, adapterDirectory?: string, mcpPackageSpecifier?: string): AgentSetupPlan;
|
|
24
|
+
/** Checks that a node_modules based install produced the configured executable. */
|
|
25
|
+
export declare function isAgentServerAvailable(plan: AgentSetupPlan): boolean;
|
|
26
|
+
/** Returns agents that support a reusable stdio MCP configuration. */
|
|
27
|
+
export declare function projectAgentTypes(): AgentType[];
|
|
28
|
+
/** Detects installed or already configured agents. */
|
|
29
|
+
export declare function detectProjectAgentCandidates(projectRoot: string): Promise<AgentType[]>;
|
|
30
|
+
/** Registers the reusable adapter and removes the current project's overriding entry. */
|
|
31
|
+
export declare function configureProjectAgents(plan: AgentSetupPlan, selectedAgents: readonly string[]): Promise<AgentInstallOutcome[]>;
|
|
32
|
+
/** Formats a complete stdio entry for agents that add-mcp cannot configure. */
|
|
33
|
+
export declare function manualAgentConfiguration(plan: AgentSetupPlan): string;
|
|
34
|
+
export declare function projectAgentChoices(detected: readonly AgentType[]): Array<{
|
|
35
|
+
readonly id: AgentType;
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly detected: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
export declare function initializerVersion(): string;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { copyFileSync, existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { agents, detectGlobalAgents, detectProjectAgents, getAgentTypes, upsertServer, listInstalledServers, removeServer, } from "add-mcp";
|
|
6
|
+
import { installCommand } from "./installer.js";
|
|
7
|
+
export const nudgeServerName = "nudge_ui";
|
|
8
|
+
/** Plans the project bridge dependency and an independent, versioned adapter installation. */
|
|
9
|
+
export function planAgentSetup(projectRoot, packageManager, nudgeUiVersion, adapterDirectory = join(homedir(), ".nudge-ui", "adapters"), mcpPackageSpecifier) {
|
|
10
|
+
const canonicalRoot = realpathSync.native(projectRoot);
|
|
11
|
+
const packageSpecifier = mcpPackageSpecifier ?? `@nudge-ui/mcp@${nudgeUiVersion ?? installedNudgeUiVersion(canonicalRoot) ?? initializerVersion()}`;
|
|
12
|
+
const adapterPackageSpecifier = mcpPackageSpecifier ?? `@nudge-ui/mcp@${initializerVersion()}`;
|
|
13
|
+
const adapterRoot = join(adapterDirectory, initializerVersion());
|
|
14
|
+
const serverEntry = join(adapterRoot, "node_modules", "@nudge-ui", "mcp", "dist", "cli.mjs");
|
|
15
|
+
const serverConfig = { command: process.execPath, args: [serverEntry] };
|
|
16
|
+
return {
|
|
17
|
+
projectRoot: canonicalRoot,
|
|
18
|
+
packageSpecifier,
|
|
19
|
+
installCommand: installCommand(packageManager, packageSpecifier),
|
|
20
|
+
serverConfig,
|
|
21
|
+
adapterRoot,
|
|
22
|
+
adapterInstallCommand: {
|
|
23
|
+
executable: "npm",
|
|
24
|
+
args: ["install", "--prefix", adapterRoot, "--no-save", "--package-lock=false", adapterPackageSpecifier],
|
|
25
|
+
},
|
|
26
|
+
serverEntry,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Checks that a node_modules based install produced the configured executable. */
|
|
30
|
+
export function isAgentServerAvailable(plan) {
|
|
31
|
+
return existsSync(plan.serverEntry);
|
|
32
|
+
}
|
|
33
|
+
/** Returns agents that support a reusable stdio MCP configuration. */
|
|
34
|
+
export function projectAgentTypes() {
|
|
35
|
+
return getAgentTypes().filter((agent) => agents[agent].supportedTransports.includes("stdio"));
|
|
36
|
+
}
|
|
37
|
+
/** Detects installed or already configured agents. */
|
|
38
|
+
export async function detectProjectAgentCandidates(projectRoot) {
|
|
39
|
+
const supported = new Set(projectAgentTypes());
|
|
40
|
+
const detected = [
|
|
41
|
+
...detectProjectAgents(projectRoot),
|
|
42
|
+
...await detectGlobalAgents(),
|
|
43
|
+
].filter((agent) => supported.has(agent));
|
|
44
|
+
return [...new Set(detected)];
|
|
45
|
+
}
|
|
46
|
+
/** Registers the reusable adapter and removes the current project's overriding entry. */
|
|
47
|
+
export async function configureProjectAgents(plan, selectedAgents) {
|
|
48
|
+
const supported = new Set(projectAgentTypes());
|
|
49
|
+
const outcomes = [];
|
|
50
|
+
for (const agent of new Set(selectedAgents)) {
|
|
51
|
+
if (!supported.has(agent)) {
|
|
52
|
+
outcomes.push({ agent, displayName: agent, unsupported: true });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const agentType = agent;
|
|
56
|
+
const global = await listInstalledServers({ agents: [agentType], global: true, cwd: plan.projectRoot });
|
|
57
|
+
const local = agents[agentType].localConfigPath
|
|
58
|
+
? await listInstalledServers({ agents: [agentType], global: false, cwd: plan.projectRoot })
|
|
59
|
+
: [];
|
|
60
|
+
const configurations = [...global, ...local];
|
|
61
|
+
const invalid = configurations.find((configuration) => configuration.error);
|
|
62
|
+
if (invalid) {
|
|
63
|
+
outcomes.push({ agent, displayName: agents[agentType].displayName, unsupported: false,
|
|
64
|
+
result: { success: false, path: invalid.configPath, error: invalid.error } });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const backups = [];
|
|
68
|
+
for (const configuration of configurations) {
|
|
69
|
+
if (!configuration.servers.some((server) => server.serverName === nudgeServerName))
|
|
70
|
+
continue;
|
|
71
|
+
const backup = `${configuration.configPath}.nudge-backup-${randomUUID()}`;
|
|
72
|
+
copyFileSync(configuration.configPath, backup);
|
|
73
|
+
backups.push(backup);
|
|
74
|
+
}
|
|
75
|
+
const result = upsertServer(agentType, nudgeServerName, plan.serverConfig, { local: false, cwd: plan.projectRoot });
|
|
76
|
+
const removal = result.success && agents[agentType].localConfigPath
|
|
77
|
+
? removeServer(agentType, nudgeServerName, { local: true, cwd: plan.projectRoot })
|
|
78
|
+
: undefined;
|
|
79
|
+
outcomes.push({
|
|
80
|
+
agent, displayName: agents[agentType].displayName, result, unsupported: false, backups,
|
|
81
|
+
...(removal && !removal.success ? { migrationError: removal.error ?? "Could not remove the project override." } : {}),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return outcomes;
|
|
85
|
+
}
|
|
86
|
+
/** Formats a complete stdio entry for agents that add-mcp cannot configure. */
|
|
87
|
+
export function manualAgentConfiguration(plan) {
|
|
88
|
+
return JSON.stringify({
|
|
89
|
+
mcpServers: {
|
|
90
|
+
[nudgeServerName]: plan.serverConfig,
|
|
91
|
+
},
|
|
92
|
+
}, null, 2);
|
|
93
|
+
}
|
|
94
|
+
export function projectAgentChoices(detected) {
|
|
95
|
+
const detectedSet = new Set(detected);
|
|
96
|
+
return projectAgentTypes()
|
|
97
|
+
.sort((left, right) => {
|
|
98
|
+
const detectionOrder = Number(detectedSet.has(right)) - Number(detectedSet.has(left));
|
|
99
|
+
return detectionOrder || agents[left].displayName.localeCompare(agents[right].displayName);
|
|
100
|
+
})
|
|
101
|
+
.map((id) => ({ id, label: agents[id].displayName, detected: detectedSet.has(id) }));
|
|
102
|
+
}
|
|
103
|
+
export function initializerVersion() {
|
|
104
|
+
const packageJsonUrl = new URL("../package.json", import.meta.url);
|
|
105
|
+
const value = JSON.parse(readFileSync(packageJsonUrl, "utf8"));
|
|
106
|
+
if (!isPackageMetadata(value))
|
|
107
|
+
throw new TypeError("create-nudge-ui package metadata has no version.");
|
|
108
|
+
return value.version;
|
|
109
|
+
}
|
|
110
|
+
function installedNudgeUiVersion(projectRoot) {
|
|
111
|
+
const manifestPath = join(projectRoot, "node_modules", "nudge-ui", "package.json");
|
|
112
|
+
try {
|
|
113
|
+
const value = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
114
|
+
return isPackageMetadata(value) ? value.version : undefined;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isPackageMetadata(value) {
|
|
121
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
122
|
+
return false;
|
|
123
|
+
return typeof value.version === "string";
|
|
124
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
/** Verifies the configured executable, initialization, and tools without claiming a project. */
|
|
4
|
+
export async function verifyAgentServer(config) {
|
|
5
|
+
if (!config.command)
|
|
6
|
+
throw new Error("The MCP adapter has no executable.");
|
|
7
|
+
const client = new Client({ name: "nudge-setup", version: "1.0.0" });
|
|
8
|
+
const transport = new StdioClientTransport({ command: config.command, args: config.args, stderr: "inherit" });
|
|
9
|
+
try {
|
|
10
|
+
await client.connect(transport, { timeout: 15_000 });
|
|
11
|
+
const { tools } = await client.listTools({}, { timeout: 15_000 });
|
|
12
|
+
const listen = tools.find((tool) => tool.name === "nudge_listen");
|
|
13
|
+
if (!listen?.inputSchema.required?.includes("workspaceRoot") || !tools.some((tool) => tool.name === "nudge_list_sessions")) {
|
|
14
|
+
throw new Error("The installed adapter does not support connection-time project selection. Upgrade @nudge-ui/mcp.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
await transport.close();
|
|
19
|
+
await client.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
package/dist/arguments.d.ts
CHANGED
|
@@ -2,9 +2,14 @@ import { type Framework, type PackageManager } from "./types.js";
|
|
|
2
2
|
export interface CliOptions {
|
|
3
3
|
readonly framework?: Framework;
|
|
4
4
|
readonly packageManager?: PackageManager;
|
|
5
|
+
readonly agentOnly: boolean;
|
|
6
|
+
readonly agents: readonly string[];
|
|
7
|
+
readonly mcpPackageSpecifier?: string;
|
|
8
|
+
readonly mcp?: boolean;
|
|
9
|
+
readonly yes: boolean;
|
|
5
10
|
readonly dryRun: boolean;
|
|
6
11
|
readonly help: boolean;
|
|
7
12
|
}
|
|
8
13
|
/** Parses the initializer command line. */
|
|
9
14
|
export declare function parseArguments(args: readonly string[]): CliOptions;
|
|
10
|
-
export declare const helpText = "Set up Nudge UI in the current project.\n\nUsage: npm create nudge-ui@latest -- [options]\n\nOptions:\n --framework <nextjs|astro|vite-react|standalone>\n --package-manager <pnpm|npm|yarn|bun>\n --dry-run\n -h, --help";
|
|
15
|
+
export declare const helpText = "Set up Nudge UI in the current project.\n\nUsage: npm create nudge-ui@latest -- [options]\n\nOptions:\n --framework <nextjs|astro|vite-react|standalone>\n --package-manager <pnpm|npm|yarn|bun>\n --mcp Configure the reusable Nudge MCP adapter\n --no-mcp Skip coding-agent setup\n --agent <id> Configure a supported agent (repeatable)\n --mcp-package <path|package> Use this MCP package for the project and adapter\n --agent-only Install or repair only the agent integration\n -y, --yes Accept defaults without prompting\n --dry-run\n -h, --help";
|
package/dist/arguments.js
CHANGED
|
@@ -3,14 +3,40 @@ import { frameworks, packageManagers } from "./types.js";
|
|
|
3
3
|
export function parseArguments(args) {
|
|
4
4
|
let framework;
|
|
5
5
|
let packageManager;
|
|
6
|
+
let agentOnly = false;
|
|
7
|
+
const agents = [];
|
|
8
|
+
let mcpPackageSpecifier;
|
|
9
|
+
let mcp;
|
|
10
|
+
let yes = false;
|
|
6
11
|
let dryRun = false;
|
|
7
12
|
let help = false;
|
|
8
13
|
for (let index = 0; index < args.length; index += 1) {
|
|
9
14
|
const argument = args[index];
|
|
10
15
|
if (argument === "--")
|
|
11
16
|
continue;
|
|
17
|
+
if (index === 0 && argument === "agent" && args[index + 1] === "setup") {
|
|
18
|
+
agentOnly = true;
|
|
19
|
+
index += 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
12
22
|
if (argument === "--dry-run")
|
|
13
23
|
dryRun = true;
|
|
24
|
+
else if (argument === "--yes" || argument === "-y")
|
|
25
|
+
yes = true;
|
|
26
|
+
else if (argument === "--agent-only")
|
|
27
|
+
agentOnly = true;
|
|
28
|
+
else if (argument === "--mcp")
|
|
29
|
+
mcp = setMcpPreference(mcp, true);
|
|
30
|
+
else if (argument === "--no-mcp")
|
|
31
|
+
mcp = setMcpPreference(mcp, false);
|
|
32
|
+
else if (argument === "--agent")
|
|
33
|
+
agents.push(requiredValue(args, ++index, argument));
|
|
34
|
+
else if (argument.startsWith("--agent="))
|
|
35
|
+
agents.push(requiredValue([argument.slice("--agent=".length)], 0, "--agent"));
|
|
36
|
+
else if (argument === "--mcp-package")
|
|
37
|
+
mcpPackageSpecifier = requiredValue(args, ++index, argument);
|
|
38
|
+
else if (argument.startsWith("--mcp-package="))
|
|
39
|
+
mcpPackageSpecifier = requiredValue([argument.slice("--mcp-package=".length)], 0, "--mcp-package");
|
|
14
40
|
else if (argument === "--help" || argument === "-h")
|
|
15
41
|
help = true;
|
|
16
42
|
else if (argument === "--framework")
|
|
@@ -26,7 +52,16 @@ export function parseArguments(args) {
|
|
|
26
52
|
else
|
|
27
53
|
throw new Error(`Unknown option: ${argument}`);
|
|
28
54
|
}
|
|
29
|
-
|
|
55
|
+
if (agents.length > 0 && mcp === false)
|
|
56
|
+
throw new Error("--agent cannot be combined with --no-mcp.");
|
|
57
|
+
if (agentOnly && mcp === false)
|
|
58
|
+
throw new Error("--agent-only cannot be combined with --no-mcp.");
|
|
59
|
+
return { framework, packageManager, agentOnly, agents, mcpPackageSpecifier, mcp, yes, dryRun, help };
|
|
60
|
+
}
|
|
61
|
+
function setMcpPreference(current, next) {
|
|
62
|
+
if (current !== undefined && current !== next)
|
|
63
|
+
throw new Error("--mcp and --no-mcp cannot be combined.");
|
|
64
|
+
return next;
|
|
30
65
|
}
|
|
31
66
|
function requiredValue(args, index, option) {
|
|
32
67
|
const value = args[index];
|
|
@@ -41,6 +76,12 @@ Usage: npm create nudge-ui@latest -- [options]
|
|
|
41
76
|
Options:
|
|
42
77
|
--framework <nextjs|astro|vite-react|standalone>
|
|
43
78
|
--package-manager <pnpm|npm|yarn|bun>
|
|
79
|
+
--mcp Configure the reusable Nudge MCP adapter
|
|
80
|
+
--no-mcp Skip coding-agent setup
|
|
81
|
+
--agent <id> Configure a supported agent (repeatable)
|
|
82
|
+
--mcp-package <path|package> Use this MCP package for the project and adapter
|
|
83
|
+
--agent-only Install or repair only the agent integration
|
|
84
|
+
-y, --yes Accept defaults without prompting
|
|
44
85
|
--dry-run
|
|
45
86
|
-h, --help`;
|
|
46
87
|
function parseFramework(value) {
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import { type PackageManager } from "./types.js";
|
|
2
|
+
export interface RunAgentSetupOptions {
|
|
3
|
+
readonly agents?: readonly string[];
|
|
4
|
+
/** Optional package specifier or tarball used for both bridge and adapter testing. */
|
|
5
|
+
readonly mcpPackageSpecifier?: string;
|
|
6
|
+
readonly dryRun?: boolean;
|
|
7
|
+
readonly nudgeUiVersion?: string;
|
|
8
|
+
readonly packageManager?: PackageManager;
|
|
9
|
+
readonly yes?: boolean;
|
|
10
|
+
}
|
|
1
11
|
/** Runs the Nudge UI project initializer. */
|
|
2
12
|
export declare function runCli(args?: string[], projectRoot?: string): Promise<void>;
|
|
13
|
+
/** Installs and configures the reusable MCP adapter and project bridge dependency. Safe to rerun for repairs. */
|
|
14
|
+
export declare function runAgentSetup(projectRoot?: string, options?: RunAgentSetupOptions): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { verifyAgentServer } from "./agent-verification.js";
|
|
2
3
|
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { stdin, stdout } from "node:process";
|
|
4
5
|
import { basename, relative } from "node:path";
|
|
6
|
+
import { configureProjectAgents, detectProjectAgentCandidates, initializerVersion, isAgentServerAvailable, manualAgentConfiguration, planAgentSetup, projectAgentChoices, } from "./agent-setup.js";
|
|
5
7
|
import { parseArguments, helpText } from "./arguments.js";
|
|
6
8
|
import { planConfiguration } from "./configuration.js";
|
|
7
9
|
import { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
|
|
8
10
|
import { formatCommand, installCommand, installPackage } from "./installer.js";
|
|
9
|
-
import { frameworks } from "./types.js";
|
|
11
|
+
import { frameworks, } from "./types.js";
|
|
10
12
|
/** Runs the Nudge UI project initializer. */
|
|
11
13
|
export async function runCli(args = process.argv.slice(2), projectRoot = process.cwd()) {
|
|
12
14
|
const options = parseArguments(args);
|
|
@@ -15,12 +17,22 @@ export async function runCli(args = process.argv.slice(2), projectRoot = process
|
|
|
15
17
|
return;
|
|
16
18
|
}
|
|
17
19
|
const manifest = readProjectManifest(projectRoot);
|
|
20
|
+
const packageManager = options.packageManager ?? detectPackageManager(projectRoot, manifest);
|
|
21
|
+
if (options.agentOnly) {
|
|
22
|
+
await runAgentSetup(projectRoot, {
|
|
23
|
+
agents: options.agents,
|
|
24
|
+
mcpPackageSpecifier: options.mcpPackageSpecifier,
|
|
25
|
+
dryRun: options.dryRun,
|
|
26
|
+
packageManager,
|
|
27
|
+
yes: options.yes,
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
18
31
|
const detected = detectFrameworks(projectRoot, manifest);
|
|
19
32
|
const framework = options.framework ?? await selectFramework(detected);
|
|
20
|
-
const packageManager = options.packageManager ?? detectPackageManager(projectRoot, manifest);
|
|
21
33
|
// One package serves every host; the framework only decides which
|
|
22
34
|
// subpath the generated configuration imports.
|
|
23
|
-
const command = installCommand(packageManager,
|
|
35
|
+
const command = installCommand(packageManager, `nudge-ui@${initializerVersion()}`);
|
|
24
36
|
const change = planConfiguration(projectRoot, framework);
|
|
25
37
|
const staticRoot = framework === "standalone" ? detectStaticRoot(projectRoot) : undefined;
|
|
26
38
|
const serveDirectory = staticRoot ? relative(projectRoot, staticRoot) || "." : ".";
|
|
@@ -31,6 +43,16 @@ export async function runCli(args = process.argv.slice(2), projectRoot = process
|
|
|
31
43
|
stdout.write(`Would ${change.created ? "create" : "update"}: ${change.path}\n`);
|
|
32
44
|
if (framework === "standalone")
|
|
33
45
|
stdout.write(`After installation, run: nudge-ui serve ${serveDirectory}\n`);
|
|
46
|
+
if (await shouldConfigureAgent(options.mcp, options.agents.length > 0, options.yes)) {
|
|
47
|
+
await runAgentSetup(projectRoot, {
|
|
48
|
+
agents: options.agents,
|
|
49
|
+
mcpPackageSpecifier: options.mcpPackageSpecifier,
|
|
50
|
+
dryRun: true,
|
|
51
|
+
nudgeUiVersion: initializerVersion(),
|
|
52
|
+
packageManager,
|
|
53
|
+
yes: options.yes,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
34
56
|
return;
|
|
35
57
|
}
|
|
36
58
|
if (change) {
|
|
@@ -51,6 +73,157 @@ export async function runCli(args = process.argv.slice(2), projectRoot = process
|
|
|
51
73
|
stdout.write(`Run \`nudge-ui serve ${serveDirectory}\` to start the instrumented development server.\n`);
|
|
52
74
|
}
|
|
53
75
|
stdout.write(`Nudge UI is configured for ${frameworkDisplayName(framework)}.\n`);
|
|
76
|
+
if (await shouldConfigureAgent(options.mcp, options.agents.length > 0, options.yes)) {
|
|
77
|
+
await runAgentSetup(projectRoot, {
|
|
78
|
+
agents: options.agents,
|
|
79
|
+
mcpPackageSpecifier: options.mcpPackageSpecifier,
|
|
80
|
+
nudgeUiVersion: initializerVersion(),
|
|
81
|
+
packageManager,
|
|
82
|
+
yes: options.yes,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Installs and configures the reusable MCP adapter and project bridge dependency. Safe to rerun for repairs. */
|
|
87
|
+
export async function runAgentSetup(projectRoot = process.cwd(), options = {}) {
|
|
88
|
+
const manifest = readProjectManifest(projectRoot);
|
|
89
|
+
const packageManager = options.packageManager ?? detectPackageManager(projectRoot, manifest);
|
|
90
|
+
const plan = planAgentSetup(projectRoot, packageManager, options.nudgeUiVersion, undefined, options.mcpPackageSpecifier);
|
|
91
|
+
const detected = await detectProjectAgentCandidates(plan.projectRoot);
|
|
92
|
+
const selected = options.agents && options.agents.length > 0
|
|
93
|
+
? options.agents
|
|
94
|
+
: options.yes || !stdin.isTTY || !stdout.isTTY
|
|
95
|
+
? detected
|
|
96
|
+
: await selectAgents(detected);
|
|
97
|
+
if (options.dryRun) {
|
|
98
|
+
stdout.write(`Would run: ${formatCommand(plan.installCommand)}\n`);
|
|
99
|
+
stdout.write(`Would run: ${formatCommand(plan.adapterInstallCommand)}\n`);
|
|
100
|
+
if (selected.length > 0) {
|
|
101
|
+
stdout.write(`Would configure global MCP and migrate this project override for: ${selected.join(", ")}\n`);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
stdout.write("No coding agent was detected. Use --agent <id> or add the configuration below manually.\n");
|
|
105
|
+
writeManualInstructions(plan);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
installPackage(plan.installCommand, plan.projectRoot);
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(plan.adapterRoot, { recursive: true });
|
|
112
|
+
installPackage(plan.adapterInstallCommand, plan.adapterRoot);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
stdout.write(`Could not install the reusable MCP adapter: ${message}\n`);
|
|
117
|
+
stdout.write(`Retry with: ${formatCommand(plan.adapterInstallCommand)}\n`);
|
|
118
|
+
stdout.write("The coding-agent configuration was not changed. After the adapter install succeeds, rerun with --agent-only.\n");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!isAgentServerAvailable(plan)) {
|
|
122
|
+
stdout.write(`Installed ${plan.packageSpecifier}, but its reusable MCP executable was not found at ${plan.serverEntry}.\n`);
|
|
123
|
+
stdout.write(`Retry with: ${formatCommand(plan.adapterInstallCommand)}\n`);
|
|
124
|
+
stdout.write("The coding-agent configuration was not changed. Check the package-manager install output, then rerun with --agent-only.\n");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await verifyAgentServer(plan.serverConfig);
|
|
129
|
+
stdout.write("Verified MCP initialization and tool discovery with a fresh client.\n");
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
stdout.write(`MCP verification failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
133
|
+
stdout.write(`Retry with: ${formatCommand(plan.adapterInstallCommand)}\n`);
|
|
134
|
+
stdout.write("The coding-agent configuration was not changed. After repairing the adapter install, rerun with --agent-only.\n");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (selected.length === 0) {
|
|
138
|
+
stdout.write(`Installed ${plan.packageSpecifier}, but no coding agent was detected.\n`);
|
|
139
|
+
writeManualInstructions(plan);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let outcomes;
|
|
143
|
+
try {
|
|
144
|
+
outcomes = await configureProjectAgents(plan, selected);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
stdout.write(`Could not update the coding-agent configuration: ${message}\n`);
|
|
149
|
+
writeManualInstructions(plan);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let configured = 0;
|
|
153
|
+
for (const outcome of outcomes) {
|
|
154
|
+
if (outcome.unsupported) {
|
|
155
|
+
stdout.write(`${outcome.displayName} is not supported for automatic global setup.\n`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
for (const backup of outcome.backups ?? [])
|
|
159
|
+
stdout.write(`Saved previous MCP configuration: ${backup}\n`);
|
|
160
|
+
if (outcome.migrationError) {
|
|
161
|
+
stdout.write(`Global adapter configured, but project override migration failed: ${outcome.migrationError}\n`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (outcome.result?.success) {
|
|
165
|
+
configured += 1;
|
|
166
|
+
stdout.write(`Configured ${outcome.displayName}: ${outcome.result.path}\n`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
stdout.write(`Could not configure ${outcome.displayName}: ${outcome.result?.error ?? "unknown error"}\n`);
|
|
170
|
+
}
|
|
171
|
+
if (configured !== outcomes.length)
|
|
172
|
+
writeManualInstructions(plan);
|
|
173
|
+
if (configured > 0) {
|
|
174
|
+
stdout.write("Adapter configuration is ready. Fully restart the agent host to load it, then ask it to listen to Nudge in the current workspace. Existing overrides in other projects require one setup rerun there.\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function shouldConfigureAgent(preference, hasExplicitAgents, yes) {
|
|
178
|
+
if (preference !== undefined)
|
|
179
|
+
return preference;
|
|
180
|
+
if (hasExplicitAgents || yes)
|
|
181
|
+
return true;
|
|
182
|
+
if (!stdin.isTTY || !stdout.isTTY)
|
|
183
|
+
return false;
|
|
184
|
+
return promptYesNo("Connect a coding agent?", true);
|
|
185
|
+
}
|
|
186
|
+
async function promptYesNo(message, defaultValue) {
|
|
187
|
+
const reader = createInterface({ input: stdin, output: stdout });
|
|
188
|
+
try {
|
|
189
|
+
const answer = (await reader.question(`${message} ${defaultValue ? "[Y/n]" : "[y/N]"} `)).trim().toLowerCase();
|
|
190
|
+
if (!answer)
|
|
191
|
+
return defaultValue;
|
|
192
|
+
return answer === "y" || answer === "yes";
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
reader.close();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function selectAgents(detected) {
|
|
199
|
+
const choices = projectAgentChoices(detected);
|
|
200
|
+
stdout.write("Select coding agents (comma-separated):\n");
|
|
201
|
+
choices.forEach((choice, index) => {
|
|
202
|
+
stdout.write(` ${index + 1}. ${choice.label}${choice.detected ? " (detected)" : ""}\n`);
|
|
203
|
+
});
|
|
204
|
+
const defaults = choices
|
|
205
|
+
.map((choice, index) => choice.detected ? String(index + 1) : undefined)
|
|
206
|
+
.filter((value) => value !== undefined)
|
|
207
|
+
.join(",");
|
|
208
|
+
const reader = createInterface({ input: stdin, output: stdout });
|
|
209
|
+
try {
|
|
210
|
+
while (true) {
|
|
211
|
+
const suffix = defaults ? ` [${defaults}]` : "";
|
|
212
|
+
const answer = (await reader.question(`Agents${suffix}: `)).trim() || defaults;
|
|
213
|
+
const indexes = answer.split(",").map((value) => Number(value.trim()) - 1);
|
|
214
|
+
if (indexes.length > 0 && indexes.every((index) => choices[index])) {
|
|
215
|
+
return [...new Set(indexes.map((index) => choices[index].id))];
|
|
216
|
+
}
|
|
217
|
+
stdout.write(`Enter one or more numbers from 1 to ${choices.length}.\n`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
reader.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function writeManualInstructions(plan) {
|
|
225
|
+
stdout.write("Add this global stdio server to your agent's MCP settings:\n");
|
|
226
|
+
stdout.write(`${manualAgentConfiguration(plan)}\n`);
|
|
54
227
|
}
|
|
55
228
|
async function selectFramework(detected) {
|
|
56
229
|
if (detected.length === 1)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { parseArguments, helpText, type CliOptions } from "./arguments.js";
|
|
2
|
+
export { configureProjectAgents, detectProjectAgentCandidates, initializerVersion, isAgentServerAvailable, manualAgentConfiguration, planAgentSetup, projectAgentChoices, projectAgentTypes, type AgentInstallOutcome, type AgentSetupPlan, } from "./agent-setup.js";
|
|
3
|
+
export { runAgentSetup, type RunAgentSetupOptions } from "./cli.js";
|
|
2
4
|
export { configureSource, planConfiguration } from "./configuration.js";
|
|
3
5
|
export { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
|
|
4
6
|
export { formatCommand, installCommand, type InstallCommand } from "./installer.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { parseArguments, helpText } from "./arguments.js";
|
|
2
|
+
export { configureProjectAgents, detectProjectAgentCandidates, initializerVersion, isAgentServerAvailable, manualAgentConfiguration, planAgentSetup, projectAgentChoices, projectAgentTypes, } from "./agent-setup.js";
|
|
3
|
+
export { runAgentSetup } from "./cli.js";
|
|
2
4
|
export { configureSource, planConfiguration } from "./configuration.js";
|
|
3
5
|
export { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
|
|
4
6
|
export { formatCommand, installCommand } from "./installer.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nudge-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Framework-detecting installer for Nudge UI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
"create-nudge-ui": "./dist/entry.js"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"
|
|
32
|
+
"add-mcp": "^2.4.0",
|
|
33
|
+
"typescript": "^5.9.3",
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"vitest": "^2.1.9"
|