@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
|
-
|
|
105882
|
-
|
|
105883
|
-
|
|
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
|
-
|
|
105902
|
-
|
|
105903
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
38
|
+
"convex": "^1.45.0",
|
|
39
39
|
"ignore": "^7.0.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|