@fixprompt/cli 0.2.0 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +105 -55
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -91,7 +91,14 @@ import { readFileSync, readdirSync, statSync } from "fs";
91
91
  import { join } from "path";
92
92
  import { spawnSync } from "child_process";
93
93
  var DEFAULT_ENDPOINT = "https://geosloghub-production.up.railway.app";
94
- var TOKEN_ENV_VAR_NAME = "FIXPROMPT_DEPLOY_TOKEN";
94
+ var ENV_VAR_BY_SCOPE = {
95
+ deploy: "FIXPROMPT_DEPLOY_TOKEN",
96
+ read: "FIXPROMPT_READ_TOKEN"
97
+ };
98
+ var ENDPOINT_BY_SCOPE = {
99
+ deploy: "deploy-tokens",
100
+ read: "read-tokens"
101
+ };
95
102
  function fileExists(p) {
96
103
  try {
97
104
  return statSync(p).isFile();
@@ -123,8 +130,9 @@ function detectProvider(cwd) {
123
130
  }
124
131
  return null;
125
132
  }
126
- async function mintDeployToken(opts) {
127
- const url = `${opts.endpoint.replace(/\/$/, "")}/admin/projects/${opts.projectId}/deploy-tokens`;
133
+ async function mintToken(opts) {
134
+ const path = ENDPOINT_BY_SCOPE[opts.scope];
135
+ const url = `${opts.endpoint.replace(/\/$/, "")}/admin/projects/${opts.projectId}/${path}`;
128
136
  const res = await fetch(url, {
129
137
  method: "POST",
130
138
  headers: {
@@ -135,28 +143,30 @@ async function mintDeployToken(opts) {
135
143
  });
136
144
  const text = await res.text();
137
145
  if (!res.ok) {
138
- throw new Error(`broker /admin/projects/${opts.projectId}/deploy-tokens responded ${res.status}: ${text.slice(0, 200)}`);
146
+ throw new Error(
147
+ `broker /admin/projects/${opts.projectId}/${path} responded ${res.status}: ${text.slice(0, 200)}`
148
+ );
139
149
  }
140
150
  return JSON.parse(text);
141
151
  }
142
- function writeEas(token, env) {
143
- console.log(`
144
- \u2022 Writing ${TOKEN_ENV_VAR_NAME} to EAS env '${env}'\u2026`);
152
+ function writeEas(envVar, token, env, force) {
153
+ console.log(` \xB7 writing ${envVar} to EAS env '${env}'\u2026`);
145
154
  const useShell = process.platform === "win32";
146
155
  const args = [
147
156
  "eas-cli",
148
157
  "env:create",
149
158
  env,
150
159
  "--name",
151
- TOKEN_ENV_VAR_NAME,
160
+ envVar,
152
161
  "--value",
153
162
  token,
154
163
  "--type",
155
- "plain",
164
+ "string",
156
165
  "--visibility",
157
166
  "plaintext",
158
167
  "--non-interactive"
159
168
  ];
169
+ if (force) args.push("--force");
160
170
  const r = spawnSync("npx", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
161
171
  stdio: "inherit",
162
172
  shell: useShell
@@ -166,15 +176,14 @@ function writeEas(token, env) {
166
176
  `eas env:create exited ${r.status}. Common causes:
167
177
  \u2022 Not logged in to EAS \u2014 run 'eas login'
168
178
  \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.`
179
+ \u2022 Variable already exists \u2014 re-run with --force to overwrite.`
170
180
  );
171
181
  }
172
182
  }
173
- function writeGitHubActions(token, cwd) {
174
- console.log(`
175
- \u2022 Writing ${TOKEN_ENV_VAR_NAME} to GitHub Actions repo secrets\u2026`);
183
+ function writeGitHubActions(envVar, token, cwd) {
184
+ console.log(` \xB7 writing ${envVar} to GitHub Actions repo secrets\u2026`);
176
185
  const useShell = process.platform === "win32";
177
- const args = ["secret", "set", TOKEN_ENV_VAR_NAME, "--body", "-"];
186
+ const args = ["secret", "set", envVar, "--body", "-"];
178
187
  const r = spawnSync("gh", useShell ? args.map((a) => `"${a.replace(/"/g, '\\"')}"`) : args, {
179
188
  input: token,
180
189
  cwd,
@@ -189,7 +198,7 @@ function writeGitHubActions(token, cwd) {
189
198
  );
190
199
  }
191
200
  }
192
- async function writeVercel(token, target) {
201
+ async function writeVercel(envVar, token, target) {
193
202
  const vt = process.env.VERCEL_TOKEN;
194
203
  if (!vt) {
195
204
  throw new Error(
@@ -215,8 +224,7 @@ directory or pass --vercel-project-id <prj_\u2026>.`
215
224
  }
216
225
  }
217
226
  const targets = target.vercelTargets ?? ["production", "preview"];
218
- console.log(`
219
- \u2022 Writing ${TOKEN_ENV_VAR_NAME} to Vercel project ${projectId} (${targets.join(", ")})\u2026`);
227
+ console.log(` \xB7 writing ${envVar} to Vercel project ${projectId} (${targets.join(", ")})\u2026`);
220
228
  const res = await fetch(`https://api.vercel.com/v10/projects/${projectId}/env`, {
221
229
  method: "POST",
222
230
  headers: {
@@ -224,7 +232,7 @@ directory or pass --vercel-project-id <prj_\u2026>.`
224
232
  "Content-Type": "application/json"
225
233
  },
226
234
  body: JSON.stringify({
227
- key: TOKEN_ENV_VAR_NAME,
235
+ key: envVar,
228
236
  value: token,
229
237
  type: "plain",
230
238
  target: targets
@@ -235,15 +243,26 @@ directory or pass --vercel-project-id <prj_\u2026>.`
235
243
  throw new Error(`Vercel /env responded ${res.status}: ${text.slice(0, 200)}`);
236
244
  }
237
245
  }
246
+ function writeToProvider(envVar, token, provider, target, force, cwd) {
247
+ switch (provider) {
248
+ case "eas":
249
+ writeEas(envVar, token, target.easEnv, force);
250
+ return;
251
+ case "github-actions":
252
+ writeGitHubActions(envVar, token, cwd);
253
+ return;
254
+ case "vercel":
255
+ return writeVercel(envVar, token, target);
256
+ }
257
+ }
238
258
  async function connect(args) {
239
259
  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
- );
260
+ const scopeRaw = (args.flags.scope ?? "deploy").toLowerCase();
261
+ if (scopeRaw !== "deploy" && scopeRaw !== "read" && scopeRaw !== "both") {
262
+ console.error(`Invalid --scope '${scopeRaw}'. Allowed: deploy | read | both.`);
245
263
  process.exit(1);
246
264
  }
265
+ const scope = scopeRaw;
247
266
  const adminToken = args.flags["admin-token"] ?? process.env.FIXPROMPT_ADMIN_TOKEN ?? process.env.INTERNAL_ADMIN_TOKEN;
248
267
  if (!adminToken) {
249
268
  console.error(
@@ -252,6 +271,27 @@ async function connect(args) {
252
271
  process.exit(1);
253
272
  }
254
273
  const endpoint = (args.flags.endpoint ?? process.env.FIXPROMPT_ENDPOINT ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
274
+ let projectId = args.flags["project-id"] ?? process.env.FIXPROMPT_PROJECT_ID ?? null;
275
+ const projectSlug = args.flags["project-slug"] ?? null;
276
+ if (!projectId && projectSlug) {
277
+ const res = await fetch(`${endpoint}/admin/projects/by-slug/${encodeURIComponent(projectSlug)}`, {
278
+ headers: { "x-internal-admin-token": adminToken }
279
+ });
280
+ const text = await res.text();
281
+ if (!res.ok) {
282
+ console.error(`Project lookup failed (${res.status}): ${text.slice(0, 200)}`);
283
+ process.exit(1);
284
+ }
285
+ const parsed = JSON.parse(text);
286
+ projectId = parsed.id;
287
+ console.log(` resolved --project-slug=${projectSlug} \u2192 ${parsed.id} (${parsed.name})`);
288
+ }
289
+ if (!projectId) {
290
+ console.error(
291
+ "Need --project-id or --project-slug (or $FIXPROMPT_PROJECT_ID).\n --project-slug is easiest: it matches the value you see in the dashboard."
292
+ );
293
+ process.exit(1);
294
+ }
255
295
  const explicit = args.flags.provider;
256
296
  const provider = explicit ?? detectProvider(cwd);
257
297
  if (!provider) {
@@ -268,50 +308,52 @@ async function connect(args) {
268
308
  vercelProjectId: args.flags["vercel-project-id"] ?? void 0,
269
309
  vercelTargets: typeof args.flags["vercel-targets"] === "string" ? args.flags["vercel-targets"].split(",").map((t) => t.trim()) : void 0
270
310
  };
271
- const label = args.flags.label ?? `${provider}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
311
+ const labelBase = args.flags.label ?? `${provider}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}`;
272
312
  console.log(`fixprompt connect`);
313
+ console.log(` scope : ${scope}`);
273
314
  console.log(` provider : ${provider}`);
274
315
  console.log(` project_id : ${projectId}`);
275
316
  console.log(` endpoint : ${endpoint}`);
276
- console.log(` label : ${label}`);
317
+ console.log(` label : ${labelBase}`);
277
318
  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;
319
+ const force = args.flags.force === true;
320
+ const scopes = scope === "both" ? ["deploy", "read"] : [scope];
321
+ const mintedTokens = [];
322
+ for (const s of scopes) {
323
+ const envVar = ENV_VAR_BY_SCOPE[s];
324
+ const label = scope === "both" ? `${labelBase}-${s}` : labelBase;
325
+ console.log(`
326
+ \u2022 Minting ${s} token via broker\u2026`);
327
+ const minted = await mintToken({ endpoint, adminToken, projectId, label, scope: s });
328
+ console.log(` \u2713 token_id ${minted.token_id}`);
329
+ await writeToProvider(envVar, minted.token, provider, target, force, cwd);
330
+ mintedTokens.push({ scope: s, token_id: minted.token_id, envVar });
297
331
  }
298
332
  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.`);
333
+ \u2713 ${mintedTokens.map((m) => m.envVar).join(" + ")} now set on ${provider}.`);
334
+ if (mintedTokens.some((m) => m.scope === "deploy")) {
335
+ if (provider === "eas") {
336
+ console.log(` Next OTA: npm run release:ota --branch=production --message="..."`);
337
+ } else if (provider === "github-actions") {
338
+ console.log(` Reference \${{ secrets.FIXPROMPT_DEPLOY_TOKEN }} in your workflow.`);
339
+ } else {
340
+ console.log(` The deploy token is in your Vercel project env.`);
341
+ }
342
+ }
343
+ if (mintedTokens.some((m) => m.scope === "read")) {
344
+ console.log(` Customer-side agents can now fetch logs via FIXPROMPT_READ_TOKEN \u2014`);
345
+ console.log(` see the fixprompt-debug skill at fixloop-dashboard.vercel.app/claude-skill.md.`);
307
346
  }
308
347
  console.log(`
309
- To revoke later: dashboard \u2192 Integrations \u2192 Deploy tokens \u2192 ${minted.token_id}.`);
348
+ Revoke any of these from the dashboard \u2192 Integrations.`);
349
+ for (const m of mintedTokens) {
350
+ console.log(` ${m.scope}: ${m.token_id}`);
351
+ }
310
352
  }
311
353
 
312
354
  // src/version.ts
313
355
  var CLI_NAME = "@fixprompt/cli";
314
- var CLI_VERSION = "0.2.0";
356
+ var CLI_VERSION = "0.3.0";
315
357
 
316
358
  // src/cli.ts
317
359
  var DEFAULT_ENDPOINT2 = "https://geosloghub-production.up.railway.app";
@@ -350,13 +392,21 @@ Usage:
350
392
  fixprompt help
351
393
 
352
394
  connect options:
353
- --project-id <uuid> FixPrompt project id (or $FIXPROMPT_PROJECT_ID)
395
+ --scope <s> deploy | read | both (default: deploy)
396
+ deploy \u2192 mints fpd_\u2026, writes FIXPROMPT_DEPLOY_TOKEN
397
+ read \u2192 mints fpr_\u2026, writes FIXPROMPT_READ_TOKEN
398
+ (lets a customer's coding agent fetch
399
+ production logs from FixLoop directly)
400
+ both \u2192 does both in one pass
401
+ --project-id <uuid> FixLoop project id (or $FIXPROMPT_PROJECT_ID)
402
+ --project-slug <slug> Look up project_id by slug (no UUID needed)
354
403
  --admin-token <fpa_\u2026> Broker admin token (or $FIXPROMPT_ADMIN_TOKEN)
355
404
  --provider <name> eas | vercel | github-actions (auto-detected from cwd)
356
405
  --eas-env <env> EAS env to write to (default: production)
357
406
  --vercel-project-id <id> Override Vercel project id (default: .vercel/project.json)
358
407
  --vercel-targets <list> Comma-separated (default: production,preview)
359
408
  --label <text> Token label (default: <provider>-<timestamp>)
409
+ --force Overwrite an existing env var value
360
410
 
361
411
  deploy-start auth (pick one):
362
412
  --deploy-token <fpd_...> Deploy-only token from dashboard (recommended for CI)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fixprompt/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {