@aexol/spectral 0.9.229 → 0.9.230

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.
@@ -3,12 +3,14 @@
3
3
  *
4
4
  * Contract (Batch 1 backend):
5
5
  * POST <backend>/api/machines/register
6
- * Headers: Authorization: Bearer <teamApiKey|userJwt>
6
+ * Headers: Authorization: Bearer <userJwt|userApiKey|teamApiKey>
7
7
  * Body: { name?: string, hostname: string, version: string, pid: number }
8
- * 200: { machineId: string, jwt: string, name: string, ownerId?: string }
8
+ * 200: { machineId: string, jwt: string, name: string, teamId?, ownerId?, visibility? }
9
9
  *
10
10
  * Auth priority:
11
11
  * - If `userJwt` is set → used as Bearer token (machine gets ownerId)
12
+ * - Else if `userApiKey` is set → used as Bearer token (machine is PRIVATE
13
+ * with ownerId derived from the key by the backend)
12
14
  * - Else → `apiKey` (team API key, machine gets no owner)
13
15
  *
14
16
  * Why this lives in its own module:
@@ -30,6 +32,12 @@ export interface RegistrationDeps {
30
32
  backendUrl: string;
31
33
  /** Team API key — used when `userJwt` is not set. */
32
34
  apiKey: string;
35
+ /**
36
+ * Optional user API key ("sk-aexol-user-"), pinned to a team but owned by a
37
+ * single user. Used when `userJwt` is not set; takes priority over `apiKey`.
38
+ * The backend registers the machine as PRIVATE with ownerId from the key.
39
+ */
40
+ userApiKey?: string;
33
41
  /** Optional user JWT from OAuth login. When set, used instead of `apiKey` for registration. */
34
42
  userJwt?: string;
35
43
  /** Display name override (from `--machine-name`). Falls back to hostname. */
@@ -1 +1 @@
1
- {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/relay/registration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAElF,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWvD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUvD;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,OAAO,CAI/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,gBAAgB,CAAC,CA+G3B"}
1
+ {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/relay/registration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAElF,MAAM,WAAW,gBAAgB;IAC/B,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWvD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUvD;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,OAAO,CAI/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,gBAAgB,CAAC,CA2I3B"}
@@ -3,12 +3,14 @@
3
3
  *
4
4
  * Contract (Batch 1 backend):
5
5
  * POST <backend>/api/machines/register
6
- * Headers: Authorization: Bearer <teamApiKey|userJwt>
6
+ * Headers: Authorization: Bearer <userJwt|userApiKey|teamApiKey>
7
7
  * Body: { name?: string, hostname: string, version: string, pid: number }
8
- * 200: { machineId: string, jwt: string, name: string, ownerId?: string }
8
+ * 200: { machineId: string, jwt: string, name: string, teamId?, ownerId?, visibility? }
9
9
  *
10
10
  * Auth priority:
11
11
  * - If `userJwt` is set → used as Bearer token (machine gets ownerId)
12
+ * - Else if `userApiKey` is set → used as Bearer token (machine is PRIVATE
13
+ * with ownerId derived from the key by the backend)
12
14
  * - Else → `apiKey` (team API key, machine gets no owner)
13
15
  *
14
16
  * Why this lives in its own module:
@@ -92,6 +94,7 @@ export async function ensureMachineRegistered(deps) {
92
94
  const skew = deps.expirySkewSec ?? 60;
93
95
  const existing = await loadMachine();
94
96
  const forceRefresh = deps.forceRefresh === true;
97
+ const usingUserApiKey = !deps.userJwt && !!deps.userApiKey;
95
98
  if (!forceRefresh && existing && !isJwtExpired(existing.machineJwt, skew)) {
96
99
  // The cached machine JWT is still valid. If the caller authenticated with a
97
100
  // user JWT (OAuth login) and the cached machine was previously owned by a
@@ -106,6 +109,21 @@ export async function ensureMachineRegistered(deps) {
106
109
  shouldReuse = false;
107
110
  }
108
111
  }
112
+ else if (usingUserApiKey) {
113
+ // A user API key carries no `sub` claim, so identity is tracked by the
114
+ // key itself: `ensureMachineRegistered` records the key used for the
115
+ // last registration in `machine.json` extra. A different key means a
116
+ // different user (the backend derives `ownerId` from the key), so the
117
+ // machine must be re-registered to transfer ownership and flip
118
+ // visibility to PRIVATE. A missing marker means the cached machine was
119
+ // registered with a team key — re-register so the backend can make it
120
+ // PRIVATE for this user.
121
+ const previousKey = existing.extra?.userApiKey;
122
+ if (previousKey !== deps.userApiKey) {
123
+ console.log("Machine registered under a different user API key, re-registering to transfer ownership...");
124
+ shouldReuse = false;
125
+ }
126
+ }
109
127
  if (shouldReuse) {
110
128
  return { reused: true, record: existing };
111
129
  }
@@ -114,6 +132,8 @@ export async function ensureMachineRegistered(deps) {
114
132
  console.warn("Re-registering machine: cached JWT was rejected by the backend (forceRefresh).");
115
133
  }
116
134
  const fetchImpl = deps.fetchImpl ?? fetch;
135
+ // Token priority: userJwt (OAuth) > userApiKey (user-pinned key) > team key.
136
+ const authToken = deps.userJwt ?? deps.userApiKey ?? deps.apiKey;
117
137
  const url = `${deps.backendUrl.replace(/\/$/, "")}/api/machines/register`;
118
138
  const body = {
119
139
  // Reuse the cached display name on JWT-expiry refresh so the backend's
@@ -129,7 +149,7 @@ export async function ensureMachineRegistered(deps) {
129
149
  res = await fetchImpl(url, {
130
150
  method: "POST",
131
151
  headers: {
132
- Authorization: `Bearer ${deps.userJwt ?? deps.apiKey}`,
152
+ Authorization: `Bearer ${authToken}`,
133
153
  "Content-Type": "application/json",
134
154
  },
135
155
  body: JSON.stringify(body),
@@ -163,6 +183,14 @@ export async function ensureMachineRegistered(deps) {
163
183
  typeof obj.name !== "string") {
164
184
  throw new Error(`Machine registration: backend response missing required fields (machineId, jwt, name)`);
165
185
  }
186
+ const extra = { ...(existing?.extra ?? {}) };
187
+ delete extra.userApiKey;
188
+ if (usingUserApiKey) {
189
+ // Record which key was used so a later run with a different user key
190
+ // (a different account) forces re-registration. machine.json is 0600,
191
+ // same as config.json where the key already lives.
192
+ extra.userApiKey = deps.userApiKey;
193
+ }
166
194
  const record = {
167
195
  machineId: obj.machineId,
168
196
  machineName: obj.name,
@@ -173,6 +201,7 @@ export async function ensureMachineRegistered(deps) {
173
201
  registeredAt: Date.now(),
174
202
  hostname: hostname(),
175
203
  version: deps.version,
204
+ extra: Object.keys(extra).length > 0 ? extra : undefined,
176
205
  };
177
206
  await saveMachine(record);
178
207
  return { reused: false, forced: forceRefresh, record };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.229",
3
+ "version": "0.9.230",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,