diff --git a/src/Commands/HydePublishViewsCommand.php b/src/Commands/HydePublishViewsCommand.php
new file mode 100644
index 00000000..7828fdbd
--- /dev/null
+++ b/src/Commands/HydePublishViewsCommand.php
@@ -0,0 +1,314 @@
+files = $files;
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $this->determineWhatShouldBePublished();
+
+ foreach ($this->tags ?: [null] as $tag) {
+ $this->publishTag($tag);
+ }
+
+ $this->info('Publishing complete.');
+ }
+
+ /**
+ * Determine the provider or tag(s) to publish.
+ *
+ * @return void
+ */
+ protected function determineWhatShouldBePublished()
+ {
+ if ($this->option('all')) {
+ $this->tags = array_flip(array_filter(
+ array_flip(ServiceProvider::publishableGroups()),
+ fn($key) => str_starts_with($key, 'hyde-'),
+ ARRAY_FILTER_USE_KEY
+ ));
+ return;
+ }
+
+ if (!$this->tags) {
+ $this->promptForProviderOrTag();
+ }
+ }
+
+ /**
+ * Prompt for which tag to publish.
+ *
+ * @return void
+ */
+ protected function promptForProviderOrTag()
+ {
+ $choice = $this->choice(
+ "Which view categories (tags) would you like to publish?",
+ $choices = $this->publishableChoices()
+ );
+
+ if ($choice == $choices[0] || is_null($choice)) {
+ $this->tags = array_flip(array_filter(
+ array_flip(ServiceProvider::publishableGroups()),
+ fn($key) => str_starts_with($key, 'hyde-'),
+ ARRAY_FILTER_USE_KEY
+ ));
+ return;
+ }
+
+ $this->parseChoice($choice);
+ }
+
+ /**
+ * The choices available via the prompt.
+ *
+ * @return array
+ */
+ protected function publishableChoices()
+ {
+ return array_merge(
+ ['Publish files from all tags listed below'],
+ preg_filter('/^/', 'Tag: ', Arr::sort(
+ array_flip(array_filter(
+ array_flip(ServiceProvider::publishableGroups()),
+ fn($key) => str_starts_with($key, 'hyde-'),
+ ARRAY_FILTER_USE_KEY
+ ))
+ ))
+ );
+ }
+
+ /**
+ * Parse the answer that was given via the prompt.
+ *
+ * @param string $choice
+ * @return void
+ */
+ protected function parseChoice($choice)
+ {
+ [$type, $value] = explode(': ', strip_tags($choice));
+
+ if ($type === 'Provider') {
+ $this->provider = $value;
+ } elseif ($type === 'Tag') {
+ $this->tags = [$value];
+ }
+ }
+
+ /**
+ * Publishes the assets for a tag.
+ *
+ * @param string $tag
+ * @return mixed
+ */
+ protected function publishTag($tag)
+ {
+ $published = false;
+
+ $pathsToPublish = $this->pathsToPublish($tag);
+
+ foreach ($pathsToPublish as $from => $to) {
+ $this->publishItem($from, $to);
+
+ $published = true;
+ }
+
+ if ($published === false) {
+ $this->comment('No publishable resources for tag ['.$tag.'].');
+ } else {
+ $this->laravel['events']->dispatch(new VendorTagPublished($tag, $pathsToPublish));
+ }
+ }
+
+ /**
+ * Get all of the paths to publish.
+ *
+ * @param string $tag
+ * @return array
+ */
+ protected function pathsToPublish($tag)
+ {
+ return ServiceProvider::pathsToPublish(
+ $this->provider, $tag
+ );
+ }
+
+
+ /**
+ * Publish the given item from and to the given location.
+ *
+ * @param string $from
+ * @param string $to
+ * @return void
+ */
+ protected function publishItem($from, $to)
+ {
+ if ($this->files->isFile($from)) {
+ return $this->publishFile($from, $to);
+ } elseif ($this->files->isDirectory($from)) {
+ return $this->publishDirectory($from, $to);
+ }
+
+ $this->error("Can't locate path: <{$from}>");
+ }
+
+ /**
+ * Publish the file to the given path.
+ *
+ * @param string $from
+ * @param string $to
+ * @return void
+ */
+ protected function publishFile($from, $to)
+ {
+ if (! $this->files->exists($to) || $this->option('force')) {
+ $this->createParentDirectory(dirname($to));
+
+ $this->files->copy($from, $to);
+
+ $this->status($from, $to, 'File');
+ }
+ }
+
+ /**
+ * Publish the directory to the given directory.
+ *
+ * @param string $from
+ * @param string $to
+ * @return void
+ */
+ protected function publishDirectory($from, $to)
+ {
+ $visibility = PortableVisibilityConverter::fromArray([], Visibility::PUBLIC);
+
+ $this->moveManagedFiles(new MountManager([
+ 'from' => new Flysystem(new LocalAdapter($from)),
+ 'to' => new Flysystem(new LocalAdapter($to, $visibility)),
+ ]));
+
+ $this->status($from, $to, 'Directory');
+ }
+
+ /**
+ * Move all the files in the given MountManager.
+ *
+ * @param \League\Flysystem\MountManager $manager
+ * @return void
+ */
+ protected function moveManagedFiles($manager)
+ {
+ foreach ($manager->listContents('from://', true) as $file) {
+ $path = Str::after($file['path'], 'from://');
+
+ if ($file['type'] === 'file' && (! $manager->fileExists('to://'.$path) || $this->option('force'))) {
+ $manager->write('to://'.$path, $manager->read($file['path']));
+ }
+ }
+ }
+
+ /**
+ * Create the directory to house the published files if needed.
+ *
+ * @param string $directory
+ * @return void
+ */
+ protected function createParentDirectory($directory)
+ {
+ if (! $this->files->isDirectory($directory)) {
+ $this->files->makeDirectory($directory, 0755, true);
+ }
+ }
+
+ /**
+ * Write a status message to the console.
+ *
+ * @param string $from
+ * @param string $to
+ * @param string $type
+ * @return void
+ */
+ protected function status($from, $to, $type)
+ {
+ $from = str_replace(base_path(), '', realpath($from));
+
+ $to = str_replace(base_path(), '', realpath($to));
+
+ $this->line('Copied '.$type.' ['.$from.'] To ['.$to.']');
+ }
+}