ralph-cli-sandboxed 0.6.5 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +1 -1
  3. package/dist/commands/action.js +6 -8
  4. package/dist/commands/ask.d.ts +6 -0
  5. package/dist/commands/ask.js +140 -0
  6. package/dist/commands/branch.js +8 -4
  7. package/dist/commands/chat.js +11 -8
  8. package/dist/commands/docker.js +19 -4
  9. package/dist/commands/fix-config.js +0 -41
  10. package/dist/commands/help.js +10 -0
  11. package/dist/commands/run.js +9 -9
  12. package/dist/config/languages.json +5 -3
  13. package/dist/index.js +2 -0
  14. package/dist/providers/telegram.js +1 -1
  15. package/dist/responders/claude-code-responder.js +1 -0
  16. package/dist/responders/cli-responder.js +1 -0
  17. package/dist/responders/llm-responder.js +1 -1
  18. package/dist/templates/macos-scripts.js +18 -18
  19. package/dist/tui/components/JsonSnippetEditor.js +7 -7
  20. package/dist/tui/components/KeyValueEditor.js +5 -1
  21. package/dist/tui/components/LLMProvidersEditor.js +7 -9
  22. package/dist/tui/components/Preview.js +1 -1
  23. package/dist/tui/components/SectionNav.js +18 -2
  24. package/dist/utils/chat-client.js +1 -0
  25. package/dist/utils/config.d.ts +1 -0
  26. package/dist/utils/config.js +3 -1
  27. package/dist/utils/config.test.d.ts +1 -0
  28. package/dist/utils/config.test.js +424 -0
  29. package/dist/utils/notification.js +1 -1
  30. package/dist/utils/prd-validator.js +16 -4
  31. package/dist/utils/prd-validator.test.d.ts +1 -0
  32. package/dist/utils/prd-validator.test.js +1095 -0
  33. package/dist/utils/responder.js +4 -1
  34. package/dist/utils/stream-json.test.d.ts +1 -0
  35. package/dist/utils/stream-json.test.js +1007 -0
  36. package/docs/DOCKER.md +14 -0
  37. package/docs/PRD-GENERATOR.md +15 -0
  38. package/package.json +16 -13
@@ -137,7 +137,7 @@
137
137
  "checkCommand": "mvn compile",
138
138
  "testCommand": "mvn test",
139
139
  "docker": {
140
- "install": "# Install Java ${version} and Maven\nRUN apt-get update && apt-get install -y \\\n openjdk-${version}-jdk \\\n maven \\\n && rm -rf /var/lib/apt/lists/* \\\n && ln -s /usr/lib/jvm/java-${version}-openjdk-$(dpkg --print-architecture) /usr/lib/jvm/java-${version}-openjdk\nENV JAVA_HOME=\"/usr/lib/jvm/java-${version}-openjdk\"",
140
+ "install": "# Install Java ${version} and Maven (via Eclipse Temurin)\nRUN mkdir -p /etc/apt/keyrings \\\n && wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg \\\n && echo \"deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main\" > /etc/apt/sources.list.d/adoptium.list \\\n && apt-get update && apt-get install -y \\\n temurin-${version}-jdk \\\n maven \\\n && rm -rf /var/lib/apt/lists/* \\\n && ln -s /usr/lib/jvm/temurin-${version}-jdk-$(dpkg --print-architecture) /usr/lib/jvm/temurin-${version}-jdk\nENV JAVA_HOME=\"/usr/lib/jvm/temurin-${version}-jdk\"\nENV PATH=\"$JAVA_HOME/bin:$PATH\"",
141
141
  "version": 17,
142
142
  "versionConfigurable": true
143
143
  },
@@ -165,7 +165,7 @@
165
165
  "checkCommand": "gradle build",
166
166
  "testCommand": "gradle test",
