@sqaoss/flowy 0.1.1 → 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,81 +1,64 @@
1
1
  # Flowy
2
2
 
3
- CLI for Flowy — 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
- [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE)
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
- > **Note:** Registration is temporarily closed while the project is in rapid development. Existing users are unaffected.
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
- ## What is Flowy
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
- Flowy is a hosted backend project management service built for AI coding agents. It uses a graph data model (nodes and edges) to represent projects, features, epics, and tasks with typed relationships between them. This package provides the CLI that agents use to interact with the Flowy API.
13
+ You get full observability on what every agent planned, built, and shipped.
14
14
 
15
- ## Quick Start
15
+ ## Get Started
16
+
17
+ ### Install (once)
16
18
 
17
19
  ```bash
18
- # Install
19
- 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
+ ```
20
23
 
21
- # Register and get your API key
22
- flowy register --email you@example.com
23
- export FLOWY_API_KEY=flowy_xxx_yyy
24
+ ### Initialize a project
24
25
 
25
- # Create a project
26
- flowy node create --type project --title "Auth System"
26
+ ```bash
27
+ cd my-project
28
+ flowy init # auto-detects repo, creates project
29
+ ```
27
30
 
28
- # Add tasks
29
- flowy node create --type task --title "Implement OAuth"
30
- flowy node create --type task --title "Write auth tests"
31
+ ### Start planning
31
32
 
32
- # Link tasks to project
33
- flowy edge create --source <task-id> --target <project-id> --relation part_of
33
+ ```bash
34
+ flowy feature create --title "User Auth" --description auth-spec.md
35
+ flowy feature set "User Auth"
36
+
37
+ flowy task create --title "Implement OAuth" --description oauth.md
38
+ flowy task create --title "Write tests" --description "Unit + integration"
34
39
 
35
- # Track status
36
40
  flowy status <task-id> in_progress
37
41
  flowy status <task-id> done
38
-
39
- # Search and explore
40
- flowy search "OAuth" --type task
41
- flowy tree subtree <project-id> --depth 3
42
42
  ```
43
43
 
44
- ## Command Reference
44
+ Every command outputs JSON. Your agent reads it, acts on it, moves to the next task.
45
45
 
46
- | Command | Description |
47
- |---------|-------------|
48
- | `register --email <email>` | Register and get API key |
49
- | `whoami` | Show current user |
50
- | `node create --type <type> --title <title> [--description] [--status] [--metadata]` | Create node |
51
- | `node get --id <id>` | Get node |
52
- | `node list [--type] [--status] [--limit] [--offset]` | List nodes |
53
- | `node update --id <id> [--title] [--description] [--status] [--metadata]` | Update node |
54
- | `node delete --id <id>` | Delete node |
55
- | `status <id> <status>` | Update status (shorthand) |
56
- | `approve <id>` | Approve node (must be pending_review) |
57
- | `edge create --source <id> --target <id> --relation <rel>` | Create edge |
58
- | `edge list [--node <id>] [--relation <rel>]` | List edges |
59
- | `edge remove --source <id> --target <id> --relation <rel>` | Remove edge |
60
- | `search <query> [--type] [--status] [--limit]` | Search nodes |
61
- | `tree subtree <id> [--depth N]` | Show subtree |
62
- | `tree ancestors <id> [--depth N] [--relation <rel>]` | Show ancestors |
63
- | `tree descendants <id> [--depth N] [--relation <rel>]` | Show descendants |
46
+ ## Agent Skill
64
47
 
65
- All commands output JSON.
48
+ Flowy installs an agent skill during setup. Your AI agent automatically knows every command. No manual configuration needed.
66
49
 
67
- ## Data Model
50
+ Or install the skill manually: `npx skills add sqaoss/flowy`
68
51
 
69
- ### Node Types
52
+ See [skills/using-flowy/SKILL.md](skills/using-flowy/SKILL.md) for the full skill reference.
70
53
 
71
- `client`, `project`, `feature`, `epic`, `task`
54
+ ## Data Model
72
55
 
73
- ### Edge Relations
56
+ ```
57
+ project -> feature -> task
58
+ 1:many 1:many
59
+ ```
74
60
 
75
- - `part_of` -- child belongs to parent
76
- - `depends_on` -- must complete before starting
77
- - `blocks` -- prevents progress on target
78
- - `informs` -- provides context to target
61
+ Every task belongs to a feature. Every feature belongs to a project. No orphans.
79
62
 
80
63
  ### Status Flow
81
64
 
@@ -83,17 +66,68 @@ All commands output JSON.
83
66
  draft -> pending_review -> approved -> in_progress -> done
