openthrottle 0.1.6 → 0.1.8
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 +15 -8
- package/package.json +1 -1
- package/templates/wake-sandbox.yml +60 -47
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
|
}
|
|
@@ -339,6 +337,9 @@ async function pushSecrets(config) {
|
|
|
339
337
|
console.log(' Skipped secret push.');
|
|
340
338
|
return;
|
|
341
339
|
}
|
|
340
|
+
// GitHub rejects secret names starting with GITHUB_ (reserved prefix)
|
|
341
|
+
const reservedPrefix = /^GITHUB_/i;
|
|
342
|
+
const skippedKeys = [];
|
|
342
343
|
let pushed = 0;
|
|
343
344
|
let failed = 0;
|
|
344
345
|
for (const [path, keys] of Object.entries(envFiles)) {
|
|
@@ -369,6 +370,10 @@ async function pushSecrets(config) {
|
|
|
369
370
|
}
|
|
370
371
|
if (!keys.includes(key))
|
|
371
372
|
continue;
|
|
373
|
+
if (reservedPrefix.test(key)) {
|
|
374
|
+
skippedKeys.push(key);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
372
377
|
try {
|
|
373
378
|
execFileSync('gh', ['secret', 'set', key, '--repo', repo], {
|
|
374
379
|
input: value,
|
|
@@ -382,17 +387,19 @@ async function pushSecrets(config) {
|
|
|
382
387
|
}
|
|
383
388
|
}
|
|
384
389
|
}
|
|
390
|
+
if (skippedKeys.length > 0) {
|
|
391
|
+
console.log(` Skipped ${skippedKeys.length} key(s) with reserved GITHUB_ prefix:`);
|
|
392
|
+
for (const key of skippedKeys) {
|
|
393
|
+
console.log(` ${key} — rename to ${key.replace(/^GITHUB_/i, 'GH_')} in your .env and workflow`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
385
396
|
console.log(` Pushed ${pushed} secret(s) to ${repo}${failed > 0 ? ` (${failed} failed)` : ''}`);
|
|
386
397
|
}
|
|
387
398
|
// ---------------------------------------------------------------------------
|
|
388
399
|
// 7. Print next steps
|
|
389
400
|
// ---------------------------------------------------------------------------
|
|
390
401
|
function printNextSteps(config) {
|
|
391
|
-
const agentSecret =
|
|
392
|
-
? ' ANTHROPIC_API_KEY <- option a: pay-per-use API key\n CLAUDE_CODE_OAUTH_TOKEN <- option b: subscription token (claude setup-token)'
|
|
393
|
-
: config.agent === 'codex'
|
|
394
|
-
? ' OPENAI_API_KEY <- required for Codex'
|
|
395
|
-
: ' OPENAI_API_KEY <- or ANTHROPIC_API_KEY (depends on your Aider model)';
|
|
402
|
+
const agentSecret = ' ANTHROPIC_API_KEY <- option a: pay-per-use API key\n CLAUDE_CODE_OAUTH_TOKEN <- option b: subscription token (claude setup-token)';
|
|
396
403
|
const secrets = [
|
|
397
404
|
' DAYTONA_API_KEY <- required',
|
|
398
405
|
agentSecret,
|
package/package.json
CHANGED
|
@@ -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@
|
|
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
|
|
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
|
-
#
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
--jq '[.comments[].body | capture("session-id: (?<id>[^ ]+)") | .id] | last // empty') || true
|
|
82
|
+
FROM_PR="${EVENT_PR_NUM}"
|
|
85
83
|
fi
|
|
86
|
-
echo "
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
--snapshot "$
|
|
117
|
-
--
|
|
118
|
-
--auto-stop
|
|
119
|
-
--
|
|
120
|
-
--label
|
|
121
|
-
--label
|
|
122
|
-
--
|
|
123
|
-
--
|
|
124
|
-
--env
|
|
125
|
-
--env
|
|
126
|
-
--env
|
|
127
|
-
--env
|
|
128
|
-
--env
|
|
129
|
-
--env
|
|
130
|
-
--env
|
|
131
|
-
--env
|
|
132
|
-
--env
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|