167
167
  "docker": {
168
- "install": "# Install Kotlin and Gradle (Java ${version})\nRUN apt-get update && apt-get install -y \\\n openjdk-${version}-jdk \\\n && rm -rf /var/lib/apt/lists/* \\\n && ln -s /usr/lib/jvm/java-${version}-openjdk-$(dpkg --print-architecture) /usr/lib/jvm/java-${version}-openjdk\nENV JAVA_HOME=\"/usr/lib/jvm/java-${version}-openjdk\"\n# Install Gradle\nRUN curl -fsSL https://services.gradle.org/distributions/gradle-8.5-bin.zip -o /tmp/gradle.zip && \\\n unzip -d /opt /tmp/gradle.zip && \\\n rm /tmp/gradle.zip\nENV PATH=\"/opt/gradle-8.5/bin:$PATH\"\n# Install Kotlin compiler\nRUN curl -fsSL https://github.com/JetBrains/kotlin/releases/download/v1.9.22/kotlin-compiler-1.9.22.zip -o /tmp/kotlin.zip && \\\n unzip -d /opt /tmp/kotlin.zip && \\\n rm /tmp/kotlin.zip\nENV PATH=\"/opt/kotlinc/bin:$PATH\"",
168
+ "install": "# Install Kotlin and Gradle (Java ${version} via Eclipse Temurin)\nRUN mkdir -p /etc/apt/keyrings \\\n && wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /etc/apt/keyrings/adoptium.gpg \\\n && echo \"deb [signed-by=/etc/apt/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb bookworm main\" > /etc/apt/sources.list.d/adoptium.list \\\n && apt-get update && apt-get install -y \\\n temurin-${version}-jdk \\\n && rm -rf /var/lib/apt/lists/* \\\n && ln -s /usr/lib/jvm/temurin-${version}-jdk-$(dpkg --print-architecture) /usr/lib/jvm/temurin-${version}-jdk\nENV JAVA_HOME=\"/usr/lib/jvm/temurin-${version}-jdk\"\nENV PATH=\"$JAVA_HOME/bin:$PATH\"\n# Install Gradle\nRUN curl -fsSL https://services.gradle.org/distributions/gradle-8.5-bin.zip -o /tmp/gradle.zip && \\\n unzip -d /opt /tmp/gradle.zip && \\\n rm /tmp/gradle.zip\nENV PATH=\"/opt/gradle-8.5/bin:$PATH\"\n# Install Kotlin compiler\nRUN curl -fsSL https://github.com/JetBrains/kotlin/releases/download/v1.9.22/kotlin-compiler-1.9.22.zip -o /tmp/kotlin.zip && \\\n unzip -d /opt /tmp/kotlin.zip && \\\n rm /tmp/kotlin.zip\nENV PATH=\"/opt/kotlinc/bin:$PATH\"",
169
169
  "version": 17,
170
170
  "versionConfigurable": true,
171
171
  "gradleVersion": "8.5",
@@ -282,7 +282,9 @@
282
282
  "checkCommand": "mix compile --warnings-as-errors",
283
283
  "testCommand": "mix test",
284
284
  "docker": {
285
- "install": "# Install Erlang and Elixir\nRUN apt-get update && apt-get install -y \\\n erlang \\\n elixir \\\n && rm -rf /var/lib/apt/lists/*\nRUN mix local.hex --force && mix local.rebar --force"
285
+ "install": "# Install Erlang/OTP 27 from RabbitMQ team's apt repo\nRUN apt-get update && apt-get install -y curl gnupg \\\n && curl -1sLf \"https://keys.openpgp.org/vks/v1/by-fingerprint/0A9AF2115F4687BD29803A206B73A36E6026DFCA\" | gpg --dearmor > /usr/share/keyrings/rabbitmq-erlang.gpg \\\n && echo \"deb [signed-by=/usr/share/keyrings/rabbitmq-erlang.gpg] https://deb1.rabbitmq.com/rabbitmq-erlang/debian/bookworm bookworm main\" > /etc/apt/sources.list.d/rabbitmq-erlang.list \\\n && apt-get update && apt-get install -y erlang-base erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssl erlang-public-key erlang-asn1 erlang-crypto erlang-runtime-tools erlang-inets erlang-mnesia erlang-eunit \\\n && rm -rf /var/lib/apt/lists/*\n# Install Elixir 1.19.5 (precompiled for OTP 27)\nRUN curl -fsSL https://github.com/elixir-lang/elixir/releases/download/v1.19.5/elixir-otp-27.zip -o /tmp/elixir.zip \\\n && mkdir -p /opt/elixir \\\n && unzip -q /tmp/elixir.zip -d /opt/elixir \\\n && rm /tmp/elixir.zip\nENV PATH=\"/opt/elixir/bin:$PATH\"\nRUN mix local.hex --force && mix local.rebar --force",
286
+ "elixirVersion": "1.19.5",
287
+ "erlangVersion": "27"
286
288
  },
287
289
  "technologies": [
288
290
  { "name": "Phoenix", "description": "Web framework for Elixir" },
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { fixPrd } from "./commands/fix-prd.js";
13
13
  import { fixConfig } from "./commands/fix-config.js";
14
14
  import { daemon } from "./commands/daemon.js";
15
15
  import { notify } from "./commands/notify.js";
16
+ import { ask } from "./commands/ask.js";
16
17
  import { chat } from "./commands/chat.js";
17
18
  import { listen } from "./commands/listen.js";
18
19
  import { config } from "./commands/config.js";
@@ -38,6 +39,7 @@ const commands = {
38
39
  docker,
39
40
  daemon,
40
41
  notify,
42
+ ask,
41
43
  chat,
42
44
  listen,
43
45
  config,
@@ -175,7 +175,7 @@ export class TelegramChatClient {
175
175
  reject(new Error(`Telegram API error: ${response.description || "Unknown error"} (code: ${response.error_code})`));
176
176
  }
177
177
  }
178
- catch (err) {
178
+ catch {
179
179
  reject(new Error(`Failed to parse Telegram response: ${data}`));
180
180
  }
181
181
  });
@@ -155,6 +155,7 @@ export async function executeClaudeCodeResponder(prompt, responderConfig, option
155
155
  */
156
156
  function formatClaudeCodeOutput(output) {
157
157
  // Remove ANSI escape codes
158
+ // eslint-disable-next-line no-control-regex
158
159
  let cleaned = output.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
159
160
  // Remove carriage returns (used for progress overwriting)
160
161
  cleaned = cleaned.replace(/\r/g, "");
@@ -244,6 +244,7 @@ function formatCLIOutput(stdout, stderr) {
244
244
  output = output.trim() + "\n\n[stderr]\n" + stderr.trim();
245
245
  }
246
246
  // Remove ANSI escape codes
247
+ // eslint-disable-next-line no-control-regex
247
248
  let cleaned = output.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
248
249
  // Remove carriage returns (used for progress overwriting)
249
250
  cleaned = cleaned.replace(/\r/g, "");
@@ -2,7 +2,7 @@
2
2
  * LLM Responder - Sends messages to LLM providers and returns responses.
3
3
  * Used by chat clients to respond to messages matched by the responder matcher.
4
4
  */
5
- import { getLLMProviders, loadConfig, } from "../utils/config.js";
5
+ import { getLLMProviders, loadConfig } from "../utils/config.js";
6
6
  import { createLLMClient } from "../utils/llm-client.js";
7
7
  import { createResponderLog } from "../utils/responder-logger.js";
8
8
  import { basename, resolve } from "path";
@@ -48,8 +48,8 @@ PROJECT_NAME="\${1:-${projectName}}"
48
48
  SCRIPT_DIR="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
49
49
  PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
50
50
 
51
- echo "Generating Xcode project: \$PROJECT_NAME"
52
- echo "Project root: \$PROJECT_ROOT"
51
+ echo "Generating Xcode project: $PROJECT_NAME"
52
+ echo "Project root: $PROJECT_ROOT"
53
53
  echo ""
54
54
 
55
55
  cd "$PROJECT_ROOT"
@@ -62,7 +62,7 @@ if [ ! -f "Package.swift" ]; then
62
62
  fi
63
63
 
64
64
  # Create Supporting Files directory for Info.plist and entitlements
65
- SUPPORT_DIR="$PROJECT_ROOT/Sources/\$PROJECT_NAME/Supporting Files"
65
+ SUPPORT_DIR="$PROJECT_ROOT/Sources/$PROJECT_NAME/Supporting Files"
66
66
  mkdir -p "$SUPPORT_DIR"
67
67
 
68
68
  # Generate Info.plist if it doesn't exist
@@ -105,7 +105,7 @@ PLIST_EOF
105
105
  fi
106
106
 
107
107
  # Generate entitlements file if it doesn't exist
108
- ENTITLEMENTS="$SUPPORT_DIR/\$PROJECT_NAME.entitlements"
108
+ ENTITLEMENTS="$SUPPORT_DIR/$PROJECT_NAME.entitlements"
109
109
  if [ ! -f "$ENTITLEMENTS" ]; then
110
110
  echo "Creating entitlements file..."
111
111
  cat > "$ENTITLEMENTS" << 'ENTITLEMENTS_EOF'
@@ -135,7 +135,7 @@ if command -v xcodegen &> /dev/null; then
135
135
  if [ ! -f "$PROJECT_YML" ]; then
136
136
  echo "Creating project.yml for xcodegen..."
137
137
  cat > "$PROJECT_YML" << YAML_EOF
138
- name: \$PROJECT_NAME
138
+ name: $PROJECT_NAME
139
139
  options:
140
140
  bundleIdPrefix: com.example
141
141
  deploymentTarget:
@@ -144,18 +144,18 @@ options:
144
144
  generateEmptyDirectories: true
145
145
 
146
146
  targets:
147
- \$PROJECT_NAME:
147
+ $PROJECT_NAME:
148
148
  type: application
149
149
  platform: macOS
150
150
  sources:
151
- - path: Sources/\$PROJECT_NAME
151
+ - path: Sources/$PROJECT_NAME
152
152
  excludes:
153
153
  - "**/*.entitlements"
154
154
  settings:
155
155
  base:
156
- PRODUCT_BUNDLE_IDENTIFIER: com.example.\$PROJECT_NAME
157
- INFOPLIST_FILE: Sources/\$PROJECT_NAME/Supporting Files/Info.plist
158
- CODE_SIGN_ENTITLEMENTS: Sources/\$PROJECT_NAME/Supporting Files/\$PROJECT_NAME.entitlements
156
+ PRODUCT_BUNDLE_IDENTIFIER: com.example.$PROJECT_NAME
157
+ INFOPLIST_FILE: Sources/$PROJECT_NAME/Supporting Files/Info.plist
158
+ CODE_SIGN_ENTITLEMENTS: Sources/$PROJECT_NAME/Supporting Files/$PROJECT_NAME.entitlements
159
159
  MACOSX_DEPLOYMENT_TARGET: "13.0"
160
160
  SWIFT_VERSION: "5.9"
161
161
  DEVELOPMENT_TEAM: ""
@@ -191,7 +191,7 @@ fi
191
191
 
192
192
  echo ""
193
193
  echo "Next steps:"
194
- echo " 1. Open \$PROJECT_NAME.xcodeproj in Xcode"
194
+ echo " 1. Open $PROJECT_NAME.xcodeproj in Xcode"
195
195
  echo " 2. Configure your Team ID for code signing"
196
196
  echo " 3. Build and run (Cmd+R)"
197
197
  echo ""
@@ -389,9 +389,9 @@ app_identifier "${bundleId}"
389
389
  * @param projectName - Name of the Xcode project/app
390
390
  */
391
391
  export function generateFastlaneReadmeSection(projectName = "App") {
392
- return `## Fastlane Deployment
392
+ return `## Fastlane Deployment for ${projectName}
393
393
 
394
- This project includes [Fastlane](https://fastlane.tools/) configuration for automated builds and deployments.
394
+ This project includes [Fastlane](https://fastlane.tools/) configuration for automated builds and deployments of ${projectName}.
395
395
 
396
396
  ### Setup
397
397
 
@@ -418,9 +418,9 @@ Run Fastlane commands from the \`scripts/\` directory:
418
418
 
419
419
  | Lane | Description |
420
420
  |------|-------------|
421
- | \`fastlane tests\` | Run all unit and UI tests |
422
- | \`fastlane beta\` | Build and upload to TestFlight |
423
- | \`fastlane release\` | Build and submit to App Store |
421
+ | \`fastlane tests\` | Run all ${projectName} unit and UI tests |
422
+ | \`fastlane beta\` | Build ${projectName} and upload to TestFlight |
423
+ | \`fastlane release\` | Build ${projectName} and submit to App Store |
424
424
 
425
425
  ### Using with Ralph
426
426
 
@@ -438,8 +438,8 @@ ralph action fastlane_release
438
438
 
439
439
  ### Customization
440
440
 
441
- - **Fastfile** (\`scripts/fastlane/Fastfile\`): Add custom lanes for your workflow
442
- - **Appfile** (\`scripts/fastlane/Appfile\`): Configure app bundle ID and team settings
441
+ - **Fastfile** (\`scripts/fastlane/Fastfile\`): Add custom lanes for your ${projectName} workflow
442
+ - **Appfile** (\`scripts/fastlane/Appfile\`): Configure ${projectName} bundle ID and team settings
443
443
 
444
444
  For more information, see the [Fastlane documentation](https://docs.fastlane.tools/).
445
445
  `;
@@ -161,16 +161,16 @@ function truncateJsonString(str, maxLen) {
161
161
  /**
162
162
  * Simple syntax highlighting for JSON preview.
163
163
  */
164
- function highlightJson(json, maxLines, maxLineWidth = 60) {
164
+ function highlightJson(json, maxLines, maxLineWidth = 60, scrollOffset = 0) {
165
165
  const lines = json.split("\n");
166
- const displayLines = lines.slice(0, maxLines);
167
- const hasMore = lines.length > maxLines;
166
+ const displayLines = lines.slice(scrollOffset, scrollOffset + maxLines);
167
+ const hasMore = lines.length > scrollOffset + maxLines;
168
168
  const elements = [];
169
169
  // Calculate max string length based on available width (account for line number, indentation)
170
170
  const maxStringLen = Math.max(20, Math.min(50, maxLineWidth - 15));
171
171
  for (let i = 0; i < displayLines.length; i++) {
172
172
  const line = displayLines[i];
173
- const lineNum = String(i + 1).padStart(3, " ");
173
+ const lineNum = String(scrollOffset + i + 1).padStart(3, " ");
174
174
  // Simple tokenization for highlighting
175
175
  const tokens = [];
176
176
  let remaining = line;
@@ -230,7 +230,7 @@ function highlightJson(json, maxLines, maxLineWidth = 60) {
230
230
  elements.push(_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: [lineNum, " "] }), tokens] }, i));
231
231
  }
232
232
  if (hasMore) {
233
- elements.push(_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [" ... (", lines.length - maxLines, " more lines)"] }) }, "more"));
233
+ elements.push(_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [" ... (", lines.length - scrollOffset - maxLines, " more lines)"] }) }, "more"));
234
234
  }
