diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7301ab..a33d51d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Inherit pesde-managed scripts from workspace root by @daimond113
 - Allow using binaries from workspace root in member packages by @daimond113
 
+### Fixed
+- Install dev packages in prod mode and remove them after use to allow them to be used in scripts by @daimond113
+
 ### Changed
 - Change handling of graphs to a flat structure by @daimond113
 - Store dependency over downloaded graphs in the lockfile by @daimond113
diff --git a/src/download_and_link.rs b/src/download_and_link.rs
index c01e3e4..b8c74e5 100644
--- a/src/download_and_link.rs
+++ b/src/download_and_link.rs
@@ -189,10 +189,6 @@ impl Project {
 				let container_folder =
 					node.container_folder_from_project(&id, self, manifest.target.kind());
 
-				if prod && node.resolved_ty == DependencyType::Dev {
-					continue;
-				}
-
 				downloaded_graph.insert(id, node);
 
 				let cas_dir = self.cas_dir().to_path_buf();
@@ -308,7 +304,50 @@ impl Project {
 				.map_err(errors::DownloadAndLinkError::Hook)?;
 		}
 
-		Ok(Arc::into_inner(graph).unwrap())
+		let mut graph = Arc::into_inner(graph).unwrap();
+
+		if prod {
+			let (dev_graph, prod_graph) = graph
+				.into_iter()
+				.partition::<DependencyGraphWithTarget, _>(|(_, node)| {
+					node.node.resolved_ty == DependencyType::Dev
+				});
+
+			graph = prod_graph;
+			let dev_graph = Arc::new(dev_graph);
+
+			let manifest_target_kind = manifest.target.kind();
+
+			// the `true` argument means it'll remove the dependencies linkers
+			self.link(
+				&dev_graph,
+				&Arc::new(manifest),
+				&Arc::new(Default::default()),
+				false,
+				true,
+			)
+			.await?;
+
+			let mut tasks = dev_graph
+				.iter()
+				.map(|(id, node)| {
+					let container_folder =
+						node.node
+							.container_folder_from_project(id, self, manifest_target_kind);
+					async move {
+						fs::remove_dir_all(&container_folder)
+							.await
+							.map_err(errors::DownloadAndLinkError::Io)
+					}
+				})
+				.collect::<JoinSet<_>>();
+
+			while let Some(task) = tasks.join_next().await {
+				task.unwrap()?;
+			}
+		}
+
+		Ok(graph)
 	}
 }
 
