syndai 0.4.0 → 0.4.1
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 +6 -0
- package/package.json +1 -1
- package/src/auth.js +9 -0
- package/src/http.js +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the `syndai` npm package. Versions follow semver.
|
|
4
4
|
|
|
5
|
+
## 0.4.1
|
|
6
|
+
|
|
7
|
+
- A running `watch`, `change` or MCP server no longer stops when `login` or a scope
|
|
8
|
+
approval in another terminal replaces its key. On a 401 the CLI reads the stored key
|
|
9
|
+
again once, and retries with it if it changed. `SYNDAI_API_KEY` is never re-read.
|
|
10
|
+
|
|
5
11
|
## 0.4.0
|
|
6
12
|
|
|
7
13
|
Every published API operation, and a project's recurring runs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "syndai",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "The Syndai owner CLI: say what to change, and Syndai builds it, checks it and puts it live. Also installs the Syndai MCP server into Claude Code, Cursor and Codex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/auth.js
CHANGED
|
@@ -69,6 +69,15 @@ export function createContext(io, flags, { stepUp = true } = {}) {
|
|
|
69
69
|
getKey: async () => credential().apiKey,
|
|
70
70
|
io,
|
|
71
71
|
onMissingScope: stepUp ? (scope) => stepUpScope(ctx, scope) : undefined,
|
|
72
|
+
onUnauthorized: async () => {
|
|
73
|
+
// A login or step-up in another process saves a new key, then revokes this one.
|
|
74
|
+
if (!cached || cached.source === "SYNDAI_API_KEY") return false;
|
|
75
|
+
const fresh = storedCredential(io, paths);
|
|
76
|
+
if (!fresh || fresh.apiKey === cached.apiKey) return false;
|
|
77
|
+
cached = fresh;
|
|
78
|
+
me = undefined;
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
72
81
|
});
|
|
73
82
|
return ctx;
|
|
74
83
|
}
|
package/src/http.js
CHANGED
|
@@ -39,11 +39,13 @@ export function interrupted() {
|
|
|
39
39
|
/**
|
|
40
40
|
* `onMissingScope(scope)` is asked once per request when a 403 names the scope it needs
|
|
41
41
|
* (`required_scope`); when it answers true (a new key was approved), the request is sent
|
|
42
|
-
* again with the new key.
|
|
42
|
+
* again with the new key. `onUnauthorized()` is asked once per request on a 401; when it
|
|
43
|
+
* answers true (another `syndai` process rotated the stored key), the request is sent again
|
|
44
|
+
* with the new key, so a long watch or MCP server outlives a login elsewhere.
|
|
43
45
|
*
|
|
44
|
-
* @param {{baseUrl: string, getKey: () => Promise<string>, io: object, onMissingScope?: (scope: string) => Promise<boolean>}} options
|
|
46
|
+
* @param {{baseUrl: string, getKey: () => Promise<string>, io: object, onMissingScope?: (scope: string) => Promise<boolean>, onUnauthorized?: () => Promise<boolean>}} options
|
|
45
47
|
*/
|
|
46
|
-
export function createClient({ baseUrl, getKey, io, onMissingScope }) {
|
|
48
|
+
export function createClient({ baseUrl, getKey, io, onMissingScope, onUnauthorized }) {
|
|
47
49
|
/** Call a named route from `routes.js`. */
|
|
48
50
|
function call(name, { params, ...opts } = {}) {
|
|
49
51
|
const [method, path] = route(name, params);
|
|
@@ -66,6 +68,7 @@ export function createClient({ baseUrl, getKey, io, onMissingScope }) {
|
|
|
66
68
|
const payload = body === undefined ? undefined : JSON.stringify(body);
|
|
67
69
|
|
|
68
70
|
let steppedUp = false;
|
|
71
|
+
let reloaded = false;
|
|
69
72
|
for (let attempt = 1; ; attempt++) {
|
|
70
73
|
let res;
|
|
71
74
|
let parsed;
|
|
@@ -95,6 +98,14 @@ export function createClient({ baseUrl, getKey, io, onMissingScope }) {
|
|
|
95
98
|
continue;
|
|
96
99
|
}
|
|
97
100
|
}
|
|
101
|
+
if (res.status === 401 && auth && onUnauthorized && !reloaded) {
|
|
102
|
+
reloaded = true;
|
|
103
|
+
if (await onUnauthorized()) {
|
|
104
|
+
headers.authorization = `Bearer ${await getKey()}`;
|
|
105
|
+
attempt = 0;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
98
109
|
const transient =
|
|
99
110
|
problem !== null &&
|
|
100
111
|
(res.status >= 500 ||
|