235
235
  return elements;
236
236
  }
@@ -246,8 +246,8 @@ export function JsonSnippetEditor({ label, value, onConfirm, onCancel, isFocused
246
246
  const [editText, setEditText] = useState(initialJson);
247
247
  const [parseError, setParseError] = useState(null);
248
248
  const [warnings, setWarnings] = useState([]);
249
- const [scrollOffset, setScrollOffset] = useState(0);
250
249
  const [copied, setCopied] = useState(false);
250
+ const [scrollOffset, setScrollOffset] = useState(0);
251
251
  // Validate JSON as user types
252
252
  useEffect(() => {
253
253
  const result = parseJsonWithLineInfo(editText);
@@ -375,7 +375,7 @@ export function JsonSnippetEditor({ label, value, onConfirm, onCancel, isFocused
375
375
  // Render view mode with syntax-highlighted preview
376
376
  // Account for border (2) and padding (2) when calculating preview width
377
377
  const previewWidth = Math.max(40, maxWidth - 4);
378
- const previewLines = highlightJson(editText, previewMaxLines, previewWidth);
378
+ const previewLines = highlightJson(editText, previewMaxLines, previewWidth, scrollOffset);
379
379
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Text, { bold: true, color: "cyan", children: ["Edit as JSON: ", label] }), copied && _jsx(Text, { color: "green", children: " Copied!" })] }), _jsx(Box, { flexDirection: "column", marginBottom: 1, children: previewLines }), _jsx(Box, { marginBottom: 1, flexDirection: "column", children: parseError ? (_jsx(Box, { children: _jsxs(Text, { color: "red", bold: true, children: ["Error:", " ", parseError.line && parseError.column
380
380
  ? `Line ${parseError.line}:${parseError.column} - `
381
381
  : "", parseError.message] }) })) : warningCount > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "yellow", bold: true, children: [warningCount, " warning", warningCount > 1 ? "s" : "", ":"] }), warnings.slice(0, 3).map((w, i) => (_jsxs(Text, { color: "yellow", dimColor: true, children: [" ", "- ", w] }, i))), warningCount > 3 && _jsxs(Text, { dimColor: true, children: [" ... and ", warningCount - 3, " more"] })] })) : (_jsx(Text, { color: "green", children: "Valid JSON" })) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "e/Enter: edit | s: save | c: copy | f: format" }), _jsx(Text, { dimColor: true, children: "j/k: scroll | PgUp/Dn: page | Esc: cancel" })] })] }));
