@lizard-build/cli 0.3.39 → 0.3.40

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 (44) hide show
  1. package/dist/commands/git.js +7 -6
  2. package/dist/commands/git.js.map +1 -1
  3. package/dist/commands/login.js +5 -1
  4. package/dist/commands/login.js.map +1 -1
  5. package/dist/commands/logs.js +4 -1
  6. package/dist/commands/logs.js.map +1 -1
  7. package/dist/commands/redeploy.js +8 -6
  8. package/dist/commands/redeploy.js.map +1 -1
  9. package/dist/commands/secrets.js +5 -0
  10. package/dist/commands/secrets.js.map +1 -1
  11. package/dist/commands/ssh.js +24 -18
  12. package/dist/commands/ssh.js.map +1 -1
  13. package/dist/commands/up.js +26 -16
  14. package/dist/commands/up.js.map +1 -1
  15. package/dist/commands/upgrade.js +20 -1
  16. package/dist/commands/upgrade.js.map +1 -1
  17. package/dist/index.js +10 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/lib/api.d.ts +8 -2
  20. package/dist/lib/api.js +28 -5
  21. package/dist/lib/api.js.map +1 -1
  22. package/dist/lib/auth.d.ts +8 -1
  23. package/dist/lib/auth.js +33 -3
  24. package/dist/lib/auth.js.map +1 -1
  25. package/dist/lib/config.js +14 -2
  26. package/dist/lib/config.js.map +1 -1
  27. package/dist/lib/updater.d.ts +22 -4
  28. package/dist/lib/updater.js +137 -49
  29. package/dist/lib/updater.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/commands/git.ts +11 -6
  32. package/src/commands/login.ts +5 -0
  33. package/src/commands/logs.ts +12 -5
  34. package/src/commands/redeploy.ts +12 -6
  35. package/src/commands/secrets.ts +8 -0
  36. package/src/commands/ssh.ts +24 -25
  37. package/src/commands/up.ts +26 -17
  38. package/src/commands/upgrade.ts +21 -1
  39. package/src/index.ts +12 -1
  40. package/src/lib/api.ts +25 -4
  41. package/src/lib/auth.ts +33 -3
  42. package/src/lib/config.ts +13 -2
  43. package/src/lib/updater.ts +130 -45
  44. package/test/unit/config.test.ts +29 -0
