Skip to content

Commit 0cde18a

Browse files
committed
Merge remote-tracking branch 'origin/main' into mcp
2 parents ba29c5b + ffce9de commit 0cde18a

File tree

117 files changed

+3962
-2466
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

117 files changed

+3962
-2466
lines changed

.execs/build.flow

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ executables:
8080
else
8181
echo "npm dependencies already installed"
8282
fi
83-
npm run generate-types
83+
npm run generate-ts
8484

8585
- verb: generate
8686
name: backend

.execs/desktop.flow

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# yaml-language-server: $schema=https://flowexec.io/schemas/flowfile_schema.json
2+
namespace: desktop
23
imports: ["../desktop/package.json"]

.execs/setup.flow

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,6 @@ executables:
1818
dir: //
1919
cmd: go install ./...
2020

21-
- verb: install
22-
name: npm
23-
aliases: [node, modules]
24-
tags: [npm, desktop]
25-
exec:
26-
dir: //desktop
27-
cmd: npm install
28-
2921
- verb: start
3022
name: site
3123
aliases: [docs]

.execs/test.flow

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
tags: [development, test]
33
executables:
44
- verb: test
5-
name: all
65
description: Run all Go tests (unit + e2e) in parallel
76
parallel:
87
failFast: false

.execs/validate.flow

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ executables:
66
serial:
77
execs:
88
- ref: generate
9-
- ref: lint go
10-
- ref: test all
9+
- ref: lint
10+
- ref: test
1111
- ref: validate generated
1212
- cmd: echo "✅ All development checks passed"
1313

@@ -17,6 +17,11 @@ executables:
1717
exec:
1818
dir: //
1919
cmd: |
20+
if [ "$CI" = "" ]; then
21+
echo "Skipping diff validation"
22+
exit 0
23+
fi
24+
2025
echo "Checking for uncommitted generated files..."
2126

2227
if [ -n "$(git status --porcelain)" ]; then
@@ -30,6 +35,11 @@ executables:
3035
echo "✅ All generated files are up to date"
3136
fi
3237

38+
- verb: lint
39+
parallel:
40+
execs:
41+
- ref: lint go
42+
3343
- verb: lint
3444
name: go
3545
aliases: [cli]

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ executable_output.txt
2323
# IDE
2424
.idea
2525