@@ -72,7 +72,11 @@ export const PROVIDER_HINTS = {
72
72
  ],
73
73
  openai: [
74
74
  { key: "type", description: "Provider type (openai)", required: true },
75
- { key: "model", description: "Model name (e.g., gpt-4o, gpt-4-turbo, gpt-3.5-turbo)", required: true },
75
+ {
76
+ key: "model",
77
+ description: "Model name (e.g., gpt-4o, gpt-4-turbo, gpt-3.5-turbo)",
78
+ required: true,
79
+ },
76
80
  { key: "apiKey", description: "API key (defaults to OPENAI_API_KEY env var)" },
77
81
  { key: "baseUrl", description: "Custom API base URL (for OpenAI-compatible services)" },
78
82
  ],
@@ -252,19 +252,13 @@ export function LLMProvidersEditor({ label, providers, onConfirm, onCancel, isFo
252
252
  }, { isActive: isFocused && mode === "select-type" });
253
253
  // Handle keyboard input for text editing modes
254
254
  useInput((_input, key) => {
255
- if (!isFocused ||
256
- mode === "list" ||
257
- mode === "select-type" ||
258
- mode === "edit-provider")
255
+ if (!isFocused || mode === "list" || mode === "select-type" || mode === "edit-provider")
259
256
  return;
260
257
  if (key.escape) {
261
258
  handleCancel();
262
259
  }
263
260
  }, {
264
- isActive: isFocused &&
265
- mode !== "list" &&
266
- mode !== "select-type" &&
267
- mode !== "edit-provider",
261
+ isActive: isFocused && mode !== "list" && mode !== "select-type" && mode !== "edit-provider",
268
262
  });
