@meshxdata/fops 0.0.1 → 0.0.4
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 +62 -40
- package/package.json +4 -3
- package/src/agent/agent.js +161 -68
- package/src/agent/agents.js +224 -0
- package/src/agent/context.js +287 -96
- package/src/agent/index.js +1 -0
- package/src/agent/llm.js +134 -20
- package/src/auth/coda.js +128 -0
- package/src/auth/index.js +1 -0
- package/src/auth/login.js +13 -13
- package/src/auth/oauth.js +4 -4
- package/src/commands/index.js +94 -21
- package/src/config.js +2 -2
- package/src/doctor.js +208 -22
- package/src/feature-flags.js +197 -0
- package/src/plugins/api.js +23 -0
- package/src/plugins/builtins/stack-api.js +36 -0
- package/src/plugins/index.js +1 -0
- package/src/plugins/knowledge.js +124 -0
- package/src/plugins/loader.js +67 -0
- package/src/plugins/registry.js +3 -0
- package/src/project.js +20 -1
- package/src/setup/aws.js +7 -7
- package/src/setup/setup.js +18 -12
- package/src/setup/wizard.js +86 -15
- package/src/shell.js +2 -2
- package/src/skills/foundation/SKILL.md +200 -66
- package/src/ui/confirm.js +3 -2
- package/src/ui/input.js +31 -34
- package/src/ui/spinner.js +39 -13
- package/src/ui/streaming.js +2 -2
- package/STRUCTURE.md +0 -43
- package/src/agent/agent.test.js +0 -233
- package/src/agent/context.test.js +0 -81
- package/src/agent/llm.test.js +0 -139
- package/src/auth/keychain.test.js +0 -185
- package/src/auth/login.test.js +0 -192
- package/src/auth/oauth.test.js +0 -118
- package/src/auth/resolve.test.js +0 -153
- package/src/config.test.js +0 -70
- package/src/doctor.test.js +0 -134
- package/src/plugins/api.test.js +0 -95
- package/src/plugins/discovery.test.js +0 -92
- package/src/plugins/hooks.test.js +0 -118
- package/src/plugins/manifest.test.js +0 -106
- package/src/plugins/registry.test.js +0 -43
- package/src/plugins/skills.test.js +0 -173
- package/src/project.test.js +0 -196
- package/src/setup/aws.test.js +0 -280
- package/src/shell.test.js +0 -72
- package/src/ui/banner.test.js +0 -97
- package/src/ui/spinner.test.js +0 -29
|
@@ -1,107 +1,241 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: foundation-stack
|
|
3
|
-
description:
|
|
3
|
+
description: Complete Foundation data mesh platform knowledge — domain model, API, operations, and troubleshooting
|
|
4
4
|
---
|
|
5
|
-
## Foundation Stack Management
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
## What is Foundation
|
|
7
|
+
|
|
8
|
+
Foundation is a data mesh platform. Users create **meshes** (domains), register **data systems** and **data sources**, define **data products** from **data objects**, and connect downstream **applications**. Data flows through compute pipelines (Spark/Ray) that transform raw data into queryable products via Trino SQL.
|
|
9
|
+
|
|
10
|
+
## Domain Model
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
```
|
|
13
|
+
Mesh (data domain)
|
|
14
|
+
├─ Data Product (SADP — source/atomic)
|
|
15
|
+
│ ├─ Data Object (file/table resource)
|
|
16
|
+
│ │ └─ Data Source (connector)
|
|
17
|
+
│ │ └─ Data System (external platform)
|
|
18
|
+
│ ├─ Compute (builder + transformations)
|
|
19
|
+
│ └─ Schema (column definitions)
|
|
20
|
+
├─ Data Product (CADP — composite/aggregated)
|
|
21
|
+
│ └─ depends on other Data Products
|
|
22
|
+
├─ Application (downstream consumer)
|
|
23
|
+
├─ Data System
|
|
24
|
+
└─ Data Source
|
|
12
25
|
```
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
### Entity Types
|
|
28
|
+
|
|
29
|
+
- **Mesh**: top-level domain container. Has name, label, description, purpose, assignees, security policies.
|
|
30
|
+
- **Data Product**: unit of data ownership. Two types:
|
|
31
|
+
- **SADP** (Source-Aligned): wraps raw data objects from external sources.
|
|
32
|
+
- **CADP** (Consumer-Aligned): derived from other data products via transformations.
|
|
33
|
+
- **Data Object**: atomic data resource (CSV, Parquet, table). Has path, delimiter, schema.
|
|
34
|
+
- **Data Source**: connection to external storage (S3, databases). Holds credentials via Vault.
|
|
35
|
+
- **Data System**: external platform (e.g. Procore, SAP, Salesforce).
|
|
36
|
+
- **Application**: downstream consumer of data products (dashboards, APIs, ML models).
|
|
37
|
+
- **Compute**: processing config — builder code (PySpark/Ray), transformations, scheduling.
|
|
38
|
+
|
|
39
|
+
## API Reference
|
|
40
|
+
|
|
41
|
+
Base URL: `http://localhost:9001/api`
|
|
42
|
+
Auth: `POST /api/iam/login` with `{"username":"compose@meshx.io","password":""}` → returns bearer token.
|
|
43
|
+
Headers: `Authorization: Bearer <token>`, `x-org: root`
|
|
44
|
+
|
|
45
|
+
### Mesh
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
POST /api/data/mesh — Create mesh
|
|
49
|
+
GET /api/data/mesh?identifier=<id> — Get mesh
|
|
50
|
+
GET /api/data/mesh/list — List meshes (paginated)
|
|
51
|
+
PUT /api/data/mesh — Update mesh
|
|
52
|
+
DELETE /api/data/mesh/cascade — Delete mesh + all contents
|
|
53
|
+
GET /api/data/mesh/landscape — Get mesh DAG (visual graph)
|
|
17
54
|
```
|
|
18
55
|
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
|
|
56
|
+
Create body:
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"entity": {"name":"My Mesh","entity_type":"mesh","label":"MM","description":"...","purpose":"...","assignees":[{"email":"user@meshx.io","role":""}]},
|
|
60
|
+
"entity_info": {"owner":"user@meshx.io","contact_ids":[],"links":[]}
|
|
61
|
+
}
|
|
22
62
|
```
|
|
23
63
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
64
|
+
### Data System
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
POST /api/data/data_system — Create
|
|
68
|
+
GET /api/data/data_system/list — List
|
|
69
|
+
DELETE /api/data/data_system — Delete
|
|
27
70
|
```
|
|
28
71
|
|
|
29
|
-
###
|
|
72
|
+
### Data Source
|
|
30
73
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
74
|
+
```
|
|
75
|
+
POST /api/data/data_source — Create
|
|
76
|
+
PUT /api/data/data_source/connection — Set connection (S3, etc.)
|
|
77
|
+
POST /api/data/data_source/secret — Set secrets (access keys)
|
|
78
|
+
GET /api/data/data_source/list — List
|
|
34
79
|
```
|
|
35
80
|
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
|
|
81
|
+
Connection body (S3):
|
|
82
|
+
```json
|
|
83
|
+
{"connection":{"connection_type":"s3","url":"http://foundation-storage-engine:8080","access_key":{"env_key":"S3_KEY"},"access_secret":{"env_key":"S3_SECRET"}}}
|
|
39
84
|
```
|
|
40
85
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
86
|
+
### Data Object
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
POST /api/data/data_object — Create
|
|
90
|
+
PUT /api/data/data_object/config — Configure (type, path, delimiter)
|
|
91
|
+
GET /api/data/data_object/schema — Get inferred schema
|
|
92
|
+
GET /api/data/data_object/list — List
|
|
44
93
|
```
|
|
45
94
|
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
fops logs frontend
|
|
50
|
-
fops logs postgres
|
|
95
|
+
Config body:
|
|
96
|
+
```json
|
|
97
|
+
{"configuration":{"data_object_type":"csv","path":"/spark/samples/file.csv","has_header":true,"delimiter":","}}
|
|
51
98
|
```
|
|
52
99
|
|
|
53
|
-
###
|
|
100
|
+
### Data Product
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
POST /api/data/data_product — Create (SADP or CADP)
|
|
104
|
+
PUT /api/data/data_product/schema — Set schema
|
|
105
|
+
PUT /api/data/data_product/compute/builder — Set builder + transformations
|
|
106
|
+
POST /api/data/data_product/compute/run — Trigger compute
|
|
107
|
+
GET /api/data/data_product/data — Query product data
|
|
108
|
+
POST /api/data/data_product/data/query — Trino SQL query
|
|
109
|
+
GET /api/data/data_product/list — List
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Linking Entities
|
|
54
113
|
|
|
55
|
-
|
|
114
|
+
All links follow the pattern:
|
|
115
|
+
```
|
|
116
|
+
POST /api/data/link/{parent_type}/{child_type}?identifier={parent_id}&child_identifier={child_id}
|
|
117
|
+
DELETE /api/data/link/{parent_type}/{child_type}?identifier={parent_id}&child_identifier={child_id}
|
|
118
|
+
```
|
|
56
119
|
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
120
|
+
Common links:
|
|
121
|
+
- `data_system → data_source` (source belongs to system)
|
|
122
|
+
- `data_source → data_object` (object comes from source)
|
|
123
|
+
- `data_object → data_product` (SADP wraps object)
|
|
124
|
+
- `data_product → data_product` (CADP depends on SADP)
|
|
125
|
+
- `data_product → application` (app consumes product)
|
|
61
126
|
|
|
62
|
-
|
|
127
|
+
### Applications
|
|
63
128
|
|
|
64
|
-
|
|
129
|
+
```
|
|
130
|
+
POST /api/data/application — Create
|
|
131
|
+
GET /api/data/application/list — List
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Search
|
|
65
135
|
|
|
66
|
-
|
|
136
|
+
```
|
|
137
|
+
GET /api/data/search?q=<term> — Full-text search across all entities
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Pagination
|
|
141
|
+
|
|
142
|
+
All list endpoints accept `page` and `per_page` (default 20, max 100). Response includes `total`, `page`, `per_page`, `total_pages`.
|
|
143
|
+
|
|
144
|
+
## Workflow: Creating a Data Mesh from Scratch
|
|
145
|
+
|
|
146
|
+
1. **Login**: `POST /api/iam/login` → get token
|
|
147
|
+
2. **Create Mesh**: `POST /api/data/mesh`
|
|
148
|
+
3. **Create Data Systems**: `POST /api/data/data_system` (one per external platform)
|
|
149
|
+
4. **Create Data Sources**: `POST /api/data/data_source` + configure connection + set secrets
|
|
150
|
+
5. **Link sources to systems**: `POST /api/data/link/data_system/data_source`
|
|
151
|
+
6. **Create Data Objects**: `POST /api/data/data_object` + configure (path, format)
|
|
152
|
+
7. **Link objects to sources**: `POST /api/data/link/data_source/data_object`
|
|
153
|
+
8. **Create SADPs**: `POST /api/data/data_product` (type: SADP)
|
|
154
|
+
9. **Link objects to products**: `POST /api/data/link/data_object/data_product`
|
|
155
|
+
10. **Set schemas + builders**: configure compute pipelines
|
|
156
|
+
11. **Create CADPs**: `POST /api/data/data_product` (type: CADP), link to SADPs
|
|
157
|
+
12. **Run compute**: `POST /api/data/data_product/compute/run`
|
|
158
|
+
13. **Create Applications**: link apps to products they consume
|
|
159
|
+
14. **Query data**: via Trino SQL or the data endpoint
|
|
160
|
+
|
|
161
|
+
## Services & Ports
|
|
162
|
+
|
|
163
|
+
| Service | Port | Purpose |
|
|
164
|
+
|---------|------|---------|
|
|
165
|
+
| Frontend | 3002 | Web UI (Next.js) |
|
|
166
|
+
| Backend | 9001 | REST API (Python/FastAPI) |
|
|
167
|
+
| Storage Engine | 9002 | S3-compatible storage (MinIO) |
|
|
168
|
+
| Trino | 8081 | Distributed SQL engine |
|
|
169
|
+
| OPA | 8181 | Policy/authorization engine |
|
|
170
|
+
| Kafka/Redpanda | 9092 | Event streaming |
|
|
171
|
+
| Postgres | 5432 | Metadata database |
|
|
172
|
+
| Hive Metastore | 9083 | Table metadata catalog |
|
|
173
|
+
| Vault | 18201 | Secrets management |
|
|
174
|
+
| NATS | 4222 | Message queue |
|
|
175
|
+
| Redis | 6379 | Caching |
|
|
176
|
+
| Watcher | 8000 | Compute orchestration |
|
|
177
|
+
| Scheduler | — | Job scheduling (cron) |
|
|
178
|
+
| Profiler | — | Data quality profiling |
|
|
179
|
+
|
|
180
|
+
### Access Points
|
|
181
|
+
- **Web UI**: http://localhost:3002
|
|
182
|
+
- **API**: http://localhost:9001/api
|
|
183
|
+
- **SQL Workbench**: http://localhost:3002/home/sql-workbench
|
|
184
|
+
- **Storage (MinIO)**: http://localhost:9002
|
|
185
|
+
|
|
186
|
+
## Stack Operations
|
|
187
|
+
|
|
188
|
+
### Lifecycle
|
|
67
189
|
```bash
|
|
68
|
-
|
|
190
|
+
fops up # start all services
|
|
191
|
+
fops down # stop services
|
|
192
|
+
fops down --clean # stop + remove volumes (clean slate)
|
|
193
|
+
fops status # show running containers
|
|
194
|
+
fops restart # restart all services
|
|
69
195
|
```
|
|
196
|
+
|
|
197
|
+
### Debugging
|
|
70
198
|
```bash
|
|
71
|
-
fops doctor
|
|
199
|
+
fops doctor # full environment diagnostics
|
|
200
|
+
fops doctor --fix # auto-fix detected issues
|
|
201
|
+
fops logs # tail all logs
|
|
202
|
+
fops logs backend # tail specific service
|
|
72
203
|
```
|
|
73
204
|
|
|
74
|
-
###
|
|
205
|
+
### First-Time Setup
|
|
206
|
+
```bash
|
|
207
|
+
npm install -g @meshxdata/fops
|
|
208
|
+
fops init # clone repo, set up .env, init submodules
|
|
209
|
+
fops up # boot the stack
|
|
210
|
+
fops doctor # verify health
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
If the context shows no containers running or .env not configured, **always lead with `fops init` (then `fops up`)** before anything else — don't assume the environment is ready.
|
|
75
214
|
|
|
76
|
-
|
|
215
|
+
### Suggesting Commands
|
|
216
|
+
Always suggest **2–3 commands** in separate fenced blocks. Pair primary action with follow-up:
|
|
217
|
+
- Restart → logs: `fops restart kafka` + `fops logs kafka`
|
|
218
|
+
- Debug → fix: `fops doctor` + `fops doctor --fix`
|
|
219
|
+
- Status → logs: `fops status` + `fops logs`
|
|
220
|
+
|
|
221
|
+
### Stale Images
|
|
222
|
+
Images older than 7 days are likely stale. For errors about missing packages or broken deps:
|
|
223
|
+
```bash
|
|
224
|
+
docker compose build --pull
|
|
225
|
+
```
|
|
77
226
|
|
|
78
|
-
|
|
227
|
+
## Common Issues
|
|
79
228
|
|
|
80
|
-
**
|
|
229
|
+
**"how do I create a foundation/mesh?"**: Use the Web UI at http://localhost:3002 or the API. Create a mesh first, then add systems, sources, objects, and products. See "Workflow" section above.
|
|
81
230
|
|
|
82
|
-
**
|
|
231
|
+
**Containers stuck unhealthy**: dependency not ready. `fops down` then `fops up`.
|
|
83
232
|
|
|
84
|
-
**
|
|
233
|
+
**Port conflicts**: `fops doctor` shows which ports are in use.
|
|
85
234
|
|
|
86
|
-
|
|
235
|
+
**ECR auth expired**: `fops doctor --fix` to re-authenticate.
|
|
87
236
|
|
|
88
|
-
|
|
89
|
-
|-----------------|-------|----------------------------|
|
|
90
|
-
| Backend | 9001 | Core API server |
|
|
91
|
-
| Frontend | 3002 | Web UI |
|
|
92
|
-
| Storage Engine | 9002 | Object storage layer |
|
|
93
|
-
| Trino | 8081 | Distributed SQL engine |
|
|
94
|
-
| OPA | 8181 | Policy engine |
|
|
95
|
-
| Kafka | 9092 | Event streaming |
|
|
96
|
-
| Postgres | 5432 | Metadata database |
|
|
97
|
-
| Hive Metastore | 9083 | Table metadata catalog |
|
|
98
|
-
| Vault | 18201 | Secrets management |
|
|
237
|
+
**Missing .env**: `fops setup` regenerates from `.env.example`.
|
|
99
238
|
|
|
100
|
-
|
|
239
|
+
**Backend migrations failed**: check `fops logs backend` — usually a Postgres connection issue. Ensure Postgres is healthy first, then restart: `docker compose restart foundation-backend-migrations`.
|
|
101
240
|
|
|
102
|
-
|
|
103
|
-
npm install -g @meshxdata/fops
|
|
104
|
-
fops init
|
|
105
|
-
fops up
|
|
106
|
-
fops doctor
|
|
107
|
-
```
|
|
241
|
+
**Compute jobs failing**: check watcher logs `fops logs watcher`, verify storage engine is healthy, check Spark/K3s resources.
|
package/src/ui/confirm.js
CHANGED
|
@@ -50,8 +50,9 @@ export function SelectPrompt({ message, options, onResult }) {
|
|
|
50
50
|
),
|
|
51
51
|
...options.map((opt, i) =>
|
|
52
52
|
h(Box, { key: i },
|
|
53
|
-
h(Text, { color: i === cursor ? "
|
|
54
|
-
|
|
53
|
+
h(Text, { color: "cyan" }, i === cursor ? " ❯ " : " "),
|
|
54
|
+
h(Text, { color: i === cursor ? "white" : undefined, dimColor: i !== cursor },
|
|
55
|
+
opt.label
|
|
55
56
|
)
|
|
56
57
|
)
|
|
57
58
|
)
|
package/src/ui/input.js
CHANGED
|
@@ -4,19 +4,24 @@ import { render, Box, Text, useInput, useApp } from "ink";
|
|
|
4
4
|
const h = React.createElement;
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Full-width horizontal separator line
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
function Separator() {
|
|
10
|
+
const cols = process.stdout.columns || 80;
|
|
11
|
+
return h(Text, { dimColor: true }, "\u2500".repeat(cols));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Input with history support — Claude Code style (lines + status bar)
|
|
16
|
+
*/
|
|
17
|
+
export function InputBox({ onSubmit, onExit, history = [], statusText }) {
|
|
10
18
|
const [input, setInput] = useState("");
|
|
11
19
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
12
20
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
13
21
|
const { exit } = useApp();
|
|
14
22
|
|
|
15
|
-
// Blinking cursor effect
|
|
16
23
|
useEffect(() => {
|
|
17
|
-
const interval = setInterval(() =>
|
|
18
|
-
setCursorVisible(v => !v);
|
|
19
|
-
}, 500);
|
|
24
|
+
const interval = setInterval(() => setCursorVisible(v => !v), 500);
|
|
20
25
|
return () => clearInterval(interval);
|
|
21
26
|
}, []);
|
|
22
27
|
|
|
@@ -69,29 +74,24 @@ export function InputBox({ onSubmit, onExit, history = [], placeholder = "Type a
|
|
|
69
74
|
}
|
|
70
75
|
});
|
|
71
76
|
|
|
72
|
-
const cursor = cursorVisible ? "
|
|
73
|
-
const displayText = input || "";
|
|
77
|
+
const cursor = cursorVisible ? "\u258b" : " ";
|
|
74
78
|
|
|
75
|
-
return h(Box, {
|
|
76
|
-
|
|
77
|
-
borderStyle: "round",
|
|
78
|
-
borderColor: "cyan",
|
|
79
|
-
paddingX: 1,
|
|
80
|
-
marginTop: 1,
|
|
81
|
-
},
|
|
79
|
+
return h(Box, { flexDirection: "column" },
|
|
80
|
+
h(Separator),
|
|
82
81
|
h(Box, null,
|
|
83
|
-
h(Text, { color: "cyan", bold: true }, "
|
|
84
|
-
h(Text, null,
|
|
82
|
+
h(Text, { color: "cyan", bold: true }, "\u276f "),
|
|
83
|
+
h(Text, null, input),
|
|
85
84
|
h(Text, { color: "cyan" }, cursor)
|
|
86
85
|
),
|
|
87
|
-
|
|
86
|
+
h(Separator),
|
|
87
|
+
statusText && h(Text, { color: "#888888" }, " " + statusText)
|
|
88
88
|
);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// State for standalone input
|
|
92
92
|
let inputState = { resolve: null, history: [] };
|
|
93
93
|
|
|
94
|
-
export function StandaloneInput({
|
|
94
|
+
export function StandaloneInput({ onResult, statusText }) {
|
|
95
95
|
const [input, setInput] = useState("");
|
|
96
96
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
|
97
97
|
const [cursorVisible, setCursorVisible] = useState(true);
|
|
@@ -151,43 +151,40 @@ export function StandaloneInput({ placeholder, onResult }) {
|
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
paddingX: 1,
|
|
159
|
-
},
|
|
154
|
+
const cursor = cursorVisible ? "\u258b" : " ";
|
|
155
|
+
|
|
156
|
+
return h(Box, { flexDirection: "column" },
|
|
157
|
+
h(Separator),
|
|
160
158
|
h(Box, null,
|
|
161
|
-
h(Text, { color: "cyan", bold: true }, "
|
|
159
|
+
h(Text, { color: "cyan", bold: true }, "\u276f "),
|
|
162
160
|
h(Text, null, input),
|
|
163
|
-
h(Text, { color: "cyan" },
|
|
161
|
+
h(Text, { color: "cyan" }, cursor)
|
|
164
162
|
),
|
|
165
|
-
|
|
163
|
+
h(Separator),
|
|
164
|
+
statusText && h(Text, { color: "#888888" }, " " + statusText)
|
|
166
165
|
);
|
|
167
166
|
}
|
|
168
167
|
|
|
169
168
|
/**
|
|
170
|
-
* Prompt for input with history support
|
|
169
|
+
* Prompt for input with history support — Claude Code style
|
|
171
170
|
* Returns the input string or null if cancelled
|
|
172
171
|
*/
|
|
173
|
-
export async function promptInput(
|
|
172
|
+
export async function promptInput(statusText = "/exit to quit \u00b7 \u2191\u2193 history \u00b7 esc to cancel") {
|
|
174
173
|
return new Promise((resolve) => {
|
|
175
174
|
let resolved = false;
|
|
176
|
-
let userInput = null;
|
|
177
175
|
const onResult = (result) => {
|
|
178
176
|
if (resolved) return;
|
|
179
177
|
resolved = true;
|
|
180
|
-
userInput = result;
|
|
181
178
|
clear();
|
|
182
179
|
unmount();
|
|
183
180
|
// Echo what user typed and reset terminal
|
|
184
181
|
process.stdout.write("\x1b[0m\n");
|
|
185
182
|
if (result) {
|
|
186
|
-
console.log("\x1b[36m
|
|
183
|
+
console.log("\x1b[36m\u276f\x1b[0m " + result + "\n");
|
|
187
184
|
}
|
|
188
185
|
setTimeout(() => resolve(result), 50);
|
|
189
186
|
};
|
|
190
|
-
const { unmount, clear } = render(h(StandaloneInput, {
|
|
187
|
+
const { unmount, clear } = render(h(StandaloneInput, { onResult, statusText }));
|
|
191
188
|
});
|
|
192
189
|
}
|
|
193
190
|
|
package/src/ui/spinner.js
CHANGED
|
@@ -6,7 +6,7 @@ const h = React.createElement;
|
|
|
6
6
|
|
|
7
7
|
const SPARKLE_FRAMES = ["✻", "✼", "✻", "✦"];
|
|
8
8
|
const SPARKLE_INTERVAL = 120;
|
|
9
|
-
const INTENT_LINE = chalk.cyan("⏺") + chalk.
|
|
9
|
+
const INTENT_LINE = chalk.cyan("⏺") + chalk.dim(" Thinking...");
|
|
10
10
|
|
|
11
11
|
// Claude-style verbs for the spinner
|
|
12
12
|
export const VERBS = [
|
|
@@ -55,11 +55,11 @@ export function ThinkingSpinner({ message }) {
|
|
|
55
55
|
return h(Box, { flexDirection: "column" },
|
|
56
56
|
h(Box, null,
|
|
57
57
|
h(Text, { color: "cyan" }, "⏺"),
|
|
58
|
-
h(Text, {
|
|
58
|
+
h(Text, { dimColor: true }, " Thinking...")
|
|
59
59
|
),
|
|
60
60
|
h(Box, null,
|
|
61
61
|
h(Text, { color: "magenta" }, SPARKLE_FRAMES[frame]),
|
|
62
|
-
h(Text, {
|
|
62
|
+
h(Text, { dimColor: true }, ` ${message || `${verb}…`} `),
|
|
63
63
|
h(Text, { dimColor: true }, "(esc to interrupt)")
|
|
64
64
|
)
|
|
65
65
|
);
|
|
@@ -89,8 +89,9 @@ export function renderSpinner(message) {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Thinking display component - shows what the agent is doing
|
|
92
|
+
* Displays reasoning text (thinking) and response preview separately
|
|
92
93
|
*/
|
|
93
|
-
function ThinkingDisplay({ status, detail, content }) {
|
|
94
|
+
function ThinkingDisplay({ status, detail, thinking, content }) {
|
|
94
95
|
const [verb, setVerb] = useState(getRandomVerb());
|
|
95
96
|
const [frame, setFrame] = useState(0);
|
|
96
97
|
|
|
@@ -106,6 +107,20 @@ function ThinkingDisplay({ status, detail, content }) {
|
|
|
106
107
|
return () => clearInterval(interval);
|
|
107
108
|
}, []);
|
|
108
109
|
|
|
110
|
+
// Show last 4 non-empty lines of thinking text
|
|
111
|
+
const thinkingPreview = (() => {
|
|
112
|
+
if (!thinking) return null;
|
|
113
|
+
const lines = thinking.split("\n").filter(l => l.trim());
|
|
114
|
+
return lines.slice(-4).join("\n");
|
|
115
|
+
})();
|
|
116
|
+
|
|
117
|
+
// Show last 4 non-empty lines of response content
|
|
118
|
+
const contentPreview = (() => {
|
|
119
|
+
if (!content) return null;
|
|
120
|
+
const lines = content.split("\n").filter(l => l.trim());
|
|
121
|
+
return lines.slice(-4).join("\n");
|
|
122
|
+
})();
|
|
123
|
+
|
|
109
124
|
return h(Box, { flexDirection: "column" },
|
|
110
125
|
// Status line with spinner
|
|
111
126
|
h(Box, null,
|
|
@@ -113,37 +128,40 @@ function ThinkingDisplay({ status, detail, content }) {
|
|
|
113
128
|
h(Text, { color: "yellow" }, ` ${status || verb}... `),
|
|
114
129
|
detail && h(Text, { dimColor: true }, detail)
|
|
115
130
|
),
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
h(Text, { dimColor: true },
|
|
119
|
-
|
|
120
|
-
|
|
131
|
+
// Thinking preview (dimmed, italic)
|
|
132
|
+
thinkingPreview && h(Box, { marginLeft: 2 },
|
|
133
|
+
h(Text, { dimColor: true }, thinkingPreview)
|
|
134
|
+
),
|
|
135
|
+
// Response content preview
|
|
136
|
+
contentPreview && h(Box, { marginLeft: 2 },
|
|
137
|
+
h(Text, { dimColor: true }, contentPreview)
|
|
121
138
|
)
|
|
122
139
|
);
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
// State for thinking display
|
|
126
|
-
let thinkingState = { status: "", detail: "", content: "", rerender: null };
|
|
143
|
+
let thinkingState = { status: "", detail: "", thinking: "", content: "", rerender: null };
|
|
127
144
|
|
|
128
145
|
/**
|
|
129
146
|
* Render thinking display
|
|
130
|
-
* Returns controls to update status and content
|
|
147
|
+
* Returns controls to update status, thinking text, and content
|
|
131
148
|
*/
|
|
132
149
|
export function renderThinking() {
|
|
133
|
-
thinkingState = { status: "", detail: "", content: "" };
|
|
150
|
+
thinkingState = { status: "", detail: "", thinking: "", content: "" };
|
|
134
151
|
|
|
135
152
|
const update = () => {
|
|
136
153
|
if (thinkingState.rerender) {
|
|
137
154
|
thinkingState.rerender(h(ThinkingDisplay, {
|
|
138
155
|
status: thinkingState.status,
|
|
139
156
|
detail: thinkingState.detail,
|
|
157
|
+
thinking: thinkingState.thinking,
|
|
140
158
|
content: thinkingState.content,
|
|
141
159
|
}));
|
|
142
160
|
}
|
|
143
161
|
};
|
|
144
162
|
|
|
145
163
|
const { rerender, unmount, clear } = render(
|
|
146
|
-
h(ThinkingDisplay, { status: "", detail: "", content: "" })
|
|
164
|
+
h(ThinkingDisplay, { status: "", detail: "", thinking: "", content: "" })
|
|
147
165
|
);
|
|
148
166
|
thinkingState.rerender = rerender;
|
|
149
167
|
|
|
@@ -153,6 +171,14 @@ export function renderThinking() {
|
|
|
153
171
|
thinkingState.detail = detail;
|
|
154
172
|
update();
|
|
155
173
|
},
|
|
174
|
+
setThinking: (text) => {
|
|
175
|
+
thinkingState.thinking = text;
|
|
176
|
+
update();
|
|
177
|
+
},
|
|
178
|
+
appendThinking: (text) => {
|
|
179
|
+
thinkingState.thinking += text;
|
|
180
|
+
update();
|
|
181
|
+
},
|
|
156
182
|
setContent: (content) => {
|
|
157
183
|
thinkingState.content = content;
|
|
158
184
|
update();
|
package/src/ui/streaming.js
CHANGED
|
@@ -11,7 +11,7 @@ export function ResponseBox({ content, title = "Claude" }) {
|
|
|
11
11
|
return h(Box, {
|
|
12
12
|
flexDirection: "column",
|
|
13
13
|
borderStyle: "round",
|
|
14
|
-
borderColor: "
|
|
14
|
+
borderColor: "white",
|
|
15
15
|
paddingX: 1,
|
|
16
16
|
marginY: 1,
|
|
17
17
|
},
|
|
@@ -64,7 +64,7 @@ export function StreamingResponse({ title = "Claude" }) {
|
|
|
64
64
|
return h(Box, {
|
|
65
65
|
flexDirection: "column",
|
|
66
66
|
borderStyle: "round",
|
|
67
|
-
borderColor: "
|
|
67
|
+
borderColor: "white",
|
|
68
68
|
paddingX: 1,
|
|
69
69
|
marginTop: 1,
|
|
70
70
|
},
|
package/STRUCTURE.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Foundation CLI — Project structure
|
|
2
|
-
|
|
3
|
-
Long-term layout for a single package inside the foundation-compose repo. No git submodules required; the CLI lives in `foundation-cli/` and is versioned with the repo.
|
|
4
|
-
|
|
5
|
-
## Layout
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
foundation-cli/
|
|
9
|
-
├── foundation.mjs # Entry point: parses argv, registers commands, runs
|
|
10
|
-
├── package.json
|
|
11
|
-
├── README.md
|
|
12
|
-
├── STRUCTURE.md # This file
|
|
13
|
-
├── install.sh # Optional curl | bash installer
|
|
14
|
-
└── src/
|
|
15
|
-
├── config.js # PKG, CLI_BRAND, printFoundationBanner
|
|
16
|
-
├── project.js # rootDir, requireRoot, isFoundationRoot, findComposeRootUp
|
|
17
|
-
├── shell.js # make(), dockerCompose()
|
|
18
|
-
├── auth.js # resolveAnthropicApiKey, resolveOpenAiApiKey, authHelp, offerClaudeLogin
|
|
19
|
-
├── setup.js # runSetup, runInitWizard, checkEcrRepos
|
|
20
|
-
├── agent.js # gatherStackContext, runAgentSingleTurn, runAgentInteractive, streaming
|
|
21
|
-
├── doctor.js # runDoctor (checks + optional --fix)
|
|
22
|
-
└── commands/
|
|
23
|
-
└── index.js # registerCommands(program) — wires all CLI commands
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Conventions
|
|
27
|
-
|
|
28
|
-
- **Single entry**: `foundation.mjs` is the only file executed; it imports `src/commands/index.js` and calls `registerCommands(program)`.
|
|
29
|
-
- **No circular imports**: Flow is entry → commands → lib (config, project, shell, auth, setup, agent, doctor). Lib modules do not import commands.
|
|
30
|
-
- **ESM only**: `"type": "module"` in package.json; all sources use `import`/`export`.
|
|
31
|
-
- **Shared helpers**: Project root detection and `make`/docker are in `project.js` and `shell.js` so every command reuses the same semantics.
|
|
32
|
-
|
|
33
|
-
## Adding a command
|
|
34
|
-
|
|
35
|
-
1. Implement logic in the appropriate `src/*.js` (or a new one if it’s a new domain).
|
|
36
|
-
2. In `src/commands/index.js`, add a `program.command(...).action(...)` that calls your function and uses `requireRoot(program)` or `rootDir()` as needed.
|
|
37
|
-
|
|
38
|
-
## Optional: separate repo (submodule)
|
|
39
|
-
|
|
40
|
-
If you later move the CLI to its own repo and add it as a submodule under `foundation-compose/foundation-cli`:
|
|
41
|
-
|
|
42
|
-
- Keep this same layout inside the submodule.
|
|
43
|
-
- Root `setup.sh` and docs can still say “run `node foundation-cli/foundation.mjs`” or “`npx foundation`” if the package is published.
|