diff --git a/src/linking/mod.rs b/src/linking/mod.rs
index b7bc5f2..dd770ec 100644
--- a/src/linking/mod.rs
+++ b/src/linking/mod.rs
@@ -68,8 +68,14 @@ impl Project {
 
 		// step 1. link all non-wally packages (and their dependencies) temporarily without types
 		// we do this separately to allow the required tools for the scripts to be installed
-		self.link(&graph, &manifest, &Arc::new(PackageTypes::default()), false)
-			.await?;
+		self.link(
+			&graph,
+			&manifest,
+			&Arc::new(PackageTypes::default()),
+			false,
+			false,
+		)
+		.await?;
 
 		if !with_types {
 			return Ok(());
@@ -150,7 +156,7 @@ impl Project {
 		}
 
 		// step 3. link all packages (and their dependencies), this time with types
-		self.link(&graph, &manifest, &Arc::new(package_types), true)
+		self.link(&graph, &manifest, &Arc::new(package_types), true, false)
 			.await
 	}
 
@@ -164,79 +170,128 @@ impl Project {
 		node: &DependencyGraphNodeWithTarget,
 		package_id: &PackageId,
 		alias: &str,
-		package_types: &PackageTypes,
-		manifest: &Manifest,
+		package_types: &Arc<PackageTypes>,
+		manifest: &Arc<Manifest>,
+		remove: bool,
+		is_root: bool,
 	) -> Result<(), errors::LinkingError> {
 		static NO_TYPES: Vec<String> = Vec::new();
 
-		if let Some(lib_file) = node.target.lib_path() {
-			let lib_module = generator::generate_lib_linking_module(
-				&generator::get_lib_require_path(
-					node.target.kind(),
-					base_folder,
-					lib_file,
-					container_folder,
-					node.node.pkg_ref.use_new_structure(),
-					root_container_folder,
-					relative_container_folder,
-					manifest,
-				)?,
-				package_types.get(package_id).unwrap_or(&NO_TYPES),
-			);
+		#[allow(clippy::result_large_err)]
+		fn into_link_result(res: std::io::Result<()>) -> Result<(), errors::LinkingError> {
+			match res {
+				Ok(_) => Ok(()),
+				Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
+				Err(e) => Err(e.into()),
+			}
+		}
 
-			write_cas(
-				base_folder.join(format!("{alias}.luau")),
-				self.cas_dir(),
-				&lib_module,
-			)
-			.await?;
+		let mut tasks = JoinSet::<Result<(), errors::LinkingError>>::new();
+
+		if let Some(lib_file) = node.target.lib_path() {
+			let destination = base_folder.join(format!("{alias}.luau"));
+
+			if remove {
+				tasks.spawn(async move { into_link_result(fs::remove_file(destination).await) });
+			} else {
+				let lib_module = generator::generate_lib_linking_module(
+					&generator::get_lib_require_path(
+						node.target.kind(),
+						base_folder,
+						lib_file,
+						container_folder,
+						node.node.pkg_ref.use_new_structure(),
+						root_container_folder,
+						relative_container_folder,
+						manifest,
+					)?,
+					package_types.get(package_id).unwrap_or(&NO_TYPES),
+				);
+				let cas_dir = self.cas_dir().to_path_buf();
+
+				tasks.spawn(async move {
+					write_cas(destination, &cas_dir, &lib_module)
+						.await
+						.map_err(Into::into)
+				});
+			}
 		}
 
 		if let Some(bin_file) = node.target.bin_path() {
-			let bin_module = generator::generate_bin_linking_module(
-				container_folder,
-				&generator::get_bin_require_path(base_folder, bin_file, container_folder),
-			);
+			let destination = base_folder.join(format!("{alias}.bin.luau"));
 
-			write_cas(
-				base_folder.join(format!("{alias}.bin.luau")),
-				self.cas_dir(),
-				&bin_module,
-			)
-			.await?;
+			if remove {
+				tasks.spawn(async move { into_link_result(fs::remove_file(destination).await) });
+			} else {
+				let bin_module = generator::generate_bin_linking_module(
+					container_folder,
+					&generator::get_bin_require_path(base_folder, bin_file, container_folder),
+				);
+				let cas_dir = self.cas_dir().to_path_buf();
+
+				tasks.spawn(async move {
+					write_cas(destination, &cas_dir, &bin_module)
+						.await
+						.map_err(Into::into)
+				});
+			}
 		}
 
-		if let Some(scripts) = node.target.scripts().filter(|s| !s.is_empty()) {
-			let scripts_base =
-				create_and_canonicalize(self.package_dir().join(SCRIPTS_LINK_FOLDER).join(alias))
-					.await?;
+		if let Some(scripts) = node
+			.target
+			.scripts()
+			.filter(|s| !s.is_empty() && node.node.direct.is_some() && is_root)
+		{
+			let scripts_container = self.package_dir().join(SCRIPTS_LINK_FOLDER);
+			let scripts_base = create_and_canonicalize(scripts_container.join(alias)).await?;
 
-			for (script_name, script_path) in scripts {
-				let script_module =
-					generator::generate_script_linking_module(&generator::get_script_require_path(
-						&scripts_base,
-						script_path,
-						container_folder,
-					));
+			if remove {
+				tasks.spawn(async move {
+					fs::remove_dir_all(scripts_base).await?;
+					if let Ok(mut entries) = fs::read_dir(&scripts_container).await {
+						if entries.next_entry().await.transpose().is_none() {
+							drop(entries);
+							fs::remove_dir(&scripts_container).await?;
+						}
+					}
 
-				write_cas(
-					scripts_base.join(format!("{script_name}.luau")),
-					self.cas_dir(),
-					&script_module,
-				)
-				.await?;
+					Ok(())
+				});
+			} else {
+				for (script_name, script_path) in scripts {
+					let destination = scripts_base.join(format!("{script_name}.luau"));
+					let script_module = generator::generate_script_linking_module(
+						&generator::get_script_require_path(
+							&scripts_base,
+							script_path,
+							container_folder,
+						),
+					);
+					let cas_dir = self.cas_dir().to_path_buf();
+
+					tasks.spawn(async move {
+						write_cas(destination, &cas_dir, &script_module)
+							.await
+							.map_err(Into::into)
+					});
+				}
 			}
 		}
 
+		while let Some(task) = tasks.join_next().await {
+			task.unwrap()?;
+		}
+
 		Ok(())
 	}
 
-	async fn link(
+	pub(crate) async fn link(
 		&self,
 		graph: &Arc<DependencyGraphWithTarget>,
 		manifest: &Arc<Manifest>,
 		package_types: &Arc<PackageTypes>,
 		is_complete: bool,
+		remove: bool,
 	) -> Result<(), errors::LinkingError> {
 		let mut tasks = graph
 			.iter()
@@ -278,6 +333,8 @@ impl Project {
 									alias,
 									&package_types,
 									&manifest,
+									remove,
+									true,
 								)
 								.await?;
 						}
@@ -330,6 +387,8 @@ impl Project {
 								dependency_alias,
 								&package_types,
 								&manifest,
+								remove,
+								false,
 							)
 							.await?;
 					}