269
263
  // Handle keyboard input for edit-provider mode (viewing a provider)
270
264
  useInput((input, key) => {
@@ -315,7 +309,11 @@ export function LLMProvidersEditor({ label, providers, onConfirm, onCancel, isFo
315
309
  if (mode === "select-type") {
316
310
  return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Select Provider Type" }), editingProvider && _jsxs(Text, { dimColor: true, children: [" for \"", editingProvider.name, "\""] })] }), PROVIDER_TYPES.map((type, index) => {
317
311
  const isHighlighted = index === typeIndex;
318
- return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : undefined, inverse: isHighlighted, children: type }), _jsxs(Text, { dimColor: true, children: [" - ", type === "anthropic" ? "Claude models" : type === "openai" ? "GPT models" : "Local models"] })] }, type));
312
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? "cyan" : undefined, children: isHighlighted ? "▸ " : " " }), _jsx(Text, { bold: isHighlighted, color: isHighlighted ? "cyan" : undefined, inverse: isHighlighted, children: type }), _jsxs(Text, { dimColor: true, children: [" ", "-", " ", type === "anthropic"
313
+ ? "Claude models"
314
+ : type === "openai"
315
+ ? "GPT models"
316
+ : "Local models"] })] }, type));
319
317
  }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "j/k: navigate | Enter: select | Esc: cancel" }) })] }));
