@mindstudio-ai/remy 0.1.0 → 0.1.2

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
@@ -43,19 +43,19 @@ Remy saves conversation history to `.remy-session.json` in the working directory
43
43
 
44
44
  ## Tools
45
45
 
46
- Remy's tool set depends on the project state. The sandbox tells remy whether the project has generated code in `dist/` via the `projectHasCode` field on messages.
46
+ Tool availability depends on the project's onboarding state, sent by the sandbox on each message.
47
47
 
48
48
  ### Always Available
49
49
 
50
50
  | Tool | Description |
51
51
  |------|-------------|
52
- | `setViewMode` | Switch the IDE view (intake, preview, spec, code, databases, scenarios, logs) |
52
+ | `setProjectOnboardingState` | Advance the onboarding flow (intake initialSpecAuthoring initialCodegen onboardingFinished) |
53
53
  | `promptUser` | Ask the user structured questions (form or inline display) |
54
- | `clearSyncStatus` | Clear sync flags after syncing spec and code |
54
+ | `confirmDestructiveAction` | Confirm a destructive or irreversible action with the user |
55
55
 
56
56
  ### Spec Tools
57
57
 
58
- Available in all sessions. Used for authoring and editing MSFM specs in `src/`.
58
+ Available in all onboarding states. Used for authoring and editing MSFM specs in `src/`.
59
59
 
60
60
  | Tool | Description |
61
61
  |------|-------------|
@@ -66,7 +66,7 @@ Available in all sessions. Used for authoring and editing MSFM specs in `src/`.
66
66
 
67
67
  ### Code Tools
68
68
 
69
- Available when the project has generated code (`projectHasCode: true`).
69
+ Available from `initialCodegen` onward.
70
70
 
71
71
  | Tool | Description |
72
72
  |------|-------------|
@@ -78,6 +78,7 @@ Available when the project has generated code (`projectHasCode: true`).
78
78
  | `glob` | Find files by pattern |
79
79
  | `listDir` | List directory contents |
80
80
  | `editsFinished` | Signal that file edits are complete for live preview |
81
+ | `askMindStudioSdk` | Ask the MindStudio SDK assistant about actions, models, connectors, and integrations |
81
82
 
82
83
  ### LSP Tools (sandbox only)
83
84
 
@@ -88,19 +89,22 @@ Available when `--lsp-url` is passed.
88
89
  | `lspDiagnostics` | Type errors and warnings for a file, with suggested quick fixes |
89
90
  | `restartProcess` | Restart a managed sandbox process (e.g., dev server after npm install) |
90
91
 
91
- ### Sync Tools (sync turns only)
92
+ ### Post-Onboarding Tools
92
93
 
93
- Available when the sandbox sends a `runCommand: "sync"` message.
94
+ Available only when `onboardingState` is `onboardingFinished`.
94
95
 
95
96
  | Tool | Description |
96
97
  |------|-------------|
98
+ | `clearSyncStatus` | Clear sync flags after syncing spec and code |
97
99
  | `presentSyncPlan` | Present a markdown sync plan to the user for approval (streams content) |
100
+ | `presentPublishPlan` | Present a publish changelog for user approval (streams content) |
101
+ | `presentPlan` | Present an implementation plan for user approval (streams content) |
98
102
 
99
103
  ### Tool Streaming
100
104
 
101
105
  Tools can opt into streaming via a `streaming` config on the tool definition:
102
106
 
103
- - **Content streaming** (writeSpec, writeFile, presentSyncPlan): Streams `tool_input_delta` events with progressive content as the LLM generates tool arguments. Tools can provide a `transform` function to customize the streamed output (e.g., writeSpec/writeFile compute a progressive diff).
107
+ - **Content streaming** (writeSpec, writeFile, presentSyncPlan, presentPublishPlan, presentPlan): Streams `tool_input_delta` events with progressive content as the LLM generates tool arguments. Tools can provide a `transform` function to customize the streamed output (e.g., writeSpec/writeFile compute a progressive diff).
104
108
  - **Input streaming** (promptUser): Streams progressive `tool_start` events with `partial: true` as structured input (like a questions array) builds up.
105
109
  - **No streaming** (all other tools): `tool_start` fires once when the complete tool arguments are available.
106
110
 
