@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.
Files changed (52) hide show
  1. package/README.md +62 -40
  2. package/package.json +4 -3
  3. package/src/agent/agent.js +161 -68
  4. package/src/agent/agents.js +224 -0
  5. package/src/agent/context.js +287 -96
  6. package/src/agent/index.js +1 -0
  7. package/src/agent/llm.js +134 -20
  8. package/src/auth/coda.js +128 -0
  9. package/src/auth/index.js +1 -0
  10. package/src/auth/login.js +13 -13
  11. package/src/auth/oauth.js +4 -4
  12. package/src/commands/index.js +94 -21
  13. package/src/config.js +2 -2
  14. package/src/doctor.js +208 -22
  15. package/src/feature-flags.js +197 -0
  16. package/src/plugins/api.js +23 -0
  17. package/src/plugins/builtins/stack-api.js +36 -0
  18. package/src/plugins/index.js +1 -0
  19. package/src/plugins/knowledge.js +124 -0
  20. package/src/plugins/loader.js +67 -0
  21. package/src/plugins/registry.js +3 -0
  22. package/src/project.js +20 -1
  23. package/src/setup/aws.js +7 -7
  24. package/src/setup/setup.js +18 -12
  25. package/src/setup/wizard.js +86 -15
  26. package/src/shell.js +2 -2
  27. package/src/skills/foundation/SKILL.md +200 -66
  28. package/src/ui/confirm.js +3 -2
  29. package/src/ui/input.js +31 -34
  30. package/src/ui/spinner.js +39 -13
  31. package/src/ui/streaming.js +2 -2
  32. package/STRUCTURE.md +0 -43
  33. package/src/agent/agent.test.js +0 -233
  34. package/src/agent/context.test.js +0 -81
  35. package/src/agent/llm.test.js +0 -139
  36. package/src/auth/keychain.test.js +0 -185
  37. package/src/auth/login.test.js +0 -192
  38. package/src/auth/oauth.test.js +0 -118
  39. package/src/auth/resolve.test.js +0 -153
  40. package/src/config.test.js +0 -70
  41. package/src/doctor.test.js +0 -134
  42. package/src/plugins/api.test.js +0 -95
  43. package/src/plugins/discovery.test.js +0 -92
  44. package/src/plugins/hooks.test.js +0 -118
  45. package/src/plugins/manifest.test.js +0 -106
  46. package/src/plugins/registry.test.js +0 -43
  47. package/src/plugins/skills.test.js +0 -173
  48. package/src/project.test.js +0 -196
  49. package/src/setup/aws.test.js +0 -280
  50. package/src/shell.test.js +0 -72
  51. package/src/ui/banner.test.js +0 -97
  52. package/src/ui/spinner.test.js +0 -29
@@ -1,107 +1,241 @@
1
1
  ---
2
2
  name: foundation-stack
3
- description: Managing the Foundation data mesh stack with fops
3
+ description: Complete Foundation data mesh platform knowledge — domain model, API, operations, and troubleshooting
4
4
  ---
5
- ## Foundation Stack Management
6
5
 
7
- ### Lifecycle
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
- Start the full stack:
10
- ```bash
11
- fops up
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
- Stop all services:
15
- ```bash
16
- fops down
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
- Stop and remove all volumes (clean slate):
20
- ```bash
21
- fops down --clean
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
- Check what's running:
25
- ```bash
26
- fops status
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
- ### Debugging
72
+ ### Data Source
30
73
 
