@sqaoss/flowy 1.0.2 → 1.1.0

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 CHANGED
@@ -1,62 +1,89 @@
1
1
  # Flowy
2
2
 
3
- Project management for AI coding agents.
3
+ Agentic persistent planning
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/@sqaoss/flowy)](https://www.npmjs.com/package/@sqaoss/flowy)
6
6
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
7
7
  [![CI](https://github.com/sqaoss/flowy/actions/workflows/ci.yml/badge.svg)](https://github.com/sqaoss/flowy/actions/workflows/ci.yml)
8
8
 
9
- ## What is Flowy
9
+ Jira, Linear, Trello are built for humans clicking boards. AI agents don't click boards. When your agent needs to plan work, track progress, and close tickets, those tools add friction, load context, and get in the way.
10
10
 
11
- Flowy is a backend + CLI that gives AI coding agents structured project management. It enforces a strict hierarchy -- **client -> project -> feature -> task** -- so agents always have clear context about what they're working on.
11
+ Flowy is where agents store plans and flow through execution. Features are master plans. Tasks are execution steps. Everything persists in a database, not as files cluttering your git history. Your agent flows through work without friction.
12
12
 
13
- Run it locally with SQLite and Docker, or connect to the hosted SaaS for multi-agent team collaboration.
13
+ You get full observability on what every agent planned, built, and shipped.
14
14
 
15
- ## Quick Start (Local Mode)
15
+ ## Get Started
16
16
 
17
- Local mode runs a Flowy server on your machine using Docker. No account needed.
17
+ ### Install (once)
18
18
 
19
19
  ```bash
20
- # Install
21
- bun add -g @sqaoss/flowy # or: npm i -g @sqaoss/flowy
20
+ npm i -g @sqaoss/flowy
21
+ flowy setup remote --email you@example.com
22
+ ```
22
23
 
23
- # Start the local server (requires Docker)
24
- flowy setup local
24
+ ### Initialize a project
25
25
 
26
- # Set your client name
27
- flowy client set name "Acme Corp"
26
+ ```bash
27
+ cd my-project
28
+ flowy init # auto-detects repo, creates project
29
+ ```
28
30
 
29
- # Create a project and map it to the current directory
30
- flowy project create "Auth System"
31
- flowy project set "Auth System"
31
+ ### Start planning
32
32
 
33
- # Plan a feature
34
- flowy feature create --title "SSO Support" --description sso-spec.md
35
- flowy feature set "SSO Support"
33
+ ```bash
34
+ flowy feature create --title "User Auth" --description auth-spec.md
35
+ flowy feature set "User Auth"
36
36
 
37
- # Create tasks
38
37
  flowy task create --title "Implement OAuth" --description oauth.md
39
- flowy task create --title "Write auth tests" --description "Unit + integration tests"
38
+ flowy task create --title "Write tests" --description "Unit + integration"
40
39
 
41
- # Track progress
42
40
  flowy status <task-id> in_progress
43
41
  flowy status <task-id> done
42
+ ```
43
+
44
+ Every command outputs JSON. Your agent reads it, acts on it, moves to the next task.
45
+
46
+ ## Agent Skill
47
+
48
+ Flowy installs an agent skill during setup. Your AI agent automatically knows every command. No manual configuration needed.
49
+
50
+ Or install the skill manually: `npx skills add sqaoss/flowy`
51
+
52
+ See [skills/using-flowy/SKILL.md](skills/using-flowy/SKILL.md) for the full skill reference.
53
+
54
+ ## Data Model
44
55
 
45
- # Search and explore
46
- flowy search "OAuth" --type task
47
- flowy tree <project-id> --depth 3
48
56
  ```
57
+ project -> feature -> task
58
+ 1:many 1:many
59
+ ```
60
+
61
+ Every task belongs to a feature. Every feature belongs to a project. No orphans.
49
62
 
50
- ## Remote Mode (Coming Soon)
63
+ ### Status Flow
51
64
 
52
- Remote mode connects to the hosted Flowy SaaS for multi-agent collaboration, shared project state across teams, and persistent history. Registration and API key setup will happen directly through the CLI -- no website needed. This is currently a work in progress.
65
+ ```
66
+ draft -> pending_review -> approved -> in_progress -> done
67
+ ```
68
+
69
+ Also: `blocked`, `cancelled`. Only `pending_review` entities can be approved.
70
+
71
+ ## Self-Hosted
72
+
73
+ Run Flowy on your own machine with SQLite and Docker. Same CLI, same commands.
74
+
75
+ ```bash
76
+ flowy setup local # starts a local server via Docker
77
+ flowy init # auto-detects repo
78
+ ```
53
79
 
54
80
  ## Command Reference
55
81
 
56
82
  | Command | Description |
57
83
  |---------|-------------|
84
+ | `setup remote --email <email>` | Register and connect to the hosted server |
58
85
  | `setup local` | Start a local Docker server and configure the CLI |
59
- | `setup remote` | Connect to the hosted SaaS (coming soon) |
86
+ | `init` | Auto-detect repo and create/map project |
60
87
  | `whoami` | Show current user |
61
88
  | `client set name <name>` | Set client display name |
62
89
  | `project create <name>` | Create project |
@@ -80,63 +107,27 @@ Remote mode connects to the hosted Flowy SaaS for multi-agent collaboration, sha
80
107
 
81
108
  All commands output JSON to stdout.
82
109
 
83
- ## Data Model
84
-
85
- ### Entity Hierarchy
86
-
87
- ```
88
- client -> project -> feature -> task
89
- 1:many 1:many 1:many
90
- ```
91
-
92
- Every entity belongs to its parent. No orphans.
93
-
94
- ### Status Flow
95
-
96
- ```
97
- draft -> pending_review -> approved -> in_progress -> done
98
- ```
99
-
100
- Also: `blocked`, `cancelled`
101
-
102
- ### Description Field
103
-
104
- `--description` accepts a file path or an inline string:
105
- - `--description spec.md` -- reads file content
106
- - `--description "Do the thing"` -- sends string as-is
107
-
108
110
  ## Configuration
109
111
 
110
112
  Config is stored at `~/.config/flowy/config.json`.
111
113
 
112
114
  | Variable | Description | Default |
113
115
  |----------|-------------|---------|
114
- | `FLOWY_API_URL` | GraphQL endpoint | `http://localhost:4000/graphql` |
115
- | `FLOWY_API_KEY` | API key (remote mode only) | -- |
116
+ | `FLOWY_API_URL` | GraphQL endpoint | `https://flowy-ai.fly.dev/graphql` |
117
+ | `FLOWY_API_KEY` | API key (remote mode) | -- |
116
118
  | `FLOWY_PROJECT` | Override active project by name | -- |
117
119
  | `FLOWY_FEATURE` | Override active feature by ID | -- |
118
120
 
119
- ## For AI Agents
120
-
121
- Flowy integrates with [TanStack Intent](https://github.com/TanStack/intent) for automatic tool discovery. Run:
122
-
123
- ```bash
124
- npx @tanstack/intent install
125
- ```
126
-
127
- This auto-discovers the Flowy skill and makes it available to your agent. See [`skills/using-flowy/SKILL.md`](skills/using-flowy/SKILL.md) for the full skill reference.
128
-
129
121
  ## Development
130
122
 
131
123
  ```bash
132
- bun run test # Run CLI tests (Vitest)
133
- bun run check # Biome lint + format
134
- bun run typecheck # TypeScript type checking
124
+ bun run test # CLI tests
125
+ bun run check # Lint + format
126
+ bun run typecheck # TypeScript
135
127
 
136
- # Server tests
137
- cd server && bunx --bun vitest run
128
+ cd server && bunx --bun vitest run # Server tests
138
129
  ```
139
130
 
140
131
  ## License
141
132
 
142
- Apache-2.0 -- Copyright 2026 SQA & Automation SRL
133
+ Apache-2.0. Copyright 2026 SQA & Automation SRL.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sqaoss/flowy",
3
- "version": "1.0.2",
4
- "description": "CLI for Flowy — project management for AI coding agents",
3
+ "version": "1.1.0",
4
+ "description": "Agentic persistent planning",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "flowy": "./src/index.ts"
@@ -42,7 +42,8 @@
42
42
  "ai-agents",
43
43
  "graphql",
44
44
  "bun",
45
- "tanstack-intent"
45
+ "agentic",
46
+ "planning"
46
47
  ],
47
48
  "scripts": {
48
49
  "cli": "bun src/index.ts",
@@ -60,7 +61,6 @@
60
61
  },
61
62
  "devDependencies": {
62
63
  "@biomejs/biome": "2.4.4",
63
- "@tanstack/intent": "^0.0.23",
64
64
  "@commitlint/cli": "^20.4.2",
65
65
  "@commitlint/config-conventional": "^20.4.2",
66
66
  "husky": "^9.1.7",
@@ -1,153 +1,128 @@
1
1
  ---
2
- name: using-flowy
3
- description: Project management CLI for AI coding agents create projects, features, tasks, track status and approvals
4
- metadata:
5
- sources:
6
- - src/commands/project.ts
7
- - src/commands/feature.ts
8
- - src/commands/task.ts
9
- - src/commands/status.ts
10
- - src/commands/approve.ts
11
- - src/commands/search.ts
12
- - src/commands/tree.ts
13
- - src/commands/setup.ts
14
- - src/util/config.ts
2
+ name: flowy
3
+ description: Store plans and track execution with Flowy CLI. Use when you need to create features, break work into tasks, track progress, or manage project structure. Trigger on any planning, task tracking, or work organization request.
15
4
  ---
16
5
 
17
- # Flowy — Project Management for AI Agents
6
+ # Flowy — Agentic Persistent Planning
18
7
 
19
- Flowy is a hosted project management backend. You interact with it via the `flowy` CLI.
8
+ Flowy gives you a persistent store for plans and execution tracking. Features are your master plans. Tasks are your execution steps. Everything persists across sessions, no files in git, no context lost.
20
9
 
21
- ## Setup
10
+ ## Why Use Flowy
22
11
 
23
- ```bash
24
- # Install globally
25
- bun add -g @sqaoss/flowy # or: npm i -g @sqaoss/flowy
12
+ Without Flowy, your plans live in markdown files that clutter git history, get deleted when done, and leave no record of what you accomplished. With Flowy, plans persist in a database. You flow through work without friction. Your human gets full observability.
26
13
 
27
- # SaaS mode (hosted)
28
- flowy setup --mode saas --email you@example.com
29
- export FLOWY_API_KEY=flowy_xxx_yyy
14
+ ## First Time in a Project
30
15
 
31
- # Self-hosted mode
32
- flowy setup --mode local --api-url http://localhost:4000/graphql
16
+ ```bash
17
+ flowy init # auto-detects the git repo, creates a project, maps this directory
33
18
  ```
34
19
 
35
- ## Hierarchy
20
+ If Flowy isn't set up yet, the human needs to run:
21
+ ```bash
22
+ npm i -g @sqaoss/flowy
23
+ flowy setup remote --email their@email.com
24
+ ```
36
25
 
37
- Strict top-down: **client → project → feature → task**. No orphans at any level.
26
+ ## Core Workflow
38
27
 
39
- - A client has many projects
40
- - A project has many features
41
- - A feature has many tasks
28
+ ```bash
29
+ # 1. Plan a feature (master plan)
30
+ flowy feature create --title "User Auth" --description auth-spec.md
31
+ flowy feature set "User Auth"
42
32
 
43
- ## Context
33
+ # 2. Break into tasks (execution steps)
34
+ flowy task create --title "Implement OAuth" --description oauth.md
35
+ flowy task create --title "Write tests" --description "Unit + integration tests"
44
36
 
45
- Flowy uses context to know which project and feature you're working in:
37
+ # 3. Execute and track
38
+ flowy status <task-id> in_progress
39
+ # ... do the work ...
40
+ flowy status <task-id> done
46
41
 
47
- - **Project**: mapped to a directory via `flowy project set <name>`, or `FLOWY_PROJECT` env var
48
- - **Feature**: set via `flowy feature set <name-or-id>`, or `FLOWY_FEATURE` env var
42
+ # 4. Move to next task or feature
43
+ flowy feature create --title "API Rate Limiting" --description rate-limit.md
44
+ flowy feature set "API Rate Limiting"
45
+ ```
49
46
 
50
- Features and tasks cannot be created without active project/feature context.
47
+ ## Entity Hierarchy
51
48
 
52
- ## Commands
53
-
54
- ### Identity
55
- ```bash
56
- flowy whoami # Show current user
49
+ ```
50
+ project -> feature -> task
51
+ 1:many 1:many
57
52
  ```
58
53
 
59
- ### Client
60
- ```bash
61
- flowy client set name "Acme Corp" # Set client display name
54
+ Every task belongs to a feature. Every feature belongs to a project. No orphans. The project is set automatically by `flowy init`.
55
+
56
+ ## Status Flow
57
+
58
+ ```
59
+ draft -> pending_review -> approved -> in_progress -> done
62
60
  ```
63
61
 
64
- ### Projects
62
+ Also: `blocked`, `cancelled`
63
+
64
+ Only `pending_review` entities can be approved via `flowy approve <id>`.
65
+
66
+ ## Commands
67
+
68
+ ### Project Context
65
69
  ```bash
66
- flowy project create "Auth System" # Create a project
67
- flowy project set "Auth System" # Map current dir to project
70
+ flowy init # Auto-detect repo, create + set project
68
71
  flowy project list # List all projects
69
- flowy project show # Show active project details
70
- flowy project show <id> # Show specific project
72
+ flowy project show [<id>] # Show project details
71
73
  ```
72
74
 
73
75
  ### Features (requires active project)
74
76
  ```bash
75
- flowy feature create --title "SSO Support" --description sso-spec.md
76
- flowy feature create --title "Stability" --description "Improve error handling"
77
- flowy feature set "SSO Support" # Set active feature
77
+ flowy feature create --title "Title" --description "description or file.md"
78
+ flowy feature set "Title or ID" # Set active feature
78
79
  flowy feature unset # Clear active feature
79
80
  flowy feature list # List features in project
80
- flowy feature show # Show active feature
81
+ flowy feature show [<id>] # Show feature details
81
82
  ```
82
83
 
83
84
  ### Tasks (requires active feature)
84
85
  ```bash
85
- flowy task create --title "Implement OAuth" --description oauth.md
86
- flowy task create --title "Write tests" --description "Unit tests for auth"
86
+ flowy task create --title "Title" --description "description or file.md"
87
87
  flowy task list # List tasks in feature
88
88
  flowy task show <id> # Show task details
89
- flowy task block <id1> <id2> # Mark id1 blocks id2
90
- flowy task unblock <id1> <id2> # Remove block
89
+ flowy task block <blocker> <blocked> # Mark dependency
90
+ flowy task unblock <blocker> <blocked>
91
91
  ```
92
92
 
93
- ### Status & Approval
93
+ ### Status and Approval
94
94
  ```bash
95
95
  flowy status <id> in_progress
96
- flowy status <id> done
97
96
  flowy status <id> pending_review
98
- flowy approve <id> # Must be pending_review
97
+ flowy approve <id> # Only works on pending_review
98
+ flowy status <id> done
99
99
  ```
100
100
 
101
- ### Search & Explore
101
+ ### Search and Explore
102
102
  ```bash
103
- flowy search "OAuth" --type task
104
- flowy search "auth" --status draft --limit 5
105
- flowy tree <id> --depth 3 # Show subtree
103
+ flowy search "query" --type task --status draft --limit 10
104
+ flowy tree <project-id> --depth 3 # Show full subtree
106
105
  ```
107
106
 
108
- ## Entity Types
109
- - `client` — a client or company
110
- - `project` — a codebase or product
111
- - `feature` — a unit of work (replaces epic)
112
- - `task` — an individual work item
113
-
114
- ## Status Flow
115
- `draft` → `pending_review` → `approved` → `in_progress` → `done`
116
-
117
- Also: `blocked`, `cancelled`
118
-
119
- ## Description Field
120
- `--description` accepts a file path or an inline string:
121
- - `--description spec.md` — reads file, sends raw content
122
- - `--description "Do the thing"` — sends string as-is
123
-
124
- ## Workflow Example
107
+ ## Validation Rules
125
108
 
126
- ```bash
127
- # One-time setup
128
- flowy setup --mode saas --email you@example.com
129
- flowy client set name "Acme Corp"
130
-
131
- # Create and activate project
132
- flowy project create "Auth System"
133
- flowy project set "Auth System"
109
+ - **Title is required** and cannot be empty
110
+ - **Description** is optional, but if provided cannot be empty
111
+ - **--description** accepts a file path (reads content) or an inline string
112
+ - **Search** requires at least 3 characters
113
+ - **Status** must be one of: draft, pending_review, approved, in_progress, done, blocked, cancelled
114
+ - **Blocking**: a task cannot block itself
115
+ - **Edges**: both source and target nodes must exist
134
116
 
135
- # Plan a feature
136
- flowy feature create --title "SSO Support" --description sso-spec.md
137
- flowy feature set "SSO Support"
117
+ ## Output Format
138
118
 
139
- # Break into tasks
140
- flowy task create --title "Implement OAuth" --description oauth.md
141
- flowy task create --title "Write auth tests" --description tests.md
119
+ All commands output JSON to stdout. Errors go to stderr as `{ "error": "message" }`.
142
120
 
143
- # Track progress
144
- flowy status <task-id> in_progress
145
- flowy status <task-id> done
121
+ ## Environment Variables
146
122
 
147
- # Move to next feature
148
- flowy feature create --title "API Rate Limiting" --description rate-limit.md
149
- flowy feature set "API Rate Limiting"
150
- ```
151
-
152
- ## Output Format
153
- All commands output JSON. Parse with `jq` or directly in your agent code.
123
+ | Variable | Description |
124
+ |----------|-------------|
125
+ | `FLOWY_PROJECT` | Override active project by name |
126
+ | `FLOWY_FEATURE` | Override active feature by ID |
127
+ | `FLOWY_API_URL` | GraphQL endpoint |
128
+ | `FLOWY_API_KEY` | API key (from setup) |
@@ -0,0 +1,174 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
2
+
3
+ let mockGraphql: ReturnType<typeof vi.fn>
4
+ let mockLoadConfig: ReturnType<typeof vi.fn>
5
+ let mockSaveConfig: ReturnType<typeof vi.fn>
6
+ let mockOutput: ReturnType<typeof vi.fn>
7
+ let mockOutputError: ReturnType<typeof vi.fn>
8
+ let mockSpawnSync: ReturnType<typeof vi.fn>
9
+
10
+ beforeEach(() => {
11
+ mockGraphql = vi.fn()
12
+ mockLoadConfig = vi.fn(() => ({
13
+ mode: 'saas',
14
+ apiUrl: 'https://flowy-ai.fly.dev/graphql',
15
+ apiKey: 'test-key',
16
+ client: { name: '' },
17
+ projects: {},
18
+ }))
19
+ mockSaveConfig = vi.fn()
20
+ mockOutput = vi.fn()
21
+ mockOutputError = vi.fn()
22
+ mockSpawnSync = vi.fn()
23
+
24
+ vi.doMock('../util/client.ts', () => ({
25
+ graphql: mockGraphql,
26
+ }))
27
+
28
+ vi.doMock('../util/config.ts', () => ({
29
+ loadConfig: mockLoadConfig,
30
+ saveConfig: mockSaveConfig,
31
+ }))
32
+
33
+ vi.doMock('../util/format.ts', () => ({
34
+ output: mockOutput,
35
+ outputError: mockOutputError,
36
+ }))
37
+
38
+ vi.doMock('node:child_process', () => ({
39
+ spawnSync: mockSpawnSync,
40
+ }))
41
+ })
42
+
43
+ afterEach(() => {
44
+ vi.resetModules()
45
+ vi.restoreAllMocks()
46
+ })
47
+
48
+ describe('init command', () => {
49
+ test('exports a command named "init" with no subcommands', async () => {
50
+ const { initCommand } = await import('./init.ts')
51
+ expect(initCommand.name()).toBe('init')
52
+ expect(initCommand.commands).toHaveLength(0)
53
+ })
54
+
55
+ test('detects repo name from SSH git remote URL', async () => {
56
+ mockSpawnSync
57
+ .mockReturnValueOnce({
58
+ status: 0,
59
+ stdout: '/home/user/my-repo\n',
60
+ })
61
+ .mockReturnValueOnce({
62
+ status: 0,
63
+ stdout: 'git@github.com:sqaoss/flowy.git\n',
64
+ })
65
+ mockGraphql.mockResolvedValue({
66
+ createNode: { id: 'proj_123', title: 'flowy' },
67
+ })
68
+
69
+ const { initCommand } = await import('./init.ts')
70
+ await initCommand.parseAsync([], { from: 'user' })
71
+
72
+ expect(mockGraphql).toHaveBeenCalledWith(
73
+ expect.stringContaining('createNode'),
74
+ expect.objectContaining({ type: 'project', title: 'flowy' }),
75
+ )
76
+ })
77
+
78
+ test('detects repo name from HTTPS git remote URL', async () => {
79
+ mockSpawnSync
80
+ .mockReturnValueOnce({
81
+ status: 0,
82
+ stdout: '/home/user/my-repo\n',
83
+ })
84
+ .mockReturnValueOnce({
85
+ status: 0,
86
+ stdout: 'https://github.com/sqaoss/flowy.git\n',
87
+ })
88
+ mockGraphql.mockResolvedValue({
89
+ createNode: { id: 'proj_123', title: 'flowy' },
90
+ })
91
+
92
+ const { initCommand } = await import('./init.ts')
93
+ await initCommand.parseAsync([], { from: 'user' })
94
+
95
+ expect(mockGraphql).toHaveBeenCalledWith(
96
+ expect.stringContaining('createNode'),
97
+ expect.objectContaining({ type: 'project', title: 'flowy' }),
98
+ )
99
+ })
100
+
101
+ test('falls back to directory name when no remote', async () => {
102
+ mockSpawnSync
103
+ .mockReturnValueOnce({
104
+ status: 0,
105
+ stdout: '/home/user/my-cool-project\n',
106
+ })
107
+ .mockReturnValueOnce({
108
+ status: 1,
109
+ stdout: '',
110
+ })
111
+ mockGraphql.mockResolvedValue({
112
+ createNode: { id: 'proj_456', title: 'my-cool-project' },
113
+ })
114
+
115
+ const { initCommand } = await import('./init.ts')
116
+ await initCommand.parseAsync([], { from: 'user' })
117
+
118
+ expect(mockGraphql).toHaveBeenCalledWith(
119
+ expect.stringContaining('createNode'),
120
+ expect.objectContaining({
121
+ type: 'project',
122
+ title: 'my-cool-project',
123
+ }),
124
+ )
125
+ })
126
+
127
+ test('calls graphql to create project and maps directory via config', async () => {
128
+ mockSpawnSync
129
+ .mockReturnValueOnce({
130
+ status: 0,
131
+ stdout: '/home/user/flowy\n',
132
+ })
133
+ .mockReturnValueOnce({
134
+ status: 0,
135
+ stdout: 'git@github.com:sqaoss/flowy.git\n',
136
+ })
137
+ mockGraphql.mockResolvedValue({
138
+ createNode: { id: 'proj_789', title: 'flowy' },
139
+ })
140
+
141
+ const { initCommand } = await import('./init.ts')
142
+ await initCommand.parseAsync([], { from: 'user' })
143
+
144
+ expect(mockGraphql).toHaveBeenCalledOnce()
145
+ expect(mockSaveConfig).toHaveBeenCalledWith(
146
+ expect.objectContaining({
147
+ projects: expect.objectContaining({
148
+ [process.cwd()]: { id: 'proj_789', name: 'flowy' },
149
+ }),
150
+ }),
151
+ )
152
+ expect(mockOutput).toHaveBeenCalledWith({
153
+ id: 'proj_789',
154
+ name: 'flowy',
155
+ directory: process.cwd(),
156
+ })
157
+ })
158
+
159
+ test('throws when not in a git repo', async () => {
160
+ mockSpawnSync.mockReturnValueOnce({
161
+ status: 128,
162
+ stdout: '',
163
+ })
164
+
165
+ const { initCommand } = await import('./init.ts')
166
+ await initCommand.parseAsync([], { from: 'user' })
167
+
168
+ expect(mockOutputError).toHaveBeenCalledWith(
169
+ expect.objectContaining({
170
+ message: expect.stringContaining('Not a git repository'),
171
+ }),
172
+ )
173
+ })
174
+ })
@@ -0,0 +1,50 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { basename } from 'node:path'
3
+ import { Command } from 'commander'
4
+ import { graphql } from '../util/client.ts'
5
+ import { loadConfig, saveConfig } from '../util/config.ts'
6
+ import { output, outputError } from '../util/format.ts'
7
+
8
+ export const initCommand = new Command('init')
9
+ .description('Initialize Flowy for the current git repository')
10
+ .action(async () => {
11
+ try {
12
+ const toplevel = spawnSync('git', ['rev-parse', '--show-toplevel'])
13
+ if (toplevel.status !== 0) {
14
+ throw new Error(
15
+ 'Not a git repository. Run flowy init from inside a git project.',
16
+ )
17
+ }
18
+
19
+ let repoName: string
20
+ const remote = spawnSync('git', ['remote', 'get-url', 'origin'])
21
+ if (remote.status === 0) {
22
+ const url = String(remote.stdout).trim()
23
+ repoName =
24
+ url
25
+ .split('/')
26
+ .pop()
27
+ ?.replace(/\.git$/, '') ?? ''
28
+ } else {
29
+ repoName = basename(String(toplevel.stdout).trim())
30
+ }
31
+
32
+ const data = await graphql<{ createNode: { id: string; title: string } }>(
33
+ `mutation CreateProject($type: String!, $title: String!) {
34
+ createNode(type: $type, title: $title) {
35
+ id type title description status metadata createdAt updatedAt
36
+ }
37
+ }`,
38
+ { type: 'project', title: repoName },
39
+ )
40
+
41
+ const { id, title } = data.createNode
42
+ const config = loadConfig()
43
+ const cwd = process.cwd()
44
+ config.projects[cwd] = { id, name: title }
45
+ saveConfig(config)
46
+ output({ id, name: title, directory: cwd })
47
+ } catch (error) {
48
+ outputError(error)
49
+ }
50
+ })
@@ -88,13 +88,47 @@ describe('setup command', () => {
88
88
  )
89
89
  })
90
90
 
91
- test('setup remote prints not-yet-implemented message', async () => {
91
+ test('setup remote requires --email', async () => {
92
92
  const { setupCommand } = await import('./setup.ts')
93
93
  await setupCommand.parseAsync(['remote'], { from: 'user' })
94
94
 
95
+ expect(mockOutputError).toHaveBeenCalledWith(
96
+ expect.objectContaining({
97
+ message: expect.stringContaining('--email is required'),
98
+ }),
99
+ )
100
+ })
101
+
102
+ test('setup remote registers, saves API key, and outputs result', async () => {
103
+ const mockGraphql = vi.fn().mockResolvedValue({
104
+ register: {
105
+ user: { id: 'user_1', email: 'test@example.com', tier: 'free' },
106
+ apiKey: 'flowy_test_key_123',
107
+ },
108
+ })
109
+ vi.doMock('../util/client.ts', () => ({
110
+ graphql: mockGraphql,
111
+ }))
112
+ mockSpawnSync.mockReturnValue({
113
+ status: 0,
114
+ stdout: Buffer.from(''),
115
+ })
116
+
117
+ const { setupCommand } = await import('./setup.ts')
118
+ await setupCommand.parseAsync(['remote', '--email', 'test@example.com'], {
119
+ from: 'user',
120
+ })
121
+
122
+ expect(mockSaveConfig).toHaveBeenCalledWith(
123
+ expect.objectContaining({
124
+ mode: 'remote',
125
+ apiKey: 'flowy_test_key_123',
126
+ }),
127
+ )
95
128
  expect(mockOutput).toHaveBeenCalledWith(
96
129
  expect.objectContaining({
97
- message: expect.stringContaining('not yet implemented'),
130
+ user: expect.objectContaining({ email: 'test@example.com' }),
131
+ apiKey: 'flowy_test_key_123',
98
132
  }),
99
133
  )
100
134
  })
@@ -70,13 +70,39 @@ setupCommand
70
70
 
71
71
  setupCommand
72
72
  .command('remote')
73
- .description('Set up Flowy with the remote SaaS server')
74
- .action(async () => {
73
+ .description('Connect to the hosted Flowy service')
74
+ .option('--email <email>', 'Email address for registration')
75
+ .action(async (opts) => {
75
76
  try {
76
- output({
77
- message:
78
- 'Remote mode is not yet implemented. It will support registration and API key setup via this CLI.',
77
+ if (!opts.email) {
78
+ throw new Error('--email is required for registration')
79
+ }
80
+
81
+ const { graphql } = await import('../util/client.ts')
82
+
83
+ const config = loadConfig()
84
+ config.mode = 'remote'
85
+ config.apiUrl = 'https://flowy-ai.fly.dev/graphql'
86
+ saveConfig(config)
87
+
88
+ const data = await graphql(
89
+ `mutation Register($email: String!) {
90
+ register(email: $email) {
91
+ user { id email tier }
92
+ apiKey
93
+ }
94
+ }`,
95
+ { email: opts.email },
96
+ )
97
+
98
+ config.apiKey = data.register.apiKey
99
+ saveConfig(config)
100
+
101
+ spawnSync('npx', ['skills', 'add', 'sqaoss/flowy', '--yes'], {
102
+ stdio: 'inherit',
79
103
  })
104
+
105
+ output(data.register)
80
106
  } catch (error) {
81
107
  outputError(error)
82
108
  }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { Command } from 'commander'
3
3
  import { approveCommand } from './commands/approve.ts'
4
4
  import { clientCommand } from './commands/client.ts'
5
5
  import { featureCommand } from './commands/feature.ts'
6
+ import { initCommand } from './commands/init.ts'
6
7
  import { projectCommand } from './commands/project.ts'
7
8
  import { searchCommand } from './commands/search.ts'
8
9
  import { setupCommand } from './commands/setup.ts'
@@ -16,6 +17,7 @@ const program = new Command()
16
17
  .description('Project management for AI coding agents')
17
18
  .version('0.2.0')
18
19
 
20
+ program.addCommand(initCommand)
19
21
  program.addCommand(setupCommand)
20
22
  program.addCommand(clientCommand)
21
23
  program.addCommand(projectCommand)