@schuttdev/kon 0.3.3 → 0.3.5

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 (2) hide show
  1. package/dist/index.js +110 -100
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -234,27 +234,37 @@ async function ensureDispatcher() {
234
234
  }
235
235
  return _dispatcher;
236
236
  }
237
- function createHttpClient(serverUrl, sessionToken) {
237
+ function createHttpClient(serverUrl, sessionToken, onAuthFailure) {
238
238
  const baseUrl = serverUrl.replace(/\/$/, "");
239
+ let currentToken = sessionToken;
240
+ async function rawFetch(url, init) {
241
+ const dispatcher = await ensureDispatcher();
242
+ const fetchOpts = { ...init };
243
+ if (dispatcher) fetchOpts.dispatcher = dispatcher;
244
+ return fetch(url, fetchOpts);
245
+ }
246
+ function authHeaders() {
247
+ const h = {};
248
+ if (currentToken) h["Authorization"] = `Bearer ${currentToken}`;
249
+ return h;
250
+ }
239
251
  async function request(path, init = {}) {
240
252
  const headers = {
241
- ...init.headers ?? {}
253
+ ...init.headers ?? {},
254
+ ...authHeaders()
242
255
  };
243
- if (sessionToken) {
244
- headers["Authorization"] = `Bearer ${sessionToken}`;
245
- }
246
256
  if (!headers["Content-Type"] && init.body && typeof init.body === "string") {
247
257
  headers["Content-Type"] = "application/json";
248
258
  }
249
- const dispatcher = await ensureDispatcher();
250
- const fetchOpts = {
251
- ...init,
252
- headers
253
- };
254
- if (dispatcher) {
255
- fetchOpts.dispatcher = dispatcher;
259
+ let res = await rawFetch(`${baseUrl}${path}`, { ...init, headers });
260
+ if (res.status === 401 && onAuthFailure) {
261
+ const newToken = await onAuthFailure();
262
+ if (newToken) {
263
+ currentToken = newToken;
264
+ headers["Authorization"] = `Bearer ${newToken}`;
265
+ res = await rawFetch(`${baseUrl}${path}`, { ...init, headers });
266
+ }
256
267
  }
257
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
258
268
  if (!res.ok) {
259
269
  let errorBody;
260
270
  try {
@@ -277,16 +287,20 @@ function createHttpClient(serverUrl, sessionToken) {
277
287
  });
278
288
  },
279
289
  async delete(path) {
280
- const headers = {};
281
- if (sessionToken) {
282
- headers["Authorization"] = `Bearer ${sessionToken}`;
283
- }
284
- const dispatcher = await ensureDispatcher();
285
- const fetchOpts = { method: "DELETE", headers };
286
- if (dispatcher) {
287
- fetchOpts.dispatcher = dispatcher;
290
+ let res = await rawFetch(`${baseUrl}${path}`, {
291
+ method: "DELETE",
292
+ headers: authHeaders()
293
+ });
294
+ if (res.status === 401 && onAuthFailure) {
295
+ const newToken = await onAuthFailure();
296
+ if (newToken) {
297
+ currentToken = newToken;
298
+ res = await rawFetch(`${baseUrl}${path}`, {
299
+ method: "DELETE",
300
+ headers: authHeaders()
301
+ });
302
+ }
288
303
  }
289
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
290
304
  if (!res.ok) {
291
305
  let errorBody;
292
306
  try {
@@ -297,20 +311,22 @@ function createHttpClient(serverUrl, sessionToken) {
297
311
  }
298
312
  },
299
313
  async postMultipart(path, formData) {
300
- const headers = {};
301
- if (sessionToken) {
302
- headers["Authorization"] = `Bearer ${sessionToken}`;
303
- }
304
- const dispatcher = await ensureDispatcher();
305
- const fetchOpts = {
314
+ let res = await rawFetch(`${baseUrl}${path}`, {
306
315
  method: "POST",
307
- headers,
316
+ headers: authHeaders(),
308
317
  body: formData
309
- };
310
- if (dispatcher) {
311
- fetchOpts.dispatcher = dispatcher;
318
+ });
319
+ if (res.status === 401 && onAuthFailure) {
320
+ const newToken = await onAuthFailure();
321
+ if (newToken) {
322
+ currentToken = newToken;
323
+ res = await rawFetch(`${baseUrl}${path}`, {
324
+ method: "POST",
325
+ headers: authHeaders(),
326
+ body: formData
327
+ });
328
+ }
312
329
  }
313
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
314
330
  if (!res.ok) {
315
331
  let errorBody;
316
332
  try {
@@ -322,16 +338,18 @@ function createHttpClient(serverUrl, sessionToken) {
322
338
  return res.json();
323
339
  },
324
340
  async getRaw(path) {
325
- const headers = {};
326
- if (sessionToken) {
327
- headers["Authorization"] = `Bearer ${sessionToken}`;
328
- }
329
- const dispatcher = await ensureDispatcher();
330
- const fetchOpts = { headers };
331
- if (dispatcher) {
332
- fetchOpts.dispatcher = dispatcher;
341
+ let res = await rawFetch(`${baseUrl}${path}`, {
342
+ headers: authHeaders()
343
+ });
344
+ if (res.status === 401 && onAuthFailure) {
345
+ const newToken = await onAuthFailure();
346
+ if (newToken) {
347
+ currentToken = newToken;
348
+ res = await rawFetch(`${baseUrl}${path}`, {
349
+ headers: authHeaders()
350
+ });
351
+ }
333
352
  }
334
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
335
353
  if (!res.ok) {
336
354
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
337
355
  }
@@ -341,7 +359,7 @@ function createHttpClient(serverUrl, sessionToken) {
341
359
  }
342
360
 
343
361
  // ../cli/src/version.ts
344
- var VERSION = "0.3.3";
362
+ var VERSION = "0.3.5";
345
363
 
346
364
  // ../cli/src/connect.ts
347
365
  async function connect(serverName) {
@@ -361,15 +379,26 @@ async function connect(serverName) {
361
379
  throw new Error("No server configured. Run 'gigai pair' first.");
362
380
  }
363
381
  const { name, entry } = active;
382
+ const onAuthFailure = async () => {
383
+ try {
384
+ const result2 = await doRefreshSession(name, entry.server, entry.token);
385
+ return result2.sessionToken;
386
+ } catch {
387
+ return void 0;
388
+ }
389
+ };
364
390
  if (entry.sessionToken && entry.sessionExpiresAt) {
365
391
  if (Date.now() < entry.sessionExpiresAt - 5 * 60 * 1e3) {
366
- await checkAndUpdateServer(entry.server, entry.sessionToken);
367
- return { serverUrl: entry.server, sessionToken: entry.sessionToken };
392
+ const token = await checkAndUpdateServer(entry.server, entry.sessionToken, name, entry.token);
393
+ const http2 = createHttpClient(entry.server, token, onAuthFailure);
394
+ return { serverUrl: entry.server, sessionToken: token, http: http2 };
368
395
  }
369
396
  }
370
- return refreshSession(name, entry.server, entry.token);
397
+ const result = await doRefreshSession(name, entry.server, entry.token);
398
+ const http = createHttpClient(entry.server, result.sessionToken, onAuthFailure);
399
+ return { serverUrl: entry.server, sessionToken: result.sessionToken, http };
371
400
  }
372
- async function refreshSession(serverName, serverUrl, encryptedToken) {
401
+ async function doRefreshSession(serverName, serverUrl, encryptedToken) {
373
402
  const orgUuid = getOrgUUID();
374
403
  const http = createHttpClient(serverUrl);
375
404
  const res = await http.post("/auth/connect", {
@@ -377,10 +406,10 @@ async function refreshSession(serverName, serverUrl, encryptedToken) {
377
406
  orgUuid
378
407
  });
379
408
  await updateServerSession(serverName, res.sessionToken, res.expiresAt);
380
- await checkAndUpdateServer(serverUrl, res.sessionToken);
381
- return { serverUrl, sessionToken: res.sessionToken };
409
+ const token = await checkAndUpdateServer(serverUrl, res.sessionToken, serverName, encryptedToken);
410
+ return { serverUrl, sessionToken: token };
382
411
  }
383
- async function checkAndUpdateServer(serverUrl, sessionToken) {
412
+ async function checkAndUpdateServer(serverUrl, sessionToken, serverName, encryptedToken) {
384
413
  try {
385
414
  const http = createHttpClient(serverUrl);
386
415
  const health = await http.get("/health");
@@ -403,12 +432,23 @@ async function checkAndUpdateServer(serverUrl, sessionToken) {
403
432
  console.log("Server updated and restarting.");
404
433
  await waitForServer(serverUrl, 15e3);
405
434
  console.log("Server is back online.");
435
+ if (serverName && encryptedToken) {
436
+ const orgUuid = getOrgUUID();
437
+ const unauthHttp = createHttpClient(serverUrl);
438
+ const connectRes = await unauthHttp.post("/auth/connect", {
439
+ encryptedToken,
440
+ orgUuid
441
+ });
442
+ await updateServerSession(serverName, connectRes.sessionToken, connectRes.expiresAt);
443
+ return connectRes.sessionToken;
444
+ }
406
445
  } else {
407
446
  console.log(`Server update failed: ${res.error ?? "unknown error"}`);
408
447
  }
409
448
  }
410
449
  } catch {
411
450
  }
451
+ return sessionToken;
412
452
  }
413
453
  async function waitForServer(serverUrl, timeoutMs) {
414
454
  const start = Date.now();
@@ -1018,43 +1058,21 @@ if (firstArg && !firstArg.startsWith("-") && !KNOWN_COMMANDS.has(firstArg)) {
1018
1058
  const toolName = firstArg;
1019
1059
  const toolArgs = process.argv.slice(3);
1020
1060
  try {
1021
- let { serverUrl, sessionToken } = await connect();
1022
- let http = createHttpClient(serverUrl, sessionToken);
1023
- const runTool = async () => {
1024
- const { tool: detail } = await fetchToolDetail(http, toolName);
1025
- if (detail.type === "mcp") {
1026
- const mcpToolName = toolArgs[0];
1027
- if (!mcpToolName) {
1028
- const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
1029
- console.log(`MCP tools for ${toolName}:
1061
+ const { http } = await connect();
1062
+ const { tool: detail } = await fetchToolDetail(http, toolName);
1063
+ if (detail.type === "mcp") {
1064
+ const mcpToolName = toolArgs[0];
1065
+ if (!mcpToolName) {
1066
+ const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
1067
+ console.log(`MCP tools for ${toolName}:
1030
1068
  ${toolNames.join("\n")}`);
1031
- } else {
1032
- const jsonArg = toolArgs.slice(1).join(" ");
1033
- const args = jsonArg ? JSON.parse(jsonArg) : {};
1034
- await execMcpTool(http, toolName, mcpToolName, args);
1035
- }
1036
- } else {
1037
- await execTool(http, toolName, toolArgs);
1038
- }
1039
- };
1040
- try {
1041
- await runTool();
1042
- } catch (e) {
1043
- const msg = e.message;
1044
- if (msg.includes("Invalid session") || msg.includes("Session expired") || msg.includes("Authorization")) {
1045
- const config = await readConfig();
1046
- const active = getActiveEntry(config);
1047
- if (active) {
1048
- const refreshed = await refreshSession(active.name, active.entry.server, active.entry.token);
1049
- sessionToken = refreshed.sessionToken;
1050
- http = createHttpClient(serverUrl, sessionToken);
1051
- await runTool();
1052
- } else {
1053
- throw e;
1054
- }
1055
1069
  } else {
1056
- throw e;
1070
+ const jsonArg = toolArgs.slice(1).join(" ");
1071
+ const args = jsonArg ? JSON.parse(jsonArg) : {};
1072
+ await execMcpTool(http, toolName, mcpToolName, args);
1057
1073
  }
1074
+ } else {
1075
+ await execTool(http, toolName, toolArgs);
1058
1076
  }
1059
1077
  } catch (e) {
1060
1078
  console.error(`Error: ${e.message}`);
@@ -1087,8 +1105,7 @@ function runCitty() {
1087
1105
  const listCommand = defineCommand({
1088
1106
  meta: { name: "list", description: "List available tools" },
1089
1107
  async run() {
1090
- const { serverUrl, sessionToken } = await connect();
1091
- const http = createHttpClient(serverUrl, sessionToken);
1108
+ const { http } = await connect();
1092
1109
  const tools = await fetchTools(http);
1093
1110
  console.log(formatToolList(tools));
1094
1111
  }
@@ -1099,8 +1116,7 @@ function runCitty() {
1099
1116
  tool: { type: "positional", description: "Tool name", required: true }
1100
1117
  },
1101
1118
  async run({ args }) {
1102
- const { serverUrl, sessionToken } = await connect();
1103
- const http = createHttpClient(serverUrl, sessionToken);
1119
+ const { http } = await connect();
1104
1120
  const { tool } = await fetchToolDetail(http, args.tool);
1105
1121
  console.log(formatToolDetail(tool));
1106
1122
  }
@@ -1118,8 +1134,7 @@ function runCitty() {
1118
1134
  file: { type: "positional", description: "File path", required: true }
1119
1135
  },
1120
1136
  async run({ args }) {
1121
- const { serverUrl, sessionToken } = await connect();
1122
- const http = createHttpClient(serverUrl, sessionToken);
1137
+ const { http } = await connect();
1123
1138
  await upload(http, args.file);
1124
1139
  }
1125
1140
  });
@@ -1130,8 +1145,7 @@ function runCitty() {
1130
1145
  dest: { type: "positional", description: "Destination path", required: true }
1131
1146
  },
1132
1147
  async run({ args }) {
1133
- const { serverUrl, sessionToken } = await connect();
1134
- const http = createHttpClient(serverUrl, sessionToken);
1148
+ const { http } = await connect();
1135
1149
  await download(http, args.id, args.dest);
1136
1150
  }
1137
1151
  });
@@ -1144,8 +1158,7 @@ function runCitty() {
1144
1158
  const skillCommand = defineCommand({
1145
1159
  meta: { name: "skill", description: "Regenerate the skill zip with current tool details" },
1146
1160
  async run() {
1147
- const { serverUrl, sessionToken } = await connect();
1148
- const http = createHttpClient(serverUrl, sessionToken);
1161
+ const { http } = await connect();
1149
1162
  const tools = await fetchTools(http);
1150
1163
  console.log(`Fetching details for ${tools.length} tool(s)...`);
1151
1164
  const toolDetails = await Promise.all(
@@ -1174,8 +1187,7 @@ Skill zip written to: ${outPath}`);
1174
1187
  at: { type: "string", description: "Human-readable time (e.g. '9:00 AM tomorrow')" }
1175
1188
  },
1176
1189
  async run({ args }) {
1177
- const { serverUrl, sessionToken } = await connect();
1178
- const http = createHttpClient(serverUrl, sessionToken);
1190
+ const { http } = await connect();
1179
1191
  const rawArgs = process.argv.slice(4);
1180
1192
  const positional = [];
1181
1193
  let atValue = args.at;
@@ -1232,8 +1244,7 @@ Skill zip written to: ${outPath}`);
1232
1244
  const cronListCommand = defineCommand({
1233
1245
  meta: { name: "list", description: "List scheduled jobs" },
1234
1246
  async run() {
1235
- const { serverUrl, sessionToken } = await connect();
1236
- const http = createHttpClient(serverUrl, sessionToken);
1247
+ const { http } = await connect();
1237
1248
  const res = await http.get("/cron");
1238
1249
  if (res.jobs.length === 0) {
1239
1250
  console.log("No scheduled jobs.");
@@ -1257,8 +1268,7 @@ Skill zip written to: ${outPath}`);
1257
1268
  id: { type: "positional", description: "Job ID", required: true }
1258
1269
  },
1259
1270
  async run({ args }) {
1260
- const { serverUrl, sessionToken } = await connect();
1261
- const http = createHttpClient(serverUrl, sessionToken);
1271
+ const { http } = await connect();
1262
1272
  await http.delete(`/cron/${encodeURIComponent(args.id)}`);
1263
1273
  console.log(`Removed: ${args.id}`);
1264
1274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schuttdev/kon",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Lightweight gigai client for Claude code execution",