320
318
  }
321
319
  // Render name input mode
@@ -89,7 +89,7 @@ function highlightJson(jsonString) {
89
89
  continue;
90
90
  }
91
91
  // Match brackets
92
- const bracketMatch = remaining.match(/^[\[\]{}]/);
92
+ const bracketMatch = remaining.match(/^[[\]{}]/);
93
93
  if (bracketMatch) {
94
94
  tokens.push({ type: "bracket", value: bracketMatch[0] });
95
95
  remaining = remaining.slice(1);
@@ -16,7 +16,16 @@ export const CONFIG_SECTIONS = [
16
16
  id: "cli",
17
17
  label: "CLI",
18
18
  icon: "⌨",
19
- fields: ["cliProvider", "cli.command", "cli.args", "cli.model", "cli.yoloArgs", "cli.promptArgs", "cli.modelArgs", "cli.fileArgs"],
19
+ fields: [
20
+ "cliProvider",
21
+ "cli.command",
22
+ "cli.args",
23
+ "cli.model",
24
+ "cli.yoloArgs",
25
+ "cli.promptArgs",
26
+ "cli.modelArgs",
27
+ "cli.fileArgs",
28
+ ],
20
29
  },
21
30
  {
22
31
  id: "docker",
@@ -52,7 +61,14 @@ export const CONFIG_SECTIONS = [
52
61
  id: "chat",
53
62
  label: "Chat",
54
63
  icon: "💬",
55
- fields: ["chat.enabled", "chat.provider", "chat.telegram", "chat.slack", "chat.discord", "chat.responders"],
64
+ fields: [
65
+ "chat.enabled",
66
+ "chat.provider",
67
+ "chat.telegram",
68
+ "chat.slack",
69
+ "chat.discord",
70
+ "chat.responders",
71
+ ],
56
72
  },
57
73
  {
58
74
  id: "notifications",
@@ -84,6 +84,7 @@ export function escapeHtml(text) {
84
84
  */
85
85
  export function stripAnsiCodes(text) {
86
86
  // Match ANSI escape sequences: ESC[...m (SGR), ESC[...K (EL), etc.
87
+ // eslint-disable-next-line no-control-regex
87
88
  return text.replace(/\x1B\[[0-9;]*[mKJHfsu]/g, "");
88
89
  }
89
90
  /**
@@ -190,6 +190,7 @@ export interface RalphConfig {
190
190
  autoStart?: boolean;
191
191
  restartCount?: number;
192
192
  worktreesPath?: string;
193
+ envFile?: string;
193
194
  };
194
195
  claude?: {
195
196
  mcpServers?: Record<string, McpServerConfig>;
@@ -134,7 +134,9 @@ export function getProjectName() {
134
134
  catch {
135
135
  // Config not available, fall back to directory name
136
136
  }
137
- return basename(process.cwd()).toLowerCase().replace(/[^a-z0-9-]/g, "-");
137
+ return basename(process.cwd())
138
+ .toLowerCase()
139
+ .replace(/[^a-z0-9-]/g, "-");
138
140
  }
139
141
  export function loadPrompt() {
140
142
  const promptPath = join(getRalphDir(), PROMPT_FILE);
@@ -0,0 +1 @@
1
+ export {};