@@ -111,10 +115,10 @@ Streaming is driven by `tool_input_delta` (Anthropic) or `tool_input_args` (Gemi
111
115
  ```
112
116
  User input
113
117
  → Agent loop (src/agent.ts)
114
- → POST /_internal/v2/agent/chat (SSE stream)
118
+ → POST /_internal/v2/agent/remy/chat (SSE stream)
115
119
  ← text, thinking, tool_input_delta, tool_input_args, tool_use events
116
120
  → Execute tools locally in parallel
117
- → External tools (promptUser, setViewMode, etc.) wait for sandbox response
121
+ → External tools wait for sandbox response
118
122
  → Send tool results back
119
123
  → Loop until done
120
124
  → Save session to .remy-session.json
@@ -136,9 +140,11 @@ src/
136
140
  headless.ts stdin/stdout JSON protocol for sandbox
137
141
 
138
142
  prompt/
139
- index.ts System prompt builder (mode-aware)
143
+ index.ts System prompt builder (onboarding-state-aware)
140
144
  actions/ Built-in prompts for runCommand actions
141
145
  sync.md
146
+ publish.md
147
+ buildFromInitialSpec.md
142
148
  static/ Behavioral instruction fragments
143
149
  identity.md
144
150
  intake.md
@@ -147,7 +153,7 @@ src/
147
153
  lsp.md
148
154
  projectContext.ts Reads manifest, spec metadata, file listing at runtime
149
155
  compiled/ Platform docs distilled for agent consumption
150
- sources/ Raw source docs (fetched + manual)
156
+ sources/ Prompt source material (hand-maintained)
151
157
 
152
158
  tools/
153
159
  index.ts Tool registry with streaming config interface
@@ -159,10 +165,13 @@ src/
159
165
  writeSpec.ts
160
166
  editSpec.ts
161
167
  listSpecFiles.ts
162
- setViewMode.ts
168
+ setProjectOnboardingState.ts
163
169
  promptUser.ts
170
+ confirmDestructiveAction.ts
164
171
  clearSyncStatus.ts
165
172
  presentSyncPlan.ts
173
+ presentPublishPlan.ts
174
+ presentPlan.ts
166
175
  _helpers.ts Heading resolution, path validation
167
176
  code/ Code tools (file editing, shell, search)
168
177
  readFile.ts
@@ -175,6 +184,7 @@ src/
175
184
  glob.ts
176
185
  listDir.ts
177
186
  editsFinished.ts
187
+ askMindStudioSdk.ts
178
188
  lspDiagnostics.ts
179
189
  restartProcess.ts
180
190
 
@@ -188,12 +198,15 @@ src/
188
198
 
189
199
  ### External Tools
190
200
 
191
- Some tools are resolved by the sandbox rather than executed locally. Remy emits `tool_start`, then waits for the sandbox to send back a `tool_result` via stdin. This is used for tools that require sandbox/user interaction:
201
+ Some tools are resolved by the sandbox rather than executed locally. Remy emits `tool_start`, then waits for the sandbox to send back a `tool_result` via stdin:
192
202
 
193
203
  - `promptUser` — renders a form or inline prompt, blocks until user responds
194
- - `setViewMode` — switches the IDE view mode
204
+ - `setProjectOnboardingState` — advances the onboarding flow
205
+ - `confirmDestructiveAction` — renders a confirmation dialog
195
206
  - `clearSyncStatus` — clears sync dirty flags and updates git sync ref
196
207
  - `presentSyncPlan` — renders a full-screen markdown plan for user approval
208
+ - `presentPublishPlan` — renders a full-screen changelog for user approval
209
+ - `presentPlan` — renders a full-screen implementation plan for user approval
197
210
 
198
211
  ### Project Instructions
199
212
 
@@ -214,15 +227,15 @@ Send JSON commands, one per line.
214
227
  Send a user message to the agent.
215
228
 
216
229
  ```json
217
- {"action": "message", "text": "fix the bug in auth.ts", "projectHasCode": true}
230
+ {"action": "message", "text": "fix the bug in auth.ts", "onboardingState": "onboardingFinished"}
218
231
  ```
219
232
 
220
233
  Fields:
221
234
  - `text` — the user message (required unless `runCommand` is set)
222
- - `projectHasCode` — controls tool availability (default: `true`)
235
+ - `onboardingState` — controls tool availability and prompt context. One of: `intake`, `initialSpecAuthoring`, `initialCodegen`, `onboardingFinished` (default: `onboardingFinished`)
223
236
  - `viewContext` — `{ mode, openFiles?, activeFile? }` for prompt context
224
237
  - `attachments` — array of `{ url, extractedTextUrl? }` for file attachments
225
- - `runCommand` — triggers a built-in action prompt (e.g., `"sync"`)
238
+ - `runCommand` — triggers a built-in action prompt (`"sync"`, `"publish"`, `"buildFromInitialSpec"`)
226
239
 
227
240
  When `runCommand` is set, the message text is replaced with a built-in prompt and the user message is marked as `hidden` in conversation history (sent to the LLM but not shown in the UI).
228
241
 
@@ -0,0 +1,5 @@
1
+ This is an automated action triggered by the user pressing "Build" in the editor after reviewing the spec.
2
+
3
+ The user has reviewed the spec and is ready to build. Build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan.
4
+
5
+ When code generation is complete, call `setProjectOnboardingState({ state: "onboardingFinished" })`.
@@ -94,6 +94,10 @@ or streams in makes an interface feel broken.
94
94
  before the image loads.
95
95
  - Loading-to-loaded transitions should swap content in-place without
96
96
  changing container size.
97
+ - Buttons must not change size during loading states. Use a fixed width or
98
+ `min-width`, and swap the label for a spinner or short text that fits the
99
+ same space. "Submit" becoming "Submitting..." should not make the button
100
+ wider and push adjacent elements around.
97
101
  - Conditional UI should use opacity/overlay transitions, not insertion into
98
102
  flow that displaces existing content.
99
103
 
@@ -1,8 +1,8 @@
1
1
  # MindStudio Agent SDK
2
2
 
3
- `@mindstudio-ai/agent` provides access to 200+ AI models and 1,000+ actions through a single API key. No separate provider keys needed MindStudio routes to the correct provider (OpenAI, Anthropic, Google, etc.) server-side.
3
+ `@mindstudio-ai/agent` provides access to 200+ AI models and 1,000+ actions through a single API key. No separate provider keys needed. MindStudio routes to the correct provider (OpenAI, Anthropic, Google, etc.) server-side.
4
4
 
5
- **Full reference:** For complete method signatures, parameters, and output types, read `dist/methods/node_modules/@mindstudio-ai/agent/llms.txt`. This file ships with the package and contains the full API reference for all 170+ actions.
5
+ There is a huge amount of capability here: hundreds of text generation models (OpenAI, Anthropic, Google, Meta, Mistral, and more), dozens of image generation models (FLUX, DALL-E, Stable Diffusion, Ideogram, and more), video generation, text-to-speech, music generation, vision analysis, web scraping, 850+ OAuth connectors, and much more. The tables below are a summary. **Always use the `askMindStudioSdk` tool to look up exact method signatures, model IDs, and config options before writing code that uses the SDK.** The SDK assistant knows every action, every model, every connector, and the user's configured OAuth connections. Don't guess at parameters or model IDs from memory.
6
6
 
7
7
  ## Usage in Methods
8
8
 
@@ -120,7 +120,7 @@ const result = await agent.runFromConnectorRegistry({
120
120
 
121
121
  ### Model Selection
122
122
 
123
- Override the default model for any AI action:
123
+ Override the default model for any AI action. Each model has its own config options (dimensions, seed, inference steps, etc.) so always use `askMindStudioSdk` to look up the correct config before specifying a model override:
124
124
 
125
125
  ```typescript
126
126
  const { content } = await agent.generateText({
@@ -133,12 +133,6 @@ const { content } = await agent.generateText({
133
133
  });
134
134
  ```
135
135
 
136
- Browse available models:
137
-
138
- ```typescript
139
- const { models } = await agent.listModelsSummaryByType('llm_chat');
140
- ```
141
-
142
136
  ### Batch Execution
143
137
 
144
138
  Run up to 50 actions in parallel:
package/dist/headless.js CHANGED
@@ -281,7 +281,7 @@ function resolveIncludes(template) {
281
281
  );
282
282
  return result.replace(/\n{3,}/g, "\n\n").trim();
283
283
  }
284
- function buildSystemPrompt(projectHasCode, viewContext) {
284
+ function buildSystemPrompt(onboardingState, viewContext) {
285
285
  const projectContext = [
286
286
  loadProjectInstructions(),
287
287
  loadProjectManifest(),
@@ -347,23 +347,32 @@ The current date is ${now}.
347
347
  {{compiled/msfm.md}}
348
348
  </mindstudio_flavored_markdown_spec_docs>
349
349
 
350
+ ${isLspConfigured() ? `<typescript_lsp>
351
+ {{static/lsp.md}}
352
+ </typescript_lsp>` : ""}
353
+
350
354
  <project_context>
351
355
  ${projectContext}
352
356
  </project_context>
353
357
 
354
- ${isLspConfigured() ? `<lsp>
355
- {{static/lsp.md}}
356
- </lsp>` : ""}
357
-
358
358
  {{static/intake.md}}
359
359
 
360
360
  {{static/authoring.md}}
361
361
 
362
362
  {{static/instructions.md}}
363
363
 
364
- <current_authoring_mode>
365
- ${projectHasCode ? "Project has code - keep code and spec in sync." : "Project does not have code yet - focus on writing the spec."}
366
- </current_authoring_mode>
364
+ <project_onboarding>
365
+ New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
366
+
367
+ - **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code.
368
+ - **initialSpecAuthoring**: Writing and refining the first spec. The user can see it in the editor as it streams in and can give feedback to iterate on it. This phase covers both the initial draft and any back-and-forth refinement before code generation.
369
+ - **initialCodegen**: First code generation from the spec. The agent is generating methods, tables, interfaces, manifest updates, and scenarios. This can take a while and involves heavy tool use. The user sees a full-screen build progress view.
370
+ - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
371
+
372
+ <current_project_onboarding_state>
373
+ ${onboardingState ?? "onboardingFinished"}
374
+ </current_project_onboarding_state>
375
+ </project_onboarding>
367
376
 
368
377
  <view_context>
369
378
  The user is currently in ${viewContext?.mode ?? "code"} mode.
@@ -376,7 +385,7 @@ ${viewContext?.activeFile ? `Active file: ${viewContext.activeFile}` : ""}
376
385
  // src/api.ts
377
386
  async function* streamChat(params) {
378
387
  const { baseUrl, apiKey, signal, ...body } = params;
379
- const url = `${baseUrl}/_internal/v2/agent/chat`;
388
+ const url = `${baseUrl}/_internal/v2/agent/remy/chat`;
380
389
  const startTime = Date.now();
381
390
  const messagesWithAttachments = body.messages.filter(
382
391
  (m) => m.attachments && m.attachments.length > 0
@@ -625,8 +634,8 @@ import path4 from "path";
625
634
  // src/tools/_helpers/diff.ts
626
635
  var CONTEXT_LINES = 3;
627
636
  function unifiedDiff(filePath, oldText, newText) {
628
- const oldLines = oldText.split("\n");
629
- const newLines = newText.split("\n");
637
+ const oldLines = oldText ? oldText.split("\n") : [];
638
+ const newLines = newText ? newText.split("\n") : [];
630
639
  let firstDiff = 0;
631
640
  while (firstDiff < oldLines.length && firstDiff < newLines.length && oldLines[firstDiff] === newLines[firstDiff]) {
632
641
  firstDiff++;
@@ -877,33 +886,29 @@ async function listRecursive(dir) {
877
886
  return results;
878
887
  }
879
888
 
880
- // src/tools/spec/setViewMode.ts
881
- var setViewModeTool = {
889
+ // src/tools/spec/setProjectOnboardingState.ts
890
+ var setProjectOnboardingStateTool = {
882
891
  definition: {
883
- name: "setViewMode",
884
- description: 'Switch the IDE view mode. Use this to navigate the user to the right context. When transitioning from intake to spec, write the first spec file BEFORE calling this \u2014 the user needs something to see when the spec editor opens. Switch to "code" during code generation, then to "preview" when done so the user sees the result.',
892
+ name: "setProjectOnboardingState",
893
+ description: "Advance the project onboarding state. Call at natural transition points: before writing the first spec (initialSpecAuthoring), before starting the first code generation (initialCodegen), after the first build succeeds (onboardingFinished). Forward-only progression.",
885
894
  inputSchema: {
886
895
  type: "object",
887
896
  properties: {
888
- mode: {
897
+ state: {
889
898
  type: "string",
890
899
  enum: [
891
- "intake",
892
- "preview",
893
- "spec",
894
- "code",
895
- "databases",
896
- "scenarios",
897
- "logs"
900
+ "initialSpecAuthoring",
901
+ "initialCodegen",
902
+ "onboardingFinished"
898
903
  ],
899
- description: "The view mode to switch to."
904
+ description: "The onboarding state to advance to."
900
905
  }
901
906
  },
902
- required: ["mode"]
907
+ required: ["state"]
903
908
  }
904
909
  },
905
910
  async execute() {
906
- return "View mode updated.";
911
+ return "ok";
907
912
  }
908
913
  };
909
914
 
@@ -935,8 +940,8 @@ var promptUserTool = {
935
940
  },
936
941
  type: {
937
942
  type: "string",
938
- enum: ["select", "text", "confirm", "file", "color"],
939
- description: "select: pick from options. text: free-form input. confirm: yes/no. file: file/image upload \u2014 returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex)."
943
+ enum: ["select", "text", "file", "color"],
944
+ description: 'select: pick from options (or options + free-form "other"). text: free-form input. file: file/image upload, returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex).'
940
945
  },
941
946
  helpText: {
942
947
  type: "string",
@@ -1032,8 +1037,6 @@ var promptUserTool = {
1032
1037
  (o) => typeof o === "string" ? o : o.label
1033
1038
  );
1034
1039
  line += q.multiple ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
1035
- } else if (q.type === "confirm") {
1036
- line += " (yes / no)";
1037
1040
  } else if (q.type === "file") {
1038
1041
  line += " (upload file)";
1039
1042
  } else if (q.type === "color") {
@@ -1127,6 +1130,35 @@ var presentPlanTool = {
1127
1130
  }
1128
1131
  };
1129
1132
 
1133
+ // src/tools/spec/confirmDestructiveAction.ts
1134
+ var confirmDestructiveActionTool = {
1135
+ definition: {
1136
+ name: "confirmDestructiveAction",
1137
+ description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or presentPlan (those already include approval). Do not use before onboarding state transitions.",
1138
+ inputSchema: {
1139
+ type: "object",
1140
+ properties: {
1141
+ message: {
1142
+ type: "string",
1143
+ description: "Explanation of what is about to happen and why confirmation is needed."
1144
+ },
1145
+ confirmLabel: {
1146
+ type: "string",
1147
+ description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
1148
+ },
1149
+ dismissLabel: {
1150
+ type: "string",
1151
+ description: 'Custom label for the dismiss button (e.g., "Keep It", "Go Back"). Defaults to "Cancel".'
1152
+ }
1153
+ },
1154
+ required: ["message"]
1155
+ }
1156
+ },
1157
+ async execute() {
1158
+ return "confirmed";
1159
+ }
1160
+ };
1161
+
1130
1162
  // src/tools/code/readFile.ts
1131
1163
  import fs9 from "fs/promises";
1132
1164
  var DEFAULT_MAX_LINES2 = 500;
@@ -1723,6 +1755,45 @@ var restartProcessTool = {
1723
1755
  }
1724
1756
  };
1725
1757
 
1758
+ // src/tools/code/askMindStudioSdk.ts
1759
+ import { exec as exec3 } from "child_process";
1760
+ var askMindStudioSdkTool = {
1761
+ definition: {
1762
+ name: "askMindStudioSdk",
1763
+ description: "Ask the MindStudio SDK assistant about actions, AI models, connectors, and integrations. Returns code examples with correct method signatures, model IDs, and config options. Use this instead of guessing SDK usage from memory. Describe what you need, not what API methods you need; the assistant will figure out the right approach. This runs its own LLM call so it has a few seconds of latency; batch related questions into a single query.",
1764
+ inputSchema: {
1765
+ type: "object",
1766
+ properties: {
1767
+ query: {
1768
+ type: "string",
1769
+ description: "Natural language question about the SDK."
1770
+ }
1771
+ },
1772
+ required: ["query"]
1773
+ }
1774
+ },
1775
+ async execute(input) {
1776
+ const query = input.query;
1777
+ return new Promise((resolve) => {
1778
+ exec3(
1779
+ `mindstudio ask ${JSON.stringify(query)}`,
1780
+ { timeout: 6e4, maxBuffer: 512 * 1024 },
1781
+ (err, stdout, stderr) => {
1782
+ if (stdout.trim()) {
1783
+ resolve(stdout.trim());
1784
+ return;
1785
+ }
1786
+ if (err) {
1787
+ resolve(`Error: ${stderr.trim() || err.message}`);
1788
+ return;
1789
+ }
1790
+ resolve("(no response)");
1791
+ }
1792
+ );
1793
+ });
1794
+ }
1795
+ };
1796
+
1726
1797
  // src/tools/index.ts
1727
1798
  function getSpecTools() {
1728
1799
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
@@ -1736,46 +1807,51 @@ function getCodeTools() {
1736
1807
  grepTool,
1737
1808
  globTool,
1738
1809
  listDirTool,
1739
- editsFinishedTool
1810
+ editsFinishedTool,
1811
+ askMindStudioSdkTool
1740
1812
  ];
1741
1813
  if (isLspConfigured()) {
1742
1814
  tools.push(lspDiagnosticsTool, restartProcessTool);
1743
1815
  }
1744
1816
  return tools;
1745
1817
  }
1746
- function getTools(projectHasCode) {
1747
- if (projectHasCode) {
1748
- return [
1749
- setViewModeTool,
1750
- promptUserTool,
1751
- clearSyncStatusTool,
1752
- presentSyncPlanTool,
1753
- presentPublishPlanTool,
1754
- presentPlanTool,
1755
- ...getSpecTools(),
1756
- ...getCodeTools()
1757
- ];
1758
- }
1818
+ function getCommonTools() {
1759
1819
  return [
1760
- setViewModeTool,
1820
+ setProjectOnboardingStateTool,
1761
1821
  promptUserTool,
1822
+ confirmDestructiveActionTool
1823
+ ];
1824
+ }
1825
+ function getPostOnboardingTools() {
1826
+ return [
1762
1827
  clearSyncStatusTool,
1763
1828
  presentSyncPlanTool,
1764
1829
  presentPublishPlanTool,
1765
- ...getSpecTools()
1830
+ presentPlanTool
1766
1831
  ];
1767
1832
  }
1768
- function getToolDefinitions(projectHasCode) {
1769
- return getTools(projectHasCode).map((t) => t.definition);
1833
+ function getTools(onboardingState) {
1834
+ switch (onboardingState) {
1835
+ case "onboardingFinished":
1836
+ return [
1837
+ ...getCommonTools(),
1838
+ ...getPostOnboardingTools(),
1839
+ ...getSpecTools(),
1840
+ ...getCodeTools()
1841
+ ];
1842
+ case "initialCodegen":
1843
+ return [...getCommonTools(), ...getSpecTools(), ...getCodeTools()];
1844
+ default:
1845
+ return [...getCommonTools(), ...getSpecTools()];
1846
+ }
1847
+ }
1848
+ function getToolDefinitions(onboardingState) {
1849
+ return getTools(onboardingState).map((t) => t.definition);
1770
1850
  }
1771
1851
  function getToolByName(name) {
1772
1852
  const allTools = [
1773
- setViewModeTool,
1774
- promptUserTool,
1775
- clearSyncStatusTool,
1776
- presentSyncPlanTool,
1777
- presentPublishPlanTool,
1778
- presentPlanTool,
1853
+ ...getCommonTools(),
1854
+ ...getPostOnboardingTools(),
1779
1855
  ...getSpecTools(),
1780
1856
  ...getCodeTools()
1781
1857
  ];
@@ -2018,11 +2094,12 @@ function parsePartialJson(jsonString) {
2018
2094
  // src/agent.ts
2019
2095
  var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
2020
2096
  "promptUser",
2021
- "setViewMode",
2097
+ "setProjectOnboardingState",
2022
2098
  "clearSyncStatus",
2023
2099
  "presentSyncPlan",
2024
2100
  "presentPublishPlan",
2025
- "presentPlan"
2101
+ "presentPlan",
2102
+ "confirmDestructiveAction"
2026
2103
  ]);
2027
2104
  function createAgentState() {
2028
2105
  return { messages: [] };
@@ -2035,13 +2112,13 @@ async function runTurn(params) {
2035
2112
  apiConfig,
2036
2113
  system,
2037
2114
  model,
2038
- projectHasCode,
2115
+ onboardingState,
2039
2116
  signal,
2040
2117
  onEvent,
2041
2118
  resolveExternalTool,
2042
2119
  hidden
2043
2120
  } = params;
2044
- const tools = getToolDefinitions(projectHasCode);
2121
+ const tools = getToolDefinitions(onboardingState);
2045
2122
  log.info("Turn started", {
2046
2123
  messageLength: userMessage.length,
2047
2124
  toolCount: tools.length,
@@ -2353,7 +2430,8 @@ async function startHeadless(opts = {}) {
2353
2430
  }
2354
2431
  let running = false;
2355
2432
  let currentAbort = null;
2356
- const externalToolPromises = /* @__PURE__ */ new Map();
2433
+ const pendingTools = /* @__PURE__ */ new Map();
2434
+ const earlyResults = /* @__PURE__ */ new Map();
2357
2435
  function onEvent(e) {
2358
2436
  switch (e.type) {
2359
2437
  case "text":
@@ -2365,22 +2443,14 @@ async function startHeadless(opts = {}) {
2365
2443
  case "tool_input_delta":
2366
2444
  emit("tool_input_delta", { id: e.id, name: e.name, result: e.result });
2367
2445
  break;
2368
- case "tool_start": {
2446
+ case "tool_start":
2369
2447
  emit("tool_start", {
2370
2448
  id: e.id,
2371
2449
  name: e.name,
2372
2450
  input: e.input,
2373
2451
  ...e.partial && { partial: true }
2374
2452
  });
2375
- if (!e.partial && !externalToolPromises.has(e.id)) {
2376
- let resolve;
2377
- const promise = new Promise((r) => {
2378
- resolve = r;
2379
- });
2380
- externalToolPromises.set(e.id, { promise, resolve });
2381
- }
2382
2453
  break;
2383
- }
2384
2454
  case "tool_done":
2385
2455
  emit("tool_done", {
2386
2456
  id: e.id,
@@ -2404,16 +2474,14 @@ async function startHeadless(opts = {}) {
2404
2474
  }
2405
2475
  }
2406
2476
  function resolveExternalTool(id, _name, _input) {
2407
- const entry = externalToolPromises.get(id);
2408
- if (entry) {
2409
- return entry.promise;
2477
+ const early = earlyResults.get(id);
2478
+ if (early !== void 0) {
2479
+ earlyResults.delete(id);
2480
+ return Promise.resolve(early);
2410
2481
  }
2411
- let resolve;
2412
- const promise = new Promise((r) => {
2413
- resolve = r;
2482
+ return new Promise((resolve) => {
2483
+ pendingTools.set(id, { resolve });
2414
2484
  });
2415
- externalToolPromises.set(id, { promise, resolve });
2416
- return promise;
2417
2485
  }
2418
2486
  const rl = createInterface({ input: process.stdin });
2419
2487
  rl.on("line", async (line) => {
@@ -2425,10 +2493,12 @@ async function startHeadless(opts = {}) {
2425
2493
  return;
2426
2494
  }
2427
2495
  if (parsed.action === "tool_result" && parsed.id) {
2428
- const entry = externalToolPromises.get(parsed.id);
2429
- if (entry) {
2430
- externalToolPromises.delete(parsed.id);
2431
- entry.resolve(parsed.result ?? "");
2496
+ const pending = pendingTools.get(parsed.id);
2497
+ if (pending) {
2498
+ pendingTools.delete(parsed.id);
2499
+ pending.resolve(parsed.result ?? "");
2500
+ } else {
2501
+ earlyResults.set(parsed.id, parsed.result ?? "");
2432
2502
  }
2433
2503
  return;
2434
2504
  }
@@ -2447,9 +2517,9 @@ async function startHeadless(opts = {}) {
2447
2517
  if (currentAbort) {
2448
2518
  currentAbort.abort();
2449
2519
  }
2450
- for (const [id, entry] of externalToolPromises) {
2451
- entry.resolve("Error: cancelled");
2452
- externalToolPromises.delete(id);
2520
+ for (const [id, pending] of pendingTools) {
2521
+ pending.resolve("Error: cancelled");
2522
+ pendingTools.delete(id);
2453
2523
  }
2454
2524
  return;
2455
2525
  }
@@ -2472,9 +2542,11 @@ async function startHeadless(opts = {}) {
2472
2542
  userMessage = loadActionPrompt("sync");
2473
2543
  } else if (parsed.runCommand === "publish") {
2474
2544
  userMessage = loadActionPrompt("publish");
2545
+ } else if (parsed.runCommand === "buildFromInitialSpec") {
2546
+ userMessage = loadActionPrompt("buildFromInitialSpec");
2475
2547
  }
2476
- const projectHasCode = parsed.projectHasCode ?? true;
2477
- const system = buildSystemPrompt(projectHasCode, parsed.viewContext);
2548
+ const onboardingState = parsed.onboardingState ?? "onboardingFinished";
2549
+ const system = buildSystemPrompt(onboardingState, parsed.viewContext);
2478
2550
  try {
2479
2551
  await runTurn({
2480
2552
  state,
@@ -2483,7 +2555,7 @@ async function startHeadless(opts = {}) {
2483
2555
  apiConfig: config,
2484
2556
  system,
2485
2557
  model: opts.model,
2486
- projectHasCode,
2558
+ onboardingState,
2487
2559
  signal: currentAbort.signal,
2488
2560
  onEvent,
2489
2561
  resolveExternalTool,
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ var init_logger = __esm({
90
90
  // src/api.ts
91
91
  async function* streamChat(params) {
92
92
  const { baseUrl, apiKey, signal, ...body } = params;
93
- const url = `${baseUrl}/_internal/v2/agent/chat`;
93
+ const url = `${baseUrl}/_internal/v2/agent/remy/chat`;
94
94
  const startTime = Date.now();
95
95
  const messagesWithAttachments = body.messages.filter(
96
96
  (m) => m.attachments && m.attachments.length > 0
@@ -351,8 +351,8 @@ var init_readSpec = __esm({
351
351
 
352
352
  // src/tools/_helpers/diff.ts
353
353
  function unifiedDiff(filePath, oldText, newText) {
354
- const oldLines = oldText.split("\n");
355
- const newLines = newText.split("\n");
354
+ const oldLines = oldText ? oldText.split("\n") : [];
355
+ const newLines = newText ? newText.split("\n") : [];
356
356
  let firstDiff = 0;
357
357
  while (firstDiff < oldLines.length && firstDiff < newLines.length && oldLines[firstDiff] === newLines[firstDiff]) {
358
358
  firstDiff++;
@@ -634,37 +634,33 @@ var init_listSpecFiles = __esm({
634
634
  }
635
635
  });
636
636
 
637
- // src/tools/spec/setViewMode.ts
638
- var setViewModeTool;
639
- var init_setViewMode = __esm({
640
- "src/tools/spec/setViewMode.ts"() {
637
+ // src/tools/spec/setProjectOnboardingState.ts
638
+ var setProjectOnboardingStateTool;
639
+ var init_setProjectOnboardingState = __esm({
640
+ "src/tools/spec/setProjectOnboardingState.ts"() {
641
641
  "use strict";
642
- setViewModeTool = {
642
+ setProjectOnboardingStateTool = {
643
643
  definition: {
644
- name: "setViewMode",
645
- description: 'Switch the IDE view mode. Use this to navigate the user to the right context. When transitioning from intake to spec, write the first spec file BEFORE calling this \u2014 the user needs something to see when the spec editor opens. Switch to "code" during code generation, then to "preview" when done so the user sees the result.',
644
+ name: "setProjectOnboardingState",
645
+ description: "Advance the project onboarding state. Call at natural transition points: before writing the first spec (initialSpecAuthoring), before starting the first code generation (initialCodegen), after the first build succeeds (onboardingFinished). Forward-only progression.",
646
646
  inputSchema: {
647
647
  type: "object",
648
648
  properties: {
649
- mode: {
649
+ state: {
650
650
  type: "string",
651
651
  enum: [
652
- "intake",
653
- "preview",
654
- "spec",
655
- "code",
656
- "databases",
657
- "scenarios",
658
- "logs"
652
+ "initialSpecAuthoring",
653
+ "initialCodegen",
654
+ "onboardingFinished"
659
655
  ],
660
- description: "The view mode to switch to."
656
+ description: "The onboarding state to advance to."
661
657
  }
662
658
  },
663
- required: ["mode"]
659
+ required: ["state"]
664
660
  }
665
661
  },
666
662
  async execute() {
667
- return "View mode updated.";
663
+ return "ok";
668
664
  }
669
665
  };
670
666
  }
@@ -702,8 +698,8 @@ var init_promptUser = __esm({
702
698
  },
703
699
  type: {
704
700
  type: "string",
705
- enum: ["select", "text", "confirm", "file", "color"],
706
- description: "select: pick from options. text: free-form input. confirm: yes/no. file: file/image upload \u2014 returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex)."
701
+ enum: ["select", "text", "file", "color"],
702
+ description: 'select: pick from options (or options + free-form "other"). text: free-form input. file: file/image upload, returns CDN URL(s) that can be referenced directly or curled onto disk. color: color picker (returns hex).'
707
703
  },
708
704
  helpText: {
709
705
  type: "string",
@@ -799,8 +795,6 @@ var init_promptUser = __esm({
799
795
  (o) => typeof o === "string" ? o : o.label
800
796
  );
801
797
  line += q.multiple ? ` (pick one or more: ${opts.join(" / ")})` : ` (${opts.join(" / ")})`;
802
- } else if (q.type === "confirm") {
803
- line += " (yes / no)";
804
798
  } else if (q.type === "file") {
805
799
  line += " (upload file)";
806
800
  } else if (q.type === "color") {
@@ -920,6 +914,41 @@ var init_presentPlan = __esm({
920
914
  }
921
915
  });
922
916
 
917
+ // src/tools/spec/confirmDestructiveAction.ts
918
+ var confirmDestructiveActionTool;
919
+ var init_confirmDestructiveAction = __esm({
920
+ "src/tools/spec/confirmDestructiveAction.ts"() {
921
+ "use strict";
922
+ confirmDestructiveActionTool = {
923
+ definition: {
924
+ name: "confirmDestructiveAction",
925
+ description: "Confirm a destructive or irreversible action with the user. Use for things like deleting data, resetting the database, or discarding draft work. Do not use after presentSyncPlan, presentPublishPlan, or presentPlan (those already include approval). Do not use before onboarding state transitions.",
926
+ inputSchema: {
927
+ type: "object",
928
+ properties: {
929
+ message: {
930
+ type: "string",
931
+ description: "Explanation of what is about to happen and why confirmation is needed."
932
+ },
933
+ confirmLabel: {
934
+ type: "string",
935
+ description: 'Custom label for the confirm button (e.g., "Delete", "Reset Database"). Defaults to "Confirm".'
936
+ },
937
+ dismissLabel: {
938
+ type: "string",
939
+ description: 'Custom label for the dismiss button (e.g., "Keep It", "Go Back"). Defaults to "Cancel".'
940
+ }
941
+ },
942
+ required: ["message"]
943
+ }
944
+ },
945
+ async execute() {
946
+ return "confirmed";
947
+ }
948
+ };
949
+ }
950
+ });
951
+
923
952
  // src/tools/code/readFile.ts
924
953
  import fs6 from "fs/promises";
925
954
  function isBinary(buffer) {
@@ -1626,6 +1655,51 @@ var init_restartProcess = __esm({
1626
1655
  }
1627
1656
  });
1628
1657
 
1658
+ // src/tools/code/askMindStudioSdk.ts
1659
+ import { exec as exec3 } from "child_process";
1660
+ var askMindStudioSdkTool;
1661
+ var init_askMindStudioSdk = __esm({
1662
+ "src/tools/code/askMindStudioSdk.ts"() {
1663
+ "use strict";
1664
+ askMindStudioSdkTool = {
1665
+ definition: {
1666
+ name: "askMindStudioSdk",
1667
+ description: "Ask the MindStudio SDK assistant about actions, AI models, connectors, and integrations. Returns code examples with correct method signatures, model IDs, and config options. Use this instead of guessing SDK usage from memory. Describe what you need, not what API methods you need; the assistant will figure out the right approach. This runs its own LLM call so it has a few seconds of latency; batch related questions into a single query.",
1668
+ inputSchema: {
1669
+ type: "object",
1670
+ properties: {
1671
+ query: {
1672
+ type: "string",
1673
+ description: "Natural language question about the SDK."
1674
+ }
1675
+ },
1676
+ required: ["query"]
1677
+ }
1678
+ },
1679
+ async execute(input) {
1680
+ const query = input.query;
1681
+ return new Promise((resolve) => {
1682
+ exec3(
1683
+ `mindstudio ask ${JSON.stringify(query)}`,
1684
+ { timeout: 6e4, maxBuffer: 512 * 1024 },
1685
+ (err, stdout, stderr) => {
1686
+ if (stdout.trim()) {
1687
+ resolve(stdout.trim());
1688
+ return;
1689
+ }
1690
+ if (err) {
1691
+ resolve(`Error: ${stderr.trim() || err.message}`);
1692
+ return;
1693
+ }
1694
+ resolve("(no response)");
1695
+ }
1696
+ );
1697
+ });
1698
+ }
1699
+ };
1700
+ }
1701
+ });
1702
+
1629
1703
  // src/tools/index.ts
1630
1704
  function getSpecTools() {
1631
1705
  return [readSpecTool, writeSpecTool, editSpecTool, listSpecFilesTool];
@@ -1639,46 +1713,51 @@ function getCodeTools() {
1639
1713
  grepTool,
1640
1714
  globTool,
1641
1715
  listDirTool,
1642
- editsFinishedTool
1716
+ editsFinishedTool,
1717
+ askMindStudioSdkTool
1643
1718
  ];
1644
1719
  if (isLspConfigured()) {
1645
1720
  tools.push(lspDiagnosticsTool, restartProcessTool);
1646
1721
  }
1647
1722
  return tools;
1648
1723
  }
1649
- function getTools(projectHasCode) {
1650
- if (projectHasCode) {
1651
- return [
1652
- setViewModeTool,
1653
- promptUserTool,
1654
- clearSyncStatusTool,
1655
- presentSyncPlanTool,
1656
- presentPublishPlanTool,
1657
- presentPlanTool,
1658
- ...getSpecTools(),
1659
- ...getCodeTools()
1660
- ];
1661
- }
1724
+ function getCommonTools() {
1662
1725
  return [
1663
- setViewModeTool,
1726
+ setProjectOnboardingStateTool,
1664
1727
  promptUserTool,
1728
+ confirmDestructiveActionTool
1729
+ ];
1730
+ }
1731
+ function getPostOnboardingTools() {
1732
+ return [
1665
1733
  clearSyncStatusTool,
1666
1734
  presentSyncPlanTool,
1667
1735
  presentPublishPlanTool,
1668
- ...getSpecTools()
1736
+ presentPlanTool
1669
1737
  ];
1670
1738
  }
1671
- function getToolDefinitions(projectHasCode) {
1672
- return getTools(projectHasCode).map((t) => t.definition);
1739
+ function getTools(onboardingState) {
1740
+ switch (onboardingState) {
1741
+ case "onboardingFinished":
1742
+ return [
1743
+ ...getCommonTools(),
1744
+ ...getPostOnboardingTools(),
1745
+ ...getSpecTools(),
1746
+ ...getCodeTools()
1747
+ ];
1748
+ case "initialCodegen":
1749
+ return [...getCommonTools(), ...getSpecTools(), ...getCodeTools()];
1750
+ default:
1751
+ return [...getCommonTools(), ...getSpecTools()];
1752
+ }
1753
+ }
1754
+ function getToolDefinitions(onboardingState) {
1755
+ return getTools(onboardingState).map((t) => t.definition);
1673
1756
  }
1674
1757
  function getToolByName(name) {
1675
1758
  const allTools = [
1676
- setViewModeTool,
1677
- promptUserTool,
1678
- clearSyncStatusTool,
1679
- presentSyncPlanTool,
1680
- presentPublishPlanTool,
1681
- presentPlanTool,
1759
+ ...getCommonTools(),
1760
+ ...getPostOnboardingTools(),
1682
1761
  ...getSpecTools(),
1683
1762
  ...getCodeTools()
1684
1763
  ];
@@ -1698,12 +1777,13 @@ var init_tools = __esm({
1698
1777
  init_writeSpec();
1699
1778
  init_editSpec();
1700
1779
  init_listSpecFiles();
1701
- init_setViewMode();
1780
+ init_setProjectOnboardingState();
1702
1781
  init_promptUser();
1703
1782
  init_clearSyncStatus();
1704
1783
  init_presentSyncPlan();
1705
1784
  init_presentPublishPlan();
1706
1785
  init_presentPlan();
1786
+ init_confirmDestructiveAction();
1707
1787
  init_readFile();
1708
1788
  init_writeFile();
1709
1789
  init_editFile();
@@ -1715,6 +1795,7 @@ var init_tools = __esm({
1715
1795
  init_lsp();
1716
1796
  init_lspDiagnostics();
1717
1797
  init_restartProcess();
1798
+ init_askMindStudioSdk();
1718
1799
  }
1719
1800
  });
1720
1801
 
@@ -1968,13 +2049,13 @@ async function runTurn(params) {
1968
2049
  apiConfig,
1969
2050
  system,
1970
2051
  model,
1971
- projectHasCode,
2052
+ onboardingState,
1972
2053
  signal,
1973
2054
  onEvent,
1974
2055
  resolveExternalTool,
1975
2056
  hidden
1976
2057
  } = params;
1977
- const tools = getToolDefinitions(projectHasCode);
2058
+ const tools = getToolDefinitions(onboardingState);
1978
2059
  log.info("Turn started", {
1979
2060
  messageLength: userMessage.length,
1980
2061
  toolCount: tools.length,
@@ -2264,11 +2345,12 @@ var init_agent = __esm({
2264
2345
  init_parsePartialJson();
2265
2346
  EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
2266
2347
  "promptUser",
2267
- "setViewMode",
2348
+ "setProjectOnboardingState",
2268
2349
  "clearSyncStatus",
2269
2350
  "presentSyncPlan",
2270
2351
  "presentPublishPlan",
2271
- "presentPlan"
2352
+ "presentPlan",
2353
+ "confirmDestructiveAction"
2272
2354
  ]);
2273
2355
  }
2274
2356
  });
@@ -2420,7 +2502,7 @@ function resolveIncludes(template) {
2420
2502
  );
2421
2503
  return result.replace(/\n{3,}/g, "\n\n").trim();
2422
2504
  }
2423
- function buildSystemPrompt(projectHasCode, viewContext) {
2505
+ function buildSystemPrompt(onboardingState, viewContext) {
2424
2506
  const projectContext = [
2425
2507
  loadProjectInstructions(),
2426
2508
  loadProjectManifest(),
@@ -2486,23 +2568,32 @@ The current date is ${now}.
2486
2568
  {{compiled/msfm.md}}
2487
2569
  </mindstudio_flavored_markdown_spec_docs>
2488
2570
 
2571
+ ${isLspConfigured() ? `<typescript_lsp>
2572
+ {{static/lsp.md}}
2573
+ </typescript_lsp>` : ""}
2574
+
2489
2575
  <project_context>
2490
2576
  ${projectContext}
2491
2577
  </project_context>
2492
2578
 
2493
- ${isLspConfigured() ? `<lsp>
2494
- {{static/lsp.md}}
2495
- </lsp>` : ""}
2496
-
2497
2579
  {{static/intake.md}}
2498
2580
 
2499
2581
  {{static/authoring.md}}
2500
2582
 
2501
2583
  {{static/instructions.md}}
2502
2584
 
2503
- <current_authoring_mode>
2504
- ${projectHasCode ? "Project has code - keep code and spec in sync." : "Project does not have code yet - focus on writing the spec."}
2505
- </current_authoring_mode>
2585
+ <project_onboarding>
2586
+ New projects progress through four onboarding states. The user might skip this entirely and jump straight into working on the existing scaffold (which defaults to onboardingFinished), but ideally new projects move through each phase:
2587
+
2588
+ - **intake**: Gathering requirements. The project has scaffold code (a "hello world" starter) but it's not the user's app yet. Focus on understanding what they want to build, not on the existing code.
2589
+ - **initialSpecAuthoring**: Writing and refining the first spec. The user can see it in the editor as it streams in and can give feedback to iterate on it. This phase covers both the initial draft and any back-and-forth refinement before code generation.
2590
+ - **initialCodegen**: First code generation from the spec. The agent is generating methods, tables, interfaces, manifest updates, and scenarios. This can take a while and involves heavy tool use. The user sees a full-screen build progress view.
2591
+ - **onboardingFinished**: The project is built and ready. Full development mode with all tools available. From here on, keep spec and code in sync as changes are made.
2592
+
2593
+ <current_project_onboarding_state>
2594
+ ${onboardingState ?? "onboardingFinished"}
2595
+ </current_project_onboarding_state>
2596
+ </project_onboarding>
2506
2597
 
2507
2598
  <view_context>
2508
2599
  The user is currently in ${viewContext?.mode ?? "code"} mode.
@@ -2609,7 +2700,8 @@ async function startHeadless(opts = {}) {
2609
2700
  }
2610
2701
  let running = false;
2611
2702
  let currentAbort = null;
2612
- const externalToolPromises = /* @__PURE__ */ new Map();
2703
+ const pendingTools = /* @__PURE__ */ new Map();
2704
+ const earlyResults = /* @__PURE__ */ new Map();
2613
2705
  function onEvent(e) {
2614
2706
  switch (e.type) {
2615
2707
  case "text":
@@ -2621,22 +2713,14 @@ async function startHeadless(opts = {}) {
2621
2713
  case "tool_input_delta":
2622
2714
  emit("tool_input_delta", { id: e.id, name: e.name, result: e.result });
2623
2715
  break;
2624
- case "tool_start": {
2716
+ case "tool_start":
2625
2717
  emit("tool_start", {
2626
2718
  id: e.id,
2627
2719
  name: e.name,
2628
2720
  input: e.input,
2629
2721
  ...e.partial && { partial: true }
2630
2722
  });
2631
- if (!e.partial && !externalToolPromises.has(e.id)) {
2632
- let resolve;
2633
- const promise = new Promise((r) => {
2634
- resolve = r;
2635
- });
2636
- externalToolPromises.set(e.id, { promise, resolve });
2637
- }
2638
2723
  break;
2639
- }
2640
2724
  case "tool_done":
2641
2725
  emit("tool_done", {
2642
2726
  id: e.id,
@@ -2660,16 +2744,14 @@ async function startHeadless(opts = {}) {
2660
2744
  }
2661
2745
  }
2662
2746
  function resolveExternalTool(id, _name, _input) {
2663
- const entry = externalToolPromises.get(id);
2664
- if (entry) {
2665
- return entry.promise;
2747
+ const early = earlyResults.get(id);
2748
+ if (early !== void 0) {
2749
+ earlyResults.delete(id);
2750
+ return Promise.resolve(early);
2666
2751
  }
2667
- let resolve;
2668
- const promise = new Promise((r) => {
2669
- resolve = r;
2752
+ return new Promise((resolve) => {
2753
+ pendingTools.set(id, { resolve });
2670
2754
  });
2671
- externalToolPromises.set(id, { promise, resolve });
2672
- return promise;
2673
2755
  }
2674
2756
  const rl = createInterface({ input: process.stdin });
2675
2757
  rl.on("line", async (line) => {
@@ -2681,10 +2763,12 @@ async function startHeadless(opts = {}) {
2681
2763
  return;
2682
2764
  }
2683
2765
  if (parsed.action === "tool_result" && parsed.id) {
2684
- const entry = externalToolPromises.get(parsed.id);
2685
- if (entry) {
2686
- externalToolPromises.delete(parsed.id);
2687
- entry.resolve(parsed.result ?? "");
2766
+ const pending = pendingTools.get(parsed.id);
2767
+ if (pending) {
2768
+ pendingTools.delete(parsed.id);
2769
+ pending.resolve(parsed.result ?? "");
2770
+ } else {
2771
+ earlyResults.set(parsed.id, parsed.result ?? "");
2688
2772
  }
2689
2773
  return;
2690
2774
  }
@@ -2703,9 +2787,9 @@ async function startHeadless(opts = {}) {
2703
2787
  if (currentAbort) {
2704
2788
  currentAbort.abort();
2705
2789
  }
2706
- for (const [id, entry] of externalToolPromises) {
2707
- entry.resolve("Error: cancelled");
2708
- externalToolPromises.delete(id);
2790
+ for (const [id, pending] of pendingTools) {
2791
+ pending.resolve("Error: cancelled");
2792
+ pendingTools.delete(id);
2709
2793
  }
2710
2794
  return;
2711
2795
  }
@@ -2728,9 +2812,11 @@ async function startHeadless(opts = {}) {
2728
2812
  userMessage = loadActionPrompt("sync");
2729
2813
  } else if (parsed.runCommand === "publish") {
2730
2814
  userMessage = loadActionPrompt("publish");
2815
+ } else if (parsed.runCommand === "buildFromInitialSpec") {
2816
+ userMessage = loadActionPrompt("buildFromInitialSpec");
2731
2817
  }
2732
- const projectHasCode = parsed.projectHasCode ?? true;
2733
- const system = buildSystemPrompt(projectHasCode, parsed.viewContext);
2818
+ const onboardingState = parsed.onboardingState ?? "onboardingFinished";
2819
+ const system = buildSystemPrompt(onboardingState, parsed.viewContext);
2734
2820
  try {
2735
2821
  await runTurn({
2736
2822
  state,
@@ -2739,7 +2825,7 @@ async function startHeadless(opts = {}) {
2739
2825
  apiConfig: config,
2740
2826
  system,
2741
2827
  model: opts.model,
2742
- projectHasCode,
2828
+ onboardingState,
2743
2829
  signal: currentAbort.signal,
2744
2830
  onEvent,
2745
2831
  resolveExternalTool,
@@ -2986,7 +3072,7 @@ function App({ apiConfig, model }) {
2986
3072
  apiConfig,
2987
3073
  system,
2988
3074
  model,
2989
- projectHasCode: true,
3075
+ onboardingState: "onboardingFinished",
2990
3076
  signal: abort.signal,
2991
3077
  onEvent: (event) => {
2992
3078
  switch (event.type) {
@@ -33,7 +33,7 @@ After writing the first draft, guide the user through it. Don't just ask "does t
33
33
  - When the user asks "is this ready?" — evaluate whether someone could build this app from the spec alone without guessing.
34
34
 
35
35
  **Building from the spec:**
36
- When the user is satisfied with the spec, use `promptUser` with a confirm to gate before building code. Once they approve, build everything in one turn methods, tables, interfaces, manifest updates, and scenarios using the spec as the master plan. Call `setViewMode({ mode: "code" })` when you start writing code so the user can see files being created. When code generation is complete, call `setViewMode({ mode: "preview" })` so the user sees a full-screen preview of what was built.
36
+ When the user clicks "Build," you will receive a build command. Build everything in one turn: methods, tables, interfaces, manifest updates, and scenarios, using the spec as the master plan. The onboarding state transitions are handled automatically as part of the build command.
37
37
 
38
38
  **Scenarios are required.** Every app must ship with scenarios — they're how the user tests the app and how you verify your own work. Write at minimum:
39
39
  - A **realistic data scenario** with enough sample records to make the app feel populated and alive (5-20 rows depending on the app). Use plausible names, dates, amounts — not "test 1", "test 2".
@@ -8,7 +8,7 @@
8
8
  - The spec is the source of truth. When in doubt, consult the spec before making code changes. When behavior changes, update the spec first.
9
9
  - Change only what the task requires. Match existing code style. Keep solutions simple.
10
10
  - Read files before editing them. Understand the context before making changes.
11
- - When the user asks you to make a change, execute it fully — all steps, no pausing for confirmation. Use `promptUser` to gate before major transitions (e.g., building code from a spec). For large changes that touch many files or involve significant design decisions, use `presentPlan` to get user approval first — but only when the scope genuinely warrants it or the user asks to see a plan. Most work should be done autonomously.
11
+ - When the user asks you to make a change, execute it fully — all steps, no pausing for confirmation. Use `confirmDestructiveAction` to gate before destructive or irreversible actions (e.g., deleting data, resetting the database). For large changes that touch many files or involve significant design decisions, use `presentPlan` to get user approval first — but only when the scope genuinely warrants it or the user asks to see a plan. Most work should be done autonomously.
12
12
  - After two failed attempts at the same approach, tell the user what's going wrong.
13
13
  - Pushing to main branch will trigger a deploy. Use git via bash when the user wants to deploy.
14
14
 
@@ -18,4 +18,4 @@
18
18
  - Always use full paths relative to the project root when mentioning files (`dist/interfaces/web/src/App.tsx`, not `App.tsx`). Paths will be rendered as clickable links for the user.
19
19
  - When summarizing changes, describe what you did in plain language rather than listing a per-file changelog.
20
20
  - Use inline `code` formatting only for things the user needs to type or search for.
21
- - Do not use emojis and avoid overuse of em dashes.
21
+ - Do not use emojis. Avoid em dashes in prose; use periods, commas, colons, or parentheses instead.
@@ -29,8 +29,8 @@ Be upfront about these early if the conversation is heading that way. Better to
29
29
  **Guiding the conversation:**
30
30
  Keep chat brief. Your goal is to understand the general idea, not to nail every detail — that's what forms and the spec are for.
31
31
 
32
- 1. **Brief chat** — Understand what they want to build and why. A few exchanges to get the shape of the idea. If the user comes in with a clear description, you may only need one exchange before moving to forms.
33
- 2. **Structured forms** — Once you have the general idea, use `promptUser` with `type: "form"` to collect details. Forms are easier for users than describing things in chat, especially when they may not have the language for what they want. Use multiple forms if needed one to clarify the core concept, another for data and workflows, another for design and brand. Each form should build on what you've already learned. Always use `type: "form"` during intake the form takes over the screen, so don't mix in inline prompts or chat questions between forms.
32
+ 1. **Brief chat** — Only when you need to understand the idea or have a conversation. If the user says "hello" or gives a vague description, chat to figure out what they're thinking. But if the user's first message gives you a clear enough idea of what they want to build, acknowledge the idea briefly and move to a form. Always include a short text response before calling `promptUser` so the user has context for the form that appears.
33
+ 2. **Structured forms** — Use `promptUser` with `type: "form"` to collect details. If you can express your questions as structured options (select, text, color), use a form instead of asking in chat. Forms are easier for users than describing things in words, especially when they may not have the language for what they want. Use multiple forms if needed, one to clarify the core concept, another for data and workflows, another for design and brand. Each form should build on what you've already learned. Always use `type: "form"` during intake. The form takes over the screen, so don't mix in inline prompts or chat questions between forms.
34
34
  3. **Write the spec** — Turn everything into a first draft and get it on screen. The spec is intentionally a starting point, not a finished product. The user will refine it from there.
35
35
 
36
36
  **What NOT to do:**
@@ -41,4 +41,4 @@ Keep chat brief. Your goal is to understand the general idea, not to nail every
41
41
  - Do not try to collect everything through chat. Use forms for structured details — they're less taxing for the user and produce better answers.
42
42
 
43
43
  **When intake is done:**
44
- Once you have a clear enough picture the core data model, the key workflows, who uses it, and which interfaces matter let them know you're ready to start writing the spec. First, clear the scaffold placeholder by writing an empty `src/app.md` with `writeSpec`. Then call `setViewMode({ mode: "spec" })` so the editor opens. Then start writing the real spec with `writeSpec` the user will see it stream in live.
44
+ Once you have a clear enough picture (the core data model, the key workflows, who uses it, and which interfaces matter) let them know you're ready to start writing the spec. First, call `setProjectOnboardingState({ state: "initialSpecAuthoring" })` so the editor opens. Then start writing the real spec with `writeSpec`. The user will see it stream in live.
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "MindStudio coding agent",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/mindstudio-ai/remy"
8
+ },
5
9
  "type": "module",
6
10
  "main": "./dist/headless.js",
7
11
  "types": "./dist/headless.d.ts",