inflight-cli 2.6.0 → 2.8.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.
@@ -1,5 +1,8 @@
1
1
  /** Ensures the Vercel CLI is available — installs globally if missing. */
2
- export declare function ensureVercelCli(log?: (msg: string) => void): Promise<boolean>;
2
+ export declare function ensureVercelCli(log?: (msg: string) => void): Promise<{
3
+ ok: boolean;
4
+ error?: string;
5
+ }>;
3
6
  /**
4
7
  * Gets a valid Vercel token. Refreshes silently via `vercel whoami` if expired.
5
8
  * Does NOT prompt for login — returns null if no valid token available.
@@ -18,14 +18,19 @@ function hasVercelCli() {
18
18
  /** Ensures the Vercel CLI is available — installs globally if missing. */
19
19
  export async function ensureVercelCli(log) {
20
20
  if (hasVercelCli())
21
- return true;
21
+ return { ok: true };
22
22
  log?.("Installing Vercel CLI...");
23
23
  try {
24
24
  await execAsync("npm install -g vercel");
25
- return true;
25
+ return { ok: true };
26
26
  }
27
- catch {
28
- return false;
27
+ catch (e) {
28
+ const stderr = e && typeof e === "object" && "stderr" in e && typeof e.stderr === "string"
29
+ ? e.stderr
30
+ : e instanceof Error
31
+ ? e.message
32
+ : "Unknown error";
33
+ return { ok: false, error: stderr };
29
34
  }
30
35
  }
31
36
  /** Returns the Vercel CLI config directory for the current platform. */
@@ -1,6 +1,7 @@
1
1
  import * as p from "@clack/prompts";
2
2
  import pc from "picocolors";
3
3
  import { parseGitRepo, getGitRoot } from "../lib/git.js";
4
+ import { isAgent } from "../lib/agent.js";
4
5
  import { writeNetlifyConfig } from "../lib/config.js";
5
6
  import { ensureNetlifyCli, ensureNetlifyAuth, readLocalNetlifySite, writeLocalNetlifySite, getNetlifySiteById, getNetlifySites, getNetlifyDeploys, matchSitesByRepo, } from "../lib/netlify.js";
6
7
  // --- Auto-detection ---
@@ -37,18 +38,20 @@ async function autoDetectSite(cwd, gitInfo, token) {
37
38
  }
38
39
  }
39
40
  // --- Fetch all sites ---
40
- const spinner = p.spinner();
41
- spinner.start("Detecting Netlify site...");
41
+ const spinner = !isAgent ? p.spinner() : null;
42
+ spinner?.start("Detecting Netlify site...");
42
43
  let allSites;
43
44
  try {
44
45
  allSites = await getNetlifySites(token);
45
46
  }