84
67
  ```
85
68
 
86
- Also: `blocked`, `cancelled`
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
+ ```
79
+
80
+ ## Command Reference
81
+
82
+ | Command | Description |
83
+ |---------|-------------|
84
+ | `setup remote --email <email>` | Register and connect to the hosted server |
85
+ | `setup local` | Start a local Docker server and configure the CLI |
86
+ | `init` | Auto-detect repo and create/map project |
87
+ | `whoami` | Show current user |
88
+ | `client set name <name>` | Set client display name |
89
+ | `project create <name>` | Create project |
90
+ | `project set <name>` | Map current directory to project |
91
+ | `project list` | List all projects |
92
+ | `project show [<id>]` | Show project details |
93
+ | `feature create --title <t> --description <d>` | Create feature (requires active project) |
94
+ | `feature set <name-or-id>` | Set active feature |
95
+ | `feature unset` | Clear active feature |
96
+ | `feature list` | List features in active project |
97
+ | `feature show [<id>]` | Show feature details |
98
+ | `task create --title <t> --description <d>` | Create task (requires active feature) |
99
+ | `task list` | List tasks in active feature |
100
+ | `task show <id>` | Show task details |
101
+ | `task block <id1> <id2>` | Mark task as blocking another |
102
+ | `task unblock <id1> <id2>` | Remove block |
103
+ | `status <id> <status>` | Update status (shorthand) |
104
+ | `approve <id>` | Approve (must be pending_review) |
105
+ | `search <query> [--type] [--status] [--limit]` | Full-text search |
106
+ | `tree <id> [--depth N]` | Show subtree |
107
+
108
+ All commands output JSON to stdout.
87
109
 
88
110
  ## Configuration
89
111
 
112
+ Config is stored at `~/.config/flowy/config.json`.
113
+
90
114
  | Variable | Description | Default |
91
115
  |----------|-------------|---------|
92
116
  | `FLOWY_API_URL` | GraphQL endpoint | `https://flowy-ai.fly.dev/graphql` |
93
- | `FLOWY_API_KEY` | API key from register | -- |
117
+ | `FLOWY_API_KEY` | API key (remote mode) | -- |
118
+ | `FLOWY_PROJECT` | Override active project by name | -- |
119
+ | `FLOWY_FEATURE` | Override active feature by ID | -- |
94
120
 
95
- ## License
121
+ ## Development
96
122
 
97
- Copyright (C) 2026 SQA & Automation SRL
123
+ ```bash
124
+ bun run test # CLI tests
125
+ bun run check # Lint + format
126
+ bun run typecheck # TypeScript
127
+
128
+ cd server && bunx --bun vitest run # Server tests
129
+ ```
130
+
131
+ ## License
98
132
 
99
- [AGPL-3.0](LICENSE)
133
+ Apache-2.0. Copyright 2026 SQA & Automation SRL.
@@ -0,0 +1,14 @@
1
+ services:
2
+ server:
3
+ build: ./server
4
+ ports:
5
+ - "4000:4000"
6
+ environment:
7
+ - NODE_ENV=production
8
+ restart: unless-stopped
9
+ healthcheck:
10
+ test: ["CMD", "bun", "-e", "const r = await fetch('http://localhost:4000/health'); if (!r.ok) process.exit(1)"]
11
+ interval: 10s
12
+ timeout: 5s
13
+ retries: 3
14
+ start_period: 5s
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "@sqaoss/flowy",
3
- "version": "0.1.1",
4
- "description": "CLI for Flowy — project management for AI coding agents. NOTE: Registration is temporarily closed while in rapid development.",
3
+ "version": "1.1.0",
4
+ "description": "Agentic persistent planning",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "flowy": "./src/index.ts"
8
8
  },
9
9
  "files": [
10
10
  "src/",
11
+ "server/src/",
12
+ "server/package.json",
13
+ "server/Dockerfile",
14
+ "skills/",
15
+ "!skills/_artifacts",
16
+ "docker-compose.yml",
11
17
  "LICENSE",
12
18
  "README.md"
13
19
  ],
14
20
  "publishConfig": {
15
- "access": "public"
21
+ "access": "public",
22
+ "provenance": true,
23
+ "registry": "https://registry.npmjs.org/"
16
24
  },
17
- "license": "AGPL-3.0-only",
25
+ "license": "Apache-2.0",
18
26
  "repository": {
19
27
  "type": "git",
20
28
  "url": "https://github.com/sqaoss/flowy.git"
@@ -33,16 +41,23 @@
33
41
  "coding-agent",
34
42
  "ai-agents",
35
43
  "graphql",
36
- "bun"
44
+ "bun",
45
+ "agentic",
46
+ "planning"
37
47
  ],
38
48
  "scripts": {
39
49
  "cli": "bun src/index.ts",
40
50
  "check": "biome check --write .",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
41
53
  "typecheck": "tsc --noEmit",
42
54
  "prepare": "husky"
43
55
  },