@@ -1 +1 @@
1
- {"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/lib/updater.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"updater.js","sourceRoot":"","sources":["../../src/lib/updater.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/cli",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
4
4
  "description": "Lizard CLI — deploy and manage apps on Lizard",
5
5
  "type": "module",
6
6
  "bin": {
@@ -112,22 +112,27 @@ export function registerGit(program: Command) {
112
112
  });
113
113
  spinner.succeed(`Branch set to ${chalk.cyan(branch)}`);
114
114
 
115
- // Trigger redeploy
115
+ // Trigger redeploy — the endpoint returns the freshly created Build
116
+ // record; using its id avoids racing against a previous build.
116
117
  const deploySpinner = ora("Starting redeploy...").start();
117
- await api.post(withScope(`/api/apps/${serviceId}/redeploy`, scope));
118
+ const build = await api.post<{ id?: string }>(
119
+ withScope(`/api/apps/${serviceId}/redeploy`, scope),
120
+ undefined,
121
+ { "X-Deploy-Source": "cli" },
122
+ );
118
123
  deploySpinner.stop();
119
124
 
120
125
  if (opts.detach || isJSONMode()) {
121
- if (isJSONMode()) printJSON({ id: serviceId, branch, status: "deploying" });
126
+ if (isJSONMode()) printJSON({ id: serviceId, buildId: build?.id, branch, status: "deploying" });
122
127
  else success(`Redeploy started on branch ${chalk.cyan(branch)}`);
123
128
  return;
124
129
  }
125
130
 
126
131
  info(`Redeploying ${chalk.bold(serviceName)} on ${chalk.cyan(branch)}...`);
127
132
 
128
- // Wait for build to appear
129
- let buildId: string | null = null;
130
- for (let i = 0; i < 30; i++) {
133
+ let buildId: string | null = build?.id ?? null;
134
+ // Fallback for older servers that respond without a Build record.
135
+ for (let i = 0; !buildId && i < 30; i++) {
131
136
  await new Promise((r) => setTimeout(r, 2000));
132
137
  try {
133
138
  const app = await api.get<{ builds?: Array<{ id: string; status: string }> }>(`/api/apps/${serviceId}`);
@@ -4,6 +4,7 @@ import { Command } from "commander";
4
4
  import {
5
5
  saveCredentials,
6
6
  openURL,
7
+ jwtExpiryMs,
7
8
  type Credentials,
8
9
  } from "../lib/auth.js";
9
10
  import { getBaseURL } from "../lib/api.js";
@@ -86,9 +87,11 @@ export async function performLogin(): Promise<Credentials> {
86
87
 
87
88
  if (result.status === "complete" && result.accessToken && result.user) {
88
89
  spinner.stop();
90
+ const expMs = jwtExpiryMs(result.accessToken);
89
91
  const creds: Credentials = {
90
92
  accessToken: result.accessToken,
91
93
  refreshToken: result.refreshToken,
94
+ expiresAt: expMs ? new Date(expMs).toISOString() : undefined,
92
95
  userId: result.user.id,
93
96
  username: result.user.username,
94
97
  email: result.user.email,
@@ -139,8 +142,10 @@ export function registerLogin(program: Command) {
139
142
  });
140
143
  if (!res.ok) throw new Error("Invalid token");
141
144
  const user = (await res.json()) as any;
145
+ const expMs = jwtExpiryMs(token);
142
146
  saveCredentials({
143
147
  accessToken: token,
148
+ expiresAt: expMs ? new Date(expMs).toISOString() : undefined,
144
149
  userId: user.id,
145
150
  username: user.username,
146
151
  email: user.email,
@@ -347,12 +347,19 @@ async function showBuildLogs(
347
347
  info(chalk.dim(`Build ${buildId}\n`));
348
348
 
349
349
  if (tailN !== undefined) {
350
+ // Snapshot semantics: the server replays history immediately; if the
351
+ // build is still running the stream would otherwise follow it forever.
352
+ // Stop after 3s without new events and print what we have.
350
353
  const lines: string[] = [];
351
- await streamSSE(`/api/builds/${buildId}/logs`, (event, data) => {
352
- if (event === "done" || event === "error") return false;
353
- lines.push(data);
354
- return true;
355
- });
354
+ await streamSSE(
355
+ `/api/builds/${buildId}/logs`,
356
+ (event, data) => {
357
+ if (event === "done" || event === "error") return false;
358
+ lines.push(data);
359
+ return true;
360
+ },
361
+ { idleTimeoutMs: 3000 },
362
+ );
356
363
  for (const line of lines.slice(-tailN)) printLogLine(line);
357
364
  return;
358
365
  }
@@ -54,24 +54,30 @@ export function registerRedeploy(program: Command) {
54
54
  }
55
55
 
56
56
  const spinner = ora("Starting redeploy...").start();
57
- await api.post(`/api/apps/${id}/redeploy`);
57
+ // The endpoint pre-creates and returns the Build record — use its id
58
+ // instead of polling builds[0], which races against a previous build.
59
+ const build = await api.post<{ id?: string; status?: string }>(
60
+ `/api/apps/${id}/redeploy`,
61
+ undefined,
62
+ { "X-Deploy-Source": "cli" },
63
+ );
58
64
  spinner.stop();
59
65
 
60
66
  if (opts.detach || isJSONMode()) {
61
67
  if (isJSONMode()) {
62
- printJSON({ id, status: "deploying" });
68
+ printJSON({ id, buildId: build?.id, status: "deploying" });
63
69
  } else {
64
70
  success("Redeploy started");
65
- info(chalk.dim(` Check status: lizard deploy status ${id}`));
71
+ info(chalk.dim(` Check status: lizard up status ${id}`));
66
72
  }
67
73
  return;
68
74
  }
69
75
 
70
76
  info("Redeploying...");
71
77
 
72
- // Poll for build
73
- let buildId: string | null = null;
74
- for (let i = 0; i < 30; i++) {
78
+ let buildId: string | null = build?.id ?? null;
79
+ // Fallback for older servers that respond without a Build record.
80
+ for (let i = 0; !buildId && i < 30; i++) {
75
81
  await new Promise((r) => setTimeout(r, 2000));
76
82
  try {
77
83
  const app = await api.get<{ builds?: Array<{ id: string; status: string }> }>(
@@ -278,6 +278,14 @@ Notes:
278
278
  .option("-s, --service <name>", "Service to scope to (overrides linked)")
279
279
  .option("-p, --project <id>", "Project to scope to")
280
280
  .action(async (opts, sub) => {
281
+ // Reading a TTY stdin waits for Ctrl+D forever — fail fast instead.
282
+ if (process.stdin.isTTY) {
283
+ throw new Error(
284
+ "secrets import reads KEY=value lines from stdin. Pipe a file:\n" +
285
+ " lizard secrets import < .env",
286
+ );
287
+ }
288
+
281
289
  const inherited = sub.parent?.opts() || {};
282
290
  const scope = await resolveScope(
283
291
  opts.project ?? inherited.project,
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import * as p from "@clack/prompts";
3
3
  import { Command } from "commander";
4
4
  import { api, getBaseURL, streamSSE, withScope } from "../lib/api.js";
5
- import { resolveProjectScope } from "../lib/resolve.js";
5
+ import { resolveProjectScope, resolveService } from "../lib/resolve.js";
6
6
  import { error, isTTY } from "../lib/format.js";
7
7
  import { getToken } from "../lib/auth.js";
8
8
  import * as https from "node:https";
@@ -22,10 +22,19 @@ Examples:
22
22
  lizard ssh -s my-app -- bash -c "ps aux | head"`)
23
23
  .action(async (cmdArgs: string[], opts) => {
24
24
  const { projectId, scope } = await resolveProjectScope(opts.project);
25
- let serviceId = opts.service as string | undefined;
26
-
27
- // Resolve service interactively if not given
28
- if (!serviceId) {
25
+ let serviceId: string | undefined;
26
+
27
+ if (opts.service) {
28
+ // Resolve by name or ID through the shared resolver — guessing
29
+ // "looks like an ID" by length breaks for long service names.
30
+ const svc = await resolveService(projectId, opts.service);
31
+ if (svc.kind !== "app") {
32
+ error(`"${svc.name}" is an addon — ssh works only for app services.`);
33
+ process.exit(1);
34
+ }
35
+ serviceId = svc.id;
36
+ } else {
37
+ // Resolve service interactively if not given
29
38
  const data = await api.get<{ apps: Array<{ id: string; name: string; status: string }> }>(
30
39
  withScope(`/api/projects/${projectId}/services`, scope),
31
40
  );
@@ -49,17 +58,6 @@ Examples:
49
58
  }
50
59
  }
51
60
 
52
- // Resolve service ID if a name was given (lookup by name)
53
- if (serviceId && !serviceId.match(/^[A-Za-z0-9_-]{20,}$/)) {
54
- const data = await api.get<{ apps: Array<{ id: string; name: string; serviceName?: string }> }>(
55
- withScope(`/api/projects/${projectId}/services`, scope),
56
- );
57
- const match = (data.apps || []).find(
58
- (a) => a.name === serviceId || a.serviceName === serviceId || a.id === serviceId,
59
- );
60
- if (match) serviceId = match.id;
61
- }
62
-
63
61
  if (cmdArgs.length === 0) {
64
62
  error("No command given. Usage: lizard ssh -s <service> -- <cmd> [args...]");
65
63
  process.exit(1);
@@ -71,29 +69,29 @@ Examples:
71
69
  const cmd = cmdArgs.map(shellQuote).join(" ");
72
70
  process.stdout.write(chalk.dim(`$ ${cmd}\n`));
73
71
 
74
- let exitCode = 0;
75
-
76
- await execStream(serviceId!, cmd, (stream, line) => {
72
+ const exitCode = await execStream(serviceId!, cmd, (stream, line) => {
77
73
  if (stream === "stderr") {
78
74
  process.stderr.write(line + "\n");
79
75
  } else {
80
76
  process.stdout.write(line + "\n");
81
77
  }
82
- }, (code) => {
83
- exitCode = code;
84
78
  });
85
79
 
86
80
  process.exit(exitCode);
87
81
  });
88
82
  }
89
83
 
84
+ /** Run a command on the VM, streaming output. Resolves with the exit code:
85
+ * the remote command's code from the `exit` event, or 1 when the server
86
+ * reported an `error` event without one. */
90
87
  function execStream(
91
88
  appId: string,
92
89
  cmd: string,
93
90
  onLine: (stream: string, line: string) => void,
94
- onExit: (code: number) => void,
95
- ): Promise<void> {
91
+ ): Promise<number> {
96
92
  return new Promise((resolve, reject) => {
93
+ let exitCode: number | null = null;
94
+ let sawError = false;
97
95
  const baseURL = getBaseURL();
98
96
  const url = new URL(`${baseURL}/api/apps/${appId}/exec`);
99
97
  const token = getToken();
@@ -141,8 +139,9 @@ function execStream(
141
139
  } else if (trimmed.startsWith("data:")) {
142
140
  const data = trimmed.slice(5).trimStart();
143
141
  if (currentEvent === "exit") {
144
- try { onExit(JSON.parse(data).exitCode ?? 0); } catch {}
142
+ try { exitCode = JSON.parse(data).exitCode ?? 0; } catch {}
145
143
  } else if (currentEvent === "error") {
144
+ sawError = true;
146
145
  error(data);
147
146
  } else {
148
147
  try {
@@ -156,7 +155,7 @@ function execStream(
156
155
  }
157
156
  });
158
157
 
159
- res.on("end", resolve);
158
+ res.on("end", () => resolve(exitCode ?? (sawError ? 1 : 0)));
160
159
  res.on("error", reject);
161
160
  },
162
161
  );
@@ -198,7 +198,7 @@ async function deployFromLocal(args: {
198
198
  : success(`Deploy started ${chalk.dim(`lizard up status ${newApp.id}`)}`);
199
199
  return;
200
200
  }
201
- await streamBuildLogs(newApp.id, args.opts.ci);
201
+ await streamBuildLogs(newApp.id, args.opts.ci, newApp.buildId);
202
202
  }
203
203
 
204
204
  // ── helpers ──────────────────────────────────────────────────────────────────
@@ -260,12 +260,16 @@ const EXCLUDE_DIRS = new Set([
260
260
  ".turbo",
261
261
  ".vercel",
262
262
  ]);
263
- const EXCLUDE_EXT = new Set([".pyc", ".pyo", ".log", ".DS_Store"]);
263
+ const EXCLUDE_EXT = new Set([".pyc", ".pyo", ".log"]);
264
+ // Matched by full name — `path.extname(".DS_Store")` is "" (dotfile), so
265
+ // extension matching never catches these.
266
+ const EXCLUDE_FILES = new Set([".DS_Store"]);
264
267
 
265
268
  function collectFilesManually(root: string, dir: string): string[] {
266
269
  const results: string[] = [];
267
270
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
268
271
  if (EXCLUDE_DIRS.has(entry.name)) continue;
272
+ if (EXCLUDE_FILES.has(entry.name)) continue;
269
273
  if (EXCLUDE_EXT.has(path.extname(entry.name))) continue;
270
274
  const full = path.join(dir, entry.name);
271
275
  if (entry.isDirectory()) results.push(...collectFilesManually(root, full));
@@ -324,23 +328,28 @@ function prompt(question: string): Promise<string> {
324
328
  });
325
329
  }
326
330
 
327
- async function streamBuildLogs(appId: string, ciMode: boolean = false) {
328
- const spinner = ora("Waiting for build...").start();
329
- let buildId: string | null = null;
330
- for (let i = 0; i < 30; i++) {
331
- await sleep(2000);
332
- try {
333
- const app = await api.get<App>(`/api/apps/${appId}`);
334
- if (app.builds?.length) {
335
- const latest = app.builds[0];
336
- if (["building", "deploying", "running", "failed"].includes(latest.status)) {
337
- buildId = latest.id;
338
- break;
331
+ async function streamBuildLogs(appId: string, ciMode: boolean = false, knownBuildId?: string) {
332
+ // Prefer the buildId returned by the upload/redeploy response — polling
333
+ // builds[0] races against a still-running previous build and can attach
334
+ // to the wrong one.
335
+ let buildId: string | null = knownBuildId ?? null;
336
+ if (!buildId) {
337
+ const spinner = ora("Waiting for build...").start();
338
+ for (let i = 0; i < 30; i++) {
339
+ await sleep(2000);
340
+ try {
341
+ const app = await api.get<App>(`/api/apps/${appId}`);
342
+ if (app.builds?.length) {
343
+ const latest = app.builds[0];
344
+ if (["building", "deploying", "running", "failed"].includes(latest.status)) {
345
+ buildId = latest.id;
346
+ break;
347
+ }
339
348
  }
340
- }
341
- } catch {}
349
+ } catch {}
350
+ }
351
+ spinner.stop();
342
352
  }
343
- spinner.stop();
344
353
  if (!buildId) {
345
354
  info(chalk.dim("No build found. Check `lizard up status <id>`."));
346
355
  return;
@@ -1,7 +1,7 @@
1
1
  import chalk from "chalk";
2
2
  import { Command } from "commander";
3
3
  import { info, success, isJSONMode, printJSON } from "../lib/format.js";
4
- import { CURRENT_VERSION, getLatestVersion, selfUpdate } from "../lib/updater.js";
4
+ import { CURRENT_VERSION, getLatestVersion, selfUpdate, isStandaloneBinary } from "../lib/updater.js";
5
5
 
6
6
  export function registerUpgrade(program: Command) {
7
7
  program
@@ -67,6 +67,26 @@ export function registerUpgrade(program: Command) {
67
67
  return;
68
68
  }
69
69
 
70
+ // npm install — self-replacing process.execPath would overwrite the
71
+ // user's node binary. Point at npm instead.
72
+ if (!isStandaloneBinary()) {
73
+ if (isJSONMode()) {
74
+ printJSON({
75
+ currentVersion: CURRENT_VERSION,
76
+ latestVersion: latest,
77
+ updateAvailable: true,
78
+ upgraded: false,
79
+ method: "npm",
80
+ hint: "npm install -g @lizard-build/cli@latest",
81
+ });
82
+ return;
83
+ }
84
+ info(`Update available: v${CURRENT_VERSION} → ${chalk.green("v" + latest)}`);
85
+ info(`This copy was installed via npm. Upgrade with:`);
86
+ info(` ${chalk.cyan("npm install -g @lizard-build/cli@latest")}`);
87
+ return;
88
+ }
89
+
70
90
  info(`Upgrading v${CURRENT_VERSION} → ${chalk.green("v" + latest)}...`);
71
91
 
72
92
  try {
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@ import chalk from "chalk";
5
5
  import { setJSONMode, isJSONMode, error } from "./lib/format.js";
6
6
  import { requireAuth, isLoggedIn } from "./lib/auth.js";
7
7
  import { setBaseURL, setAccessToken, APIError } from "./lib/api.js";
8
- import { checkForUpdateInBackground, CURRENT_VERSION } from "./lib/updater.js";
8
+ import { checkForUpdateInBackground, runBackgroundUpdate, CURRENT_VERSION } from "./lib/updater.js";
9
9
 
10
10
  const BANNER = chalk.rgb(16, 185, 129)(
11
11
  [
@@ -223,6 +223,13 @@ function dumpCommand(cmd: Command): any {
223
223
  }
224
224
 
225
225
  async function main() {
226
+ // Hidden entry point: detached child spawned by checkForUpdateInBackground.
227
+ // Handled before commander so it never shows up in help or telemetry.
228
+ if (process.argv.includes("__lizard-update")) {
229
+ await runBackgroundUpdate();
230
+ process.exit(0);
231
+ }
232
+
226
233
  // Set JSON mode from argv *before* parseAsync so the catch block below
227
234
  // honors --json even when commander rejects before our preAction hook
228
235
  // fires (e.g. unknown command, malformed global flag). Non-TTY auto-mode
@@ -280,10 +287,14 @@ async function main() {
280
287
  );
281
288
  } else {
282
289
  error(msg);
290
+ if (status === 401) {
291
+ process.stderr.write("Run `lizard login` to re-authenticate.\n");
292
+ }
283
293
  }
284
294
 
285
295
  // Exit codes derived from APIError.status (or tagged error codes), not message text
286
296
  const isAuth = status === 401 || status === 403 || code === "NOT_AUTHENTICATED";
297
+
287
298
  const isNotFound = status === 404;
288
299
  const isTimeout =
289
300
  status === 408 ||
package/src/lib/api.ts CHANGED
@@ -129,10 +129,15 @@ export const api = {
129
129
  delete: <T = any>(path: string) => request<T>("DELETE", path),
130
130
  };
131
131
 
132
- /** Stream SSE and call handler for each data line. Return false to stop. */
132
+ /** Stream SSE and call handler for each data line. Return false to stop.
133
+ *
134
+ * `opts.idleTimeoutMs` — stop (resolve) when no *event* arrives for that
135
+ * long. Heartbeat comments don't reset the timer. Used by `--tail`-style
136
+ * snapshot reads that must not follow a live stream forever. */
133
137
  export function streamSSE(
134
138
  path: string,
135
139
  handler: (event: string, data: string) => boolean | void,
140
+ opts: { idleTimeoutMs?: number } = {},
136
141
  ): Promise<void> {
137
142
  return new Promise((resolve, reject) => {
138
143
  const url = new URL(baseURL + path);
@@ -155,6 +160,19 @@ export function streamSSE(
155
160
  return;
156
161
  }
157
162
 
163
+ let idleTimer: NodeJS.Timeout | undefined;
164
+ const finish = () => {
165
+ if (idleTimer) clearTimeout(idleTimer);
166
+ req.destroy();
167
+ resolve();
168
+ };
169
+ const armIdleTimer = () => {
170
+ if (!opts.idleTimeoutMs) return;
171
+ if (idleTimer) clearTimeout(idleTimer);
172
+ idleTimer = setTimeout(finish, opts.idleTimeoutMs);
173
+ };
174
+ armIdleTimer();
175
+
158
176
  let buffer = "";
159
177
  let currentEvent = "";
160
178
  let currentData = "";
@@ -169,10 +187,10 @@ export function streamSSE(
169
187
  const trimmed = line.replace(/\r$/, "");
170
188
  if (trimmed === "") {
171
189
  if (currentData) {
190
+ armIdleTimer();
172
191
  const cont = handler(currentEvent, currentData);
173
192
  if (cont === false) {
174
- req.destroy();
175
- resolve();
193
+ finish();
176
194
  return;
177
195
  }
178
196
  }
@@ -186,7 +204,10 @@ export function streamSSE(
186
204
  }
187
205
  });
188
206
 
189
- res.on("end", resolve);
207
+ res.on("end", () => {
208
+ if (idleTimer) clearTimeout(idleTimer);
209
+ resolve();
210
+ });
190
211
  res.on("error", reject);
191
212
  },
192
213
  );
package/src/lib/auth.ts CHANGED
@@ -38,7 +38,32 @@ function isTTY(): boolean {
38
38
  }
39
39
 
40
40
  /**
41
- * Ensure the user is authenticated. If not logged in and TTY, auto-login.
41
+ * Expiry of a JWT in epoch-ms, decoded from the `exp` claim. Returns null
42
+ * for opaque/undecodable tokens — those are treated as valid and left for
43
+ * the server to reject.
44
+ */
45
+ export function jwtExpiryMs(token: string): number | null {
46
+ try {
47
+ const payload = token.split(".")[1];
48
+ if (!payload) return null;
49
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
50
+ return typeof decoded.exp === "number" ? decoded.exp * 1000 : null;
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function isExpired(creds: Credentials): boolean {
57
+ const expMs =
58
+ jwtExpiryMs(creds.accessToken) ??
59
+ (creds.expiresAt ? Date.parse(creds.expiresAt) : null);
60
+ if (expMs === null || Number.isNaN(expMs)) return false;
61
+ return Date.now() > expMs - 60_000; // 60s margin
62
+ }
63
+
64
+ /**
65
+ * Ensure the user is authenticated. If not logged in (or the saved token
66
+ * has expired — there is no refresh endpoint) and TTY, auto-login.
42
67
  * Returns credentials or throws.
43
68
  */
44
69
  export async function requireAuth(): Promise<Credentials> {
@@ -51,16 +76,21 @@ export async function requireAuth(): Promise<Credentials> {
51
76
  }
52
77
 
53
78
  const creds = loadCredentials();
54
- if (creds) return creds;
79
+ if (creds && !isExpired(creds)) return creds;
55
80
 
56
81
  if (!isTTY()) {
57
82
  const err = new Error(
58
- "Not authenticated. Set LIZARD_TOKEN or run `lizard login` first.",
83
+ creds
84
+ ? "Session expired. Run `lizard login` again or set LIZARD_TOKEN."
85
+ : "Not authenticated. Set LIZARD_TOKEN or run `lizard login` first.",
59
86
  ) as Error & { code: string };
60
87
  err.code = "NOT_AUTHENTICATED";
61
88
  throw err;
62
89
  }
63
90
 
91
+ if (creds) {
92
+ process.stderr.write("Session expired — logging in again...\n");
93
+ }
64
94
  const { performLogin } = await import("../commands/login.js");
65
95
  return performLogin();
66
96
  }
package/src/lib/config.ts CHANGED
@@ -96,7 +96,13 @@ export function updateProjectLink(
96
96
  ) {
97
97
  const existing = getProjectLink(cwd);
98
98
  if (!existing) return;
99
- setProjectLink({ ...existing, ...patch }, cwd);
99
+ const merged = { ...existing, ...patch };
100
+ // An explicit serviceId/serviceName in the patch (including `undefined`,
101
+ // i.e. "clear it") must override the legacy mirror too — otherwise
102
+ // setProjectLink resurrects the old value from appId/appName.
103
+ if ("serviceId" in patch) merged.appId = patch.serviceId;
104
+ if ("serviceName" in patch) merged.appName = patch.serviceName;
105
+ setProjectLink(merged, cwd);
100
106
  }
101
107
 
102
108
  export function clearProjectLink(cwd: string = process.cwd()) {
@@ -120,8 +126,13 @@ export async function resolveProjectId(flagValue?: string): Promise<string> {
120
126
  }
121
127
  const { api } = await import("./api.js");
122
128
  const projects = await api.get<Array<{ id: string; name: string; slug: string }>>("/api/projects");
129
+ // Case-insensitive, matching `lizard link` / `lizard init` behaviour.
130
+ const lower = flagValue.toLowerCase();
123
131
  const match = projects.find(
124
- (p) => p.id === flagValue || p.slug === flagValue || p.name === flagValue,
132
+ (p) =>
133
+ p.id.toLowerCase() === lower ||
134
+ p.slug?.toLowerCase() === lower ||
135
+ p.name?.toLowerCase() === lower,
125
136
  );
126
137
  if (!match) throw new Error(`Project "${flagValue}" not found.`);
127
138
  return match.id;