pleumrouter 0.5.12 → 0.5.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pleumrouter",
3
- "version": "0.5.12",
3
+ "version": "0.5.14",
4
4
  "description": "Official PleumRouter terminal CLI — launch any coding agent against PleumRouter.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/config.mjs CHANGED
@@ -105,24 +105,6 @@ export async function requireAuth() {
105
105
  return refreshed;
106
106
  }
107
107
 
108
- /** @deprecated use requireAuth — kept as alias for gradual call-site migration */
109
- export function requireKey() {
110
- // sync callers still exist; they must switch to await requireAuth().
111
- // For sync contexts we only check presence and return access_token (no refresh).
112
- const cfg = readConfig();
113
- if (cfg?.key && !cfg?.access_token) {
114
- console.error(
115
- t("Legacy API key login is no longer supported. Run:\n pleum login"),
116
- );
117
- process.exit(1);
118
- }
119
- if (!cfg?.access_token) {
120
- console.error(t("Login required. Run this first:\n pleum login"));
121
- process.exit(1);
122
- }
123
- return cfg.access_token;
124
- }
125
-
126
108
  export async function refreshAccessToken(cfg = readConfig()) {
127
109
  if (!cfg?.refresh_token) return null;
128
110
  const base = (cfg.base_url || ROOT).replace(/\/+$/, "").replace(/\/v1$/, "");
package/src/run.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { readFileSync, writeFileSync, mkdirSync, lstatSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { V1, requireAuth, HISTORY_FILE, SESSIONS_DIR } from "./config.mjs";
4
+ import { requireAuth, authFetch, HISTORY_FILE, SESSIONS_DIR } from "./config.mjs";
5
5
  import { fetchModels, formatTable, matchModels, formatModelLabel, safeModelText } from "./models.mjs";
6
6
  import { style, divider, sanitizeTerminalText } from "./ansi.mjs";
7
7
  import { createMarkdownStream } from "./markdown.mjs";
@@ -200,7 +200,8 @@ export async function run(rest) {
200
200
  let model = argModel;
201
201
  const piped = await readStdin();
202
202
  const oneShot = Boolean(inlinePrompt || piped || attachments.length);
203
- const key = await requireAuth();
203
+ // 부팅 시점 1회 로그인 게이트. access_token은 각 요청이 authFetch에서 refresh한다.
204
+ await requireAuth();
204
205
  const callOpts = { tags, skipCache, reasoningEffort, serviceTier, reasoningMode };
205
206
 
206
207
  if (!model && !oneShot) {
@@ -226,7 +227,7 @@ export async function run(rest) {
226
227
  console.error(`ℹ ${t("Attaching and uploading {n} explicit local image(s).", { n })}`);
227
228
  }
228
229
  const messages = [{ role: "user", content }];
229
- await streamChat(key, model, messages, { verbose, quiet: true, ...callOpts });
230
+ await streamChat(model, messages, { verbose, quiet: true, ...callOpts });
230
231
  } catch (e) {
231
232
  console.error(`${style.red("✗")} ${sanitizeTerminalText(e.message)}`);
232
233
  process.exit(1);
@@ -234,10 +235,10 @@ export async function run(rest) {
234
235
  process.exit(0);
235
236
  }
236
237
 
237
- await repl(key, model, verbose, callOpts);
238
+ await repl(model, verbose, callOpts);
238
239
  }
239
240
 
240
- async function repl(key, initialModel, initialVerbose, {
241
+ async function repl(initialModel, initialVerbose, {
241
242
  tags = [],
242
243
  skipCache = false,
243
244
  reasoningEffort = null,
@@ -263,7 +264,7 @@ async function repl(key, initialModel, initialVerbose, {
263
264
  const req = system ? [{ role: "system", content: system }, ...messages] : messages;
264
265
  const ac = new AbortController();
265
266
  currentAbort = ac;
266
- const reply = await streamChat(key, model, req, {
267
+ const reply = await streamChat(model, req, {
267
268
  verbose,
268
269
  signal: ac.signal,
269
270
  tags,
@@ -487,7 +488,7 @@ async function handleSlash(text, ctx) {
487
488
 
488
489
  // SSE 스트리밍 채팅. fetchImpl 주입 가능(테스트용 — 네트워크 없이 SSE 파싱 검증).
489
490
  // 비용은 마지막 SSE 청크의 cost.krw — 스트리밍 응답엔 X-Cost-Krw 헤더가 없다(백엔드 계약).
490
- export async function streamChat(key, model, messages, {
491
+ export async function streamChat(model, messages, {
491
492
  verbose = false,
492
493
  quiet = false,
493
494
  signal,
@@ -496,21 +497,25 @@ export async function streamChat(key, model, messages, {
496
497
  reasoningEffort = null,
497
498
  serviceTier = null,
498
499
  reasoningMode = null,
499
- fetchImpl = fetch,
500
+ fetchImpl = null,
500
501
  } = {}) {
501
502
  const t0 = Date.now();
502
503
  const sp = spinner(t("{model} thinking…", { model: sanitizeTerminalText(model) }));
503
504
  let res;
504
505
  try {
505
506
  // 게이트웨이 패리티(0095): X-Pleum-Skip-Cache 헤더 + bounded tags 본문.
506
- const headers = { "Content-Type": "application/json", Authorization: `Bearer ${key}` };
507
+ // authFetch가 Bearer + 401→refresh→재시도를 소유한다. 긴 REPL 세션 도중
508
+ // access_token이 만료돼도 자동 갱신돼야 한다(2026-07-27 버그 수정).
509
+ const headers = { "Content-Type": "application/json" };
507
510
  if (skipCache) headers["X-Pleum-Skip-Cache"] = "1";
508
511
  const bodyObj = { model, messages, stream: true, stream_options: { include_usage: true } };
509
512
  if (tags.length > 0) bodyObj.tags = tags.slice(0, 10);
510
513
  if (reasoningEffort) bodyObj.reasoning_effort = reasoningEffort;
511
514
  if (serviceTier) bodyObj.service_tier = serviceTier;
512
515
  if (reasoningMode) bodyObj.reasoning_mode = reasoningMode;
513
- res = await fetchImpl(`${V1}/chat/completions`, {
516
+ // fetchImpl: 테스트용 네트워크 치환. 프로덕션은 authFetch(Bearer + 401 refresh).
517
+ const doFetch = fetchImpl || ((path, init) => authFetch(path, init));
518
+ res = await doFetch("/chat/completions", {
514
519
  method: "POST",
515
520
  headers,
516
521
  body: JSON.stringify(bodyObj),
package/src/upgrade.mjs CHANGED
@@ -3,9 +3,10 @@ import { spawn } from "node:child_process";
3
3
  // 최신 버전으로 전역 재설치. brew tap 보류 상태라 npm 경로([[project_pleum_cli]]).
4
4
  export function upgrade() {
5
5
  console.log("▶ npm install -g pleumrouter@latest\n");
6
- const child = spawn("npm", ["install", "-g", "pleumrouter@latest"], {
6
+ const bin = process.platform === "win32" ? "npm.cmd" : "npm";
7
+ const child = spawn(bin, ["install", "-g", "pleumrouter@latest"], {
7
8
  stdio: "inherit",
8
- shell: process.platform === "win32",
9
+ shell: false,
9
10
  });
10
11
  child.on("error", (e) => {
11
12
  console.error(`✗ ${e.message}`);