@flowrail/init 0.0.15 → 0.0.17
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 +15 -14
- package/dist/ci-workflow.js +258 -0
- package/dist/env.js +6 -0
- package/dist/init.js +53 -1
- package/package.json +4 -10
package/README.md
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
# @flowrail/init
|
|
2
2
|
|
|
3
|
-
One-shot installer that wires FlowRail
|
|
3
|
+
One-shot installer that wires FlowRail — security review for AI coding agents — into a [Claude Code](https://claude.com/claude-code) project: PreToolUse hooks, MCP server config, and agent-facing skills.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
6
|
+
# 1. Sign up and mint an API key at https://flowrail.ai/settings/keys
|
|
7
|
+
export FLOWRAIL_API_KEY=<your key>
|
|
8
|
+
|
|
9
|
+
# 2. Run the installer from the repo you want FlowRail to protect
|
|
7
10
|
cd ~/code/your-repo
|
|
8
|
-
npx @flowrail/init
|
|
11
|
+
npx @flowrail/init@latest
|
|
9
12
|
```
|
|
10
13
|
|
|
11
14
|
What it writes:
|
|
12
15
|
|
|
13
16
|
- `.claude/settings.json` — PreToolUse hooks for Write/Edit/MultiEdit + Bash. Tracked. No credentials.
|
|
14
|
-
- `.mcp.json` — Claude Code MCP wiring. Tracked. Carries `Bearer ${FLOWRAIL_API_KEY}` env-reference, never the literal.
|
|
15
|
-
- `.claude/skills/flowrail-*/SKILL.md` —
|
|
16
|
-
- `flowrail.yaml` —
|
|
17
|
-
- `.gitignore` — appends `.flowrail/` so per-session runtime state (active
|
|
18
|
-
|
|
19
|
-
The installer also probes `https://api.flowrail.ai/mcp` with the env-supplied bearer to confirm end-to-end auth before exiting. A 401 gets a tester-actionable message; transport failures get a different one.
|
|
20
|
-
|
|
21
|
-
**Full install guide**: <https://github.com/anshumanbh/flowstate/blob/main/docs/guides/INSTALL.md>
|
|
17
|
+
- `.mcp.json` — Claude Code MCP wiring. Tracked. Carries a `Bearer ${FLOWRAIL_API_KEY}` env-reference, never the literal key.
|
|
18
|
+
- `.claude/skills/flowrail-*/SKILL.md` — the agent-facing skills (design-review, verify, dep-check, review-app, lineage).
|
|
19
|
+
- `flowrail.yaml` — project config placeholder for scanner orchestration and org defaults.
|
|
20
|
+
- `.gitignore` — appends `.flowrail/` so per-session runtime state (the active design review id) stays local.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
The installer finishes by probing `https://api.flowrail.ai/mcp` with your key to confirm end-to-end auth before exiting. A 401 gets an actionable message; transport failures get a different one.
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
## Links
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
- Website: <https://flowrail.ai>
|
|
27
|
+
- Docs: <https://flowrail.ai/docs>
|
|
28
|
+
- Mint or manage API keys: <https://flowrail.ai/settings/keys>
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical copy-in CI gate workflow generated by @flowrail/init.
|
|
4
|
+
*
|
|
5
|
+
* This package cannot depend on the repository workflow at runtime, so the
|
|
6
|
+
* caller-ready form is embedded here. The drift test pins the gate-authority
|
|
7
|
+
* tail to the reusable workflow maintained at the repository root.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.CI_WORKFLOW_RELATIVE_PATH = void 0;
|
|
14
|
+
exports.renderCiWorkflow = renderCiWorkflow;
|
|
15
|
+
exports.writeCiWorkflow = writeCiWorkflow;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
exports.CI_WORKFLOW_RELATIVE_PATH = '.github/workflows/flowrail-verify.yml';
|
|
19
|
+
const PROJECT_ID_PATTERN = /^prj_[0-9a-f]{32}$/;
|
|
20
|
+
const PROJECT_ID_PLACEHOLDER = '__FLOWRAIL_PROJECT_ID__';
|
|
21
|
+
const CI_WORKFLOW_TEMPLATE = String.raw `# generated by @flowrail/init v0.0.16 (--ci-gate)
|
|
22
|
+
# workflow-protocol: 1
|
|
23
|
+
name: flowrail-verify
|
|
24
|
+
|
|
25
|
+
on:
|
|
26
|
+
pull_request:
|
|
27
|
+
|
|
28
|
+
permissions:
|
|
29
|
+
contents: read
|
|
30
|
+
pull-requests: write
|
|
31
|
+
|
|
32
|
+
jobs:
|
|
33
|
+
verify:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
fetch-depth: 0
|
|
39
|
+
# PR #102 review round 4 (P1): without this, actions/checkout
|
|
40
|
+
# persists the workflow's GitHub token in .git/config. An
|
|
41
|
+
# attacker who lands a symlink such as \`\`leak -> .git/config\`\`
|
|
42
|
+
# in the PR would have that file read + uploaded to
|
|
43
|
+
# \`\`/api/verify\`\` by the payload builder below — leaking
|
|
44
|
+
# repository credentials to the FlowRail endpoint. The
|
|
45
|
+
# symlink skip below is the second layer; this is the first.
|
|
46
|
+
persist-credentials: false
|
|
47
|
+
|
|
48
|
+
- name: Build changed-file payload
|
|
49
|
+
env:
|
|
50
|
+
BASE_SHA: @@{{ github.event.pull_request.base.sha || github.event.before }}
|
|
51
|
+
HEAD_SHA: @@{{ github.event.pull_request.head.sha || github.sha }}
|
|
52
|
+
PR_NUMBER: @@{{ github.event.pull_request.number }}
|
|
53
|
+
run: |
|
|
54
|
+
python - <<'PY'
|
|
55
|
+
import json
|
|
56
|
+
import os
|
|
57
|
+
import subprocess
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
base = os.environ.get("BASE_SHA") or "HEAD~1"
|
|
61
|
+
head = os.environ.get("HEAD_SHA") or "HEAD"
|
|
62
|
+
raw_names = subprocess.check_output(
|
|
63
|
+
[
|
|
64
|
+
"git",
|
|
65
|
+
"-c",
|
|
66
|
+
"core.quotepath=false",
|
|
67
|
+
"diff",
|
|
68
|
+
"-z",
|
|
69
|
+
"--name-only",
|
|
70
|
+
f"{base}...{head}",
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
names = [
|
|
74
|
+
name
|
|
75
|
+
# \`\`surrogateescape\`\` so a legitimately non-UTF-8 filename in the
|
|
76
|
+
# diff cannot raise UnicodeDecodeError and fail the whole
|
|
77
|
+
# collector step; such names round-trip back to bytes for the
|
|
78
|
+
# symlink/is_file/read_text checks below.
|
|
79
|
+
for name in raw_names.decode("utf-8", "surrogateescape").split("\x00")
|
|
80
|
+
if name
|
|
81
|
+
]
|
|
82
|
+
files = []
|
|
83
|
+
for name in names:
|
|
84
|
+
path = Path(name)
|
|
85
|
+
# PR #102 review round 4 (P1): skip symlinks. \`\`is_file\`\`
|
|
86
|
+
# follows the link, so a PR that adds \`\`leak -> /etc/passwd\`\`
|
|
87
|
+
# or \`\`leak -> .git/config\`\` would have the *target* read
|
|
88
|
+
# and uploaded. Defense paired with persist-credentials:
|
|
89
|
+
# false on the checkout step.
|
|
90
|
+
if path.is_symlink():
|
|
91
|
+
continue
|
|
92
|
+
if not path.is_file():
|
|
93
|
+
continue
|
|
94
|
+
try:
|
|
95
|
+
content = path.read_text(encoding="utf-8")
|
|
96
|
+
except UnicodeDecodeError:
|
|
97
|
+
continue
|
|
98
|
+
# The surrogateescaped \`\`name\`\` is correct for the filesystem
|
|
99
|
+
# checks above, but a lone surrogate must NOT go on the wire:
|
|
100
|
+
# the path is only a display label server-side, and strict-UTF-8
|
|
101
|
+
# response encoding would raise UnicodeEncodeError when a finding
|
|
102
|
+
# echoes it. Send a JSON-safe backslash-escaped display path.
|
|
103
|
+
display_path = os.fsencode(name).decode("utf-8", "backslashreplace")
|
|
104
|
+
files.append({"path": display_path, "content": content})
|
|
105
|
+
payload = {
|
|
106
|
+
"commit_sha": head,
|
|
107
|
+
"pr_number": int(os.environ["PR_NUMBER"]) if os.environ.get("PR_NUMBER") else None,
|
|
108
|
+
"files": files,
|
|
109
|
+
}
|
|
110
|
+
Path("flowrail-payload.json").write_text(json.dumps(payload), encoding="utf-8")
|
|
111
|
+
PY
|
|
112
|
+
|
|
113
|
+
- name: Call FlowRail verify
|
|
114
|
+
env:
|
|
115
|
+
FLOWSTATE_URL: https://api.flowrail.ai
|
|
116
|
+
PROJECT_ID: __FLOWRAIL_PROJECT_ID__
|
|
117
|
+
FLOWRAIL_TOKEN: @@{{ secrets.FLOWRAIL_PROJECT_TOKEN }}
|
|
118
|
+
run: |
|
|
119
|
+
# --fail-with-body: exit non-zero on 4xx/5xx but still write
|
|
120
|
+
# the error body to stdout so failures are debuggable. Without
|
|
121
|
+
# this flag a 401 from a wrong/unconfigured token would write
|
|
122
|
+
# a JSON error envelope to flowrail-results.json and the next
|
|
123
|
+
# step would publish a misleading "No custom guardrail findings"
|
|
124
|
+
# comment.
|
|
125
|
+
curl -sS --fail-with-body \
|
|
126
|
+
-H "Authorization: Bearer @@{FLOWRAIL_TOKEN}" \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
--data @flowrail-payload.json \
|
|
129
|
+
"@@{FLOWSTATE_URL}/api/verify?project_id=@@{PROJECT_ID}" \
|
|
130
|
+
> flowrail-results.json
|
|
131
|
+
|
|
132
|
+
- name: Extract PR comment
|
|
133
|
+
# Server renders the PR-comment markdown into
|
|
134
|
+
# \`\`pr_comment_markdown\`\` so the workflow does not have to ship
|
|
135
|
+
# FlowRail tooling into caller repos. \`\`actions/checkout\`\` above
|
|
136
|
+
# checked out the CALLER's repo, not flowstate, so
|
|
137
|
+
# \`\`tools/format_pr_comment.py\`\` is not available here.
|
|
138
|
+
run: |
|
|
139
|
+
python3 - <<'PY'
|
|
140
|
+
import json
|
|
141
|
+
import sys
|
|
142
|
+
|
|
143
|
+
# Degrade gracefully: a truncated/corrupt response must not crash the
|
|
144
|
+
# step with a raw traceback. The verdict-enforcement step below is the
|
|
145
|
+
# gate authority (it fails closed on malformed JSON); here we only
|
|
146
|
+
# render a comment, so fall back to a placeholder on any parse error.
|
|
147
|
+
try:
|
|
148
|
+
with open("flowrail-results.json", encoding="utf-8") as f:
|
|
149
|
+
markdown = json.load(f).get("pr_comment_markdown")
|
|
150
|
+
except Exception as exc:
|
|
151
|
+
markdown = None
|
|
152
|
+
print(f"FlowRail: could not parse results for the PR comment: {exc}", file=sys.stderr)
|
|
153
|
+
|
|
154
|
+
# Guard against a parseable-but-non-string field (e.g. 123): writing
|
|
155
|
+
# it would raise TypeError and fail the step before the gate runs.
|
|
156
|
+
if not isinstance(markdown, str) or not markdown:
|
|
157
|
+
markdown = (
|
|
158
|
+
"<!-- flowrail-verify-comment -->\n"
|
|
159
|
+
"## FlowRail verify\n\n"
|
|
160
|
+
"FlowRail could not render a PR comment (empty or unparseable "
|
|
161
|
+
"response). The gate result is reported by the FlowRail check "
|
|
162
|
+
"on this pull request."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# \`\`backslashreplace\`\` so a stray unpaired surrogate in the markdown
|
|
166
|
+
# (however it arose) becomes an escape rather than crashing the write
|
|
167
|
+
# and failing the job before the gate's verdict step runs.
|
|
168
|
+
with open("flowrail-comment.md", "w", encoding="utf-8", errors="backslashreplace") as f:
|
|
169
|
+
f.write(markdown)
|
|
170
|
+
PY
|
|
171
|
+
|
|
172
|
+
- name: Upsert PR comment
|
|
173
|
+
if: @@{{ github.event.pull_request.number }}
|
|
174
|
+
env:
|
|
175
|
+
GH_TOKEN: @@{{ github.token }}
|
|
176
|
+
PR_NUMBER: @@{{ github.event.pull_request.number }}
|
|
177
|
+
REPO: @@{{ github.repository }}
|
|
178
|
+
run: |
|
|
179
|
+
marker='<!-- flowrail-verify-comment -->'
|
|
180
|
+
existing=$(
|
|
181
|
+
gh api "repos/@@{REPO}/issues/@@{PR_NUMBER}/comments" \
|
|
182
|
+
--jq ".[] | select(.body | contains(\"@@{marker}\")) | .id" \
|
|
183
|
+
| head -1
|
|
184
|
+
)
|
|
185
|
+
if [ -n "$existing" ]; then
|
|
186
|
+
gh api "repos/@@{REPO}/issues/comments/@@{existing}" \
|
|
187
|
+
--method PATCH \
|
|
188
|
+
--field body="$(cat flowrail-comment.md)"
|
|
189
|
+
else
|
|
190
|
+
gh pr comment "$PR_NUMBER" --body-file flowrail-comment.md
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
- name: Enforce verdict
|
|
194
|
+
run: |
|
|
195
|
+
python3 - <<'PY'
|
|
196
|
+
import json
|
|
197
|
+
import sys
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
with open("flowrail-results.json", encoding="utf-8") as f:
|
|
201
|
+
result = json.load(f)
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
print("FlowRail verdict: status=contract_error mode=enforce findings=0")
|
|
204
|
+
print(f"FlowRail contract mismatch: could not load results: {exc}", file=sys.stderr)
|
|
205
|
+
raise SystemExit(1)
|
|
206
|
+
|
|
207
|
+
if not isinstance(result, dict):
|
|
208
|
+
status = None
|
|
209
|
+
mode = "enforce"
|
|
210
|
+
findings = []
|
|
211
|
+
else:
|
|
212
|
+
status = result.get("status")
|
|
213
|
+
mode = result.get("mode")
|
|
214
|
+
findings = result.get("findings") or []
|
|
215
|
+
findings_count = len(findings) if isinstance(findings, list) else 0
|
|
216
|
+
print(f"FlowRail verdict: status={status} mode={mode} findings={findings_count}")
|
|
217
|
+
if status not in {"pass", "fail"}:
|
|
218
|
+
print(f"FlowRail contract mismatch: invalid status {status!r}", file=sys.stderr)
|
|
219
|
+
raise SystemExit(1)
|
|
220
|
+
if mode not in {"enforce", "advisory"}:
|
|
221
|
+
print(f"FlowRail contract mismatch: invalid mode {mode!r}", file=sys.stderr)
|
|
222
|
+
raise SystemExit(1)
|
|
223
|
+
if status == "fail" and mode != "advisory":
|
|
224
|
+
raise SystemExit(1)
|
|
225
|
+
PY
|
|
226
|
+
`
|
|
227
|
+
// String.raw preserves the escape needed to carry Markdown backticks
|
|
228
|
+
// through this TypeScript template. Remove only those deliberate escapes.
|
|
229
|
+
.split('\\`')
|
|
230
|
+
.join('`')
|
|
231
|
+
// Avoid TypeScript template interpolation while preserving GitHub syntax.
|
|
232
|
+
.split('@@{{')
|
|
233
|
+
.join('${{')
|
|
234
|
+
// Shell parameter expansion uses the same interpolation opener.
|
|
235
|
+
.split('@@{')
|
|
236
|
+
.join('${');
|
|
237
|
+
function renderCiWorkflow(projectId) {
|
|
238
|
+
if (!PROJECT_ID_PATTERN.test(projectId)) {
|
|
239
|
+
throw new Error('projectId must match prj_<32 lowercase hex>');
|
|
240
|
+
}
|
|
241
|
+
return CI_WORKFLOW_TEMPLATE.replace(PROJECT_ID_PLACEHOLDER, projectId);
|
|
242
|
+
}
|
|
243
|
+
function writeCiWorkflow(projectRoot, projectId) {
|
|
244
|
+
const target = path_1.default.join(projectRoot, exports.CI_WORKFLOW_RELATIVE_PATH);
|
|
245
|
+
if (fs_1.default.existsSync(target)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
249
|
+
try {
|
|
250
|
+
fs_1.default.writeFileSync(target, renderCiWorkflow(projectId), { flag: 'wx' });
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (error.code === 'EEXIST') {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/env.js
CHANGED
|
@@ -25,6 +25,12 @@ class InitError extends Error {
|
|
|
25
25
|
exports.InitError = InitError;
|
|
26
26
|
function rejectKeyArgv(argv) {
|
|
27
27
|
for (const arg of argv) {
|
|
28
|
+
// Project tokens are CI-plane credentials. They never belong on the init
|
|
29
|
+
// command line, including as the value of an otherwise unknown flag.
|
|
30
|
+
if (arg.includes('fs_prj_')) {
|
|
31
|
+
throw new InitError('A FlowRail project token was supplied on argv. Project tokens are ' +
|
|
32
|
+
'credentials and must never be placed in shell history or process listings.');
|
|
33
|
+
}
|
|
28
34
|
// Match --key=, --key SPACE, --api-key=, etc. We don't need a
|
|
29
35
|
// permissive parser — any flag-shaped token mentioning "key" is
|
|
30
36
|
// worth rejecting because the credential never belongs in argv.
|
package/dist/init.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.runInit = runInit;
|
|
|
31
31
|
const app_instance_1 = require("./app-instance");
|
|
32
32
|
const claude_settings_1 = require("./claude-settings");
|
|
33
33
|
const connectivity_1 = require("./connectivity");
|
|
34
|
+
const ci_workflow_1 = require("./ci-workflow");
|
|
34
35
|
const doctor_script_1 = require("./doctor-script");
|
|
35
36
|
const env_1 = require("./env");
|
|
36
37
|
const flowrail_yaml_1 = require("./flowrail-yaml");
|
|
@@ -43,6 +44,44 @@ const preflight_1 = require("./preflight");
|
|
|
43
44
|
const repo_scan_trigger_1 = require("./repo-scan-trigger");
|
|
44
45
|
const skills_1 = require("./skills");
|
|
45
46
|
const verify_write_1 = require("./verify-write");
|
|
47
|
+
const PROJECT_ID_PATTERN = /^prj_[0-9a-f]{32}$/;
|
|
48
|
+
function parseCiGateArgs(argv) {
|
|
49
|
+
const enabled = argv.includes('--ci-gate');
|
|
50
|
+
if (!enabled)
|
|
51
|
+
return { enabled: false };
|
|
52
|
+
let projectId;
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const arg = argv[i];
|
|
55
|
+
let value;
|
|
56
|
+
if (arg === '--project-id') {
|
|
57
|
+
value = argv[i + 1];
|
|
58
|
+
if (value === undefined || value.startsWith('--')) {
|
|
59
|
+
throw new env_1.InitError('--ci-gate requires --project-id prj_<32 lowercase hex>.');
|
|
60
|
+
}
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
else if (arg.startsWith('--project-id=')) {
|
|
64
|
+
value = arg.slice('--project-id='.length);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (projectId !== undefined) {
|
|
70
|
+
throw new env_1.InitError('--project-id may be supplied only once.');
|
|
71
|
+
}
|
|
72
|
+
projectId = value;
|
|
73
|
+
}
|
|
74
|
+
if (projectId === undefined || projectId === '') {
|
|
75
|
+
throw new env_1.InitError('--ci-gate requires --project-id prj_<32 lowercase hex>.');
|
|
76
|
+
}
|
|
77
|
+
if (projectId.startsWith('fs_')) {
|
|
78
|
+
throw new env_1.InitError('--project-id accepts the public prj_ identifier, never a FlowRail token.');
|
|
79
|
+
}
|
|
80
|
+
if (!PROJECT_ID_PATTERN.test(projectId)) {
|
|
81
|
+
throw new env_1.InitError('--project-id must match prj_<32 lowercase hex>.');
|
|
82
|
+
}
|
|
83
|
+
return { enabled: true, projectId };
|
|
84
|
+
}
|
|
46
85
|
async function runInit(options = {}) {
|
|
47
86
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
48
87
|
const argv = options.argv ?? process.argv.slice(2);
|
|
@@ -51,6 +90,9 @@ async function runInit(options = {}) {
|
|
|
51
90
|
const gitChecker = options.gitChecker ?? git_check_1.isMcpJsonGitTracked;
|
|
52
91
|
const noScan = argv.includes('--no-scan') || argv.includes('--no-retro-scan');
|
|
53
92
|
(0, env_1.rejectKeyArgv)(argv);
|
|
93
|
+
// Validate all CI-gate inputs before reading or writing project state. A
|
|
94
|
+
// placeholder or token-shaped value must never leave a partial install.
|
|
95
|
+
const ciGate = parseCiGateArgs(argv);
|
|
54
96
|
const { apiKey } = (0, env_1.readApiKeyFromEnv)(env);
|
|
55
97
|
// ``FLOWRAIL_MCP_URL`` is the BASE URL — same shape @flowrail/hook
|
|
56
98
|
// reads at runtime. Default to the prod base; staging / local-dev
|
|
@@ -79,6 +121,10 @@ async function runInit(options = {}) {
|
|
|
79
121
|
}
|
|
80
122
|
(0, flowrail_yaml_1.writeFlowrailYaml)(projectRoot);
|
|
81
123
|
log('✓ wrote flowrail.yaml');
|
|
124
|
+
if (ciGate.enabled) {
|
|
125
|
+
(0, ci_workflow_1.writeCiWorkflow)(projectRoot, ciGate.projectId);
|
|
126
|
+
log(`✓ wrote ${ci_workflow_1.CI_WORKFLOW_RELATIVE_PATH}`);
|
|
127
|
+
}
|
|
82
128
|
(0, gitignore_1.appendFlowrailToGitignore)(projectRoot);
|
|
83
129
|
log('✓ ensured .flowrail/ is in .gitignore');
|
|
84
130
|
(0, gitignore_1.appendMcpJsonToGitignore)(projectRoot);
|
|
@@ -122,13 +168,19 @@ async function runInit(options = {}) {
|
|
|
122
168
|
// covers unexpected throws (e.g. an unwritable .flowrail/).
|
|
123
169
|
try {
|
|
124
170
|
const rebind = argv.includes('--rebind') || env['FLOWRAIL_APP_REBIND'] === '1';
|
|
171
|
+
const appRegistrar = options.appRegistrar;
|
|
172
|
+
const registerApp = appRegistrar === undefined
|
|
173
|
+
? undefined
|
|
174
|
+
: async (args) => (await appRegistrar(args)) ?? {
|
|
175
|
+
error: 'empty app registration response',
|
|
176
|
+
};
|
|
125
177
|
const appResult = await (0, app_instance_1.ensureAppInstance)({
|
|
126
178
|
projectRoot,
|
|
127
179
|
mcpBaseUrl,
|
|
128
180
|
apiKey,
|
|
129
181
|
rebind,
|
|
130
182
|
log,
|
|
131
|
-
registerApp
|
|
183
|
+
registerApp,
|
|
132
184
|
});
|
|
133
185
|
log(`✓ app instance ${appResult.appInstanceId} (${appResult.status}) — .flowrail/app.json`);
|
|
134
186
|
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowrail/init",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "One-shot FlowRail installer for Claude Code. Wires PreToolUse hooks, MCP server config, and skill markdown into
|
|
3
|
+
"version": "0.0.17",
|
|
4
|
+
"description": "One-shot FlowRail installer for Claude Code. Wires PreToolUse hooks, MCP server config, and skill markdown into your repo, then probes the FlowRail server with your API key.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
|
-
"
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/anshumanbh/flowstate.git",
|
|
13
|
-
"directory": "packages/init"
|
|
14
|
-
},
|
|
15
|
-
"homepage": "https://github.com/anshumanbh/flowstate#readme",
|
|
16
|
-
"bugs": "https://github.com/anshumanbh/flowstate/issues",
|
|
10
|
+
"homepage": "https://flowrail.ai",
|
|
17
11
|
"main": "dist/index.js",
|
|
18
12
|
"bin": {
|
|
19
13
|
"flowrail-init": "dist/index.js"
|
|
@@ -31,7 +25,7 @@
|
|
|
31
25
|
"prepublishOnly": "npm run build && npm test"
|
|
32
26
|
},
|
|
33
27
|
"dependencies": {
|
|
34
|
-
"@flowrail/skills": "0.0.
|
|
28
|
+
"@flowrail/skills": "0.0.7"
|
|
35
29
|
},
|
|
36
30
|
"devDependencies": {
|
|
37
31
|
"@types/jest": "^29.5.12",
|