44
56
  "dependencies": {
45
- "commander": "^14.0.3"
57
+ "@semantic-release/changelog": "6.0.3",
58
+ "@semantic-release/git": "10.0.1",
59
+ "commander": "^14.0.3",
60
+ "semantic-release": "25.0.3"
46
61
  },
47
62
  "devDependencies": {
48
63
  "@biomejs/biome": "2.4.4",
@@ -50,7 +65,9 @@
50
65
  "@commitlint/config-conventional": "^20.4.2",
51
66
  "husky": "^9.1.7",
52
67
  "lint-staged": "^16.3.0",
53
- "typescript": "^5"
68
+ "tdd-guard-vitest": "^0.1.6",
69
+ "typescript": "^5",
70
+ "vitest": "^4.1.2"
54
71
  },
55
72
  "lint-staged": {
56
73
  "*.{ts,tsx,js,jsx}": [
@@ -0,0 +1,14 @@
1
+ FROM oven/bun:1.3.11 AS base
2
+ WORKDIR /app
3
+
4
+ FROM base AS install
5
+ COPY package.json bun.lock ./
6
+ RUN bun install --frozen-lockfile --production
7
+
8
+ FROM base
9
+ COPY --from=install /app/node_modules node_modules
10
+ COPY src src
11
+ COPY tsconfig.json .
12
+
13
+ EXPOSE 4000
14
+ CMD ["bun", "src/index.ts"]
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@sqaoss/flowy-server",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun --watch src/index.ts",
8
+ "start": "bun src/index.ts",
9
+ "test": "bunx --bun vitest run",
10
+ "test:watch": "bunx --bun vitest",
11
+ "check": "biome check --write .",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "graphql": "16.13.2",
16
+ "graphql-yoga": "5.18.1",
17
+ "nanoid": "5.1.7"
18
+ },
19
+ "devDependencies": {
20
+ "@biomejs/biome": "2.4.4",
21
+ "@types/bun": "^1.3.11",
22
+ "typescript": "^5",
23
+ "vitest": "^4.1.2"
24
+ }
25
+ }
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createDb } from './db.ts'
3
+
4
+ describe('createDb', () => {
5
+ it('creates nodes and edges tables', () => {
6
+ const db = createDb(':memory:')
7
+
8
+ const tables = db.raw
9
+ .query<{ name: string }, []>(
10
+ "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name",
11
+ )
12
+ .all()
13
+ .map((r) => r.name)
14
+
15
+ expect(tables).toContain('nodes')
16
+ expect(tables).toContain('edges')
17
+
18
+ db.close()
19
+ })
20
+
21
+ it('rejects invalid node type', () => {
22
+ const db = createDb(':memory:')
23
+
24
+ expect(() =>
25
+ db.raw.run(
26
+ "INSERT INTO nodes (id, type, title) VALUES ('n1', 'invalid_type', 'Test')",
27
+ ),
28
+ ).toThrow()
29
+
30
+ db.close()
31
+ })
32
+
33
+ it('rejects invalid status', () => {
34
+ const db = createDb(':memory:')
35
+
36
+ expect(() =>
37
+ db.raw.run(
38
+ "INSERT INTO nodes (id, type, title, status) VALUES ('n1', 'project', 'Test', 'invalid_status')",
39
+ ),
40
+ ).toThrow()
41
+
42
+ db.close()
43
+ })
44
+
45
+ it('rejects invalid edge relation', () => {
46
+ const db = createDb(':memory:')
47
+
48
+ db.raw.run(
49
+ "INSERT INTO nodes (id, type, title) VALUES ('n1', 'project', 'P1')",
50
+ )
51
+ db.raw.run(
52
+ "INSERT INTO nodes (id, type, title) VALUES ('n2', 'feature', 'F1')",
53
+ )
54
+
55
+ expect(() =>
56
+ db.raw.run(
57
+ "INSERT INTO edges (source_id, target_id, relation) VALUES ('n2', 'n1', 'invalid_rel')",
58
+ ),
59
+ ).toThrow()
60
+
61
+ db.close()
62
+ })
63
+
64
+ it('creates indexes on nodes and edges', () => {
65
+ const db = createDb(':memory:')
66
+
67
+ const indexes = db.raw
68
+ .query<{ name: string }, []>(
69
+ "SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%' ORDER BY name",
70
+ )
71
+ .all()
72
+ .map((r) => r.name)
73
+
74
+ expect(indexes).toContain('idx_nodes_type')
75
+ expect(indexes).toContain('idx_nodes_status')
76
+ expect(indexes).toContain('idx_edges_target')
77
+ expect(indexes).toContain('idx_edges_source')
78
+
79
+ db.close()
80
+ })
81
+
82
+ it('enables foreign keys', () => {
83
+ const db = createDb(':memory:')
84
+
85
+ const result = db.raw
86
+ .query<{ foreign_keys: number }, []>('PRAGMA foreign_keys')
87
+ .get()
88
+
89
+ expect(result?.foreign_keys).toBe(1)
90
+
91
+ db.close()
92
+ })
93
+ })
@@ -0,0 +1,47 @@
1
+ import { Database } from 'bun:sqlite'
2
+
3
+ export type FlowyDb = ReturnType<typeof createDb>
4
+
5
+ export function createDb(path: string) {
6
+ const db = new Database(path)
7
+
8
+ db.run('PRAGMA journal_mode = WAL')
9
+ db.run('PRAGMA foreign_keys = ON')
10
+
11
+ db.run(`
12
+ CREATE TABLE IF NOT EXISTS nodes (
13
+ id TEXT PRIMARY KEY,
14
+ type TEXT NOT NULL CHECK(type IN ('project', 'feature', 'task')),
15
+ title TEXT NOT NULL,
16
+ description TEXT,
17
+ status TEXT NOT NULL DEFAULT 'draft' CHECK(status IN ('draft', 'pending_review', 'approved', 'in_progress', 'done', 'blocked', 'cancelled')),
18
+ metadata TEXT,
19
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
20
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
21
+ )
22
+ `)
23
+
24
+ db.run(`
25
+ CREATE TABLE IF NOT EXISTS edges (
26
+ source_id TEXT NOT NULL REFERENCES nodes(id),
27
+ target_id TEXT NOT NULL REFERENCES nodes(id),
28
+ relation TEXT NOT NULL CHECK(relation IN ('part_of', 'blocks')),
29
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
30
+ PRIMARY KEY (source_id, target_id, relation)
31
+ )
32
+ `)
33
+
34
+ db.run('CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type)')
35
+ db.run('CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status)')
36
+ db.run(
37
+ 'CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id, relation)',
38
+ )
39
+ db.run(
40
+ 'CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id, relation)',
41
+ )
42
+
43
+ return {
44
+ raw: db,
45
+ close: () => db.close(),
46
+ }
47
+ }
@@ -0,0 +1,25 @@
1
+ import { afterEach, describe, expect, it } from 'vitest'
2
+ import { createServer } from './index.ts'
3
+
4
+ describe('createServer', () => {
5
+ let instance: ReturnType<typeof createServer> | undefined
6
+
7
+ afterEach(() => {
8
+ instance?.close()
9
+ instance = undefined
10
+ })
11
+
12
+ it('exports a createServer function', () => {
13
+ expect(typeof createServer).toBe('function')
14
+ })
15
+
16
+ it('responds to /health with status ok', async () => {
17
+ instance = createServer({ dbPath: ':memory:', port: 0 })
18
+
19
+ const res = await fetch(`http://localhost:${instance.port}/health`)
20
+ const json = await res.json()
21
+
22
+ expect(res.status).toBe(200)
23
+ expect(json).toEqual({ status: 'ok' })
24
+ })
25
+ })
@@ -0,0 +1,45 @@
1
+ import { createSchema, createYoga } from 'graphql-yoga'
2
+ import { createDb } from './db.ts'
3
+ import { createResolvers } from './resolvers.ts'
4
+ import { typeDefs } from './schema.ts'
5
+
6
+ export function createServer(opts?: { dbPath?: string; port?: number }) {
7
+ const dbPath = opts?.dbPath ?? process.env.FLOWY_DB_PATH ?? './flowy.sqlite'
8
+ const port = opts?.port ?? Number(process.env.PORT ?? 4000)
9
+
10
+ const db = createDb(dbPath)
11
+ const resolvers = createResolvers(db)
12
+
13
+ const yoga = createYoga({
14
+ schema: createSchema({ typeDefs, resolvers }),
15
+ graphqlEndpoint: '/graphql',
16
+ })
17
+
18
+ const server = Bun.serve({
19
+ port,
20
+ fetch(req) {
21
+ const url = new URL(req.url)
22
+ if (url.pathname === '/health' && req.method === 'GET') {
23
+ return Response.json({ status: 'ok' })
24
+ }
25
+ return yoga.fetch(req)
26
+ },
27
+ })
28
+
29
+ return {
30
+ server,
31
+ port: server.port,
32
+ db,
33
+ close() {
34
+ server.stop()
35
+ db.close()
36
+ },
37
+ }
38
+ }
39
+
40
+ if (import.meta.main) {
41
+ const { port } = createServer()
42
+ console.log(`Flowy local server running on http://localhost:${port}`)
43
+ console.log(` GraphQL: http://localhost:${port}/graphql`)
44
+ console.log(` Health: http://localhost:${port}/health`)
45
+ }