Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/internal/flags/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ var TemplateOutputPathFlag = &Metadata{
var TemplateFlag = &Metadata{
Name: "template",
Shorthand: "t",
Usage: "Registered template name. Templates can be registered in the flow configuration file or with `flow set template`.",
Usage: "Registered template name. Templates can be registered in the flow configuration file or with 'flow template add'.",
Default: "",
Required: false,
}
Expand Down Expand Up @@ -338,7 +338,7 @@ var RunningFlag = &Metadata{
var CmdFlag = &Metadata{
Name: "cmd",
Usage: "Run an ad-hoc shell command through flow instead of a named executable. " +
"The command runs with the current workspace's environment and is recorded in `flow logs`. " +
"The command runs with the current workspace's environment and is recorded in flow logs. " +
"Repeat --cmd to run multiple commands in one invocation (see --mode).",
Default: []string{},
Required: false,
Expand Down Expand Up @@ -386,7 +386,7 @@ var SpecFlag = &Metadata{
Name: "spec",
Usage: "Run a transient executable from an inline definition (any type: exec, serial, parallel, request, " +
"render, launch). Accepts inline YAML/JSON, '@path' to read a file, or '-' to read stdin. " +
"The executable is not saved to disk but is recorded in `flow logs`.",
"The executable is not saved to disk but is recorded in flow logs.",
Default: "",
Required: false,
}
Expand Down
117 changes: 105 additions & 12 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,95 @@ import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
import flowfileGrammar from './flowfile.tmLanguage.json'

// https://vitepress.dev/reference/site-config
const SITE = 'https://flowexec.io'
const SITE_NAME = 'flow'
const SITE_DESCRIPTION =
'Write your workflows down, then run them from any project on your machine — with the right secrets, the right environment, and a record of what happened.'
const OG_IMAGE = `${SITE}/og-default.png`

export default defineConfig({
title: "flow",
description: "Local developer automation platform that flows with you.",
title: SITE_NAME,
description: SITE_DESCRIPTION,
base: '/',
outDir: './dist',
lang: 'en-US',

// Cloudflare Pages redirects /foo.html to /foo, so without this the sitemap
// advertises URLs that immediately redirect and the site links to a different
// form than it declares canonical.
cleanUrls: true,

sitemap: {
hostname: 'https://flowexec.io'
hostname: SITE
},

head: [
['link', { rel: 'icon', href: '/favicon.ico' }],
['link', { rel: 'alternate', type: 'text/plain', title: 'llms.txt', href: 'https://flowexec.io/llms.txt' }]
['link', { rel: 'icon', href: '/favicon.ico', sizes: '48x48' }],
['link', { rel: 'icon', type: 'image/png', href: '/icon.png' }],
['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png' }],
['meta', { name: 'theme-color', content: '#2D353B' }],
['meta', { name: 'author', content: 'Dockery Labs' }],
['link', { rel: 'alternate', type: 'text/plain', title: 'llms.txt', href: `${SITE}/llms.txt` }],

// Per-page og:title / og:description / og:url and the canonical link are
// added in transformPageData below; these are the values that never vary.
['meta', { property: 'og:site_name', content: SITE_NAME }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:locale', content: 'en_US' }],
['meta', { property: 'og:image', content: OG_IMAGE }],
['meta', { property: 'og:image:width', content: '1200' }],
['meta', { property: 'og:image:height', content: '630' }],
['meta', { property: 'og:image:alt', content: 'flow' }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:image', content: OG_IMAGE }],

['script', { type: 'application/ld+json' }, JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: SITE_NAME,
description: SITE_DESCRIPTION,
url: SITE,
applicationCategory: 'DeveloperApplication',
operatingSystem: 'macOS, Linux, Windows',
license: 'https://github.com/flowexec/flow/blob/main/LICENSE',
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
author: { '@type': 'Organization', name: 'Dockery Labs', url: 'https://jahvon.dev' },
sameAs: ['https://github.com/flowexec/flow', 'https://discord.gg/CtByNKNMxM']
})]
],

transformPageData(pageData) {
const path = pageData.relativePath
.replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, '')
const canonical = `${SITE}/${path}`
const title = pageData.frontmatter.title
? `${pageData.frontmatter.title} | ${SITE_NAME}`
: SITE_NAME
const description = pageData.frontmatter.description || SITE_DESCRIPTION

pageData.frontmatter.head ??= []
pageData.frontmatter.head.push(
['link', { rel: 'canonical', href: canonical }],
['meta', { property: 'og:url', content: canonical }],
['meta', { property: 'og:title', content: title }],
['meta', { property: 'og:description', content: description }],
['meta', { name: 'twitter:title', content: title }],
['meta', { name: 'twitter:description', content: description }]
)
},

markdown: {
config(md) {
md.use(tabsMarkdownPlugin)
},
languages: [flowfileGrammar as never],
// Shiki bundles both, so code blocks land on the same palette as the rest
// of the site instead of GitHub's default blues.
theme: {
light: 'everforest-light',
dark: 'everforest-dark',
},
},

themeConfig: {
Expand All @@ -33,12 +102,22 @@ export default defineConfig({

siteTitle: false,

// Three items, not five. The logo already goes home, and the two reference
// sections are destinations you arrive at from a guide rather than things
// you browse cold — so they collapse into one menu and give the search and
// Ask controls room to breathe.
nav: [
{ text: 'Home', link: '/' },
{ text: 'Guides', link: '/guides/', activeMatch: '/guides/'},
{ text: 'Guides', link: '/guides/', activeMatch: '/guides/' },
{ text: 'Examples', link: '/examples', activeMatch: '/examples' },
{ text: 'CLI Reference', link: '/cli/', activeMatch: '/cli/' },
{ text: 'Config Reference', link: '/types/', activeMatch: '/types/' }
{
text: 'Reference',
activeMatch: '/(cli|types)/',
items: [
{ text: 'CLI Reference', link: '/cli/' },
{ text: 'Configuration Reference', link: '/types/' },
{ text: 'Contributing', link: '/development' }
]
}
],

sidebar: {
Expand Down Expand Up @@ -69,12 +148,17 @@ export default defineConfig({
{ text: 'Templates & Workflow Generation', link: '/guides/templating' },
]
},
{ text: 'Integrations & Tools',
{ text: 'Interfaces',
items: [
{ text: 'Interactive UI', link: '/guides/interactive' },
{ text: 'AI Tools', link: '/guides/ai-tools' },
{ text: 'Run Provenance', link: '/guides/run-provenance' },
{ text: 'Integrations', link: '/guides/integrations' },
]
},
{ text: 'Integrations',
items: [
{ text: 'AI Tools & MCP', link: '/guides/ai-tools' },
{ text: 'Containers', link: '/guides/containers' },
{ text: 'GitHub Actions', link: '/guides/github-actions' },
]
},
]
Expand Down Expand Up @@ -242,6 +326,15 @@ export default defineConfig({
provider: 'local'
},

footer: {
message: 'Released under the <a href="https://github.com/flowexec/flow/blob/main/LICENSE">Apache 2.0 License</a>.',
copyright: [
`&copy; ${new Date().getFullYear()} <a href="https://jahvon.dev">Dockery Labs</a>`,
'<a href="https://jahvon.dev/architecture/flow/">Architecture</a>',
'<a href="https://mochiexec.io">Mochi</a>',
].join(' &middot; ')
},

outline: {
level: [2, 3]
}
Expand Down
130 changes: 130 additions & 0 deletions docs/.vitepress/theme/AskBridge.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import Icon from './Icon.vue'
import { openAsk } from './askState'

/**
* A row inside VitePress's own search modal that hands the current query over
* to the Ask panel — the "no page matched, ask the codebase" path.
*
* VitePress teleports its search box to <body> and exposes no slot, so the
* only way in is to observe the DOM. That means depending on three internal
* selectors, so every one of them is checked before the row renders: if a
* VitePress upgrade renames any of them the bridge quietly disappears and the
* standalone Ask button carries on working.
*/
const SELECTORS = {
box: '.VPLocalSearchBox',
shell: '.VPLocalSearchBox .shell',
input: '#localsearch-input',
backdrop: '.VPLocalSearchBox .backdrop',
}

const ready = ref(false)
const query = ref('')

let observer: MutationObserver | null = null
let input: HTMLInputElement | null = null

function syncQuery() {
query.value = input?.value.trim() ?? ''
}

function attach() {
if (ready.value) return
const shell = document.querySelector(SELECTORS.shell)
const found = document.querySelector<HTMLInputElement>(SELECTORS.input)
if (!shell || !found) return

input = found
input.addEventListener('input', syncQuery)
syncQuery()
ready.value = true
}

function detach() {
input?.removeEventListener('input', syncQuery)
input = null
ready.value = false
query.value = ''
}

function onMutation() {
if (document.querySelector(SELECTORS.box)) attach()
else if (ready.value) detach()
}

async function handoff() {
const q = query.value
// Drop the teleport before VitePress removes the node it renders into.
ready.value = false
await nextTick()
document.querySelector<HTMLElement>(SELECTORS.backdrop)?.click()
openAsk(q)
}

onMounted(() => {
observer = new MutationObserver(onMutation)
observer.observe(document.body, { childList: true, subtree: true })
})

onUnmounted(() => {
observer?.disconnect()
observer = null
detach()
})
</script>

<template>
<Teleport v-if="ready" :to="SELECTORS.shell">
<button class="ask-bridge" type="button" @click="handoff">
<Icon name="sparkle" />
<span class="ask-bridge__text">
<template v-if="query">Ask the codebase about “{{ query }}”</template>
<template v-else>Ask the codebase a question</template>
</span>
<Icon name="arrowRight" />
</button>
</Teleport>
</template>

<style scoped>
.ask-bridge {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 12px 16px;
border: 0;
border-top: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
font-size: 13.5px;
text-align: left;
cursor: pointer;
transition: color 0.18s ease, background-color 0.18s ease;
}

.ask-bridge:hover {
background: var(--vp-c-default-soft);
color: var(--vp-c-text-1);
}

.ask-bridge__text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.ask-bridge :deep(.ui-icon) {
width: 14px;
height: 14px;
}

.ask-bridge :deep(.ui-icon:first-child) {
color: var(--vp-c-brand-1);
opacity: 1;
}
</style>
Loading