setorra 0.2.5 → 0.3.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.
- package/README.md +5 -4
- package/dist/setorra.js +162 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,13 +4,13 @@ Onboards a GitHub repository for automated API migration. It installs two workfl
|
|
|
4
4
|
and opens a pull request for you to review.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
|
-
npx setorra init claude # or: codex
|
|
7
|
+
npx setorra init claude # or: codex, cursor
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
## What it writes
|
|
11
11
|
|
|
12
|
-
One agent workflow (Claude shown; Codex
|
|
13
|
-
completion workflow:
|
|
12
|
+
One agent workflow (Claude shown; Codex and Cursor use `api-migration-codex.yml` and
|
|
13
|
+
`api-migration-cursor.yml` respectively) and a shared completion workflow:
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
.github/workflows/api-migration-claude.yml
|
|
@@ -52,6 +52,7 @@ The workflow needs the selected agent credential:
|
|
|
52
52
|
| --- | --- |
|
|
53
53
|
| `claude` | `CLAUDE_CODE_OAUTH_TOKEN` (or `ANTHROPIC_API_KEY` with `--credential api_key`) |
|
|
54
54
|
| `codex` | `OPENAI_API_KEY` |
|
|
55
|
+
| `cursor` | `CURSOR_API_KEY` (a Cursor Enterprise service-account key is recommended) |
|
|
55
56
|
|
|
56
57
|
Add it under **Settings → Secrets and variables → Actions**.
|
|
57
58
|
|
|
@@ -70,7 +71,7 @@ revoke execution. Removing the agent secret alone does not disable PR completion
|
|
|
70
71
|
|
|
71
72
|
| Command | Purpose |
|
|
72
73
|
| --- | --- |
|
|
73
|
-
| `setorra init <claude\|codex>` | Install both workflows and open a pull request. |
|
|
74
|
+
| `setorra init <claude\|codex\|cursor>` | Install both workflows and open a pull request. |
|
|
74
75
|
| `setorra status` | Check the agent workflow and reviewed completion contract. |
|
|
75
76
|
| `setorra sync` | Upgrade both workflows, including adding a missing companion. |
|
|
76
77
|
|
package/dist/setorra.js
CHANGED
|
@@ -7301,13 +7301,15 @@ var require_dist = __commonJS({
|
|
|
7301
7301
|
var import_yaml = __toESM(require_dist());
|
|
7302
7302
|
|
|
7303
7303
|
// src/workflow/contracts.ts
|
|
7304
|
+
var AGENT_KINDS = ["claude", "codex", "cursor"];
|
|
7304
7305
|
var WORKFLOW_CONTRACT_VERSION = "agent-workflow/1";
|
|
7305
7306
|
var RESULT_CONTRACT_VERSION = "cloud-agent-result/v1";
|
|
7306
7307
|
var RESULT_ARTIFACT_NAME = "cloud-agent-result";
|
|
7307
7308
|
var RESULT_ARTIFACT_FILE = "cloud-agent-result.json";
|
|
7308
7309
|
var AGENT_WORKFLOW_PATHS = {
|
|
7309
7310
|
claude: ".github/workflows/api-migration-claude.yml",
|
|
7310
|
-
codex: ".github/workflows/api-migration-codex.yml"
|
|
7311
|
+
codex: ".github/workflows/api-migration-codex.yml",
|
|
7312
|
+
cursor: ".github/workflows/api-migration-cursor.yml"
|
|
7311
7313
|
};
|
|
7312
7314
|
var COMPLETION_WORKFLOW_PATH = ".github/workflows/setorra-pr-completion.yml";
|
|
7313
7315
|
var COMPLETION_MARKER = "# setorra-pr-completion/v1";
|
|
@@ -21835,7 +21837,7 @@ var UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]
|
|
|
21835
21837
|
var DIGEST = /^[a-f0-9]{64}$/u;
|
|
21836
21838
|
var SHA = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/u;
|
|
21837
21839
|
var GITHUB_ID = /^[1-9][0-9]{0,19}$/u;
|
|
21838
|
-
var WORKFLOW_PATH = /^\.github\/workflows\/api-migration-(?:claude|codex)\.yml$/u;
|
|
21840
|
+
var WORKFLOW_PATH = /^\.github\/workflows\/api-migration-(?:claude|codex|cursor)\.yml$/u;
|
|
21839
21841
|
var HandoffFields = {
|
|
21840
21842
|
handoffId: external_exports.string().regex(UUID),
|
|
21841
21843
|
handoffDigest: external_exports.string().regex(DIGEST),
|
|
@@ -21935,7 +21937,7 @@ function renderProvenanceValidationScript() {
|
|
|
21935
21937
|
"const digest = /^[a-f0-9]{64}$/;",
|
|
21936
21938
|
"const sha = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;",
|
|
21937
21939
|
"const githubId = /^[1-9][0-9]{0,19}$/;",
|
|
21938
|
-
"const workflowPath = /^\\.github\\/workflows\\/api-migration-(?:claude|codex)\\.yml$/;",
|
|
21940
|
+
"const workflowPath = /^\\.github\\/workflows\\/api-migration-(?:claude|codex|cursor)\\.yml$/;",
|
|
21939
21941
|
"if (!['release-agent-handoff/v2', 'release-agent-handoff/v3', 'release-agent-handoff/v4'].includes(provenance.schemaVersion) || provenance.workflowContractVersion !== 'agent-workflow/1' || provenance.resultContractVersion !== 'cloud-agent-result/v1' || !uuid.test(provenance.handoffId) || (provenance.runId !== undefined && !uuid.test(provenance.runId)) || !digest.test(provenance.handoffDigest) || !digest.test(provenance.contextDigest) || !githubId.test(provenance.repositoryId) || !sha.test(provenance.baseSha) || !githubId.test(provenance.workflowId) || !workflowPath.test(provenance.workflowPath)) fail('invalid_fields');",
|
|
21940
21942
|
"if (['release-agent-handoff/v3', 'release-agent-handoff/v4'].includes(provenance.schemaVersion) && !digest.test(provenance.contextPayloadDigest)) fail('invalid_fields');",
|
|
21941
21943
|
"if (provenance.schemaVersion === 'release-agent-handoff/v2' && provenance.contextPayloadDigest !== undefined && !digest.test(provenance.contextPayloadDigest)) fail('invalid_fields');",
|
|
@@ -22024,7 +22026,7 @@ const baseBranch = repository.default_branch;
|
|
|
22024
22026
|
const run = request(prefix + '/actions/runs/' + inputs.original_run_id);
|
|
22025
22027
|
const attempt = request(prefix + '/actions/runs/' + inputs.original_run_id + '/attempts/' + inputs.original_attempt);
|
|
22026
22028
|
for (const r of [run, attempt]) {
|
|
22027
|
-
if (String(r.id) !== inputs.original_run_id || String(r.run_attempt) !== inputs.original_attempt || r.status !== 'completed' || r.event !== 'issues' || r.head_sha !== inputs.expected_base_sha || String(r.repository?.id) !== process.env.GITHUB_REPOSITORY_ID || r.display_title !== 'api-migration-' + inputs.issue_number || !/^\.github\/workflows\/api-migration-(claude|codex)\.yml$/.test(r.path)) fail('original_workflow_changed');
|
|
22029
|
+
if (String(r.id) !== inputs.original_run_id || String(r.run_attempt) !== inputs.original_attempt || r.status !== 'completed' || r.event !== 'issues' || r.head_sha !== inputs.expected_base_sha || String(r.repository?.id) !== process.env.GITHUB_REPOSITORY_ID || r.display_title !== 'api-migration-' + inputs.issue_number || !/^\.github\/workflows\/api-migration-(claude|codex|cursor)\.yml$/.test(r.path)) fail('original_workflow_changed');
|
|
22028
22030
|
}
|
|
22029
22031
|
if (run.workflow_id !== attempt.workflow_id || run.path !== attempt.path) fail('original_workflow_changed');
|
|
22030
22032
|
function validateIssue(issue) {
|
|
@@ -22118,6 +22120,11 @@ var AGENT_ACTION_REPOSITORIES = {
|
|
|
22118
22120
|
claude: "anthropics/claude-code-action",
|
|
22119
22121
|
codex: "openai/codex-action"
|
|
22120
22122
|
};
|
|
22123
|
+
var AGENT_TITLES = {
|
|
22124
|
+
claude: "Claude",
|
|
22125
|
+
codex: "Codex",
|
|
22126
|
+
cursor: "Cursor"
|
|
22127
|
+
};
|
|
22121
22128
|
var CHECKOUT_ACTION_REPOSITORY = "actions/checkout";
|
|
22122
22129
|
var UPLOAD_ARTIFACT_ACTION_REPOSITORY = "actions/upload-artifact";
|
|
22123
22130
|
var AGENT_CREDENTIALS = {
|
|
@@ -22130,6 +22137,10 @@ var AGENT_CREDENTIALS = {
|
|
|
22130
22137
|
},
|
|
22131
22138
|
codex: {
|
|
22132
22139
|
api_key: { input: "openai-api-key", secret: "OPENAI_API_KEY" }
|
|
22140
|
+
},
|
|
22141
|
+
// The Cursor CLI reads its key from the environment, so its input is a variable name.
|
|
22142
|
+
cursor: {
|
|
22143
|
+
api_key: { input: "CURSOR_API_KEY", secret: "CURSOR_API_KEY" }
|
|
22133
22144
|
}
|
|
22134
22145
|
};
|
|
22135
22146
|
function agentCredentialInputs(agent) {
|
|
@@ -22144,15 +22155,49 @@ var PinnedActionSchema = external_exports.object({
|
|
|
22144
22155
|
sha: external_exports.string().regex(/^[a-f0-9]{40}$/u),
|
|
22145
22156
|
version: external_exports.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u)
|
|
22146
22157
|
}).strict();
|
|
22147
|
-
var
|
|
22148
|
-
|
|
22158
|
+
var CursorCliPinSchema = external_exports.object({
|
|
22159
|
+
version: external_exports.string().regex(/^[0-9]{4}\.[0-9]{2}\.[0-9]{2}-[a-f0-9]{7,40}$/u),
|
|
22160
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/u)
|
|
22161
|
+
}).strict();
|
|
22162
|
+
var CURSOR_CLI_DOWNLOAD_PREFIX = "https://downloads.cursor.com/lab/";
|
|
22163
|
+
function cursorCliUrl(pin) {
|
|
22164
|
+
return `${CURSOR_CLI_DOWNLOAD_PREFIX}${pin.version}/linux/x64/agent-cli-package.tar.gz`;
|
|
22165
|
+
}
|
|
22166
|
+
var CURSOR_DENIED_PERMISSIONS = [
|
|
22167
|
+
"Shell(git)",
|
|
22168
|
+
"Shell(gh)",
|
|
22169
|
+
"Shell(curl)",
|
|
22170
|
+
"Shell(wget)",
|
|
22171
|
+
"Shell(ssh)",
|
|
22172
|
+
"Shell(scp)",
|
|
22173
|
+
"Shell(sftp)",
|
|
22174
|
+
"Shell(nc)",
|
|
22175
|
+
"Shell(ncat)",
|
|
22176
|
+
"Shell(sudo)",
|
|
22177
|
+
"Write(.github/**)",
|
|
22178
|
+
"Write(.git/**)"
|
|
22179
|
+
];
|
|
22180
|
+
var BotLoginSchema = external_exports.string().min(4).max(64).regex(/^[a-z0-9][a-z0-9-]*\[bot\]$/u);
|
|
22181
|
+
var LabelSchema = external_exports.string().min(1).max(50).regex(/^[a-z0-9][a-z0-9._-]*$/u);
|
|
22182
|
+
var TemplateCommonFields = {
|
|
22149
22183
|
credential: external_exports.enum(["api_key", "oauth_token"]).default("api_key"),
|
|
22150
|
-
botLogin:
|
|
22151
|
-
label:
|
|
22184
|
+
botLogin: BotLoginSchema,
|
|
22185
|
+
label: LabelSchema,
|
|
22152
22186
|
checkoutAction: PinnedActionSchema,
|
|
22153
|
-
agentAction: PinnedActionSchema,
|
|
22154
22187
|
uploadArtifactAction: PinnedActionSchema
|
|
22155
|
-
}
|
|
22188
|
+
};
|
|
22189
|
+
var WorkflowTemplateInputSchema = external_exports.discriminatedUnion("agent", [
|
|
22190
|
+
external_exports.object({
|
|
22191
|
+
agent: external_exports.enum(["claude", "codex"]),
|
|
22192
|
+
...TemplateCommonFields,
|
|
22193
|
+
agentAction: PinnedActionSchema
|
|
22194
|
+
}).strict(),
|
|
22195
|
+
external_exports.object({
|
|
22196
|
+
agent: external_exports.literal("cursor"),
|
|
22197
|
+
...TemplateCommonFields,
|
|
22198
|
+
cursorCli: CursorCliPinSchema
|
|
22199
|
+
}).strict()
|
|
22200
|
+
]);
|
|
22156
22201
|
function yamlString(value) {
|
|
22157
22202
|
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
22158
22203
|
}
|
|
@@ -22295,6 +22340,9 @@ function renderHelperStep() {
|
|
|
22295
22340
|
];
|
|
22296
22341
|
}
|
|
22297
22342
|
function renderAgentSteps(input, selected) {
|
|
22343
|
+
if (input.agent === "cursor") {
|
|
22344
|
+
return renderCursorAgentStep(input.cursorCli, selected);
|
|
22345
|
+
}
|
|
22298
22346
|
const { agent, agentAction, botLogin } = input;
|
|
22299
22347
|
const credential = `${selected.input}: \${{ secrets.${selected.secret} }}`;
|
|
22300
22348
|
const allowlist = `${AGENT_BOT_ALLOWLIST_INPUTS[agent]}: ${yamlString(botLogin)}`;
|
|
@@ -22347,6 +22395,38 @@ function renderAgentSteps(input, selected) {
|
|
|
22347
22395
|
" prompt-file: ${{ runner.temp }}/agent-prompt.md"
|
|
22348
22396
|
];
|
|
22349
22397
|
}
|
|
22398
|
+
function renderCursorAgentStep(pin, selected) {
|
|
22399
|
+
const config2 = JSON.stringify({
|
|
22400
|
+
version: 1,
|
|
22401
|
+
editor: { vimMode: false },
|
|
22402
|
+
permissions: { allow: [], deny: CURSOR_DENIED_PERMISSIONS }
|
|
22403
|
+
});
|
|
22404
|
+
return [
|
|
22405
|
+
" - name: Run the Cursor coding agent",
|
|
22406
|
+
" id: agent",
|
|
22407
|
+
" continue-on-error: true",
|
|
22408
|
+
" env:",
|
|
22409
|
+
" GITHUB_ENV: /dev/null",
|
|
22410
|
+
" GITHUB_PATH: /dev/null",
|
|
22411
|
+
` ${selected.input}: \${{ secrets.${selected.secret} }}`,
|
|
22412
|
+
` CURSOR_CLI_URL: ${yamlString(cursorCliUrl(pin))}`,
|
|
22413
|
+
` CURSOR_CLI_SHA256: ${yamlString(pin.sha256)}`,
|
|
22414
|
+
" CURSOR_CONFIG_DIR: ${{ runner.temp }}/cursor-config",
|
|
22415
|
+
" AGENT_PROMPT_FILE: ${{ runner.temp }}/agent-prompt.md",
|
|
22416
|
+
" shell: bash",
|
|
22417
|
+
" run: |",
|
|
22418
|
+
` sudo -n sh -c 'user="$1"; gpasswd -d "$user" sudo >/dev/null 2>&1 || true; for file in /etc/sudoers /etc/sudoers.d/*; do if [ -f "$file" ]; then sed -i "/^$user[[:space:]]/d" "$file"; fi; done' sh "$(id -un)"`,
|
|
22419
|
+
" sudo -K || true",
|
|
22420
|
+
" if sudo -n true 2>/dev/null; then echo 'sudo is still available to the agent' >&2; exit 1; fi",
|
|
22421
|
+
' CURSOR_CLI_DIR="$(mktemp -d "$RUNNER_TEMP/cursor-cli.XXXXXX")"',
|
|
22422
|
+
` curl --fail --silent --show-error --retry 3 --retry-all-errors --proto '=https' --tlsv1.2 --output "$CURSOR_CLI_DIR/cli.tar.gz" "$CURSOR_CLI_URL"`,
|
|
22423
|
+
` printf '%s %s\\n' "$CURSOR_CLI_SHA256" "$CURSOR_CLI_DIR/cli.tar.gz" | sha256sum --check --strict --quiet`,
|
|
22424
|
+
' tar -xzf "$CURSOR_CLI_DIR/cli.tar.gz" -C "$CURSOR_CLI_DIR"',
|
|
22425
|
+
' mkdir -p "$CURSOR_CONFIG_DIR"',
|
|
22426
|
+
` printf '%s\\n' '${config2}' > "$CURSOR_CONFIG_DIR/cli-config.json"`,
|
|
22427
|
+
' "$CURSOR_CLI_DIR/dist-package/cursor-agent" --print --force --disable-auto-update < "$AGENT_PROMPT_FILE"'
|
|
22428
|
+
];
|
|
22429
|
+
}
|
|
22350
22430
|
function renderPreparationStep() {
|
|
22351
22431
|
return [
|
|
22352
22432
|
" - name: Assess the agent result",
|
|
@@ -22580,7 +22660,7 @@ function renderAgentWorkflow(input) {
|
|
|
22580
22660
|
);
|
|
22581
22661
|
}
|
|
22582
22662
|
const value = parsed.data;
|
|
22583
|
-
if (value.agentAction.repository !== AGENT_ACTION_REPOSITORIES[value.agent]) {
|
|
22663
|
+
if (value.agent !== "cursor" && value.agentAction.repository !== AGENT_ACTION_REPOSITORIES[value.agent]) {
|
|
22584
22664
|
throw new RepositoryAgentConfigError(
|
|
22585
22665
|
"invalid_template_input",
|
|
22586
22666
|
`agentAction.repository must be ${AGENT_ACTION_REPOSITORIES[value.agent]}`
|
|
@@ -22606,7 +22686,7 @@ function renderAgentWorkflow(input) {
|
|
|
22606
22686
|
);
|
|
22607
22687
|
}
|
|
22608
22688
|
const workflowPath = AGENT_WORKFLOW_PATHS[value.agent];
|
|
22609
|
-
const title = value.agent
|
|
22689
|
+
const title = AGENT_TITLES[value.agent];
|
|
22610
22690
|
return [
|
|
22611
22691
|
"# Managed by Setorra. Regenerate with `setorra sync`.",
|
|
22612
22692
|
"# The agent can edit only; deterministic workflow steps own GitHub mutations.",
|
|
@@ -22631,7 +22711,9 @@ function renderAgentWorkflow(input) {
|
|
|
22631
22711
|
" contents: write",
|
|
22632
22712
|
" issues: write",
|
|
22633
22713
|
" pull-requests: write",
|
|
22634
|
-
|
|
22714
|
+
// The marketplace actions may exchange an OIDC token; the Cursor CLI never does, and
|
|
22715
|
+
// its agent has a shell that could otherwise request one.
|
|
22716
|
+
...value.agent === "cursor" ? [] : [" id-token: write"],
|
|
22635
22717
|
" steps:",
|
|
22636
22718
|
...renderProvenanceStep(workflowPath),
|
|
22637
22719
|
" - name: Check out the handoff commit",
|
|
@@ -22662,8 +22744,8 @@ function renderAgentWorkflow(input) {
|
|
|
22662
22744
|
].join("\n");
|
|
22663
22745
|
}
|
|
22664
22746
|
function renderCompletionWorkflow(input) {
|
|
22665
|
-
|
|
22666
|
-
|
|
22747
|
+
BotLoginSchema.parse(input.botLogin);
|
|
22748
|
+
LabelSchema.parse(input.label);
|
|
22667
22749
|
const upload = PinnedActionSchema.parse(input.uploadArtifactAction);
|
|
22668
22750
|
if (upload.repository !== UPLOAD_ARTIFACT_ACTION_REPOSITORY) {
|
|
22669
22751
|
throw new Error("completion requires actions/upload-artifact");
|
|
@@ -22785,7 +22867,7 @@ function stripProvenance(content) {
|
|
|
22785
22867
|
|
|
22786
22868
|
// src/workflow/plan.ts
|
|
22787
22869
|
function managedPaths() {
|
|
22788
|
-
return [
|
|
22870
|
+
return [...Object.values(AGENT_WORKFLOW_PATHS), COMPLETION_WORKFLOW_PATH];
|
|
22789
22871
|
}
|
|
22790
22872
|
function planFile(path, desired, existing, force) {
|
|
22791
22873
|
const current = existing.get(path);
|
|
@@ -22806,21 +22888,14 @@ function planFile(path, desired, existing, force) {
|
|
|
22806
22888
|
return { kind: "update", path, contents: desired };
|
|
22807
22889
|
}
|
|
22808
22890
|
function planRepositoryAgentFiles(input) {
|
|
22891
|
+
const { existing, force, ...template } = input;
|
|
22809
22892
|
const actions = [
|
|
22810
|
-
planFile(COMPLETION_WORKFLOW_PATH, stampProvenance(renderCompletionWorkflow(
|
|
22893
|
+
planFile(COMPLETION_WORKFLOW_PATH, stampProvenance(renderCompletionWorkflow(template)), existing, force),
|
|
22811
22894
|
planFile(
|
|
22812
22895
|
AGENT_WORKFLOW_PATHS[input.agent],
|
|
22813
|
-
stampProvenance(renderAgentWorkflow(
|
|
22814
|
-
|
|
22815
|
-
|
|
22816
|
-
botLogin: input.botLogin,
|
|
22817
|
-
label: input.label,
|
|
22818
|
-
checkoutAction: input.checkoutAction,
|
|
22819
|
-
agentAction: input.agentAction,
|
|
22820
|
-
uploadArtifactAction: input.uploadArtifactAction
|
|
22821
|
-
})),
|
|
22822
|
-
input.existing,
|
|
22823
|
-
input.force
|
|
22896
|
+
stampProvenance(renderAgentWorkflow(template)),
|
|
22897
|
+
existing,
|
|
22898
|
+
force
|
|
22824
22899
|
)
|
|
22825
22900
|
];
|
|
22826
22901
|
for (const [agent, path] of Object.entries(AGENT_WORKFLOW_PATHS)) {
|
|
@@ -22858,6 +22933,10 @@ var AGENT_ACTIONS = {
|
|
|
22858
22933
|
version: "v1"
|
|
22859
22934
|
}
|
|
22860
22935
|
};
|
|
22936
|
+
var CURSOR_CLI = {
|
|
22937
|
+
version: "2026.10.01-e373342",
|
|
22938
|
+
sha256: "a79726c6e644520e993970be4c45775a6889802b67abe461a677a53219ae28e8"
|
|
22939
|
+
};
|
|
22861
22940
|
var UPLOAD_ARTIFACT_ACTION = {
|
|
22862
22941
|
repository: UPLOAD_ARTIFACT_ACTION_REPOSITORY,
|
|
22863
22942
|
sha: "ea165f8d65b6e75b540449e92b4886f43607fa02",
|
|
@@ -22866,6 +22945,8 @@ var UPLOAD_ARTIFACT_ACTION = {
|
|
|
22866
22945
|
|
|
22867
22946
|
// src/workflow/workflow-validate.ts
|
|
22868
22947
|
var PINNED_ACTION = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[^@\s]+@[a-f0-9]{40}$/u;
|
|
22948
|
+
var CURSOR_AGENT_INVOCATION = '"$CURSOR_CLI_DIR/dist-package/cursor-agent" --print --force --disable-auto-update < "$AGENT_PROMPT_FILE"';
|
|
22949
|
+
var PINNED_CURSOR_CLI_URL = /^https:\/\/downloads\.cursor\.com\/lab\/[0-9]{4}\.[0-9]{2}\.[0-9]{2}-[a-f0-9]{7,40}\/linux\/x64\/agent-cli-package\.tar\.gz$/u;
|
|
22869
22950
|
var BASE_SHA_OUTPUT = "${{ steps.provenance.outputs.base_sha }}";
|
|
22870
22951
|
var HANDOFF_BRANCH = "setorra/${{ steps.provenance.outputs.handoff_id }}";
|
|
22871
22952
|
function asRecord(value) {
|
|
@@ -22942,9 +23023,9 @@ function checkGuards(job, input, findings) {
|
|
|
22942
23023
|
push(findings, "missing_label_guard", `The workflow must require the '${input.label}' label.`);
|
|
22943
23024
|
}
|
|
22944
23025
|
}
|
|
22945
|
-
function checkAgentInputs(step, input, findings) {
|
|
23026
|
+
function checkAgentInputs(step, agent, input, findings) {
|
|
22946
23027
|
const withInputs = stepWith(step);
|
|
22947
|
-
const allowlistInput = AGENT_BOT_ALLOWLIST_INPUTS[
|
|
23028
|
+
const allowlistInput = AGENT_BOT_ALLOWLIST_INPUTS[agent];
|
|
22948
23029
|
const allowlist = asString(withInputs?.[allowlistInput]);
|
|
22949
23030
|
if (allowlist === void 0) {
|
|
22950
23031
|
push(findings, "missing_bot_allowlist_input", `The agent step must set '${allowlistInput}'.`);
|
|
@@ -22958,11 +23039,38 @@ function checkAgentInputs(step, input, findings) {
|
|
|
22958
23039
|
} else if (!allowlist.split(",").map((entry) => entry.trim()).includes(input.botLogin)) {
|
|
22959
23040
|
push(findings, "bot_allowlist_mismatch", `'${allowlistInput}' must list '${input.botLogin}'.`);
|
|
22960
23041
|
}
|
|
22961
|
-
const credentialInputs = agentCredentialInputs(
|
|
23042
|
+
const credentialInputs = agentCredentialInputs(agent);
|
|
22962
23043
|
if (!credentialInputs.some((name) => (asString(withInputs?.[name]) ?? "").trim() !== "")) {
|
|
22963
23044
|
push(findings, "missing_credential_input", "The agent step has no supported credential input.");
|
|
22964
23045
|
}
|
|
22965
23046
|
}
|
|
23047
|
+
function checkCursorAgent(job, step, findings) {
|
|
23048
|
+
const env = stepEnv(step);
|
|
23049
|
+
const run2 = stepRun(step);
|
|
23050
|
+
if (asString(env?.["CURSOR_API_KEY"]) !== "${{ secrets.CURSOR_API_KEY }}") {
|
|
23051
|
+
push(findings, "missing_credential_input", "The agent step must read 'CURSOR_API_KEY' from secrets.");
|
|
23052
|
+
}
|
|
23053
|
+
if (!PINNED_CURSOR_CLI_URL.test(asString(env?.["CURSOR_CLI_URL"]) ?? "") || !/^[a-f0-9]{64}$/u.test(asString(env?.["CURSOR_CLI_SHA256"]) ?? "") || !run2.includes('"$CURSOR_CLI_URL"') || !run2.includes('"$CURSOR_CLI_SHA256"') || !run2.includes("sha256sum --check --strict")) {
|
|
23054
|
+
push(
|
|
23055
|
+
findings,
|
|
23056
|
+
"unpinned_agent_cli",
|
|
23057
|
+
"The Cursor CLI must be one exact release verified against its SHA-256."
|
|
23058
|
+
);
|
|
23059
|
+
}
|
|
23060
|
+
if (!run2.includes(CURSOR_AGENT_INVOCATION)) {
|
|
23061
|
+
push(findings, "missing_agent_invocation", "The Cursor step must run the pinned CLI on the prompt file.");
|
|
23062
|
+
}
|
|
23063
|
+
if (!run2.includes("if sudo -n true 2>/dev/null; then")) {
|
|
23064
|
+
push(findings, "agent_keeps_sudo", "The Cursor step must drop sudo before the agent runs.");
|
|
23065
|
+
}
|
|
23066
|
+
const permissions = asRecord(job["permissions"]);
|
|
23067
|
+
if (permissions === void 0 || permissions["id-token"] !== void 0) {
|
|
23068
|
+
push(findings, "agent_receives_oidc_token", "The Cursor job must not request an OIDC token.");
|
|
23069
|
+
}
|
|
23070
|
+
}
|
|
23071
|
+
function isAgentStep(agent, step) {
|
|
23072
|
+
return agent === "cursor" ? asString(stepEnv(step)?.["CURSOR_CLI_URL"])?.startsWith(CURSOR_CLI_DOWNLOAD_PREFIX) === true : stepUses(step)?.startsWith(`${AGENT_ACTION_REPOSITORIES[agent]}@`) === true;
|
|
23073
|
+
}
|
|
22966
23074
|
function oneStep(steps, id) {
|
|
22967
23075
|
const matches = steps.filter((step) => stepId(step) === id);
|
|
22968
23076
|
return matches.length === 1 ? matches[0] : void 0;
|
|
@@ -23073,18 +23181,16 @@ function validateAgentWorkflow(document, input) {
|
|
|
23073
23181
|
for (const { steps } of jobs) {
|
|
23074
23182
|
checkPinning(steps, findings);
|
|
23075
23183
|
}
|
|
23076
|
-
const
|
|
23077
|
-
const otherRepositories = Object.entries(AGENT_ACTION_REPOSITORIES).filter(([agent]) => agent !== input.agent).map(([, repository]) => repository);
|
|
23184
|
+
const otherAgents = AGENT_KINDS.filter((agent) => agent !== input.agent);
|
|
23078
23185
|
const expectedMatches = [];
|
|
23079
23186
|
const agentSteps = [];
|
|
23080
23187
|
let foundOther = false;
|
|
23081
23188
|
for (const entry of jobs) {
|
|
23082
23189
|
for (const step of entry.steps) {
|
|
23083
|
-
|
|
23084
|
-
if (uses?.startsWith(`${expectedRepository}@`)) {
|
|
23190
|
+
if (isAgentStep(input.agent, step)) {
|
|
23085
23191
|
expectedMatches.push({ ...entry, step });
|
|
23086
23192
|
agentSteps.push(step);
|
|
23087
|
-
} else if (
|
|
23193
|
+
} else if (otherAgents.some((agent) => isAgentStep(agent, step))) {
|
|
23088
23194
|
foundOther = true;
|
|
23089
23195
|
agentSteps.push(step);
|
|
23090
23196
|
}
|
|
@@ -23118,12 +23224,16 @@ function validateAgentWorkflow(document, input) {
|
|
|
23118
23224
|
push(
|
|
23119
23225
|
findings,
|
|
23120
23226
|
foundOther ? "unexpected_agent_action" : "missing_agent_action",
|
|
23121
|
-
foundOther ? "The workflow runs the wrong agent action." : `No step uses '${
|
|
23227
|
+
foundOther ? "The workflow runs the wrong agent action." : input.agent === "cursor" ? "No step runs the pinned Cursor CLI." : `No step uses '${AGENT_ACTION_REPOSITORIES[input.agent]}'.`
|
|
23122
23228
|
);
|
|
23123
23229
|
return { valid: false, findings };
|
|
23124
23230
|
}
|
|
23125
23231
|
checkGuards(matched.job, input, findings);
|
|
23126
|
-
|
|
23232
|
+
if (input.agent === "cursor") {
|
|
23233
|
+
checkCursorAgent(matched.job, matched.step, findings);
|
|
23234
|
+
} else {
|
|
23235
|
+
checkAgentInputs(matched.step, input.agent, input, findings);
|
|
23236
|
+
}
|
|
23127
23237
|
checkContractControls(workflow, matched.steps, allSteps, findings);
|
|
23128
23238
|
return { valid: !findings.some((finding) => finding.severity === "error"), findings };
|
|
23129
23239
|
}
|
|
@@ -23134,7 +23244,8 @@ var BOT_LOGIN = process.env["SETORRA_BOT_LOGIN"] ?? "setorra[bot]";
|
|
|
23134
23244
|
var LABEL = process.env["SETORRA_LABEL"] ?? "api-migration";
|
|
23135
23245
|
var DEFAULT_CREDENTIALS = {
|
|
23136
23246
|
claude: "oauth_token",
|
|
23137
|
-
codex: "api_key"
|
|
23247
|
+
codex: "api_key",
|
|
23248
|
+
cursor: "api_key"
|
|
23138
23249
|
};
|
|
23139
23250
|
var UsageError = class extends Error {
|
|
23140
23251
|
};
|
|
@@ -23142,7 +23253,7 @@ function parseArguments(argv) {
|
|
|
23142
23253
|
const [command, ...rest] = argv;
|
|
23143
23254
|
if (command !== "init" && command !== "status" && command !== "sync") {
|
|
23144
23255
|
throw new UsageError(
|
|
23145
|
-
"usage: setorra <init|status|sync> [claude|codex] [--credential api_key|oauth_token] [--force] [--dry-run]"
|
|
23256
|
+
"usage: setorra <init|status|sync> [claude|codex|cursor] [--credential api_key|oauth_token] [--force] [--dry-run]"
|
|
23146
23257
|
);
|
|
23147
23258
|
}
|
|
23148
23259
|
let agent;
|
|
@@ -23151,7 +23262,7 @@ function parseArguments(argv) {
|
|
|
23151
23262
|
let dryRun = false;
|
|
23152
23263
|
for (let index = 0; index < rest.length; index += 1) {
|
|
23153
23264
|
const argument = rest[index];
|
|
23154
|
-
if (
|
|
23265
|
+
if (AGENT_KINDS.some((kind) => kind === argument)) {
|
|
23155
23266
|
agent = argument;
|
|
23156
23267
|
} else if (argument === "--force") {
|
|
23157
23268
|
force = true;
|
|
@@ -23198,10 +23309,14 @@ function readManaged(root) {
|
|
|
23198
23309
|
}
|
|
23199
23310
|
return existing;
|
|
23200
23311
|
}
|
|
23312
|
+
var AGENT_WORKFLOW_MARKERS = {
|
|
23313
|
+
...AGENT_ACTION_REPOSITORIES,
|
|
23314
|
+
cursor: CURSOR_CLI_DOWNLOAD_PREFIX
|
|
23315
|
+
};
|
|
23201
23316
|
function detectAgent(existing) {
|
|
23202
|
-
for (const agent of
|
|
23317
|
+
for (const agent of AGENT_KINDS) {
|
|
23203
23318
|
const workflow = existing.get(AGENT_WORKFLOW_PATHS[agent]);
|
|
23204
|
-
if (workflow?.includes(
|
|
23319
|
+
if (workflow?.includes(AGENT_WORKFLOW_MARKERS[agent]) === true) {
|
|
23205
23320
|
return agent;
|
|
23206
23321
|
}
|
|
23207
23322
|
}
|
|
@@ -23324,7 +23439,7 @@ pull request updated: ${url2}`);
|
|
|
23324
23439
|
}
|
|
23325
23440
|
function runStatus(options) {
|
|
23326
23441
|
const existing = readManaged(options.root);
|
|
23327
|
-
const present =
|
|
23442
|
+
const present = AGENT_KINDS.filter((agent2) => existing.has(AGENT_WORKFLOW_PATHS[agent2]));
|
|
23328
23443
|
if (present.length === 0) {
|
|
23329
23444
|
console.error("no migration workflow; run `setorra init <agent>` first");
|
|
23330
23445
|
return 1;
|
|
@@ -23332,7 +23447,7 @@ function runStatus(options) {
|
|
|
23332
23447
|
let failures = 0;
|
|
23333
23448
|
if (present.length > 1) {
|
|
23334
23449
|
console.error(
|
|
23335
|
-
`error: ${present.length} migration workflows exist (${present.join(", ")}).
|
|
23450
|
+
`error: ${present.length} migration workflows exist (${present.join(", ")}). Each would run on the same issue.`
|
|
23336
23451
|
);
|
|
23337
23452
|
failures += 1;
|
|
23338
23453
|
}
|
|
@@ -23383,7 +23498,7 @@ function runReconcile(options) {
|
|
|
23383
23498
|
const agent = options.command === "sync" ? detectAgent(existing) : options.agent ?? detectAgent(existing);
|
|
23384
23499
|
if (agent === void 0) {
|
|
23385
23500
|
console.error(
|
|
23386
|
-
options.command === "sync" ? "no migration workflow; run `setorra init <agent>` first" : "could not determine the agent; pass `claude` or `
|
|
23501
|
+
options.command === "sync" ? "no migration workflow; run `setorra init <agent>` first" : "could not determine the agent; pass `claude`, `codex` or `cursor`"
|
|
23387
23502
|
);
|
|
23388
23503
|
return 1;
|
|
23389
23504
|
}
|
|
@@ -23396,12 +23511,11 @@ function runReconcile(options) {
|
|
|
23396
23511
|
return 1;
|
|
23397
23512
|
}
|
|
23398
23513
|
const plan = planRepositoryAgentFiles({
|
|
23399
|
-
agent,
|
|
23400
23514
|
credential,
|
|
23401
23515
|
botLogin: BOT_LOGIN,
|
|
23402
23516
|
label: LABEL,
|
|
23403
23517
|
checkoutAction: CHECKOUT_ACTION,
|
|
23404
|
-
agentAction: AGENT_ACTIONS[agent],
|
|
23518
|
+
...agent === "cursor" ? { agent, cursorCli: CURSOR_CLI } : { agent, agentAction: AGENT_ACTIONS[agent] },
|
|
23405
23519
|
uploadArtifactAction: UPLOAD_ARTIFACT_ACTION,
|
|
23406
23520
|
existing,
|
|
23407
23521
|
force: options.force
|