claude360 0.2.9 → 0.3.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/src/ui.js CHANGED
@@ -18,6 +18,9 @@ function palette(level) {
18
18
 
19
19
  // eslint-disable-next-line no-control-regex
20
20
  const ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
21
+ const PAGE_DIVIDER = "────────────────────────────────────────";
22
+ const SECTION_DIVIDER = "----------------------------------------";
23
+ const LIGHT_DIVIDER = "········································";
21
24
 
22
25
  export function stripAnsi(text) {
23
26
  return String(text ?? "").replace(ANSI_PATTERN, "");
@@ -221,3 +224,255 @@ export function renderModelTable(models = [], { color = false, width = 0 } = {})
221
224
  ]),
222
225
  }, { color, width });
223
226
  }
227
+
228
+ // ──────────────────────────────────────────────
229
+ // 执行流程组件(优化需求 V2):任务页 / 步骤 / 分割线 / 选择卡片 / 结构化错误
230
+ // ──────────────────────────────────────────────
231
+
232
+ export function renderDivider(type = "section") {
233
+ if (type === "page") {
234
+ return PAGE_DIVIDER;
235
+ }
236
+ if (type === "light") {
237
+ return LIGHT_DIVIDER;
238
+ }
239
+ return SECTION_DIVIDER;
240
+ }
241
+
242
+ export function renderTaskStart(title, { intro = [] } = {}) {
243
+ const lines = [
244
+ PAGE_DIVIDER,
245
+ `🚀 任务:${title}`,
246
+ PAGE_DIVIDER,
247
+ ];
248
+ const items = Array.isArray(intro) ? intro.filter(Boolean) : [];
249
+ if (items.length > 0) {
250
+ lines.push("", "本流程将完成:");
251
+ lines.push(...items.slice(0, 5).map((item, index) => `${index + 1}. ${item}`));
252
+ }
253
+ return lines.join("\n");
254
+ }
255
+
256
+ export function renderTaskEnd(title, { summary = [] } = {}) {
257
+ const lines = [
258
+ PAGE_DIVIDER,
259
+ `🎉 ${title}`,
260
+ PAGE_DIVIDER,
261
+ ];
262
+ const items = Array.isArray(summary) ? summary : [];
263
+ if (items.length > 0) {
264
+ lines.push("");
265
+ for (const [label, value] of items) {
266
+ lines.push(`${label}:${value}`);
267
+ }
268
+ }
269
+ return lines.join("\n");
270
+ }
271
+
272
+ export function renderTaskStep(current, total, title, { icon = "▶" } = {}) {
273
+ return `${icon} [${current}/${total}] ${title}`;
274
+ }
275
+
276
+ function normalizeColumns(columns = []) {
277
+ return columns.map((column) => {
278
+ if (Array.isArray(column)) {
279
+ return { key: column[0], label: column[1] ?? column[0] };
280
+ }
281
+ if (typeof column === "string") {
282
+ return { key: column, label: column };
283
+ }
284
+ return { key: column.key, label: column.label ?? column.key };
285
+ }).filter((column) => column.key);
286
+ }
287
+
288
+ function rowValue(row, key) {
289
+ if (Array.isArray(row)) {
290
+ return row[key] ?? "";
291
+ }
292
+ return row?.[key] ?? "";
293
+ }
294
+
295
+ function limitRows(rows, maxRows) {
296
+ const limit = Number(maxRows);
297
+ if (!Number.isInteger(limit) || limit <= 0 || rows.length <= limit) {
298
+ return { visibleRows: rows, hiddenCount: 0 };
299
+ }
300
+ return {
301
+ visibleRows: rows.slice(0, limit),
302
+ hiddenCount: rows.length - limit,
303
+ };
304
+ }
305
+
306
+ function foldedHint(hiddenCount) {
307
+ return `还有 ${hiddenCount} 项,使用 --verbose 或详情命令查看更多。`;
308
+ }
309
+
310
+ export function renderChoiceTable({ columns = [], rows = [] } = {}, { color = false, width = 0, cardBreakpoint = 72, maxRows = 0, titleKey = "" } = {}) {
311
+ const cols = normalizeColumns(columns);
312
+ const { visibleRows, hiddenCount } = limitRows(rows, maxRows);
313
+ const table = renderSeparatedTable({
314
+ head: cols.map((column) => column.label),
315
+ rows: visibleRows.map((row) => cols.map((column) => String(rowValue(row, column.key)))),
316
+ }, { color, width });
317
+ const tableWidth = cellWidth(table.split("\n")[0] ?? "");
318
+ const appendHint = (output) => hiddenCount > 0 ? `${output}\n${foldedHint(hiddenCount)}` : output;
319
+ if (!width || (width >= cardBreakpoint && tableWidth <= width)) {
320
+ return appendHint(table);
321
+ }
322
+ return appendHint(visibleRows.map((row) => renderChoiceCard(row, cols, { color, width, titleKey })).join("\n\n"));
323
+ }
324
+
325
+ function renderSeparatedTable({ head = [], rows = [] } = {}, { color = false, width = 0 } = {}) {
326
+ const level = toLevel(color);
327
+ const widths = resolveWidths(head, rows, width);
328
+ const fit = (row) => widths.map((w, i) => truncateDisplay(row[i] ?? "", w));
329
+ const lines = [borderLine(widths, ["┌", "┬", "┐"], level)];
330
+ if (head.length > 0) {
331
+ lines.push(contentLine(fit(head), widths, { level, bold: true }));
332
+ lines.push(borderLine(widths, ["├", "┼", "┤"], level));
333
+ }
334
+ rows.forEach((row, index) => {
335
+ lines.push(contentLine(fit(row), widths, { level }));
336
+ if (index < rows.length - 1) {
337
+ lines.push(borderLine(widths, ["├", "┼", "┤"], level));
338
+ }
339
+ });
340
+ lines.push(borderLine(widths, ["└", "┴", "┘"], level));
341
+ return lines.join("\n");
342
+ }
343
+
344
+ function renderChoiceCard(row, columns, options) {
345
+ const { titleKey = "" } = options;
346
+ const indexKey = columns.find((column) => column.key === "index")?.key;
347
+ const nameKey = titleKey
348
+ ? columns.find((column) => column.key === titleKey)?.key
349
+ : columns.find((column) => column.key === "name" || column.key === "label")?.key;
350
+ const markKey = columns.find((column) => column.key === "mark" || column.key === "badge")?.key;
351
+ const title = [
352
+ !titleKey && indexKey ? `${rowValue(row, indexKey)}.` : "",
353
+ nameKey ? rowValue(row, nameKey) : "",
354
+ ].filter(Boolean).join(" ").trim() || String(rowValue(row, columns[0]?.key) || "");
355
+ const badge = markKey ? rowValue(row, markKey) : "";
356
+ const fields = columns
357
+ .filter((column) => ![indexKey, nameKey, markKey].includes(column.key))
358
+ .map((column) => [column.label, rowValue(row, column.key)])
359
+ .filter(([, value]) => value !== "" && value !== null && value !== undefined);
360
+ return renderCard(title, fields, { ...options, badge });
361
+ }
362
+
363
+ export function renderCard(title, fields = [], { badge = "", color = false, width = 0 } = {}) {
364
+ const level = toLevel(color);
365
+ const c = level ? palette(level) : null;
366
+ const maxWidth = width > 0 ? width : 0;
367
+ const desiredInner = Math.max(
368
+ cellWidth(`${title}${badge ? ` ${badge}` : ""}`),
369
+ ...fields.map(([label, value]) => cellWidth(`${label}:${value}`)),
370
+ 12,
371
+ ) + 2;
372
+ const inner = maxWidth ? Math.max(8, Math.min(desiredInner, maxWidth - 2)) : desiredInner;
373
+ const contentWidth = Math.max(1, inner - 2);
374
+ const edge = (left, right) => {
375
+ const text = `${left}${"─".repeat(inner)}${right}`;
376
+ return level ? `${c.border}${text}${RESET}` : text;
377
+ };
378
+ const line = (text) => {
379
+ const fitted = truncateDisplay(text, contentWidth);
380
+ const body = ` ${padCell(fitted, contentWidth)} `;
381
+ return level ? `${c.border}│${RESET}${body}${c.border}│${RESET}` : `│${body}│`;
382
+ };
383
+ const badgeText = String(badge);
384
+ const badgeW = cellWidth(badgeText);
385
+ // badge(当前/推荐/异常标记)优先保留:窄卡片下截断 title 而非 badge(需求第七节)
386
+ let titleText;
387
+ if (badgeText) {
388
+ const titleMax = Math.max(1, contentWidth - badgeW - 1);
389
+ const fittedTitle = truncateDisplay(title, titleMax);
390
+ const gap = Math.max(1, contentWidth - cellWidth(fittedTitle) - badgeW);
391
+ titleText = `${fittedTitle}${" ".repeat(gap)}${badgeText}`;
392
+ } else {
393
+ titleText = truncateDisplay(title, contentWidth);
394
+ }
395
+ const lines = [edge("┌", "┐"), line(titleText)];
396
+ for (const [label, value] of fields) {
397
+ lines.push(line(`${label}:${value}`));
398
+ }
399
+ lines.push(edge("└", "┘"));
400
+ return lines.join("\n");
401
+ }
402
+
403
+ export function renderStructuredError(title, {
404
+ reason = "",
405
+ suggestions = [],
406
+ detailCommand = "",
407
+ } = {}) {
408
+ const lines = [`❌ ${title}`];
409
+ if (reason) {
410
+ lines.push("", "原因:", String(reason));
411
+ }
412
+ const items = Array.isArray(suggestions) ? suggestions.filter(Boolean) : [];
413
+ if (items.length > 0) {
414
+ lines.push("", "建议:");
415
+ lines.push(...items.map((item, index) => `${index + 1}. ${item}`));
416
+ }
417
+ if (detailCommand) {
418
+ lines.push("", "查看详细日志:", detailCommand);
419
+ }
420
+ return lines.join("\n");
421
+ }
422
+
423
+ export const taskStart = renderTaskStart;
424
+ export const taskEnd = renderTaskEnd;
425
+ export const step = renderTaskStep;
426
+ export const divider = renderDivider;
427
+ export const errorBlock = renderStructuredError;
428
+ export const card = renderCard;
429
+
430
+ export function warningBlock(title, {
431
+ action = "",
432
+ impact = "",
433
+ prompt = "",
434
+ } = {}) {
435
+ const lines = [`⚠️ ${title}`];
436
+ if (action) {
437
+ lines.push("", "将执行:", String(action));
438
+ }
439
+ if (impact) {
440
+ lines.push("", "影响范围:", String(impact));
441
+ }
442
+ if (prompt) {
443
+ lines.push("", String(prompt));
444
+ }
445
+ return lines.join("\n");
446
+ }
447
+
448
+ export function selectedSummary(title, pairs = []) {
449
+ const lines = [String(title)];
450
+ const body = summary(pairs);
451
+ if (body) {
452
+ lines.push(body);
453
+ }
454
+ return lines.join("\n");
455
+ }
456
+
457
+ export function table(data = {}, options = {}) {
458
+ if (Array.isArray(data.columns)) {
459
+ return renderChoiceTable(data, options);
460
+ }
461
+ return renderTable(data, options);
462
+ }
463
+
464
+ export function summary(items = []) {
465
+ return items.map(([label, value]) => `${label}:${value}`).join("\n");
466
+ }
467
+
468
+ export function command(text) {
469
+ return String(text ?? "");
470
+ }
471
+
472
+ export function filePath(text) {
473
+ return String(text ?? "");
474
+ }
475
+
476
+ export function current(label, value) {
477
+ return `${label}:${value}`;
478
+ }
package/src/zcf-notice.js CHANGED
@@ -17,6 +17,16 @@ export const OPEN_SOURCE_NOTICE = [
17
17
  "提供授权登录、API Key、余额充值、Claude Code / Codex 配置注入等服务。",
18
18
  ].join("\n");
