pleumrouter 0.5.48 → 0.5.49
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 +1 -1
- package/src/interactive.mjs +18 -5
- package/src/locales/es.mjs +1 -0
- package/src/locales/ja.mjs +1 -0
- package/src/locales/ko.mjs +1 -0
- package/src/locales/zh.mjs +1 -0
- package/src/workspace.mjs +30 -15
package/package.json
CHANGED
package/src/interactive.mjs
CHANGED
|
@@ -102,6 +102,7 @@ export function rowsFor(s, width = terminalWidth()) {
|
|
|
102
102
|
// 화살표 이동 · 타이핑 필터 · Enter 선택 · Esc/Ctrl+C 취소(null).
|
|
103
103
|
// 비TTY면 즉시 null — 호출자가 기존 번호 피커 등으로 폴백한다.
|
|
104
104
|
// process.exit는 절대 호출하지 않는다(취소 처리는 호출자 몫).
|
|
105
|
+
// `{ header: true }` 항목은 묶음 제목 — 커서가 건너뛰고 선택되지 않으며 필터 중엔 숨긴다.
|
|
105
106
|
export async function select(items, {
|
|
106
107
|
message = t("Select"),
|
|
107
108
|
formatItem = String,
|
|
@@ -118,6 +119,16 @@ export async function select(items, {
|
|
|
118
119
|
let cursor = Math.max(0, Math.min(initialCursor, Math.max(0, (items?.length ?? 1) - 1)));
|
|
119
120
|
let windowStart = 0;
|
|
120
121
|
let lastRows = 0;
|
|
122
|
+
const isHeader = (it) => it?.header === true;
|
|
123
|
+
const poolOf = () => {
|
|
124
|
+
const pool = matches(items, query);
|
|
125
|
+
return query ? pool.filter((it) => !isHeader(it)) : pool;
|
|
126
|
+
};
|
|
127
|
+
// from에서 dir 방향 첫 비제목 위치. 없으면 fallback(현재 위치 유지).
|
|
128
|
+
const seek = (pool, from, dir, fallback) => {
|
|
129
|
+
for (let i = from; i >= 0 && i < pool.length; i += dir) if (!isHeader(pool[i])) return i;
|
|
130
|
+
return fallback;
|
|
131
|
+
};
|
|
121
132
|
|
|
122
133
|
const erase = () => {
|
|
123
134
|
if (!lastRows) return;
|
|
@@ -126,8 +137,9 @@ export async function select(items, {
|
|
|
126
137
|
};
|
|
127
138
|
|
|
128
139
|
const render = () => {
|
|
129
|
-
const pool =
|
|
140
|
+
const pool = poolOf();
|
|
130
141
|
if (cursor >= pool.length) cursor = Math.max(0, pool.length - 1);
|
|
142
|
+
if (isHeader(pool[cursor])) cursor = seek(pool, cursor, 1, seek(pool, cursor, -1, cursor));
|
|
131
143
|
if (cursor < windowStart) {
|
|
132
144
|
windowStart = cursor;
|
|
133
145
|
} else if (cursor >= windowStart + limit) {
|
|
@@ -146,6 +158,7 @@ export async function select(items, {
|
|
|
146
158
|
rawLines.push(
|
|
147
159
|
...shown.map((it, idx) => {
|
|
148
160
|
const itemIndex = windowStart + idx;
|
|
161
|
+
if (isHeader(it)) return ` ${style.gray(formatItem(it))}`;
|
|
149
162
|
return itemIndex === cursor
|
|
150
163
|
? `${style.plum("❯")} ${style.bold(formatItem(it))}`
|
|
151
164
|
: ` ${style.dim(formatItem(it))}`;
|
|
@@ -180,15 +193,15 @@ export async function select(items, {
|
|
|
180
193
|
for (const key of feed(chunk)) {
|
|
181
194
|
if (key.type === "ctrlc" || key.type === "escape") return finish(null);
|
|
182
195
|
if (key.type === "enter") {
|
|
183
|
-
const pool =
|
|
196
|
+
const pool = poolOf();
|
|
184
197
|
const safeCursor = Math.max(0, Math.min(cursor, pool.length - 1));
|
|
198
|
+
if (isHeader(pool[safeCursor])) continue;
|
|
185
199
|
return finish(pool[safeCursor] ?? null);
|
|
186
200
|
}
|
|
187
201
|
if (key.type === "up") {
|
|
188
|
-
cursor =
|
|
202
|
+
cursor = seek(poolOf(), cursor - 1, -1, cursor);
|
|
189
203
|
} else if (key.type === "down") {
|
|
190
|
-
|
|
191
|
-
cursor = Math.min(Math.max(0, pool.length - 1), cursor + 1);
|
|
204
|
+
cursor = seek(poolOf(), cursor + 1, 1, cursor);
|
|
192
205
|
} else if (key.type === "backspace") {
|
|
193
206
|
query = query.slice(0, -1);
|
|
194
207
|
cursor = 0;
|
package/src/locales/es.mjs
CHANGED
|
@@ -260,6 +260,7 @@ export const ES = {
|
|
|
260
260
|
"Team": "Equipo",
|
|
261
261
|
"Personal": "Personal",
|
|
262
262
|
"Personal Credit": "Crédito personal",
|
|
263
|
+
"Invited workspaces": "Espacios de trabajo invitados",
|
|
263
264
|
"Credit workspace": "Espacio de crédito",
|
|
264
265
|
"Credit Balance:": "Saldo de crédito:",
|
|
265
266
|
"credits": "créditos",
|
package/src/locales/ja.mjs
CHANGED
package/src/locales/ko.mjs
CHANGED
package/src/locales/zh.mjs
CHANGED
package/src/workspace.mjs
CHANGED
|
@@ -56,15 +56,24 @@ export function workspaceLabel(context) {
|
|
|
56
56
|
return t("Personal");
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// 서버가 소유 공간(role=owner)을 먼저 보낸다. 소유=개인 크레딧 아래, 나머지=초대받은 공간 묶음.
|
|
60
|
+
export function splitCandidates(candidates = []) {
|
|
61
|
+
return {
|
|
62
|
+
owned: candidates.filter((c) => c.role === "owner"),
|
|
63
|
+
invited: candidates.filter((c) => c.role !== "owner"),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
export function formatWorkspaceList(context, candidates = []) {
|
|
60
68
|
const activeId = context?.type === "workspace" ? String(context.workspace_id) : null;
|
|
61
|
-
const
|
|
62
|
-
|
|
69
|
+
const { owned, invited } = splitCandidates(candidates);
|
|
70
|
+
const line = (candidate) => {
|
|
63
71
|
const mark = String(candidate.workspace_id) === activeId ? style.bold(style.plum("▸ ")) : " ";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
72
|
+
return `${mark}${candidate.workspace_id} ${sanitizeTerminalText(candidate.name)} (${sanitizeTerminalText(candidate.role)})`;
|
|
73
|
+
};
|
|
74
|
+
const lines = [`${t("Credit workspace")}: ${workspaceLabel(context)}`];
|
|
75
|
+
if (owned.length) lines.push(`${t("Personal Credit")}:`, ...owned.map(line));
|
|
76
|
+
if (invited.length) lines.push(`${t("Invited workspaces")}:`, ...invited.map(line));
|
|
68
77
|
return lines.join("\n");
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -118,6 +127,15 @@ export async function selectAndBindWorkspace({
|
|
|
118
127
|
const active = data?.active_context ?? getActiveContext();
|
|
119
128
|
const activeId = active?.type === "workspace" ? String(active.workspace_id) : null;
|
|
120
129
|
|
|
130
|
+
const { owned, invited } = splitCandidates(candidates);
|
|
131
|
+
const option = (c, title) => ({
|
|
132
|
+
id: String(c.workspace_id),
|
|
133
|
+
type: "workspace",
|
|
134
|
+
workspace_id: c.workspace_id,
|
|
135
|
+
name: c.name,
|
|
136
|
+
title,
|
|
137
|
+
label: `${activeId === String(c.workspace_id) ? "✓ " : " "} └ ${title}`,
|
|
138
|
+
});
|
|
121
139
|
const options = [
|
|
122
140
|
{
|
|
123
141
|
id: "personal",
|
|
@@ -125,20 +143,17 @@ export async function selectAndBindWorkspace({
|
|
|
125
143
|
title: t("Personal Credit"),
|
|
126
144
|
label: `${activeId ? " " : "✓ "}${t("Personal Credit")}`,
|
|
127
145
|
},
|
|
128
|
-
...
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
title: `${sanitizeTerminalText(c.name)} (${sanitizeTerminalText(c.role)})`,
|
|
134
|
-
label: `${activeId === String(c.workspace_id) ? "✓ " : " "}${sanitizeTerminalText(c.name)} (${sanitizeTerminalText(c.role)})`,
|
|
135
|
-
})),
|
|
146
|
+
...owned.map((c) => option(c, sanitizeTerminalText(c.name))),
|
|
147
|
+
...(invited.length
|
|
148
|
+
? [{ id: "header:invited", header: true, label: ` ${t("Invited workspaces")}` }]
|
|
149
|
+
: []),
|
|
150
|
+
...invited.map((c) => option(c, `${sanitizeTerminalText(c.name)} (${sanitizeTerminalText(c.role)})`)),
|
|
136
151
|
];
|
|
137
152
|
|
|
138
153
|
const picked = await selectFn(options, {
|
|
139
154
|
message: t("Select billing workspace for this agent"),
|
|
140
155
|
formatItem: (x) => x.label,
|
|
141
|
-
limit:
|
|
156
|
+
limit: 12,
|
|
142
157
|
});
|
|
143
158
|
|
|
144
159
|
if (!picked) return active;
|