@neocompose/cli 0.36.12 → 0.36.13

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.36.13] - 2026-08-22
4
+
5
+ ### Fixed
6
+
7
+ - Refresh short-lived Convex JWTs during long-running commands. History plan
8
+ polling and other multi-request operations now keep using the existing CLI
9
+ device session instead of failing when the initial 15-minute JWT expires.
10
+
3
11
  ## [0.36.12] - 2026-08-22
4
12
 
5
13
  ### Fixed
package/dist/neo.mjs CHANGED
@@ -105821,6 +105821,7 @@ var init_api = __esm({
105821
105821
 
105822
105822
  // src/convex.ts
105823
105823
  import { ConvexHttpClient } from "convex/browser";
105824
+ import { Buffer as Buffer2 } from "node:buffer";
105824
105825
  async function mintConvexJwt(apiBaseUrl) {
105825
105826
  const sessionToken = loadToken(apiBaseUrl);
105826
105827
  if (sessionToken === null) {
@@ -105878,9 +105879,10 @@ async function resolveConvexUrl(workspace) {
105878
105879
  }
105879
105880
  async function createConvexClient(workspace) {
105880
105881
  const convexUrl = await resolveConvexUrl(workspace);
105881
- const client = new ConvexHttpClient(convexUrl);
105882
- client.setAuth(await mintConvexJwt(workspace.config.apiBaseUrl));
105883
- return client;
105882
+ return await createAuthenticatedConvexClient(
105883
+ convexUrl,
105884
+ workspace.config.apiBaseUrl
105885
+ );
105884
105886
  }
105885
105887
  async function createConvexClientForApi(apiBaseUrl) {
105886
105888
  const fromEnv = process.env.NEXT_PUBLIC_CONVEX_URL;
@@ -105898,9 +105900,52 @@ async function createConvexClientForApi(apiBaseUrl) {
105898
105900
  });
105899
105901
  convexUrl = await discoverConvexUrlFromProjectsResponse(response);
105900
105902
  }
105901
- const client = new ConvexHttpClient(convexUrl);
105902
- client.setAuth(await mintConvexJwt(apiBaseUrl));
105903
- return client;
105903
+ return await createAuthenticatedConvexClient(convexUrl, apiBaseUrl);
105904
+ }
105905
+ async function createAuthenticatedConvexClient(convexUrl, apiBaseUrl) {
105906
+ const tokenCache = new ConvexJwtCache(apiBaseUrl);
105907
+ const initialToken = await tokenCache.getToken();
105908
+ const authenticatedFetch = async (input, init) => {
105909
+ const headers = new Headers(
105910
+ init?.headers ?? (input instanceof Request ? input.headers : void 0)
105911
+ );
105912
+ headers.set("Authorization", `Bearer ${await tokenCache.getToken()}`);
105913
+ return await fetch(input, { ...init, headers });
105914
+ };
105915
+ return new ConvexHttpClient(convexUrl, {
105916
+ auth: initialToken,
105917
+ fetch: authenticatedFetch
105918
+ });
105919
+ }
105920
+ function convexJwtExpiration(token) {
105921
+ const segments = token.split(".");
105922
+ if (segments.length !== 3) {
105923
+ throw new Error("Convex token response is not a three-segment JWT.");
105924
+ }
105925
+ let payloadJson;
105926
+ try {
105927
+ payloadJson = Buffer2.from(segments[1], "base64url").toString("utf8");
105928
+ } catch {
105929
+ throw new Error(
105930
+ "Convex token response has an invalid JWT payload encoding."
105931
+ );
105932
+ }
105933
+ let payload;
105934
+ try {
105935
+ payload = JSON.parse(payloadJson);
105936
+ } catch {
105937
+ throw new Error("Convex token response has an invalid JWT payload.");
105938
+ }
105939
+ if (typeof payload !== "object" || payload === null) {
105940
+ throw new Error("Convex token response has a non-object JWT payload.");
105941
+ }
105942
+ if (!("exp" in payload)) {
105943
+ throw new Error('Convex token response JWT is missing an "exp" claim.');
105944
+ }
105945
+ if (typeof payload.exp !== "number" || !Number.isFinite(payload.exp)) {
105946
+ throw new Error('Convex token response JWT has an invalid "exp" claim.');
105947
+ }
105948
+ return payload.exp * 1e3;
105904
105949
  }
105905
105950
  async function discoverConvexUrlFromProjectsResponse(response) {
105906
105951
  const headerUrl = response.headers.get("x-neo-convex-url");
@@ -105925,7 +105970,7 @@ function missingProjectsConvexUrlError() {
105925
105970
  "Could not discover the Convex deployment URL (/api/projects supplied neither a non-empty x-neo-convex-url header nor a non-empty convexUrl JSON field)."
105926
105971
  );
105927
105972
  }
105928
- var generatedApiModule, generatedApiValue, api2;
105973
+ var generatedApiModule, generatedApiValue, api2, CONVEX_JWT_REFRESH_LEEWAY_MS, ConvexJwtCache;
105929
105974
  var init_convex = __esm({
105930
105975
  "src/convex.ts"() {
105931
105976
  "use strict";
@@ -105938,6 +105983,38 @@ var init_convex = __esm({
105938
105983
  throw new Error("The generated Convex API module did not export api.");
105939
105984
  }
105940
105985
  api2 = generatedApiValue;
105986
+ CONVEX_JWT_REFRESH_LEEWAY_MS = 6e4;
105987
+ ConvexJwtCache = class {
105988
+ constructor(apiBaseUrl) {
105989
+ this.apiBaseUrl = apiBaseUrl;
105990
+ }
105991
+ apiBaseUrl;
105992
+ token = null;
105993
+ expiresAt = 0;
105994
+ refreshPromise = null;
105995
+ async getToken() {
105996
+ if (this.token !== null && Date.now() + CONVEX_JWT_REFRESH_LEEWAY_MS < this.expiresAt) {
105997
+ return this.token;
105998
+ }
105999
+ const pending = this.refreshPromise ?? this.refresh();
106000
+ this.refreshPromise = pending;
106001
+ try {
106002
+ return await pending;
106003
+ } finally {
106004
+ if (this.refreshPromise === pending) this.refreshPromise = null;
106005
+ }
106006
+ }
106007
+ async refresh() {
106008
+ const token = await mintConvexJwt(this.apiBaseUrl);
106009
+ const expiresAt = convexJwtExpiration(token);
106010
+ if (expiresAt <= Date.now()) {
106011
+ throw new Error("Convex token response contains an expired JWT.");
106012
+ }
106013
+ this.token = token;
106014
+ this.expiresAt = expiresAt;
106015
+ return token;
106016
+ }
106017
+ };
105941
106018
  }
105942
106019
  });
105943
106020
 
@@ -114668,7 +114745,7 @@ var init_registry2 = __esm({
114668
114745
  PROJECT_SCHEMA_CONTRACT = Object.freeze({
114669
114746
  formatVersion: 3,
114670
114747
  contractVersion: "3.14",
114671
- cliVersion: "0.36.12",
114748
+ cliVersion: "0.36.13",
114672
114749
  projectFileUploadBatchSize: 32,
114673
114750
  documentRecords: {
114674
114751
  member: {
@@ -121287,7 +121364,7 @@ There is no --keep-current: preserving current semantic state defines flatten.
121287
121364
  async function main() {
121288
121365
  const args = parseArgs(process.argv.slice(2));
121289
121366
  if (args.command === "--version") {
121290
- console.log("0.36.12");
121367
+ console.log("0.36.13");
121291
121368
  return;
121292
121369
  }
121293
121370
  if (args.command === null || args.command === "help" || args.command === "--help" || args.command === "-h") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neocompose/cli",
3
- "version": "0.36.12",
3
+ "version": "0.36.13",
4
4
  "description": "Neo Compose native project-source CLI with bidirectional sync.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@inquirer/prompts": "^8.5.2",
38
- "convex": "^1.40.0",
38
+ "convex": "^1.45.0",
39
39
  "ignore": "^7.0.6"
40
40
  },
41
41
  "devDependencies": {
@@ -9,7 +9,7 @@ description: >-
9
9
  `@neocompose/cli` or `node cli/bin/neo.mjs` in the neo-compose repository.
10
10
  ---
11
11
 
12
- <!-- reviewed-through-cli: 0.36.12 -->
12
+ <!-- reviewed-through-cli: 0.36.13 -->
13
13
 
14
14
  # Neo Compose CLI
15
15
 
@@ -83,7 +83,7 @@ wrappers.
83
83
  The marker near the top of `SKILL.md` must exactly match the package version:
84
84
 
85
85
  ```html
86
- <!-- reviewed-through-cli: 0.36.12 -->
86
+ <!-- reviewed-through-cli: 0.36.13 -->
87
87
  ```
88
88
 
89
89
  The quoted version above is checked too, so this instruction cannot go stale
@@ -146,7 +146,7 @@ Require the release login profile for release operations.
146
146
  History rewrites require project owner or admin authority. `prune` and
147
147
  `flatten` create immutable plans without deleting anything. `apply` starts the
148
148
  destructive run after the plan has sealed. Production also requires the exact
149
- scope confirmation shown by the plan and a recent configured backup. CLI
149
+ scope confirmation shown by the plan. CLI
150
150
  tokens issued before 0.36.12 do not hold the history write grant, so run
151
151
  `neo logout` followed by `neo login` once after upgrading.
152
152
 
@@ -167,7 +167,9 @@ targeted repair is handed instead of scanning a project to rediscover them.
167
167
  The Node CLI talks directly to authenticated Convex APIs through the
168
168
  session-gated CAS boundary. Tokens use the OS credential store when available
169
169
  and a protected file only as fallback. Use `NEO_COMPOSE_TOKEN` or
170
- `--token-stdin` in CI. The editor profile cannot publish releases; server
170
+ `--token-stdin` in CI. The CLI automatically re-mints its short-lived Convex
171
+ JWT before expiry during long-running commands; the stored device session
172
+ remains the credential. The editor profile cannot publish releases; server
171
173
  scopes remain the security boundary.
172
174
 
173
175
  `neo logout [--api <url>]` deletes the stored credential for exactly one API