31
- Run full environment diagnostics:
32
- ```bash
33
- fops doctor
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
- Auto-fix detected issues:
37
- ```bash
38
- fops doctor --fix
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
- Tail logs for all services:
42
- ```bash
43
- fops logs
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
- Tail logs for a specific service:
47
- ```bash
48
- fops logs backend
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
- ### Suggesting Commands
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
- Always suggest **2–3 commands** in separate fenced blocks so the user can choose. Pair the primary action with a useful follow-up:
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
- - Restart → then logs: `fops restart kafka` + `fops logs kafka`
58
- - Debugthen fix: `fops doctor` + `fops doctor --fix`
59
- - Statusthen logs: `fops status` + `fops logs`
60
- - Setupthen verify: `fops init` + `fops doctor`
120
+ Common links:
121
+ - `data_systemdata_source` (source belongs to system)
122
+ - `data_sourcedata_object` (object comes from source)
123
+ - `data_objectdata_product` (SADP wraps object)
124
+ - `data_product → data_product` (CADP depends on SADP)
125
+ - `data_product → application` (app consumes product)
61
126
 
62
- Never suggest only 1 command when a follow-up would be useful.
127
+ ### Applications
63
128
 
64
- ### Stale Images
129
+ ```
130
+ POST /api/data/application — Create
131
+ GET /api/data/application/list — List
132
+ ```
133
+
134
+ ### Search
65
135
 
66
- If an image is more than 7 days old, it's likely stale — dependencies may have changed. When you see errors about missing packages, commands not found, or broken virtualenvs, **always check image ages first** and suggest a rebuild:
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
- docker compose build --pull
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
- ### Common Issues
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
- **Containers stuck in "unhealthy"**: Check logs for the specific service, then restart. Often caused by a dependency not being ready yet — `fops down` then `fops up` usually resolves it.
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
- **Port conflicts**: Run `fops doctor` to see which ports are in use. Kill the conflicting process or change the port mapping in `.env`.
227
+ ## Common Issues
79
228
 
80
- **ECR auth expired**: Run `fops login` or `fops doctor --fix` to re-authenticate with AWS ECR.
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
- **Missing .env**: Run `fops setup` to regenerate from `.env.example`.
231
+ **Containers stuck unhealthy**: dependency not ready. `fops down` then `fops up`.
83
232
 
84
- **Submodules out of date**: Run `fops setup` to re-init and pull submodules.
233
+ **Port conflicts**: `fops doctor` shows which ports are in use.
85
234
 
86
- ### Services & Ports
235
+ **ECR auth expired**: `fops doctor --fix` to re-authenticate.
87
236
 
88
- | Service | Port | Purpose |
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
- ### First-Time Setup
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
- ```bash
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 ? "cyan" : "gray" },
54
- ` ${i === cursor ? "" : " "} ${opt.label}`
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
- * Input box with history support
7
+ * Full-width horizontal separator line
8
8
  */
9
- export function InputBox({ onSubmit, onExit, history = [], placeholder = "Type a message..." }) {
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
- flexDirection: "column",
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, displayText),
82
+ h(Text, { color: "cyan", bold: true }, "\u276f "),
83
+ h(Text, null, input),
85
84
  h(Text, { color: "cyan" }, cursor)
86
85
  ),
87
- !input && h(Text, { dimColor: true }, placeholder)
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({ placeholder, onResult }) {
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
- return h(Box, {
155
- flexDirection: "column",
156
- borderStyle: "round",
157
- borderColor: "cyan",
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" }, cursorVisible ? "▋" : " ")
161
+ h(Text, { color: "cyan" }, cursor)
164
162
  ),
165
- !input && placeholder && h(Text, { dimColor: true }, placeholder)
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(placeholder = "Type a message... (↑↓ history, esc to exit)") {
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❯\x1b[0m " + result + "\n");
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, { placeholder, onResult }));
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.gray(" Thinking...");
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, { color: "gray" }, " Thinking...")
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, { color: "gray" }, ` ${message || `${verb}…`} `),
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
- // Content preview (truncated)
117
- content && h(Box, { marginTop: 1, marginLeft: 2 },
118
- h(Text, { dimColor: true },
119
- content.length > 100 ? content.slice(0, 100) + "..." : content
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();
@@ -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: "gray",
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: "gray",
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.