@valescoagency/runway 0.1.1 → 0.2.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 +13 -3
- package/dist/commands/doctor.js +10 -0
- package/dist/commands/init.js +4 -2
- package/dist/commands/run.js +22 -2
- package/dist/config.js +9 -0
- package/dist/linear.js +25 -0
- package/dist/orchestrator.js +40 -6
- package/package.json +2 -1
- package/prompts/implement.md +37 -0
- package/prompts/review.md +45 -0
- package/templates/.env.schema.target-repo +10 -12
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# runway
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@valescoagency/runway) [](https://github.com/ValescoAgency/runway/blob/master/LICENSE) [](https://github.com/ValescoAgency/runway/actions/workflows/release.yml) [](https://www.npmjs.com/package/@valescoagency/runway)
|
|
4
|
+
|
|
3
5
|
A small CLI for two jobs: **scaffold** a target repo for autonomous
|
|
4
6
|
coding-agent runs, then **drain** a Linear queue against it. Wraps
|
|
5
7
|
[Sandcastle](https://github.com/mattpocock/sandcastle) (Claude Code
|
|
@@ -75,7 +77,9 @@ runway init \
|
|
|
75
77
|
(No `--op-account` — runway uses 1Password service-account auth
|
|
76
78
|
(`OP_SERVICE_ACCOUNT_TOKEN`) exclusively, and the token already
|
|
77
79
|
encodes the tenant. `op://` URIs runway writes are
|
|
78
|
-
`op://<vault>/<item
|
|
80
|
+
`op://<vault>/<item>/credential`, not `op://<account>/<vault>/<item>`.
|
|
81
|
+
The `/credential` field selector is required for `API_CREDENTIAL`
|
|
82
|
+
items, which is the canonical 1Password category for API keys.)
|
|
79
83
|
|
|
80
84
|
This runs `npx sandcastle init`, patches the generated `.sandcastle/Dockerfile`
|
|
81
85
|
to bake in `varlock` + the 1Password CLI + a `claude` shim, scaffolds
|
|
@@ -116,6 +120,7 @@ Export runway's own env (in your shell rc, or wherever you keep API keys):
|
|
|
116
120
|
export LINEAR_API_KEY=lin_api_...
|
|
117
121
|
# Optional overrides:
|
|
118
122
|
# export RUNWAY_LINEAR_TEAM=VA
|
|
123
|
+
# export RUNWAY_LINEAR_PROJECT=<project-id-or-slug> # optional, scopes queue to one project
|
|
119
124
|
# export RUNWAY_READY_STATUS="Todo"
|
|
120
125
|
# export RUNWAY_IN_PROGRESS_STATUS="In Progress"
|
|
121
126
|
# export RUNWAY_IN_REVIEW_STATUS="In Review"
|
|
@@ -155,13 +160,18 @@ per-issue comments for what happened.
|
|
|
155
160
|
Runway picks up issues that are:
|
|
156
161
|
|
|
157
162
|
- in team `RUNWAY_LINEAR_TEAM` (default `VA`)
|
|
163
|
+
- (optionally) in project `RUNWAY_LINEAR_PROJECT` (override per-run
|
|
164
|
+
with `runway run --project=<id-or-slug-or-name>`; unset = team-wide)
|
|
158
165
|
- in workflow state `RUNWAY_READY_STATUS` (default `Todo`)
|
|
159
166
|
|
|
160
167
|
It transitions them through:
|
|
161
168
|
|
|
162
|
-
- `In Progress` while the agent is running
|
|
169
|
+
- `In Progress` while the agent is running (specifically: once the
|
|
170
|
+
agent has committed to its branch — startup failures before any
|
|
171
|
+
commits revert the issue back to `Todo` rather than stranding it)
|
|
163
172
|
- `In Review` when the PR opens
|
|
164
|
-
- (label `needs-human`) if the agent or reviewer can't finish
|
|
173
|
+
- (label `needs-human`) if the agent or reviewer can't finish *after*
|
|
174
|
+
the agent has committed real work
|
|
165
175
|
|
|
166
176
|
These names are configurable per env var; the queries match by name so
|
|
167
177
|
your Linear workspace's actual state names need to line up with what
|
package/dist/commands/doctor.js
CHANGED
|
@@ -232,6 +232,16 @@ async function checkGhAuth() {
|
|
|
232
232
|
function checkEnvironment(tier) {
|
|
233
233
|
const checks = new Map();
|
|
234
234
|
checks.set("LINEAR_API_KEY", envSet("LINEAR_API_KEY", "fail"));
|
|
235
|
+
// Informational: which Linear scope a `runway run` would use.
|
|
236
|
+
const team = process.env.RUNWAY_LINEAR_TEAM?.trim() || "VA";
|
|
237
|
+
const project = process.env.RUNWAY_LINEAR_PROJECT?.trim();
|
|
238
|
+
checks.set("linear_scope", {
|
|
239
|
+
status: "ok",
|
|
240
|
+
label: "linear scope",
|
|
241
|
+
detail: project
|
|
242
|
+
? `team ${team} / project ${project}`
|
|
243
|
+
: `team ${team} (team-wide — RUNWAY_LINEAR_PROJECT unset)`,
|
|
244
|
+
});
|
|
235
245
|
if (tier === 2) {
|
|
236
246
|
// Tier 2: needed by varlock to resolve op:// refs in the container.
|
|
237
247
|
checks.set("OP_SERVICE_ACCOUNT_TOKEN", envSet("OP_SERVICE_ACCOUNT_TOKEN", "fail"));
|
package/dist/commands/init.js
CHANGED
|
@@ -35,8 +35,10 @@ NOTE
|
|
|
35
35
|
No --op-account flag — runway uses 1Password service-account auth
|
|
36
36
|
exclusively (OP_SERVICE_ACCOUNT_TOKEN). The token already encodes
|
|
37
37
|
which 1Password tenant to talk to, so the op:// URI omits the
|
|
38
|
-
account segment: \`op://<vault>/<item>\` rather than
|
|
39
|
-
\`op://<account>/<vault>/<item>\`.
|
|
38
|
+
account segment: \`op://<vault>/<item>/<field>\` rather than
|
|
39
|
+
\`op://<account>/<vault>/<item>/<field>\`. Runway hard-codes the
|
|
40
|
+
\`credential\` field, which is the canonical field name on
|
|
41
|
+
1Password API_CREDENTIAL items.
|
|
40
42
|
|
|
41
43
|
WHAT THIS COMMAND DOES
|
|
42
44
|
1. Preflight: docker, gh, node, (tier 2) varlock + op CLI, git state.
|
package/dist/commands/run.js
CHANGED
|
@@ -17,6 +17,16 @@ function parseRunArgs(argv) {
|
|
|
17
17
|
opts.max = n;
|
|
18
18
|
i += 1;
|
|
19
19
|
}
|
|
20
|
+
else if (a === "--project") {
|
|
21
|
+
const v = argv[i + 1];
|
|
22
|
+
if (!v)
|
|
23
|
+
throw new Error("--project requires a value");
|
|
24
|
+
opts.project = v;
|
|
25
|
+
i += 1;
|
|
26
|
+
}
|
|
27
|
+
else if (a?.startsWith("--project=")) {
|
|
28
|
+
opts.project = a.slice("--project=".length);
|
|
29
|
+
}
|
|
20
30
|
else if (a === "--help" || a === "-h") {
|
|
21
31
|
printRunUsage();
|
|
22
32
|
process.exit(0);
|
|
@@ -36,11 +46,15 @@ USAGE
|
|
|
36
46
|
|
|
37
47
|
OPTIONS
|
|
38
48
|
--max, -n N Process at most N issues then exit. Default: drain queue.
|
|
49
|
+
--project ID Scope the queue to a single Linear project under the
|
|
50
|
+
team. Accepts project UUID, slug, or name. Overrides
|
|
51
|
+
RUNWAY_LINEAR_PROJECT. Default: team-wide.
|
|
39
52
|
--help, -h Show this help.
|
|
40
53
|
|
|
41
54
|
ENVIRONMENT
|
|
42
55
|
LINEAR_API_KEY required
|
|
43
56
|
RUNWAY_LINEAR_TEAM default "VA"
|
|
57
|
+
RUNWAY_LINEAR_PROJECT optional — scope to one project
|
|
44
58
|
RUNWAY_READY_STATUS default "Todo"
|
|
45
59
|
RUNWAY_IN_PROGRESS_STATUS default "In Progress"
|
|
46
60
|
RUNWAY_IN_REVIEW_STATUS default "In Review"
|
|
@@ -52,10 +66,16 @@ export async function runCommand(argv) {
|
|
|
52
66
|
const opts = parseRunArgs(argv);
|
|
53
67
|
const cwd = process.cwd();
|
|
54
68
|
assertSandcastleInitialised(cwd);
|
|
55
|
-
const
|
|
69
|
+
const baseConfig = loadConfig();
|
|
70
|
+
const config = opts.project
|
|
71
|
+
? { ...baseConfig, linearProject: opts.project }
|
|
72
|
+
: baseConfig;
|
|
56
73
|
const linear = createLinearGateway(config);
|
|
57
74
|
const github = createGithubGateway();
|
|
58
|
-
|
|
75
|
+
const scope = config.linearProject
|
|
76
|
+
? `team ${config.linearTeam} / project ${config.linearProject}`
|
|
77
|
+
: `team ${config.linearTeam}`;
|
|
78
|
+
console.log(`[runway] draining queue from ${scope} (status="${config.readyStatus}") against ${cwd}`);
|
|
59
79
|
const result = await drainQueue({ config, linear, github, cwd }, { max: opts.max });
|
|
60
80
|
console.log(`[runway] done — processed=${result.processed} opened=${result.opened} hitl=${result.hitl} errored=${result.errored}`);
|
|
61
81
|
}
|
package/dist/config.js
CHANGED
|
@@ -25,6 +25,14 @@ const ConfigSchema = z.object({
|
|
|
25
25
|
*/
|
|
26
26
|
opServiceAccountToken: z.string().optional(),
|
|
27
27
|
linearTeam: z.string().default("VA"),
|
|
28
|
+
/**
|
|
29
|
+
* Optional. Scopes the `runway run` queue to a single project under
|
|
30
|
+
* `linearTeam`. Resolved by Linear project ID, slug, or name. When
|
|
31
|
+
* unset, runway drains every `Todo` issue on the team (legacy
|
|
32
|
+
* behavior). Source: `RUNWAY_LINEAR_PROJECT` env var or
|
|
33
|
+
* `--project` CLI flag on `runway run`.
|
|
34
|
+
*/
|
|
35
|
+
linearProject: z.string().optional(),
|
|
28
36
|
readyStatus: z.string().default("Todo"),
|
|
29
37
|
inProgressStatus: z.string().default("In Progress"),
|
|
30
38
|
inReviewStatus: z.string().default("In Review"),
|
|
@@ -36,6 +44,7 @@ export function loadConfig() {
|
|
|
36
44
|
linearApiKey: process.env.LINEAR_API_KEY,
|
|
37
45
|
opServiceAccountToken: process.env.OP_SERVICE_ACCOUNT_TOKEN,
|
|
38
46
|
linearTeam: process.env.RUNWAY_LINEAR_TEAM,
|
|
47
|
+
linearProject: process.env.RUNWAY_LINEAR_PROJECT,
|
|
39
48
|
readyStatus: process.env.RUNWAY_READY_STATUS,
|
|
40
49
|
inProgressStatus: process.env.RUNWAY_IN_PROGRESS_STATUS,
|
|
41
50
|
inReviewStatus: process.env.RUNWAY_IN_REVIEW_STATUS,
|
package/dist/linear.js
CHANGED
|
@@ -25,14 +25,39 @@ export function createLinearGateway(config) {
|
|
|
25
25
|
}
|
|
26
26
|
return team.id;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a project identifier (UUID, slug, or name) to its Linear
|
|
30
|
+
* project ID. Tries each shape in order so user-facing flags like
|
|
31
|
+
* `--project=bedrock` work without forcing users to copy the UUID.
|
|
32
|
+
*/
|
|
33
|
+
async function findProjectId(identifier) {
|
|
34
|
+
const projects = await client.projects({
|
|
35
|
+
filter: {
|
|
36
|
+
or: [
|
|
37
|
+
{ id: { eq: identifier } },
|
|
38
|
+
{ slugId: { eq: identifier } },
|
|
39
|
+
{ name: { eq: identifier } },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const project = projects.nodes[0];
|
|
44
|
+
if (!project) {
|
|
45
|
+
throw new Error(`Linear project "${identifier}" not found`);
|
|
46
|
+
}
|
|
47
|
+
return project.id;
|
|
48
|
+
}
|
|
28
49
|
return {
|
|
29
50
|
async fetchReady() {
|
|
30
51
|
const teamId = await findTeamId();
|
|
31
52
|
const readyStateId = await findStateId(teamId, config.readyStatus);
|
|
53
|
+
const projectId = config.linearProject
|
|
54
|
+
? await findProjectId(config.linearProject)
|
|
55
|
+
: null;
|
|
32
56
|
const issues = await client.issues({
|
|
33
57
|
filter: {
|
|
34
58
|
team: { id: { eq: teamId } },
|
|
35
59
|
state: { id: { eq: readyStateId } },
|
|
60
|
+
...(projectId ? { project: { id: { eq: projectId } } } : {}),
|
|
36
61
|
},
|
|
37
62
|
// Stable order: oldest first so the queue drains FIFO.
|
|
38
63
|
orderBy: "createdAt",
|
package/dist/orchestrator.js
CHANGED
|
@@ -43,12 +43,30 @@ export async function drainQueue(deps, opts = {}) {
|
|
|
43
43
|
catch (err) {
|
|
44
44
|
errored += 1;
|
|
45
45
|
console.error(`[runway] error on ${issue.identifier}:`, err);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
// If the agent crashed before producing any commits (missing
|
|
47
|
+
// image, varlock validation, container failed to boot, etc.),
|
|
48
|
+
// it's an infrastructure failure — not a HITL. Revert the issue
|
|
49
|
+
// to `Todo` and skip the `needs-human` label so the next run
|
|
50
|
+
// can pick it up cleanly. `In Progress` is reserved for "agent
|
|
51
|
+
// has committed to the branch".
|
|
52
|
+
const branch = `agent/${issue.identifier.toLowerCase()}`;
|
|
53
|
+
const startedRealWork = await hasCommits(deps.cwd, branch);
|
|
54
|
+
if (!startedRealWork) {
|
|
55
|
+
await linear
|
|
56
|
+
.transition(issue.id, config.readyStatus)
|
|
57
|
+
.catch(() => undefined);
|
|
58
|
+
await linear
|
|
59
|
+
.comment(issue.id, `Runway hit a startup failure before the agent produced any commits — reverting to \`${config.readyStatus}\` for retry:\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\``)
|
|
60
|
+
.catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
await linear
|
|
64
|
+
.applyLabel(issue.id, config.hitlLabel)
|
|
65
|
+
.catch(() => undefined);
|
|
66
|
+
await linear
|
|
67
|
+
.comment(issue.id, `Runway hit an unrecoverable error and flagged for human review:\n\n\`\`\`\n${err instanceof Error ? err.message : String(err)}\n\`\`\``)
|
|
68
|
+
.catch(() => undefined);
|
|
69
|
+
}
|
|
52
70
|
}
|
|
53
71
|
}
|
|
54
72
|
return { processed, opened, hitl, errored };
|
|
@@ -113,6 +131,22 @@ async function flagHitl(issue, deps, reason) {
|
|
|
113
131
|
await linear.applyLabel(issue.id, config.hitlLabel);
|
|
114
132
|
await linear.comment(issue.id, `Runway flagged for human review: ${reason}`);
|
|
115
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Whether the agent branch has any commits beyond `main`. Used by the
|
|
136
|
+
* drain loop to distinguish "agent crashed mid-run, after producing
|
|
137
|
+
* real work" (→ HITL) from "agent crashed during startup, no work
|
|
138
|
+
* done" (→ revert to Todo). If the branch doesn't exist or git fails,
|
|
139
|
+
* treat as "no commits" so we revert rather than strand the issue.
|
|
140
|
+
*/
|
|
141
|
+
async function hasCommits(repoPath, branch) {
|
|
142
|
+
try {
|
|
143
|
+
const { stdout } = await execa("git", ["rev-list", "--count", `main..${branch}`], { cwd: repoPath, reject: false });
|
|
144
|
+
return Number.parseInt(stdout.trim(), 10) > 0;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
116
150
|
async function captureDiff(repoPath, branch) {
|
|
117
151
|
const { stdout } = await execa("git", ["diff", `main...${branch}`], {
|
|
118
152
|
cwd: repoPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valescoagency/runway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Linear-driven orchestrator + scaffolder for coding agents on Sandcastle. `runway init` scaffolds a target repo (sandcastle + varlock + 1Password); `runway run` drains a Linear queue against it; `runway doctor`, `runway upgrade`, `runway upgrade-repo` round out the lifecycle.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"dist",
|
|
36
|
+
"prompts",
|
|
36
37
|
"templates",
|
|
37
38
|
"LICENSE",
|
|
38
39
|
"README.md"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
You are an autonomous coding agent working on a single Linear issue.
|
|
2
|
+
|
|
3
|
+
# Issue
|
|
4
|
+
|
|
5
|
+
**{{ISSUE_IDENTIFIER}} — {{ISSUE_TITLE}}**
|
|
6
|
+
|
|
7
|
+
{{ISSUE_DESCRIPTION}}
|
|
8
|
+
|
|
9
|
+
# Repository context
|
|
10
|
+
|
|
11
|
+
You are operating inside a clean checkout of the target repository on a
|
|
12
|
+
fresh branch named `agent/{{ISSUE_IDENTIFIER}}`. Branch off `main`.
|
|
13
|
+
|
|
14
|
+
# What done looks like
|
|
15
|
+
|
|
16
|
+
1. Code changes that satisfy the issue body.
|
|
17
|
+
2. All existing tests still pass. Run them: `!`pnpm test 2>&1 | tail -40``.
|
|
18
|
+
3. New tests for any new behavior, where it's reasonable to add them.
|
|
19
|
+
4. Lint / typecheck clean: `!`pnpm typecheck 2>&1 | tail -20`` and
|
|
20
|
+
`!`pnpm lint 2>&1 | tail -20``.
|
|
21
|
+
5. A clear commit message in conventional-commits style describing the
|
|
22
|
+
change. The commit body should reference the Linear issue ID
|
|
23
|
+
(`Refs {{ISSUE_IDENTIFIER}}`).
|
|
24
|
+
|
|
25
|
+
# Working style
|
|
26
|
+
|
|
27
|
+
- Read before writing. Skim related files. Match existing patterns.
|
|
28
|
+
- Surgical changes. Touch only what the issue requires.
|
|
29
|
+
- If the issue is ambiguous and you can't make a reasonable judgment
|
|
30
|
+
call, stop and explain what's missing in your final message — runway
|
|
31
|
+
will route to a human.
|
|
32
|
+
- Never modify `.github/workflows/**`, `.env*`, `*.pem`, `*.key`,
|
|
33
|
+
`pnpm-lock.yaml` (unless the task is a dep bump), or `.sandcastle/**`.
|
|
34
|
+
|
|
35
|
+
# Stop conditions
|
|
36
|
+
|
|
37
|
+
When all five "done" criteria pass, stop. Don't keep polishing.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
You are an adversarial code reviewer. You did NOT write this code; your
|
|
2
|
+
job is to find reasons it should NOT ship.
|
|
3
|
+
|
|
4
|
+
# Issue the change claims to address
|
|
5
|
+
|
|
6
|
+
**{{ISSUE_IDENTIFIER}} — {{ISSUE_TITLE}}**
|
|
7
|
+
|
|
8
|
+
{{ISSUE_DESCRIPTION}}
|
|
9
|
+
|
|
10
|
+
# The diff
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
{{DIFF}}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
# Commits
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
{{COMMITS}}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
# Your job
|
|
23
|
+
|
|
24
|
+
Score the change against these axes. For each, give a brief verdict
|
|
25
|
+
(`PASS` / `CONCERN` / `BLOCK`) and one to two sentences of reasoning.
|
|
26
|
+
|
|
27
|
+
1. **Addresses the issue** — does the diff actually solve what was asked?
|
|
28
|
+
2. **Surgical** — only touched what was needed; no scope creep, no
|
|
29
|
+
"drive-by" refactors.
|
|
30
|
+
3. **Tests** — new behavior covered; existing tests still meaningful.
|
|
31
|
+
4. **Safety** — no secret leakage, no dangerous defaults, no protected
|
|
32
|
+
paths touched (workflows, env files, keys, lockfiles for non-dep work).
|
|
33
|
+
5. **Clarity** — commit messages and code are readable.
|
|
34
|
+
|
|
35
|
+
# Output format
|
|
36
|
+
|
|
37
|
+
End your response with EXACTLY one of these two lines, alone, no other
|
|
38
|
+
text on the line:
|
|
39
|
+
|
|
40
|
+
REVIEW: APPROVED
|
|
41
|
+
REVIEW: REJECTED — <one-line reason>
|
|
42
|
+
|
|
43
|
+
If you output `REVIEW: REJECTED`, the agent will get one more iteration
|
|
44
|
+
to address your concerns. Be specific about what to fix. Don't reject
|
|
45
|
+
for nits.
|
|
@@ -14,19 +14,17 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Note on the op:// shape: with service-account auth (the only mode
|
|
16
16
|
# runway uses), the token already encodes the 1Password tenant, so the
|
|
17
|
-
# URI omits the account segment — `op://<vault>/<item>`, not
|
|
18
|
-
# `op://<account>/<vault>/<item>`.
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
|
|
17
|
+
# URI omits the account segment — `op://<vault>/<item>/<field>`, not
|
|
18
|
+
# `op://<account>/<vault>/<item>/<field>`. For API_CREDENTIAL items
|
|
19
|
+
# (the natural category for API keys), the field is `credential`.
|
|
20
|
+
#
|
|
21
|
+
# To add another secret, copy one of the two live entries below. Do
|
|
22
|
+
# NOT leave a commented-out example block here: varlock parses any
|
|
23
|
+
# `# @decorator` line as a real decorator, and a decorator with no
|
|
24
|
+
# attached config line fails validation ("detached comment block").
|
|
22
25
|
|
|
23
26
|
# @sensitive @required
|
|
24
|
-
|
|
27
|
+
ANTHROPIC_API_KEY=exec('op read "op://{{OP_VAULT}}/{{ANTHROPIC_ITEM}}/credential"')
|
|
25
28
|
|
|
26
|
-
# Add other secrets the agent needs at runtime here. Examples:
|
|
27
|
-
#
|
|
28
|
-
# @sensitive @required
|
|
29
|
-
# OPENAI_API_KEY=exec('op read "op://{{OP_VAULT}}/openai-api-key"')
|
|
30
|
-
#
|
|
31
29
|
# @sensitive @required
|
|
32
|
-
|
|
30
|
+
GH_TOKEN=exec('op read "op://{{OP_VAULT}}/{{GH_TOKEN_ITEM}}/credential"')
|