@fixprompt/cli 0.0.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.
Files changed (2) hide show
  1. package/dist/cli.js +277 -19
  2. package/package.json +5 -2
package/dist/cli.js CHANGED
@@ -86,12 +86,235 @@ function detectContext() {
86
86
  return merged;
87
87
  }
88
88
 
89
+ // src/connect.ts
90
+ import { readFileSync, readdirSync, statSync } from "fs";
91
+ import { join } from "path";
92
+ import { spawnSync } from "child_process";
93
+ var DEFAULT_ENDPOINT = "https://geosloghub-production.up.railway.app";
94
+ var TOKEN_ENV_VAR_NAME = "FIXPROMPT_DEPLOY_TOKEN";
95
+ function fileExists(p) {
96
+ try {
97
+ return statSync(p).isFile();
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ function dirExists(p) {
103
+ try {
104
+ return statSync(p).isDirectory();
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ function detectProvider(cwd) {
110
+ if (fileExists(join(cwd, "app.json")) || fileExists(join(cwd, "app.config.js")) || fileExists(join(cwd, "app.config.ts")) || fileExists(join(cwd, "eas.json"))) {
111
+ return "eas";
112
+ }
113
+ if (fileExists(join(cwd, "vercel.json")) || fileExists(join(cwd, ".vercel", "project.json"))) {
114
+ return "vercel";
115
+ }
116
+ const wfDir = join(cwd, ".github", "workflows");
117
+ if (dirExists(wfDir)) {
118
+ try {
119
+ const files = readdirSync(wfDir);
120
+ if (files.some((f) => /\.ya?ml$/i.test(f))) return "github-actions";
121
+ } catch {
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ async function mintDeployToken(opts) {
127
+ const url = `${opts.endpoint.replace(/\/$/, "")}/admin/projects/${opts.projectId}/deploy-tokens`;
128
+ const res = await fetch(url, {
129
+ method: "POST",
130
+ headers: {
131
+ "x-internal-admin-token": opts.adminToken,
132
+ "Content-Type": "application/json"
133
+ },
134
+ body: JSON.stringify({ label: opts.label })
135
+ });
136
+ const text = await res.text();
137
+ if (!res.ok) {
138
+ throw new Error(`broker /admin/projects/${opts.projectId}/deploy-tokens responded ${res.status}: ${text.slice(0, 200)}`);
139
+ }
140
+ return JSON.parse(text);
141
+ }
142
+ function writeEas(token, env) {
143
+ console.log(`
144
+ \u2022 Writing ${TOKEN_ENV_VAR_NAME} to EAS env '${env}'\u2026`);
145
+ const useShell = process.platform === "win32";
146
+ const args = [
147
+ "eas-cli",
148
+ "env:create",
149
+ env,
150
+ "--name",
151
+ TOKEN_ENV_VAR_NAME,
152
+ "--value",
153
+ token,
154
+ "--type",
155
+ "plain",
156
+ "--visibility",
157
+ "plaintext",
158
+ "--non-interactive"
159
+ ];
160
+ const r = spawnSync("npx", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
161
+ stdio: "inherit",
162
+ shell: useShell
163
+ });
164
+ if (r.status !== 0) {
165
+ throw new Error(
166
+ `eas env:create exited ${r.status}. Common causes:
167
+ \u2022 Not logged in to EAS \u2014 run 'eas login'
168
+ \u2022 Wrong env name \u2014 pass --eas-env <production|preview|development>
169
+ \u2022 Variable already exists with a different value \u2014 delete it first or use the rotate flow.`
170
+ );
171
+ }
172
+ }
173
+ function writeGitHubActions(token, cwd) {
174
+ console.log(`
175
+ \u2022 Writing ${TOKEN_ENV_VAR_NAME} to GitHub Actions repo secrets\u2026`);
176
+ const useShell = process.platform === "win32";
177
+ const args = ["secret", "set", TOKEN_ENV_VAR_NAME, "--body", "-"];
178
+ const r = spawnSync("gh", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
179
+ input: token,
180
+ cwd,
181
+ stdio: ["pipe", "inherit", "inherit"],
182
+ shell: useShell
183
+ });
184
+ if (r.status !== 0) {
185
+ throw new Error(
186
+ `gh secret set exited ${r.status}. Common causes:
187
+ \u2022 Not logged in to gh \u2014 run 'gh auth login'
188
+ \u2022 Wrong repo \u2014 run 'fixprompt connect' from the repo root, or set GH_REPO.`
189
+ );
190
+ }
191
+ }
192
+ async function writeVercel(token, target) {
193
+ const vt = process.env.VERCEL_TOKEN;
194
+ if (!vt) {
195
+ throw new Error(
196
+ `Vercel provider needs VERCEL_TOKEN env. Generate one at
197
+ https://vercel.com/account/tokens
198
+ then re-run with that token exported.`
199
+ );
200
+ }
201
+ let projectId = target.vercelProjectId;
202
+ if (!projectId) {
203
+ const pj = join(process.cwd(), ".vercel", "project.json");
204
+ if (!fileExists(pj)) {
205
+ throw new Error(
206
+ `Could not resolve Vercel project id. Either run from a 'vercel link'-ed
207
+ directory or pass --vercel-project-id <prj_\u2026>.`
208
+ );
209
+ }
210
+ try {
211
+ const parsed = JSON.parse(readFileSync(pj, "utf8"));
212
+ projectId = parsed.projectId;
213
+ } catch (e) {
214
+ throw new Error(`failed to read .vercel/project.json: ${e.message}`);
215
+ }
216
+ }
217
+ const targets = target.vercelTargets ?? ["production", "preview"];
218
+ console.log(`
219
+ \u2022 Writing ${TOKEN_ENV_VAR_NAME} to Vercel project ${projectId} (${targets.join(", ")})\u2026`);
220
+ const res = await fetch(`https://api.vercel.com/v10/projects/${projectId}/env`, {
221
+ method: "POST",
222
+ headers: {
223
+ Authorization: `Bearer ${vt}`,
224
+ "Content-Type": "application/json"
225
+ },
226
+ body: JSON.stringify({
227
+ key: TOKEN_ENV_VAR_NAME,
228
+ value: token,
229
+ type: "plain",
230
+ target: targets
231
+ })
232
+ });
233
+ const text = await res.text();
234
+ if (!res.ok) {
235
+ throw new Error(`Vercel /env responded ${res.status}: ${text.slice(0, 200)}`);
236
+ }
237
+ }
238
+ async function connect(args) {
239
+ const cwd = process.cwd();
240
+ const projectId = args.flags["project-id"] ?? process.env.FIXPROMPT_PROJECT_ID;
241
+ if (!projectId) {
242
+ console.error(
243
+ "Missing --project-id (or $FIXPROMPT_PROJECT_ID).\n Find it in the FixPrompt dashboard URL after picking your project."
244
+ );
245
+ process.exit(1);
246
+ }
247
+ const adminToken = args.flags["admin-token"] ?? process.env.FIXPROMPT_ADMIN_TOKEN ?? process.env.INTERNAL_ADMIN_TOKEN;
248
+ if (!adminToken) {
249
+ console.error(
250
+ "Missing --admin-token (or $FIXPROMPT_ADMIN_TOKEN / $INTERNAL_ADMIN_TOKEN).\n Per-user device-code auth lands in a later release; for now this is the\n broker-wide admin token (single-tenant). Treat it as a root secret."
251
+ );
252
+ process.exit(1);
253
+ }
254
+ const endpoint = (args.flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
255
+ const explicit = args.flags.provider;
256
+ const provider = explicit ?? detectProvider(cwd);
257
+ if (!provider) {
258
+ console.error(
259
+ `Couldn't detect a provider from ${cwd}. Pass --provider <eas|vercel|github-actions>.
260
+ Detection looks for: app.json/eas.json (EAS), .vercel/project.json (Vercel),
261
+ .github/workflows/*.yml (GitHub Actions).`
262
+ );
263
+ process.exit(1);
264
+ }
265
+ const target = {
266
+ provider,
267
+ easEnv: args.flags["eas-env"] ?? "production",
268
+ vercelProjectId: args.flags["vercel-project-id"] ?? void 0,
269
+ vercelTargets: typeof args.flags["vercel-targets"] === "string" ? args.flags["vercel-targets"].split(",").map((t) => t.trim()) : void 0
270
+ };
271
+ const label = args.flags.label ?? `${provider}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
272
+ console.log(`fixprompt connect`);
273
+ console.log(` provider : ${provider}`);
274
+ console.log(` project_id : ${projectId}`);
275
+ console.log(` endpoint : ${endpoint}`);
276
+ console.log(` label : ${label}`);
277
+ if (provider === "eas") console.log(` eas-env : ${target.easEnv}`);
278
+ console.log(`
279
+ \u2022 Minting deploy token via broker\u2026`);
280
+ const minted = await mintDeployToken({
281
+ endpoint,
282
+ adminToken,
283
+ projectId,
284
+ label
285
+ });
286
+ console.log(` \u2713 token_id ${minted.token_id} (value hidden \u2014 written to CI store only)`);
287
+ switch (provider) {
288
+ case "eas":
289
+ writeEas(minted.token, target.easEnv);
290
+ break;
291
+ case "github-actions":
292
+ writeGitHubActions(minted.token, cwd);
293
+ break;
294
+ case "vercel":
295
+ await writeVercel(minted.token, target);
296
+ break;
297
+ }
298
+ console.log(`
299
+ \u2713 ${TOKEN_ENV_VAR_NAME} is now set on ${provider}.`);
300
+ console.log(` Next deploy can run with no shell paste:`);
301
+ if (provider === "eas") {
302
+ console.log(` npm run release:ota --branch=production --message="..."`);
303
+ } else if (provider === "github-actions") {
304
+ console.log(` Reference \${{ secrets.${TOKEN_ENV_VAR_NAME} }} in your workflow.`);
305
+ } else {
306
+ console.log(` The token is in your Vercel project env for the next deploy.`);
307
+ }
308
+ console.log(`
309
+ To revoke later: dashboard \u2192 Integrations \u2192 Deploy tokens \u2192 ${minted.token_id}.`);
310
+ }
311
+
89
312
  // src/version.ts
90
313
  var CLI_NAME = "@fixprompt/cli";
91
- var CLI_VERSION = "0.0.1";
314
+ var CLI_VERSION = "0.2.0";
92
315
 
93
316
  // src/cli.ts
94
- var DEFAULT_ENDPOINT = "https://geosloghub-production.up.railway.app";
317
+ var DEFAULT_ENDPOINT2 = "https://geosloghub-production.up.railway.app";
95
318
  function parseArgs(argv) {
96
319
  const flags = {};
97
320
  const positional = [];
@@ -122,13 +345,29 @@ function helpText() {
122
345
  return `${CLI_NAME} v${CLI_VERSION}
123
346
 
124
347
  Usage:
348
+ fixprompt connect [options]
125
349
  fixprompt deploy-start [options]
126
350
  fixprompt help
127
351
 
352
+ connect options:
353
+ --project-id <uuid> FixPrompt project id (or $FIXPROMPT_PROJECT_ID)
354
+ --admin-token <fpa_\u2026> Broker admin token (or $FIXPROMPT_ADMIN_TOKEN)
355
+ --provider <name> eas | vercel | github-actions (auto-detected from cwd)
356
+ --eas-env <env> EAS env to write to (default: production)
357
+ --vercel-project-id <id> Override Vercel project id (default: .vercel/project.json)
358
+ --vercel-targets <list> Comma-separated (default: production,preview)
359
+ --label <text> Token label (default: <provider>-<timestamp>)
360
+
361
+ deploy-start auth (pick one):
362
+ --deploy-token <fpd_...> Deploy-only token from dashboard (recommended for CI)
363
+ (or $FIXPROMPT_DEPLOY_TOKEN env)
364
+ --key <k_...> Runtime SDK key, paired with --source
365
+ (or $LOGHUB_KEY / $FIXPROMPT_KEY env)
366
+
128
367
  deploy-start options:
129
- --source <slug> LOGHUB_SOURCE (default: $LOGHUB_SOURCE or $FIXPROMPT_SOURCE env)
130
- --key <k_...> LOGHUB_KEY (default: $LOGHUB_KEY or $FIXPROMPT_KEY env)
131
- --endpoint <url> Broker URL (default: $FIXPROMPT_ENDPOINT or ${DEFAULT_ENDPOINT})
368
+ --source <slug> LOGHUB_SOURCE (required only with --key; optional with --deploy-token)
369
+ (default: $LOGHUB_SOURCE or $FIXPROMPT_SOURCE env)
370
+ --endpoint <url> Broker URL (default: $FIXPROMPT_ENDPOINT or ${DEFAULT_ENDPOINT2})
132
371
  --status <state> in_progress | success | failed (default: in_progress)
133
372
  --commit-sha <sha> Override auto-detected commit
134
373
  --branch <name> Override auto-detected branch
@@ -137,9 +376,14 @@ deploy-start options:
137
376
  Auto-detected CI providers: GitHub Actions, Vercel, Railway.
138
377
  `;
139
378
  }
140
- function readSource(flags) {
379
+ function readDeployToken(flags) {
380
+ const v = flags["deploy-token"] ?? process.env.FIXPROMPT_DEPLOY_TOKEN;
381
+ return v || null;
382
+ }
383
+ function readSource(flags, required) {
141
384
  const v = flags.source ?? process.env.LOGHUB_SOURCE ?? process.env.FIXPROMPT_SOURCE;
142
385
  if (!v) {
386
+ if (!required) return null;
143
387
  console.error("Missing --source (or $LOGHUB_SOURCE / $FIXPROMPT_SOURCE)");
144
388
  process.exit(1);
145
389
  }
@@ -147,19 +391,26 @@ function readSource(flags) {
147
391
  }
148
392
  function readKey(flags) {
149
393
  const v = flags.key ?? process.env.LOGHUB_KEY ?? process.env.FIXPROMPT_KEY;
150
- if (!v) {
151
- console.error("Missing --key (or $LOGHUB_KEY / $FIXPROMPT_KEY)");
152
- process.exit(1);
153
- }
154
- return v;
394
+ return v || null;
155
395
  }
156
396
  function readEndpoint(flags) {
157
- const v = flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT;
397
+ const v = flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT2;
158
398
  return v.replace(/\/$/, "");
159
399
  }
160
400
  async function deployStart(args) {
161
- const source = readSource(args.flags);
162
- const key = readKey(args.flags);
401
+ const deployToken = readDeployToken(args.flags);
402
+ const key = deployToken ? null : readKey(args.flags);
403
+ const source = readSource(
404
+ args.flags,
405
+ /* required */
406
+ !deployToken
407
+ );
408
+ if (!deployToken && !key) {
409
+ console.error(
410
+ "Missing auth: pass --deploy-token (fpd_\u2026) or --key (k_\u2026).\n $FIXPROMPT_DEPLOY_TOKEN, $LOGHUB_KEY, $FIXPROMPT_KEY env vars are checked."
411
+ );
412
+ process.exit(1);
413
+ }
163
414
  const endpoint = readEndpoint(args.flags);
164
415
  const status = args.flags.status ?? "in_progress";
165
416
  const ctx = detectContext();
@@ -180,14 +431,18 @@ async function deployStart(args) {
180
431
  fixprompt_version: CLI_VERSION,
181
432
  status
182
433
  };
434
+ const headers = { "Content-Type": "application/json" };
435
+ if (deployToken) {
436
+ headers["x-fixprompt-deploy-token"] = deployToken;
437
+ if (source) headers["x-loghub-source"] = source;
438
+ } else {
439
+ headers["x-loghub-source"] = source;
440
+ headers["x-loghub-key"] = key;
441
+ }
183
442
  const url = `${endpoint}/deployments`;
184
443
  const res = await fetch(url, {
185
444
  method: "POST",
186
- headers: {
187
- "Content-Type": "application/json",
188
- "x-loghub-source": source,
189
- "x-loghub-key": key
190
- },
445
+ headers,
191
446
  body: JSON.stringify(payload)
192
447
  });
193
448
  const text = await res.text();
@@ -209,6 +464,9 @@ async function deployStart(args) {
209
464
  async function main() {
210
465
  const args = parseArgs(process.argv.slice(2));
211
466
  switch (args.command) {
467
+ case "connect":
468
+ await connect(args);
469
+ break;
212
470
  case "deploy-start":
213
471
  await deployStart(args);
214
472
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixprompt/cli",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "FixPrompt CLI — annotate deployments and ship them to the broker so the dashboard knows what changed.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -15,7 +15,10 @@
15
15
  "bin": {
16
16
  "fixprompt": "dist/cli.js"
17
17
  },
18
- "files": ["dist", "README.md"],
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
19
22
  "scripts": {
20
23
  "build": "tsup",
21
24
  "dev": "tsup --watch",