19
19
 
20
+ export const OPEN_SOURCE_NOTICE_SUMMARY = [
21
+ "开源参考声明",
22
+ "",
23
+ "本流程参考 NPX ZCF 的交互式初始化与推荐配置体验。",
24
+ "感谢作者 UfoMiao 的开源贡献。",
25
+ `项目地址:${ZCF_PROJECT_URL}`,
26
+ "",
27
+ "查看完整声明:claude360 about",
28
+ ].join("\n");
29
+
20
30
  // 工作流 / AGENTS 等改编内容的文件头 attribution(PRD 2.3 / 6.5)
21
31
  export const ZCF_ATTRIBUTION_COMMENT = [
22
32
  "<!--",
@@ -25,12 +35,16 @@ export const ZCF_ATTRIBUTION_COMMENT = [
25
35
  "-->",
26
36
  ].join("\n");
27
37
 
38
+ export function formatOpenSourceNoticeDetail() {
39
+ return OPEN_SOURCE_NOTICE;
40
+ }
41
+
28
42
  // 展示声明并询问是否继续;返回 true 表示继续,false 表示返回上级菜单。
29
43
  export async function showOpenSourceNotice({ promptSelect, writeLine = console.log } = {}) {
30
44
  if (typeof promptSelect !== "function") {
31
45
  throw new Error("缺少选择输入");
32
46
  }
33
- writeLine(OPEN_SOURCE_NOTICE);
47
+ writeLine(OPEN_SOURCE_NOTICE_SUMMARY);
34
48
  writeLine("");
35
49
  const action = await promptSelect("是否继续?", [
36
50
  { label: "继续", value: "continue" },