46
- catch {
47
- spinner.stop("Could not fetch Netlify sites.");
47
+ catch (e) {
48
+ spinner?.stop("Could not fetch Netlify sites.");
49
+ if (!isAgent && e instanceof Error)
50
+ p.log.message(pc.dim(e.message));
48
51
  return null;
49
52
  }
50
53
  if (allSites.length === 0) {
51
- spinner.stop("No Netlify sites found.");
54
+ spinner?.stop("No Netlify sites found.");
52
55
  return null;
53
56
  }
54
57
  // --- Try exact match on git remote ---
@@ -56,7 +59,7 @@ async function autoDetectSite(cwd, gitInfo, token) {
56
59
  if (gitRepo) {
57
60
  const matches = matchSitesByRepo(allSites, gitRepo.owner, gitRepo.name);
58
61
  if (matches.length === 1) {
59
- spinner.stop(`Detected Netlify site: ${pc.bold(matches[0].name)}`);
62
+ spinner?.stop(`Detected Netlify site: ${pc.bold(matches[0].name)}`);
60
63
  return cacheResult({
61
64
  siteId: matches[0].id,
62
65
  siteName: matches[0].name,
@@ -65,25 +68,25 @@ async function autoDetectSite(cwd, gitInfo, token) {
65
68
  });
66
69
  }
67
70
  if (matches.length > 1) {
68
- spinner.stop(`Found ${matches.length} Netlify sites for this repo.`);
71
+ spinner?.stop(`Found ${matches.length} Netlify sites for this repo.`);
69
72
  const picked = await pickFromList(matches);
70
73
  return picked ? cacheResult(picked) : null;
71
74
  }
72
75
  }
73
76
  // --- No match ---
74
- spinner.stop("Could not auto-detect Netlify site.");
77
+ spinner?.stop("Could not auto-detect Netlify site.");
75
78
  const picked = await pickFromList(allSites);
76
79
  return picked ? cacheResult(picked) : null;
77
80
  }
78
81
  async function pickFromList(sites) {
82
+ if (isAgent)
83
+ return null;
79
84
  const maxName = Math.max(...sites.map((s) => s.name.length));
80
85
  const selected = await p.select({
81
86
  message: "Select a Netlify site",
82
87
  options: sites.map((s) => ({
83
88
  value: s,
84
- label: s.account_name
85
- ? `${s.name.padEnd(maxName)} ${pc.dim(`(${s.account_name})`)}`
86
- : s.name,
89
+ label: s.account_name ? `${s.name.padEnd(maxName)} ${pc.dim(`(${s.account_name})`)}` : s.name,
87
90
  })),
88
91
  });
89
92
  if (p.isCancel(selected)) {
@@ -91,15 +94,23 @@ async function pickFromList(sites) {
91
94
  process.exit(0);
92
95
  }
93
96
  const match = selected;
94
- return { siteId: match.id, siteName: match.name, teamSlug: match.account_slug, repoPath: match.build_settings?.repo_path ?? null };
97
+ return {
98
+ siteId: match.id,
99
+ siteName: match.name,
100
+ teamSlug: match.account_slug,
101
+ repoPath: match.build_settings?.repo_path ?? null,
102
+ };
95
103
  }
96
104
  // --- Manual picker (used by `inflight netlify` command) ---
97
105
  export async function pickNetlifySite(token) {
98
106
  const allSites = await getNetlifySites(token);
99
107
  if (allSites.length === 0) {
100
- p.log.error("No Netlify sites found.");
108
+ if (!isAgent)
109
+ p.log.error("No Netlify sites found.");
101
110
  return null;
102
111
  }
112
+ if (isAgent)
113
+ return null;
103
114
  const selected = await p.select({
104
115
  message: "Select a Netlify site",
105
116
  options: allSites.map((s) => ({
@@ -122,14 +133,22 @@ export async function pickNetlifySite(token) {
122
133
  }
123
134
  // --- Main resolve function ---
124
135
  export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
125
- const cliOk = await ensureNetlifyCli((msg) => p.log.step(msg));
126
- if (!cliOk) {
127
- p.log.error("Failed to install Netlify CLI. Install manually: " + pc.cyan("npm install -g netlify-cli"));
128
- return null;
136
+ const cli = await ensureNetlifyCli((msg) => { if (!isAgent)
137
+ p.log.step(msg); });
138
+ if (!cli.ok) {
139
+ if (!isAgent) {
140
+ p.log.error("Could not install the Netlify CLI automatically.");
141
+ if (cli.error) {
142
+ p.log.message(pc.dim(cli.error.trim()));
143
+ }
144
+ p.log.info(`Paste the error above into your AI agent — it can fix this for you.\n\nThen re-run ${pc.cyan("inflight share")}.`);
145
+ }
146
+ process.exit(0);
129
147
  }
130
148
  const token = await ensureNetlifyAuth();
131
149
  if (!token) {
132
- p.log.error("Netlify login failed.");
150
+ if (!isAgent)
151
+ p.log.error("Netlify login failed.");
133
152
  return null;
134
153
  }
135
154
  const site = await autoDetectSite(cwd, gitInfo, token);
@@ -142,27 +161,27 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
142
161
  const repoMatches = site.repoPath && localRepoPath ? site.repoPath.toLowerCase() === localRepoPath : false;
143
162
  // Only filter by branch if we're in the matching repo — otherwise show all deploys
144
163
  let deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
145
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
164
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
146
165
  });
147
166
  let commitDeploy = repoMatches ? deploys.find((d) => d.commitRef === commitSha) : undefined;
148
167
  // If just pushed, poll until the commit deployment appears
149
168
  if (!commitDeploy && repoMatches && opts?.justPushed) {
150
- const pollSpinner = p.spinner();
151
- pollSpinner.start("Waiting for Netlify deployment...");
169
+ const pollSpinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
170
+ pollSpinner?.start("Waiting for deploy");
152
171
  for (let i = 0; i < 30; i++) {
153
172
  await new Promise((r) => setTimeout(r, 2000));
154
173
  deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
155
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
174
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
156
175
  });
157
176
  commitDeploy = deploys.find((d) => d.commitRef === commitSha);
158
177
  if (commitDeploy)
159
178
  break;
160
179
  }
161
180
  if (commitDeploy) {
162
- pollSpinner.stop("Netlify deployment found!");
181
+ pollSpinner?.clear();
163
182
  }
164
183
  else {
165
- pollSpinner.stop("Netlify deployment is still building...");
184
+ pollSpinner?.stop("No deployment detected yet — Netlify may still be processing.");
166
185
  }
167
186
  }
168
187
  // If we found a commit-specific deployment, handle based on state
@@ -172,55 +191,50 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
172
191
  ? pc.dim(` — ${truncate(commitDeploy.commitMessage.split("\n")[0], 50)}`)
173
192
  : "";
174
193
  if (commitDeploy.state === "error") {
175
- p.log.error(`Netlify deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
194
+ if (!isAgent)
195
+ p.log.error(`Netlify deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
176
196
  commitDeploy = undefined;
177
197
  // Fall through to the picker
178
198
  }
179
199
  else if (commitDeploy.state !== "ready") {
180
200
  // Any non-ready, non-error state (building, enqueued, uploading, preparing, processing, etc.)
181
- p.log.info(`Netlify deployment for ${commitLabel}${message} — ${pc.yellow(commitDeploy.state)}`);
201
+ if (!isAgent)
202
+ p.log.info(`Netlify deployment for ${commitLabel}${message} — ${pc.yellow(commitDeploy.state)}`);
182
203
  const buildingUrl = commitDeploy.deploySslUrl;
183
204
  const startTime = Date.now();
184
205
  const maxWaitMs = 120_000;
185
206
  const pollIntervalMs = 5_000;
186
207
  let resolved = false;
187
- const spinner = p.spinner();
188
- const updateMessage = () => {
189
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
190
- spinner.message(`Waiting for Netlify to finish building... (${elapsed}s)`);
191
- };
192
- spinner.start("Waiting for Netlify to finish building... (0s)");
193
- const ticker = setInterval(updateMessage, 1000);
208
+ const spinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
209
+ spinner?.start("Waiting for build");
194
210
  try {
195
211
  while (Date.now() - startTime < maxWaitMs) {
196
212
  await new Promise((r) => setTimeout(r, pollIntervalMs));
197
- updateMessage();
198
213
  const freshDeps = await getNetlifyDeploys(token, site.siteId, site.siteName, {
199
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
214
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
200
215
  });
201
216
  const fresh = freshDeps.find((d) => d.commitRef === commitSha);
202
217
  if (!fresh)
203
218
  break;
204
219
  if (fresh.state === "ready") {
205
- clearInterval(ticker);
206
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
207
- spinner.stop(`Netlify deployment ready! (${elapsed}s)`);
208
- p.log.info(`Netlify deployment for ${commitLabel}${message}:\n → ${pc.cyan(fresh.deploySslUrl)}`);
220
+ spinner?.stop("Netlify deployment ready!");
221
+ if (!isAgent)
222
+ p.log.info(`Netlify deployment for ${commitLabel}${message}:\n → ${pc.cyan(fresh.deploySslUrl)}`);
209
223
  return fresh.deploySslUrl;
210
224
  }
211
225
  if (fresh.state === "error") {
212
- clearInterval(ticker);
213
- spinner.stop(pc.red("Netlify deployment failed."));
214
- p.log.error(`Netlify deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
226
+ spinner?.stop(pc.red("Netlify deployment failed."));
227
+ if (!isAgent)
228
+ p.log.error(`Netlify deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
215
229
  commitDeploy = undefined;
216
230
  resolved = true;
217
231
  break;
218
232
  }
219
233
  }
220
234
  if (!resolved) {
221
- clearInterval(ticker);
222
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
223
- spinner.stop(`Still building after ${elapsed}s.`);
235
+ spinner?.stop("Still building...");
236
+ if (isAgent)
237
+ return buildingUrl;
224
238
  const action = await p.select({
225
239
  message: "Netlify deployment is still building.",
226
240
  options: [
@@ -240,31 +254,39 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
240
254
  // Fall through to picker
241
255
  }
242
256
  }
243
- catch {
244
- clearInterval(ticker);
245
- spinner.stop("Error checking deployment status.");
257
+ catch (e) {
258
+ spinner?.stop("Error checking deployment status.");
259
+ if (!isAgent && e instanceof Error)
260
+ p.log.message(pc.dim(e.message));
246
261
  }
247
262
  }
248
263
  else {
249
264
  // ready — use it
250
- p.log.info(`Netlify deployment for ${commitLabel}${message}:\n → ${pc.cyan(commitDeploy.deploySslUrl)}`);
265
+ if (!isAgent)
266
+ p.log.info(`Netlify deployment for ${commitLabel}${message}:\n → ${pc.cyan(commitDeploy.deploySslUrl)}`);
251
267
  return commitDeploy.deploySslUrl;
252
268
  }
253
269
  }
254
270
  // Refresh deployments so the picker shows current states
255
271
  deploys = await getNetlifyDeploys(token, site.siteId, site.siteName, {
256
- branch: repoMatches ? (gitInfo.branch ?? undefined) : undefined,
272
+ branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
257
273
  });
258
274
  // Fallback: no commit deployment — let user pick from recent or paste manually
259
275
  if (deploys.length === 0) {
260
- p.log.warn("No deployments found. Paste a URL instead.");
276
+ if (!isAgent)
277
+ p.log.warn("No deployments found. Paste a URL instead.");
261
278
  return null;
262
279
  }
263
280
  const hasWorkingDeploy = deploys.some((d) => d.state !== "error");
264
281
  if (!hasWorkingDeploy) {
265
- p.log.warn("All recent deployments failed. Fix the build and push again, or paste a URL.");
282
+ if (!isAgent)
283
+ p.log.warn("All recent deployments failed. Fix the build and push again, or paste a URL.");
266
284
  return null;
267
285
  }
286
+ if (isAgent) {
287
+ const ready = deploys.find((d) => d.state === "ready");
288
+ return ready?.deploySslUrl ?? deploys[0]?.deploySslUrl ?? null;
289
+ }
268
290
  const maxBranch = Math.max(...deploys.map((d) => (d.branch ?? "unknown").length));
269
291
  const selected = await p.select({
270
292
  message: "No deployment found for current commit. Select one:",
@@ -296,6 +318,8 @@ export async function resolveNetlifyUrl(cwd, gitInfo, opts) {
296
318
  // Warn if the user picked a failed deployment
297
319
  const pickedDeploy = deploys.find((d) => d.deploySslUrl === selected);
298
320
  if (pickedDeploy && pickedDeploy.state === "error") {
321
+ if (isAgent)
322
+ return null;
299
323
  p.log.warn("This deployment failed — the URL may not load.");
300
324
  const confirm = await p.confirm({ message: "Use it anyway?" });
301
325
  if (p.isCancel(confirm) || !confirm)
@@ -2,6 +2,7 @@ import * as p from "@clack/prompts";
2
2
  import pc from "picocolors";
3
3
  import { execSync } from "child_process";
4
4
  import { parseGitRepo, getGitRoot } from "../lib/git.js";
5
+ import { isAgent } from "../lib/agent.js";
5
6
  import { ensureVercelCli, ensureVercelAuth, readLocalVercelProject, writeLocalVercelProject, getVercelProjectById, getVercelProjects, matchVercelProjectsByRepo, createVercelProject, getVercelDeployments, } from "../lib/vercel.js";
6
7
  // --- Auto-detection ---
7
8
  /**
@@ -40,18 +41,21 @@ async function autoDetectProject(cwd, gitInfo, token) {
40
41
  }
41
42
  }
42
43
  // --- Fetch all projects ---
43
- const spinner = p.spinner();
44
- spinner.start("Detecting Vercel project...");
44
+ const spinner = !isAgent ? p.spinner() : null;
45
+ spinner?.start("Detecting Vercel project...");
45
46
  let allProjects;
46
47
  try {
47
48
  allProjects = await getVercelProjects(token);
48
49
  }
49
- catch {
50
- spinner.stop("Could not fetch Vercel projects.");
50
+ catch (e) {
51
+ spinner?.stop("Could not fetch Vercel projects.");
52
+ if (e instanceof Error)
53
+ if (!isAgent)
54
+ p.log.message(pc.dim(e.message));
51
55
  return null;
52
56
  }
53
57
  if (allProjects.length === 0) {
54
- spinner.stop("No Vercel projects found.");
58
+ spinner?.stop("No Vercel projects found.");
55
59
  return null;
56
60
  }
57
61
  // --- Try exact match on git remote ---
@@ -59,7 +63,7 @@ async function autoDetectProject(cwd, gitInfo, token) {
59
63
  if (gitRepo) {
60
64
  const matches = matchVercelProjectsByRepo(allProjects, gitRepo.owner, gitRepo.name);
61
65
  if (matches.length === 1) {
62
- spinner.stop(`Detected Vercel project: ${pc.bold(matches[0].name)}`);
66
+ spinner?.stop(`Detected Vercel project: ${pc.bold(matches[0].name)}`);
63
67
  return cacheResult({
64
68
  teamId: matches[0].teamId,
65
69
  projectId: matches[0].id,
@@ -69,18 +73,20 @@ async function autoDetectProject(cwd, gitInfo, token) {
69
73
  });
70
74
  }
71
75
  if (matches.length > 1) {
72
- spinner.stop(`Found ${matches.length} Vercel projects for this repo.`);
76
+ spinner?.stop(`Found ${matches.length} Vercel projects for this repo.`);
73
77
  const picked = await pickFromList(allProjects);
74
78
  return picked ? cacheResult(picked) : null;
75
79
  }
76
80
  }
77
81
  // --- No match ---
78
- spinner.stop("Could not auto-detect Vercel project.");
82
+ spinner?.stop("Could not auto-detect Vercel project.");
79
83
  const canCreate = gitRepo && gitRepo.provider !== "unknown";
80
84
  const picked = await pickFromList(allProjects, canCreate ? { token, gitRepo } : undefined);
81
85
  return picked ? cacheResult(picked) : null;
82
86
  }
83
87
  async function pickFromList(projects, createCtx) {
88
+ if (isAgent)
89
+ return null;
84
90
  const maxName = Math.max(...projects.map((proj) => proj.name.length));
85
91
  const selected = await p.select({
86
92
  message: "Select a Vercel project",
@@ -111,6 +117,8 @@ async function pickFromList(projects, createCtx) {
111
117
  };
112
118
  }
113
119
  async function createProjectFlow(projects, ctx) {
120
+ if (isAgent)
121
+ return null;
114
122
  const { token, gitRepo } = ctx;
115
123
  // Pick team from unique teams
116
124
  const teams = [...new Map(projects.map((proj) => [proj.teamId, proj.teamName])).entries()];
@@ -182,14 +190,23 @@ async function createProjectFlow(projects, ctx) {
182
190
  }
183
191
  // --- Main resolve function ---
184
192
  export async function resolveVercelUrl(cwd, gitInfo, opts) {
185
- const cliOk = await ensureVercelCli((msg) => p.log.step(msg));
186
- if (!cliOk) {
187
- p.log.error("Failed to install Vercel CLI. Install manually: " + pc.cyan("npm install -g vercel"));
188
- return null;
193
+ const cli = await ensureVercelCli((msg) => { if (!isAgent)
194
+ p.log.step(msg); });
195
+ if (!cli.ok) {
196
+ if (!isAgent)
197
+ p.log.error("Could not install the Vercel CLI automatically.");
198
+ if (cli.error) {
199
+ if (!isAgent)
200
+ p.log.message(pc.dim(cli.error.trim()));
201
+ }
202
+ if (!isAgent)
203
+ p.log.info(`Paste the error above into your AI agent — it can fix this for you.\n\nThen re-run ${pc.cyan("inflight share")}.`);
204
+ process.exit(0);
189
205
  }
190
206
  const token = await ensureVercelAuth();
191
207
  if (!token) {
192
- p.log.error("Vercel login failed.");
208
+ if (!isAgent)
209
+ p.log.error("Vercel login failed.");
193
210
  return null;
194
211
  }
195
212
  const project = await autoDetectProject(cwd, gitInfo, token);
@@ -209,8 +226,8 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
209
226
  let commitDeploy = repoMatches ? deployments.find((d) => d.commitSha === commitSha) : undefined;
210
227
  // If just pushed, poll until the commit deployment appears
211
228
  if (!commitDeploy && repoMatches && opts?.justPushed) {
212
- const pollSpinner = p.spinner();
213
- pollSpinner.start("Waiting for Vercel deployment...");
229
+ const pollSpinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
230
+ pollSpinner?.start("Waiting for deploy");
214
231
  for (let i = 0; i < 30; i++) {
215
232
  await new Promise((r) => setTimeout(r, 2000));
216
233
  deployments = await getVercelDeployments(token, project.teamId, project.projectId, {
@@ -221,10 +238,10 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
221
238
  break;
222
239
  }
223
240
  if (commitDeploy) {
224
- pollSpinner.stop("Vercel deployment found!");
241
+ pollSpinner?.clear();
225
242
  }
226
243
  else {
227
- pollSpinner.stop("Vercel deployment is still building...");
244
+ pollSpinner?.stop("No deployment detected yet — Vercel may still be processing.");
228
245
  }
229
246
  }
230
247
  // If we found a commit-specific deployment, handle based on state
@@ -234,7 +251,8 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
234
251
  ? pc.dim(` — ${truncate(commitDeploy.commitMessage.split("\n")[0], 50)}`)
235
252
  : "";
236
253
  if (commitDeploy.state === "ERROR" || commitDeploy.state === "CANCELED") {
237
- p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
254
+ if (!isAgent)
255
+ p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
238
256
  commitDeploy = undefined;
239
257
  // Fall through to the picker
240
258
  }
@@ -242,24 +260,19 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
242
260
  commitDeploy.state === "QUEUED" ||
243
261
  commitDeploy.state === "INITIALIZING") {
244
262
  // Show what we're waiting on
245
- p.log.info(`Vercel deployment for ${commitLabel}${message} — ${pc.yellow(commitDeploy.state.toLowerCase())}`);
263
+ if (!isAgent)
264
+ p.log.info(`Vercel deployment for ${commitLabel}${message} — ${pc.yellow(commitDeploy.state.toLowerCase())}`);
246
265
  // Poll with a ticking timer for up to ~2 minutes
247
266
  const buildingUrl = commitDeploy.url;
248
267
  const startTime = Date.now();
249
268
  const maxWaitMs = 120_000;
250
- const pollIntervalMs = 5_000;
269
+ const pollIntervalMs = 3_000;
251
270
  let resolved = false;
252
- const spinner = p.spinner();
253
- const updateMessage = () => {
254
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
255
- spinner.message(`Waiting for Vercel to finish building... (${elapsed}s)`);
256
- };
257
- spinner.start("Waiting for Vercel to finish building... (0s) ");
258
- const ticker = setInterval(updateMessage, 1000);
271
+ const spinner = !isAgent ? p.spinner({ indicator: "timer" }) : null;
272
+ spinner?.start("Waiting for build");
259
273
  try {
260
274
  while (Date.now() - startTime < maxWaitMs) {
261
275
  await new Promise((r) => setTimeout(r, pollIntervalMs));
262
- updateMessage();
263
276
  const freshDeps = await getVercelDeployments(token, project.teamId, project.projectId, {
264
277
  branch: repoMatches ? gitInfo.branch ?? undefined : undefined,
265
278
  });
@@ -267,25 +280,22 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
267
280
  if (!fresh)
268
281
  break;
269
282
  if (fresh.state === "READY") {
270
- clearInterval(ticker);
271
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
272
- spinner.stop(`Vercel deployment ready! (${elapsed}s)`);
273
- p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(fresh.url)}`);
283
+ spinner?.stop("Vercel deployment ready!");
274
284
  return fresh.url;
275
285
  }
276
286
  if (fresh.state === "ERROR" || fresh.state === "CANCELED") {
277
- clearInterval(ticker);
278
- spinner.stop(pc.red("Vercel deployment failed."));
279
- p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
287
+ spinner?.stop(pc.red("Vercel deployment failed."));
288
+ if (!isAgent)
289
+ p.log.error(`Vercel deployment for ${commitLabel}${message} ${pc.red("failed")}.\n Fix the build and push again, or select a different deployment below.`);
280
290
  commitDeploy = undefined;
281
291
  resolved = true;
282
292
  break;
283
293
  }
284
294
  }
285
295
  if (!resolved) {
286
- clearInterval(ticker);
287
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
288
- spinner.stop(`Still building after ${elapsed}s.`);
296
+ if (isAgent)
297
+ return buildingUrl;
298
+ spinner?.stop("Still building...");
289
299
  const action = await p.select({
290
300
  message: "Vercel deployment is still building.",
291
301
  options: [
@@ -298,21 +308,25 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
298
308
  process.exit(0);
299
309
  }
300
310
  if (action === "continue") {
301
- p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(buildingUrl)} ${pc.yellow("(building)")}`);
311
+ if (!isAgent)
312
+ p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(buildingUrl)} ${pc.yellow("(building)")}`);
302
313
  return buildingUrl;
303
314
  }
304
315
  commitDeploy = undefined;
305
316
  // Fall through to picker
306
317
  }
307
318
  }
308
- catch {
309
- clearInterval(ticker);
310
- spinner.stop("Error checking deployment status.");
319
+ catch (e) {
320
+ spinner?.stop("Error checking deployment status.");
321
+ if (e instanceof Error)
322
+ if (!isAgent)
323
+ p.log.message(pc.dim(e.message));
311
324
  }
312
325
  }
313
326
  else {
314
327
  // READY or any other terminal state — use it
315
- p.log.info(`Vercel deployment for ${commitLabel}${message}:\n → ${pc.cyan(commitDeploy.url)}`);
328
+ if (!isAgent)
329
+ p.log.info(`Vercel deployment for ${commitLabel}${message}`);
316
330
  return commitDeploy.url;
317
331
  }
318
332
  }
@@ -322,14 +336,20 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
322
336
  });
323
337
  // Fallback: no commit deployment found — let user pick from recent or paste manually
324
338
  if (deployments.length === 0) {
325
- p.log.warn("No deployments found. Paste a URL instead.");
339
+ if (!isAgent)
340
+ p.log.warn("No deployments found. Paste a URL instead.");
326
341
  return null;
327
342
  }
328
343
  const hasWorkingDeployment = deployments.some((d) => d.state !== "ERROR" && d.state !== "CANCELED");
329
344
  if (!hasWorkingDeployment) {
330
- p.log.warn("All recent deployments failed. Fix the build and push again, or paste a URL.");
345
+ if (!isAgent)
346
+ p.log.warn("All recent deployments failed. Fix the build and push again, or paste a URL.");
331
347
  return null;
332
348
  }
349
+ if (isAgent) {
350
+ const ready = deployments.find((d) => d.state === "READY");
351
+ return ready?.url ?? deployments[0]?.url ?? null;
352
+ }
333
353
  const maxBranchPick = Math.max(...deployments.map((d) => (d.branch ?? "unknown").length));
334
354
  const selected = await p.select({
335
355
  message: "No deployment found for current commit. Select one:",
@@ -361,6 +381,8 @@ export async function resolveVercelUrl(cwd, gitInfo, opts) {
361
381
  // Warn if the user picked a failed deployment
362
382
  const pickedDeploy = deployments.find((d) => d.url === selected);
363
383
  if (pickedDeploy && (pickedDeploy.state === "ERROR" || pickedDeploy.state === "CANCELED")) {
384
+ if (isAgent)
385
+ return null;
364
386
  p.log.warn("This deployment failed — the URL may not load.");
365
387
  const confirm = await p.confirm({ message: "Use it anyway?" });
366
388
  if (p.isCancel(confirm) || !confirm)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inflight-cli",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Get feedback directly on your staging URL",
5
5
  "bin": {
6
6
  "inflight": "dist/index.js",
@@ -19,8 +19,8 @@
19
19
  "prepublishOnly": "npm run build"
20
20
  },
21
21
  "dependencies": {
22
- "@clack/core": "1.2.0",
23
- "@clack/prompts": "1.2.0",
22
+ "@clack/core": "^1.3.0",
23
+ "@clack/prompts": "^1.3.0",
24
24
  "commander": "12.1.0",
25
25
  "open": "11.0.0",
26
26
  "picocolors": "1.1.1",