26+
.claude/*
27+
2628
# build files
2729
.bin
2830
flow

CLAUDE.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# flow repo LLM Context
2+
3+
## Project Overview
4+
5+
**flow** is a workflow automation hub that helps with organizing automation across multiple projects (workspaces) with built-in secrets, templates, and cross-workspace composition. Users define workflows in YAML flow files, discover them visually, and run them anywhere.
6+
7+
This is the main repository for the flow CLI tool and desktop application, written in Go with additional desktop components in Rust/React/TypeScript.
8+
9+
## Repository Structure
10+
11+
```
12+
flow/
13+
├── cmd/ # CLI entry point and command handlers
14+
├── internal/ # Core application logic
15+
│ ├── cache/ # Executable and workspace caching logic
16+
│ ├── context/ # Global application context
17+
│ ├── io/ # Terminal user interface and I/O
18+
│ ├── runner/ # Executable execution engine
19+
│ ├── services/ # Business logic services
20+
│ ├── templates/ # Templating system for workflows
21+
│ └── vault/ # Secret management
22+
├── types/ # Generated Go types from YAML schemas
23+
├── tests/ # CLI end-to-end test suite
24+
├── docs/ # Documentation (hosted at flowexec.io)
25+
├── desktop/ # Desktop application (Tauri + React + TypeScript)
26+
│ ├── src/ # React frontend code
27+
│ ├── src-tauri/ # Rust backend code
28+
│ └── scripts/ # Build and type generation scripts
29+
└── tools/ # Code generation and build tools
30+
```
31+
32+
## Key Technologies & Frameworks
33+
34+
### Go CLI Application
35+
- **Language**: Go 1.24+ (see go.mod:3)
36+
- **CLI Framework**: Cobra (github.com/spf13/cobra)
37+
- **TUI Framework**: Custom tuikit (github.com/flowexec/tuikit) based on github.com/charmbracelet/bubbletea
38+
- **Testing**: Ginkgo BDD framework (github.com/onsi/ginkgo/v2)
39+
40+
### Desktop Application
41+
- **Frontend**: React 18 with TypeScript
42+
- **UI Library**: Mantine v8 (@mantine/core)
43+
- **Backend**: Rust with Tauri v2
44+
- **Build Tool**: Vite
45+
- **Testing**: Vitest with Storybook for component development
46+
47+
## Development Workflow
48+
49+
The project uses **flow itself** for development automation. Key commands:
50+
51+
```bash
52+
# Build the CLI binary
53+
flow build binary ./bin/flow
54+
55+
# Run all validation (tests, linting, code generation)
56+
flow validate
57+
58+
# Run specific checks
59+
flow test # All tests
60+
flow generate # Code generation
61+
flow lint # Linting only
62+
flow install tools # Install/update Go tools
63+
```
64+
65+
These "executables" are defined in the flow files in the `.execs` directory.
66+
67+
## Go Testing Strategy
68+
69+
- **Go Tests**: Uses Ginkgo BDD framework for both unit and e2e tests
70+
- **Location**: Unit tests in `internal/*_test.go`, e2e tests in `tests/`
71+
- **Run Command**: `flow test` or standard `go test`
72+
- **Focusing test**: `FDescribe`, `FIt`, `FEntry`, etc. should be used temporarily to filter when troubleshooting / writing tests
73+
74+
## Code Generation
75+
76+
The project heavily uses code generation:
77+
78+
1. **Go Types**: Generated from YAML schemas in `types/*/schema.yaml` using go-jsonschema
79+
2. **Documentation**: CLI and type docs auto-generated from the go schema definitions
80+
3. **TypeScript and Rust Types**: Generated for desktop app from JSON schemas that docgen creates
81+
82+
**Important**: Always edit schema files, not generated code!
83+
84+
## Configuration Files
85+
86+
- **flow.yaml**: Workspace configuration for the flow repo itself
87+
- **go.mod**: Go dependencies and version (Go 1.24+)
88+
- **desktop/package.json**: Node.js dependencies for desktop app
89+
- **desktop/src-tauri/tauri.conf.json**: Tauri desktop app configuration
90+
- **.execs/**: flow development workflows (executables)
91+
92+
## Development Setup
93+
94+
1. **Prerequisites**: Go 1.24+, flow CLI installed
95+
2. **Setup**: `flow workspace add flow . --set`
96+
3. **Dependencies**: `flow install tools`
97+
4. **Verification**: `flow validate`
98+
99+
## Important Context for Claude Code Sessions
100+
101+
- **Testing**: Always use `flow test` or `go test` - Ginkgo is the testing framework
102+
- **Linting**: Use `flow lint` for Go linting
103+
- **Code Gen**: Run `flow generate` after schema changes
104+
- **Desktop**: The `desktop/` directory is a separate Tauri/React app
105+
- Use `flow build desktop` to build the Tauri app
106+
- Code should be written in a way that would enable easy testing in the future
107+
- **Documentation**: Lives in `docs/` and is hosted at flowexec.io
108+
- **Build**: Use `flow build binary ./bin/flow` for development CLI builds

cmd/internal/browse.go

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/flowexec/flow/internal/io"
1313
execIO "github.com/flowexec/flow/internal/io/executable"
1414
"github.com/flowexec/flow/internal/io/library"
15+
"github.com/flowexec/flow/internal/logger"
1516
"github.com/flowexec/flow/types/executable"
1617
)
1718

@@ -32,7 +33,7 @@ func RegisterBrowseCmd(ctx *context.Context, rootCmd *cobra.Command) {
3233
),
3334
Args: cobra.MaximumNArgs(2),
3435
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
35-
execList, err := ctx.ExecutableCache.GetExecutableList(ctx.Logger)
36+
execList, err := ctx.ExecutableCache.GetExecutableList()
3637
if err != nil {
3738
return nil, cobra.ShellCompDirectiveError
3839
}
@@ -63,7 +64,7 @@ func browseFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
6364
return
6465
}
6566

66-
listMode := flags.ValueFor[bool](ctx, cmd, *flags.ListFlag, false)
67+
listMode := flags.ValueFor[bool](cmd, *flags.ListFlag, false)
6768
if listMode || !TUIEnabled(ctx, cmd) {
6869
listExecutables(ctx, cmd, args)
6970
return
@@ -73,42 +74,41 @@ func browseFunc(ctx *context.Context, cmd *cobra.Command, args []string) {
7374
}
7475

7576
func executableLibrary(ctx *context.Context, cmd *cobra.Command, _ []string) {
76-
logger := ctx.Logger
7777
if !TUIEnabled(ctx, cmd) {
78-
logger.FatalErr(errors.New("interactive discovery requires an interactive terminal"))
78+
logger.Log().FatalErr(errors.New("interactive discovery requires an interactive terminal"))
7979
}
8080

81-
wsFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterWorkspaceFlag, false)
81+
wsFilter := flags.ValueFor[string](cmd, *flags.FilterWorkspaceFlag, false)
8282
switch wsFilter {
8383
case ".":
8484
wsFilter = ctx.Config.CurrentWorkspace
8585
case executable.WildcardWorkspace:
8686
wsFilter = ""
8787
}
8888

89-
nsFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterNamespaceFlag, false)
90-
allNs := flags.ValueFor[bool](ctx, cmd, *flags.AllNamespacesFlag, false)
89+
nsFilter := flags.ValueFor[string](cmd, *flags.FilterNamespaceFlag, false)
90+
allNs := flags.ValueFor[bool](cmd, *flags.AllNamespacesFlag, false)
9191
switch {
9292
case allNs && nsFilter != "":
93-
logger.PlainTextWarn("cannot use both --all and --namespace flags, ignoring --namespace")
93+
logger.Log().PlainTextWarn("cannot use both --all and --namespace flags, ignoring --namespace")
9494
fallthrough
9595
case allNs:
9696
nsFilter = executable.WildcardNamespace
9797
case nsFilter == ".":
9898
nsFilter = ctx.Config.CurrentNamespace
9999
}
100100

101-
verbFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterVerbFlag, false)
102-
tagsFilter := flags.ValueFor[[]string](ctx, cmd, *flags.FilterTagFlag, false)
103-
subStr := flags.ValueFor[string](ctx, cmd, *flags.FilterExecSubstringFlag, false)
101+
verbFilter := flags.ValueFor[string](cmd, *flags.FilterVerbFlag, false)
102+
tagsFilter := flags.ValueFor[[]string](cmd, *flags.FilterTagFlag, false)
103+
subStr := flags.ValueFor[string](cmd, *flags.FilterExecSubstringFlag, false)
104104

105-
allExecs, err := ctx.ExecutableCache.GetExecutableList(logger)
105+
allExecs, err := ctx.ExecutableCache.GetExecutableList()
106106
if err != nil {
107-
logger.FatalErr(err)
107+
logger.Log().FatalErr(err)
108108
}
109-
allWs, err := ctx.WorkspacesCache.GetWorkspaceConfigList(logger)
109+
allWs, err := ctx.WorkspacesCache.GetWorkspaceConfigList()
110110
if err != nil {
111-
logger.FatalErr(err)
111+
logger.Log().FatalErr(err)
112112
}
113113

114114
runFunc := func(ref string) error { return runByRef(ctx, cmd, ref) }
@@ -128,32 +128,31 @@ func executableLibrary(ctx *context.Context, cmd *cobra.Command, _ []string) {
128128
}
129129

130130
func listExecutables(ctx *context.Context, cmd *cobra.Command, _ []string) {
131-
logger := ctx.Logger
132-
wsFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterWorkspaceFlag, false)
131+
wsFilter := flags.ValueFor[string](cmd, *flags.FilterWorkspaceFlag, false)
133132
if wsFilter == "." {
134133
wsFilter = ctx.Config.CurrentWorkspace
135134
}
136135

137-
nsFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterNamespaceFlag, false)
138-
allNs := flags.ValueFor[bool](ctx, cmd, *flags.AllNamespacesFlag, false)
136+
nsFilter := flags.ValueFor[string](cmd, *flags.FilterNamespaceFlag, false)
137+
allNs := flags.ValueFor[bool](cmd, *flags.AllNamespacesFlag, false)
139138
switch {
140139
case allNs && nsFilter != "":
141-
logger.PlainTextWarn("cannot use both --all and --namespace flags, ignoring --namespace")
140+
logger.Log().PlainTextWarn("cannot use both --all and --namespace flags, ignoring --namespace")
142141
fallthrough
143142
case allNs:
144143
nsFilter = executable.WildcardNamespace
145144
case nsFilter == ".":
146145
nsFilter = ctx.Config.CurrentNamespace
147146
}
148147

149-
verbFilter := flags.ValueFor[string](ctx, cmd, *flags.FilterVerbFlag, false)
150-
tagsFilter := flags.ValueFor[[]string](ctx, cmd, *flags.FilterTagFlag, false)
151-
outputFormat := flags.ValueFor[string](ctx, cmd, *flags.OutputFormatFlag, false)
152-
substr := flags.ValueFor[string](ctx, cmd, *flags.FilterExecSubstringFlag, false)
148+
verbFilter := flags.ValueFor[string](cmd, *flags.FilterVerbFlag, false)
149+
tagsFilter := flags.ValueFor[[]string](cmd, *flags.FilterTagFlag, false)
150+
outputFormat := flags.ValueFor[string](cmd, *flags.OutputFormatFlag, false)
151+
substr := flags.ValueFor[string](cmd, *flags.FilterExecSubstringFlag, false)
153152

154-
allExecs, err := ctx.ExecutableCache.GetExecutableList(logger)
153+
allExecs, err := ctx.ExecutableCache.GetExecutableList()
155154
if err != nil {
156-
logger.FatalErr(err)
155+
logger.Log().FatalErr(err)
157156
}
158157
filteredExec := allExecs
159158
filteredExec = filteredExec.
@@ -168,17 +167,15 @@ func listExecutables(ctx *context.Context, cmd *cobra.Command, _ []string) {
168167
view := execIO.NewExecutableListView(ctx, filteredExec, runFunc)
169168
SetView(ctx, cmd, view)
170169
} else {
171-
execIO.PrintExecutableList(logger, outputFormat, filteredExec)
170+
execIO.PrintExecutableList(outputFormat, filteredExec)
172171
}
173172
}
174173

175174
func viewExecutable(ctx *context.Context, cmd *cobra.Command, args []string) {
176-
logger := ctx.Logger
177-
178175
verbStr := args[0]
179176
verb := executable.Verb(verbStr)
180177
if err := verb.Validate(); err != nil {
181-
logger.FatalErr(err)
178+
logger.Log().FatalErr(err)
182179
}
183180

184181
var execID string
@@ -195,26 +192,26 @@ func viewExecutable(ctx *context.Context, cmd *cobra.Command, args []string) {
195192
}
196193
ref := executable.NewRef(execID, verb)
197194

198-
exec, err := ctx.ExecutableCache.GetExecutableByRef(logger, ref)
195+
exec, err := ctx.ExecutableCache.GetExecutableByRef(ref)
199196
if err != nil && errors.Is(cache.NewExecutableNotFoundError(ref.String()), err) {
200-
logger.Debugf("Executable %s not found in cache, syncing cache", ref)
201-
if err := ctx.ExecutableCache.Update(logger); err != nil {
202-
logger.FatalErr(err)
197+
logger.Log().Debugf("Executable %s not found in cache, syncing cache", ref)
198+
if err := ctx.ExecutableCache.Update(); err != nil {
199+
logger.Log().FatalErr(err)
203200
}
204-
exec, err = ctx.ExecutableCache.GetExecutableByRef(logger, ref)
201+
exec, err = ctx.ExecutableCache.GetExecutableByRef(ref)
205202
}
206203
if err != nil {
207-
logger.FatalErr(err)
204+
logger.Log().FatalErr(err)
208205
} else if exec == nil {
209-
logger.Fatalf("executable %s not found", ref)
206+
logger.Log().Fatalf("executable %s not found", ref)
210207
}
211208

212-
outputFormat := flags.ValueFor[string](ctx, cmd, *flags.OutputFormatFlag, false)
209+
outputFormat := flags.ValueFor[string](cmd, *flags.OutputFormatFlag, false)
213210
if TUIEnabled(ctx, cmd) {
214211
runFunc := func(ref string) error { return runByRef(ctx, cmd, ref) }
215212
view := execIO.NewExecutableView(ctx, exec, runFunc)
216213
SetView(ctx, cmd, view)
217214
} else {
218-
execIO.PrintExecutable(logger, outputFormat, exec)
215+
execIO.PrintExecutable(outputFormat, exec)
219216
}
220217
}

0 commit comments

Comments
 (0)