openthrottle 0.1.5 → 0.1.7

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/dist/init.js CHANGED
@@ -142,8 +142,6 @@ async function promptConfig(detected) {
142
142
  type: 'select', name: 'agent', message: 'Agent runtime',
143
143
  choices: [
144
144
  { title: 'Claude', value: 'claude' },
145
- { title: 'Codex', value: 'codex' },
146
- { title: 'Aider', value: 'aider' },
147
145
  ],
148
146
  initial: 0,
149
147
  },
@@ -245,7 +243,7 @@ function copyWorkflow(config) {
245
243
  content = content.replace(/ # @@ENV_SECRETS@@ — scaffolder inserts project-specific secrets here/, envSecrets);
246
244
  // Add --env flags for daytona create
247
245
  const envFlags = allKeys
248
- .map(k => ` --env ${k}=\${${k}} \\`)
246
+ .map(k => ` --env "${k}=\${${k}}" \\`)
249
247
  .join('\n');
250
248
  content = content.replace(/ # @@ENV_FLAGS@@ — scaffolder inserts --env flags for project secrets here/, envFlags);
251
249
  }
@@ -287,7 +285,9 @@ function setupDaytona(config) {
287
285
  console.log(` Snapshot already exists: ${snapshotName}`);
288
286
  }
289
287
  else {
290
- console.log(` Snapshot creation failed. You can create it manually:`);
288
+ const detail = getErrorMessage(err);
289
+ console.log(` Snapshot creation failed: ${detail}`);
290
+ console.log(` You can create it manually:`);
291
291
  console.log(` daytona snapshot create ${snapshotName} --image ${image} --cpu 2 --memory 4 --disk 10`);
292
292
  }
293
293
  }
@@ -304,9 +304,10 @@ async function pushSecrets(config) {
304
304
  try {
305
305
  execFileSync('gh', ['auth', 'status'], { stdio: 'pipe' });
306
306
  }
307
- catch {
308
- console.log('\n gh CLI not authenticated — skipping secret push.');
309
- console.log(' Run "gh auth login" then set secrets manually.\n');
307
+ catch (err) {
308
+ const detail = getErrorMessage(err);
309
+ console.log(`\n gh CLI check failed: ${detail}`);
310
+ console.log(' Skipping secret push. Run "gh auth login" then set secrets manually.\n');
310
311
  return;
311
312
  }
312
313
  // Detect repo
@@ -316,8 +317,9 @@ async function pushSecrets(config) {
316
317
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
317
318
  }).trim();
318
319
  }
319
- catch {
320
- console.log('\n Could not detect GitHub repo — skipping secret push.\n');
320
+ catch (err) {
321
+ console.log(`\n Could not detect GitHub repo: ${getErrorMessage(err)}`);
322
+ console.log(' Skipping secret push.\n');
321
323
  return;
322
324
  }
323
325
  // Show what we'd push
@@ -384,11 +386,7 @@ async function pushSecrets(config) {
384
386
  // 7. Print next steps
385
387
  // ---------------------------------------------------------------------------
386
388
  function printNextSteps(config) {
387
- const agentSecret = config.agent === 'claude'
388
- ? ' ANTHROPIC_API_KEY <- option a: pay-per-use API key\n CLAUDE_CODE_OAUTH_TOKEN <- option b: subscription token (claude setup-token)'
389
- : config.agent === 'codex'
390
- ? ' OPENAI_API_KEY <- required for Codex'
391
- : ' OPENAI_API_KEY <- or ANTHROPIC_API_KEY (depends on your Aider model)';
389
+ const agentSecret = ' ANTHROPIC_API_KEY <- option a: pay-per-use API key\n CLAUDE_CODE_OAUTH_TOKEN <- option b: subscription token (claude setup-token)';
392
390
  const secrets = [
393
391
  ' DAYTONA_API_KEY <- required',
394
392
  agentSecret,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openthrottle",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "CLI for Open Throttle — ship prompts to Daytona sandboxes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,12 +37,10 @@ permissions:
37
37
 
38
38
  jobs:
39
39
  run-task:
40
- if: >-
41
- contains(fromJSON('["prd-queued", "bug-queued", "needs-review", "needs-investigation"]'), github.event.label.name) ||
42
- (github.event.review.state == 'changes_requested')
40
+ if: ${{ contains(fromJSON('["prd-queued","bug-queued","needs-review","needs-investigation"]'), github.event.label.name) || (github.event.review.state == 'changes_requested') }}
43
41
  runs-on: ubuntu-latest
44
42
  steps:
45
- - uses: actions/checkout@v4
43
+ - uses: actions/checkout@v6
46
44
 
47
45
  - name: Resolve work item
48
46
  id: work
@@ -64,7 +62,7 @@ jobs:
64
62
  fi
65
63
  echo "item=$WORK_ITEM" >> "$GITHUB_OUTPUT"
66
64
 
67
- # Determine task type (using env vars, not inline ${{ }} expressions)
65
+ # Determine task type (using env vars, not inline expressions)
68
66
  TASK_TYPE="prd"
69
67
  if [[ "$EVENT_LABEL" == "bug-queued" ]]; then
70
68
  TASK_TYPE="bug"
@@ -77,13 +75,19 @@ jobs:
77
75
  fi
78
76
  echo "task_type=$TASK_TYPE" >> "$GITHUB_OUTPUT"
79
77
 
80
- # Extract session ID for review fixes
81
- RESUME_SESSION=""
78
+ # For review-fix tasks, pass the PR number so the sandbox can
79
+ # use `claude --from-pr` to resume conversation context
80
+ FROM_PR=""
82
81
  if [[ "$TASK_TYPE" == "review-fix" ]]; then
83
- RESUME_SESSION=$(gh pr view "$EVENT_PR_NUM" --json comments \
84
- --jq '[.comments[].body | capture("session-id: (?<id>[^ ]+)") | .id] | last // empty') || true
82
+ FROM_PR="${EVENT_PR_NUM}"
85
83
  fi
86
- echo "resume_session=$RESUME_SESSION" >> "$GITHUB_OUTPUT"
84
+ echo "from_pr=$FROM_PR" >> "$GITHUB_OUTPUT"
85
+
86
+ - name: Install Daytona CLI
87
+ run: curl -Lo daytona "https://download.daytona.io/cli/latest/daytona-linux-amd64" && sudo chmod +x daytona && sudo mv daytona /usr/local/bin/
88
+
89
+ - name: Install yq
90
+ run: sudo snap install yq
87
91
 
88
92
  - name: Validate config
89
93
  id: config
@@ -95,11 +99,25 @@ jobs:
95
99
  fi
96
100
  echo "snapshot=$SNAPSHOT" >> "$GITHUB_OUTPUT"
97
101
 
98
- - name: Activate snapshot (reactivates if idle >2 weeks)
102
+ - name: Authenticate Daytona CLI
103
+ run: daytona login --api-key "$DAYTONA_API_KEY"
104
+ env:
105
+ DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
106
+
107
+ - name: Verify snapshot exists
99
108
  env:
100
109
  DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
110
+ SNAPSHOT: ${{ steps.config.outputs.snapshot }}
101
111
  run: |
102
- daytona snapshot activate "${{ steps.config.outputs.snapshot }}" 2>/dev/null || true
112
+ # Reactivate if idle >2 weeks, then verify it exists
113
+ daytona snapshot activate "$SNAPSHOT" 2>/dev/null || true
114
+ if ! daytona snapshot list 2>/dev/null | grep -q "$SNAPSHOT"; then
115
+ echo "::error::Snapshot '$SNAPSHOT' not found. Create it first: daytona snapshot create $SNAPSHOT --image <your-image:tag>"
116
+ echo "Available snapshots:"
117
+ daytona snapshot list 2>/dev/null || echo " (could not list snapshots)"
118
+ exit 1
119
+ fi
120
+ echo "Snapshot verified: $SNAPSHOT"
103
121
 
104
122
  - name: Create and run sandbox
105
123
  env:
@@ -109,42 +127,37 @@ jobs:
109
127
  CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
110
128
  OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
111
129
  SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
130
+ SNAPSHOT: ${{ steps.config.outputs.snapshot }}
131
+ WORK_ITEM: ${{ steps.work.outputs.item }}
132
+ TASK_TYPE: ${{ steps.work.outputs.task_type }}
133
+ FROM_PR: ${{ steps.work.outputs.from_pr }}
112
134
  # @@ENV_SECRETS@@ — scaffolder inserts project-specific secrets here
113
135
  run: |
136
+ SANDBOX_NAME="ot-${TASK_TYPE}-${WORK_ITEM}-${GITHUB_RUN_ID}"
137
+
114
138
  # Create ephemeral sandbox
115
- OUTPUT=$(daytona create \
116
- --snapshot "${{ steps.config.outputs.snapshot }}" \
117
- --auto-delete 0 \
118
- --auto-stop 60 \
119
- --cpu 2 --memory 4096 --disk 10 \
120
- --label project=${{ github.event.repository.name }} \
121
- --label task_type="${{ steps.work.outputs.task_type }}" \
122
- --label issue="${{ steps.work.outputs.item }}" \
123
- --volume openthrottle-${{ github.repository_id }}:/home/daytona/.claude \
124
- --env GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
125
- --env GITHUB_REPO=${{ github.repository }} \
126
- --env ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} \
127
- --env CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN} \
128
- --env OPENAI_API_KEY=${OPENAI_API_KEY} \
129
- --env SUPABASE_ACCESS_TOKEN=${SUPABASE_ACCESS_TOKEN} \
130
- --env TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }} \
131
- --env TELEGRAM_CHAT_ID=${{ secrets.TELEGRAM_CHAT_ID }} \
132
- --env WORK_ITEM="${{ steps.work.outputs.item }}" \
133
- --env TASK_TYPE="${{ steps.work.outputs.task_type }}" \
134
- --env RESUME_SESSION="${{ steps.work.outputs.resume_session }}" \
139
+ daytona create \
140
+ --snapshot "$SNAPSHOT" \
141
+ --name "$SANDBOX_NAME" \
142
+ --auto-stop 180 \
143
+ --label "project=${{ github.event.repository.name }}" \
144
+ --label "task_type=${TASK_TYPE}" \
145
+ --label "issue=${WORK_ITEM}" \
146
+ --env "GITHUB_TOKEN=${{ secrets.GH_PAT }}" \
147
+ --env "GITHUB_REPO=${{ github.repository }}" \
148
+ --env "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}" \
149
+ --env "CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN}" \
150
+ --env "OPENAI_API_KEY=${OPENAI_API_KEY}" \
151
+ --env "SUPABASE_ACCESS_TOKEN=${SUPABASE_ACCESS_TOKEN}" \
152
+ --env "TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}" \
153
+ --env "TELEGRAM_CHAT_ID=${{ secrets.TELEGRAM_CHAT_ID }}" \
154
+ --env "WORK_ITEM=${WORK_ITEM}" \
155
+ --env "TASK_TYPE=${TASK_TYPE}" \
156
+ --env "FROM_PR=${FROM_PR}" \
135
157
  # @@ENV_FLAGS@@ — scaffolder inserts --env flags for project secrets here
136
- 2>&1) || {
137
- # Redact secrets from error output
138
- SAFE_OUTPUT=$(echo "$OUTPUT" | sed \
139
- -e "s/${ANTHROPIC_API_KEY:-___}/[REDACTED]/g" \
140
- -e "s/${CLAUDE_CODE_OAUTH_TOKEN:-___}/[REDACTED]/g" \
141
- -e "s/${OPENAI_API_KEY:-___}/[REDACTED]/g" \
142
- -e "s/${SUPABASE_ACCESS_TOKEN:-___}/[REDACTED]/g" \
143
- -e "s/${GH_TOKEN:-___}/[REDACTED]/g")
144
- echo "::error::Sandbox creation failed: $SAFE_OUTPUT"
145
- exit 1
146
- }
147
- SANDBOX_ID="$OUTPUT"
148
-
149
- echo "Sandbox created: $SANDBOX_ID"
150
- echo "Task: ${{ steps.work.outputs.task_type }} #${{ steps.work.outputs.item }}"
158
+
159
+ echo "Sandbox created: $SANDBOX_NAME"
160
+ echo "Task: ${TASK_TYPE} #${WORK_ITEM}"
161
+ if [[ -n "$FROM_PR" ]]; then
162
+ echo "Review-fix mode: sandbox will use 'claude --from-pr $FROM_PR' to resume PR context"
163
+ fi