nolo-cli 0.6.1 → 0.8.0

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/index.js +791 -102
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -16077,12 +16077,14 @@ function buildLocalToolExecutors(args2) {
16077
16077
  })();
16078
16078
  const question = String(parsedArgs.question ?? "").trim();
16079
16079
  const choices = Array.isArray(parsedArgs.choices) ? parsedArgs.choices : [];
16080
+ const questions = Array.isArray(parsedArgs.questions) ? parsedArgs.questions : void 0;
16080
16081
  const blocking = parsedArgs.blocking !== false;
16081
- if (!question || choices.length === 0) {
16082
+ const hasQuestions = questions && questions.length > 0;
16083
+ if (!hasQuestions && (!question || choices.length === 0)) {
16082
16084
  return {
16083
16085
  content: JSON.stringify({
16084
16086
  error: "ui_ask_choice",
16085
- detail: "ui_ask_choice \u9700\u8981 question \u548C\u81F3\u5C11\u4E00\u4E2A choice\u3002"
16087
+ detail: "ui_ask_choice \u9700\u8981 question+choices \u6216 questions\u3002"
16086
16088
  }),
16087
16089
  metadata: { uiAskChoice: true, error: true }
16088
16090
  };
@@ -16090,10 +16092,28 @@ function buildLocalToolExecutors(args2) {
16090
16092
  if (args2.requestUserChoice) {
16091
16093
  try {
16092
16094
  const result = await args2.requestUserChoice({
16093
- question,
16094
- choices,
16095
- blocking
16095
+ question: hasQuestions ? questions[0].question : question,
16096
+ choices: hasQuestions ? questions[0].choices : choices,
16097
+ blocking,
16098
+ ...hasQuestions ? { questions } : {}
16096
16099
  });
16100
+ if (result.kind === "multi-submitted") {
16101
+ return {
16102
+ content: JSON.stringify({
16103
+ type: "ui_ask_choice",
16104
+ question,
16105
+ choices,
16106
+ blocking,
16107
+ ...hasQuestions ? { questions } : {},
16108
+ answers: result.answers,
16109
+ selected: {
16110
+ label: result.answers.map((a) => a.userMessage).join(", "),
16111
+ userMessage: result.userMessage
16112
+ }
16113
+ }),
16114
+ metadata: { uiAskChoice: true, resolved: true }
16115
+ };
16116
+ }
16097
16117
  if (result.kind === "selected") {
16098
16118
  return {
16099
16119
  content: JSON.stringify({
@@ -16101,6 +16121,7 @@ function buildLocalToolExecutors(args2) {
16101
16121
  question,
16102
16122
  choices,
16103
16123
  blocking,
16124
+ ...hasQuestions ? { questions } : {},
16104
16125
  selected: {
16105
16126
  label: result.label,
16106
16127
  userMessage: result.userMessage
@@ -16115,6 +16136,7 @@ function buildLocalToolExecutors(args2) {
16115
16136
  question,
16116
16137
  choices,
16117
16138
  blocking,
16139
+ ...hasQuestions ? { questions } : {},
16118
16140
  selected: { label: "", userMessage: "" },
16119
16141
  cancelled: true
16120
16142
  }),
@@ -16128,7 +16150,8 @@ function buildLocalToolExecutors(args2) {
16128
16150
  type: "ui_ask_choice",
16129
16151
  question,
16130
16152
  choices,
16131
- blocking
16153
+ blocking,
16154
+ ...hasQuestions ? { questions } : {}
16132
16155
  }),
16133
16156
  metadata: { uiAskChoice: true }
16134
16157
  };
@@ -37355,11 +37378,24 @@ var init_dialogUrl = __esm({
37355
37378
 
37356
37379
  // packages/ai/tools/uiAskChoiceTool.ts
37357
37380
  async function uiAskChoiceFunc(args2, _thunkApi) {
37381
+ const blocking = args2?.blocking !== false;
37382
+ if (Array.isArray(args2?.questions) && args2.questions.length > 0) {
37383
+ const firstQ = args2.questions[0];
37384
+ return {
37385
+ rawData: {
37386
+ type: "ui_ask_choice",
37387
+ question: firstQ?.question ?? "",
37388
+ choices: firstQ?.choices ?? [],
37389
+ blocking,
37390
+ questions: args2.questions
37391
+ },
37392
+ displayData: args2.questions.map((q) => q.question).join(" / ")
37393
+ };
37394
+ }
37358
37395
  const question = String(args2?.question ?? "").trim();
37359
37396
  const choices = Array.isArray(args2?.choices) ? args2.choices : [];
37360
- const blocking = args2?.blocking !== false;
37361
37397
  if (!question || choices.length === 0) {
37362
- throw new Error("ui_ask_choice \u9700\u8981 question \u548C\u81F3\u5C11\u4E00\u4E2A choice\u3002");
37398
+ throw new Error("ui_ask_choice \u9700\u8981 question+choices \u6216 questions\u3002");
37363
37399
  }
37364
37400
  return {
37365
37401
  rawData: {
@@ -37408,14 +37444,20 @@ var init_uiAskChoiceTool = __esm({
37408
37444
  "\u5728\u4EE3\u7801\u534F\u4F5C\u573A\u666F\u4E0B\u7684\u63A8\u8350\u7528\u6CD5\uFF1A",
37409
37445
  "- \u5982\u679C\u4F60\u5DF2\u7ECF\u5728\u672C\u8F6E assistant \u7684 content \u4E2D\u7ED9\u51FA\u4E86\u5206\u6790\u3001\u8BF4\u660E\u6216\u591A\u4E2A\u5907\u9009\u65B9\u6848\uFF0C\u53EF\u4EE5\u5728\u540C\u4E00\u8F6E\u6D88\u606F\u7684\u7ED3\u5C3E\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u8BF7\u7528\u6237\u9009\u62E9\u201C\u662F\u5426\u6839\u636E\u4E0A\u8FF0\u65B9\u6848\u5F00\u59CB\u5B9E\u9645\u4FEE\u6539\u4EE3\u7801\u201D\u6216\u201C\u4F18\u5148\u6267\u884C\u54EA\u4E00\u4E2A\u65B9\u6848/\u6B65\u9AA4\u201D\u3002",
37410
37446
  "- \u5728\u8FD9\u79CD\u60C5\u51B5\u4E0B\uFF0Cquestion \u5B57\u6BB5\u901A\u5E38\u662F\u4E00\u4E2A\u7B80\u77ED\u7684\u95EE\u9898\uFF08\u4F8B\u5982\uFF1A\u201C\u63A5\u4E0B\u6765\u4F60\u5E0C\u671B\u6211\u6309\u54EA\u4E2A\u65B9\u6848\u6765\u5177\u4F53\u4FEE\u6539\u4EE3\u7801\uFF1F\u201D\uFF09\uFF0C\u800C\u8BE6\u7EC6\u7684\u89E3\u91CA\u548C\u65B9\u6848\u63CF\u8FF0\u653E\u5728 content \u4E2D\u3002",
37411
- "- \u5982\u679C\u5F53\u524D\u5BF9\u8BDD\u53EA\u9700\u8981\u4E00\u4E2A\u7B80\u5355\u7684\u9009\u62E9\uFF0C\u800C\u4E0D\u9700\u8981\u989D\u5916\u89E3\u91CA\uFF0C\u4F60\u4E5F\u53EF\u4EE5\u4E0D\u8F93\u51FA\u989D\u5916\u7684 content\uFF0C\u800C\u53EA\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7531 question \u5B57\u6BB5\u76F4\u63A5\u5411\u7528\u6237\u63D0\u95EE\u3002"
37447
+ "- \u5982\u679C\u5F53\u524D\u5BF9\u8BDD\u53EA\u9700\u8981\u4E00\u4E2A\u7B80\u5355\u7684\u9009\u62E9\uFF0C\u800C\u4E0D\u9700\u8981\u989D\u5916\u89E3\u91CA\uFF0C\u4F60\u4E5F\u53EF\u4EE5\u4E0D\u8F93\u51FA\u989D\u5916\u7684 content\uFF0C\u800C\u53EA\u8C03\u7528\u672C\u5DE5\u5177\uFF0C\u7531 question \u5B57\u6BB5\u76F4\u63A5\u5411\u7528\u6237\u63D0\u95EE\u3002",
37448
+ "",
37449
+ "\u591A\u95EE\u9898 & \u591A\u9009\u652F\u6301\uFF1A",
37450
+ "- \u5F53\u4F60\u9700\u8981\u4E00\u6B21\u95EE\u591A\u4E2A\u95EE\u9898\u65F6\uFF0C\u4F7F\u7528 questions \u6570\u7EC4\u4EE3\u66FF question+choices\u3002",
37451
+ "- \u6BCF\u4E2A question \u53EF\u4EE5\u8BBE\u7F6E multiSelect: true \u5141\u8BB8\u591A\u9009\u3002",
37452
+ "- \u6BCF\u4E2A question \u53EF\u4EE5\u8BBE\u7F6E allowOther: false \u9690\u85CF\u201C\u5176\u4ED6\u201D\u8F93\u5165\u6846\u3002",
37453
+ "- \u6BCF\u4E2A choice \u53EF\u4EE5\u52A0 detail \u5B57\u6BB5\u63D0\u4F9B\u66F4\u957F\u7684\u63CF\u8FF0\u3002"
37412
37454
  ].join("\n"),
37413
37455
  parameters: {
37414
37456
  type: "object",
37415
37457
  properties: {
37416
37458
  question: {
37417
37459
  type: "string",
37418
- description: "\u5C55\u793A\u7ED9\u7528\u6237\u7684\u95EE\u9898\u6587\u6848\uFF0C\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u8981\u505A\u4EC0\u4E48\u9009\u62E9\u3002\u4F8B\u5982\uFF1A\u201C\u63A5\u4E0B\u6765\u4F60\u66F4\u5E0C\u671B\u6211\u5E2E\u4F60\u505A\u54EA\u4EF6\u4E8B\uFF1F\u201D\u3002\u4E0D\u8981\u5728\u540C\u4E00\u8F6E assistant \u666E\u901A\u6587\u672C\u91CC\u91CD\u590D\u8FD9\u53E5\u8BDD\u3002"
37460
+ description: "\u5C55\u793A\u7ED9\u7528\u6237\u7684\u95EE\u9898\u6587\u6848\uFF08\u5355\u95EE\u9898\u6A21\u5F0F\uFF09\u3002\u4E0E questions \u4E8C\u9009\u4E00\u3002"
37419
37461
  },
37420
37462
  choices: {
37421
37463
  type: "array",
@@ -37431,6 +37473,10 @@ var init_uiAskChoiceTool = __esm({
37431
37473
  type: "string",
37432
37474
  description: "\u663E\u793A\u7ED9\u7528\u6237\u770B\u7684\u6309\u94AE\u6587\u5B57\u3002\u4F8B\u5982\uFF1A\u201C\u751F\u6210\u672C\u5468\u5468\u62A5\u201D\u3002"
37433
37475
  },
37476
+ detail: {
37477
+ type: "string",
37478
+ description: "\u9009\u9879\u7684\u8865\u5145\u63CF\u8FF0\uFF0C\u663E\u793A\u5728 label \u4E0B\u65B9\u3002\u53EF\u9009\u3002"
37479
+ },
37434
37480
  userMessage: {
37435
37481
  type: "string",
37436
37482
  description: [
@@ -37443,6 +37489,56 @@ var init_uiAskChoiceTool = __esm({
37443
37489
  required: ["id", "label"]
37444
37490
  }
37445
37491
  },
37492
+ questions: {
37493
+ type: "array",
37494
+ description: "\u591A\u95EE\u9898\u6A21\u5F0F\uFF1A\u4E00\u6B21\u95EE\u591A\u4E2A\u95EE\u9898\uFF0C\u6BCF\u4E2A\u95EE\u9898\u72EC\u7ACB\u6E32\u67D3\u4E3A\u4E00\u4E2A tab\u3002\u4E0E question+choices \u4E8C\u9009\u4E00\u3002",
37495
+ items: {
37496
+ type: "object",
37497
+ properties: {
37498
+ id: {
37499
+ type: "string",
37500
+ description: "\u95EE\u9898\u6807\u8BC6\uFF0C\u7528\u4E8E\u7ED3\u679C\u5BF9\u5E94\u3002"
37501
+ },
37502
+ question: {
37503
+ type: "string",
37504
+ description: "\u95EE\u9898\u6587\u6848\u3002"
37505
+ },
37506
+ choices: {
37507
+ type: "array",
37508
+ description: "\u8BE5\u95EE\u9898\u7684\u5907\u9009\u9879\u3002",
37509
+ items: {
37510
+ type: "object",
37511
+ properties: {
37512
+ id: { type: "string" },
37513
+ label: { type: "string" },
37514
+ detail: {
37515
+ type: "string",
37516
+ description: "\u8865\u5145\u63CF\u8FF0\u3002"
37517
+ },
37518
+ userMessage: { type: "string" }
37519
+ },
37520
+ required: ["id", "label"]
37521
+ }
37522
+ },
37523
+ multiSelect: {
37524
+ type: "boolean",
37525
+ description: "\u5141\u8BB8\u591A\u9009\u3002\u9ED8\u8BA4 false\u3002",
37526
+ default: false
37527
+ },
37528
+ allowOther: {
37529
+ type: "boolean",
37530
+ description: "\u663E\u793A\u201C\u5176\u4ED6\u201D\u81EA\u7531\u8F93\u5165\u884C\u3002\u9ED8\u8BA4 true\u3002",
37531
+ default: true
37532
+ },
37533
+ required: {
37534
+ type: "boolean",
37535
+ description: "\u662F\u5426\u5FC5\u987B\u56DE\u7B54\u624D\u80FD\u63D0\u4EA4\u3002\u9ED8\u8BA4 true\u3002",
37536
+ default: true
37537
+ }
37538
+ },
37539
+ required: ["id", "question", "choices"]
37540
+ }
37541
+ },
37446
37542
  blocking: {
37447
37543
  type: "boolean",
37448
37544
  description: [
@@ -37452,7 +37548,7 @@ var init_uiAskChoiceTool = __esm({
37452
37548
  default: true
37453
37549
  }
37454
37550
  },
37455
- required: ["question", "choices"]
37551
+ required: []
37456
37552
  }
37457
37553
  };
37458
37554
  }
@@ -106909,13 +107005,15 @@ function renderDialogOverflow(text, colorEnabled = resolveCliColorEnabled()) {
106909
107005
  function renderDialogCommand(text, colorEnabled = resolveCliColorEnabled()) {
106910
107006
  return themeText(` ${text}`, "danger", colorEnabled);
106911
107007
  }
106912
- var DIALOG_CURSOR;
107008
+ var DIALOG_CURSOR, DIALOG_CHECKED, DIALOG_UNCHECKED;
106913
107009
  var init_dialogFrame = __esm({
106914
107010
  "packages/cli/tui/dialogFrame.ts"() {
106915
107011
  "use strict";
106916
107012
  init_terminalStyles();
106917
107013
  init_theme();
106918
107014
  DIALOG_CURSOR = "\u276F";
107015
+ DIALOG_CHECKED = "\u25C9";
107016
+ DIALOG_UNCHECKED = "\u25CB";
106919
107017
  }
106920
107018
  });
106921
107019
 
@@ -107257,6 +107355,496 @@ var init_confirmDialog = __esm({
107257
107355
  }
107258
107356
  });
107259
107357
 
107358
+ // packages/ai/tools/askChoiceState.ts
107359
+ function normalizeAskChoiceArgs(args2) {
107360
+ const blocking = args2.blocking !== false;
107361
+ if (Array.isArray(args2.questions) && args2.questions.length > 0) {
107362
+ return {
107363
+ questions: args2.questions.map(
107364
+ (q, i) => normalizeQuestion(q, i)
107365
+ ),
107366
+ blocking
107367
+ };
107368
+ }
107369
+ const question = String(args2.question ?? "").trim();
107370
+ const choices = Array.isArray(args2.choices) ? args2.choices : [];
107371
+ if (!question || choices.length === 0) {
107372
+ return { questions: [], blocking };
107373
+ }
107374
+ return {
107375
+ questions: [
107376
+ normalizeQuestion(
107377
+ { question, choices, multiSelect: false, allowOther: true, required: true },
107378
+ 0
107379
+ )
107380
+ ],
107381
+ blocking
107382
+ };
107383
+ }
107384
+ function normalizeQuestion(raw, index) {
107385
+ return {
107386
+ id: String(raw?.id ?? `q${index}`),
107387
+ question: String(raw?.question ?? "").trim(),
107388
+ choices: (Array.isArray(raw?.choices) ? raw.choices : []).map(
107389
+ (c, ci) => ({
107390
+ id: String(c?.id ?? `c${ci}`),
107391
+ label: String(c?.label ?? "").trim(),
107392
+ ...typeof c?.detail === "string" && c.detail.trim() ? { detail: c.detail.trim() } : {},
107393
+ ...typeof c?.userMessage === "string" && c.userMessage.trim() ? { userMessage: c.userMessage.trim() } : {}
107394
+ })
107395
+ ),
107396
+ multiSelect: raw?.multiSelect === true,
107397
+ allowOther: raw?.allowOther !== false,
107398
+ required: raw?.required !== false
107399
+ };
107400
+ }
107401
+ function createInitialAskChoiceState(questions) {
107402
+ return {
107403
+ questions,
107404
+ activeIndex: 0,
107405
+ questionStates: questions.map(() => ({
107406
+ cursorIndex: 0,
107407
+ selectedIds: [],
107408
+ pickedId: null,
107409
+ otherText: "",
107410
+ otherFocused: false
107411
+ })),
107412
+ phase: "active"
107413
+ };
107414
+ }
107415
+ function askChoiceReducer(state4, action3) {
107416
+ if (state4.phase !== "active") return state4;
107417
+ switch (action3.type) {
107418
+ case "CANCEL":
107419
+ return { ...state4, phase: "cancelled" };
107420
+ case "SUBMIT": {
107421
+ if (!canSubmit(state4)) return state4;
107422
+ return { ...state4, phase: "submitted" };
107423
+ }
107424
+ case "SWITCH_TAB": {
107425
+ const idx = clamp3(action3.index, 0, state4.questions.length - 1);
107426
+ return { ...state4, activeIndex: idx };
107427
+ }
107428
+ case "NEXT_TAB": {
107429
+ const idx = Math.min(state4.activeIndex + 1, state4.questions.length - 1);
107430
+ return { ...state4, activeIndex: idx };
107431
+ }
107432
+ case "PREV_TAB": {
107433
+ const idx = Math.max(state4.activeIndex - 1, 0);
107434
+ return { ...state4, activeIndex: idx };
107435
+ }
107436
+ case "MOVE_CURSOR": {
107437
+ const qs = state4.questionStates[state4.activeIndex];
107438
+ const q = state4.questions[state4.activeIndex];
107439
+ const maxIndex = q.allowOther ? q.choices.length : q.choices.length - 1;
107440
+ const next = clamp3(qs.cursorIndex + action3.delta, 0, maxIndex);
107441
+ const newQs = [...state4.questionStates];
107442
+ newQs[state4.activeIndex] = {
107443
+ ...qs,
107444
+ cursorIndex: next,
107445
+ otherFocused: false
107446
+ };
107447
+ return { ...state4, questionStates: newQs };
107448
+ }
107449
+ case "TOGGLE_AT_CURSOR": {
107450
+ const qs = state4.questionStates[state4.activeIndex];
107451
+ const q = state4.questions[state4.activeIndex];
107452
+ if (!q.multiSelect) return state4;
107453
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107454
+ if (isOtherRow) {
107455
+ const newQs2 = [...state4.questionStates];
107456
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: !qs.otherFocused };
107457
+ return { ...state4, questionStates: newQs2 };
107458
+ }
107459
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107460
+ if (!choiceId) return state4;
107461
+ const has = qs.selectedIds.includes(choiceId);
107462
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107463
+ const newQs = [...state4.questionStates];
107464
+ newQs[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107465
+ return { ...state4, questionStates: newQs };
107466
+ }
107467
+ case "SELECT_AT_CURSOR": {
107468
+ const qs = state4.questionStates[state4.activeIndex];
107469
+ const q = state4.questions[state4.activeIndex];
107470
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107471
+ if (isOtherRow) {
107472
+ const newQs2 = [...state4.questionStates];
107473
+ newQs2[state4.activeIndex] = { ...qs, otherFocused: true };
107474
+ return { ...state4, questionStates: newQs2 };
107475
+ }
107476
+ const choiceId = q.choices[qs.cursorIndex]?.id;
107477
+ if (!choiceId) return state4;
107478
+ if (q.multiSelect) {
107479
+ const has = qs.selectedIds.includes(choiceId);
107480
+ const newSelected = has ? qs.selectedIds.filter((id) => id !== choiceId) : [...qs.selectedIds, choiceId];
107481
+ const newQs2 = [...state4.questionStates];
107482
+ newQs2[state4.activeIndex] = { ...qs, selectedIds: newSelected };
107483
+ return { ...state4, questionStates: newQs2 };
107484
+ }
107485
+ const newQs = [...state4.questionStates];
107486
+ newQs[state4.activeIndex] = { ...qs, pickedId: choiceId, otherFocused: false };
107487
+ if (state4.questions.length === 1) {
107488
+ return {
107489
+ ...state4,
107490
+ questionStates: newQs,
107491
+ phase: "submitted"
107492
+ };
107493
+ }
107494
+ const nextTab = Math.min(
107495
+ state4.activeIndex + 1,
107496
+ state4.questions.length - 1
107497
+ );
107498
+ return {
107499
+ ...state4,
107500
+ questionStates: newQs,
107501
+ activeIndex: nextTab
107502
+ };
107503
+ }
107504
+ case "FOCUS_OTHER": {
107505
+ const qs = state4.questionStates[state4.activeIndex];
107506
+ const newQs = [...state4.questionStates];
107507
+ newQs[state4.activeIndex] = { ...qs, otherFocused: true };
107508
+ return { ...state4, questionStates: newQs };
107509
+ }
107510
+ case "BLUR_OTHER": {
107511
+ const qs = state4.questionStates[state4.activeIndex];
107512
+ const newQs = [...state4.questionStates];
107513
+ newQs[state4.activeIndex] = { ...qs, otherFocused: false };
107514
+ return { ...state4, questionStates: newQs };
107515
+ }
107516
+ case "SET_OTHER_TEXT": {
107517
+ const qs = state4.questionStates[state4.activeIndex];
107518
+ const newQs = [...state4.questionStates];
107519
+ newQs[state4.activeIndex] = { ...qs, otherText: action3.text };
107520
+ return { ...state4, questionStates: newQs };
107521
+ }
107522
+ default:
107523
+ return state4;
107524
+ }
107525
+ }
107526
+ function isQuestionAnswered(q, qs) {
107527
+ if (!q.required) return true;
107528
+ const hasSelection = q.multiSelect ? qs.selectedIds.length > 0 : qs.pickedId !== null;
107529
+ const hasOther = q.allowOther && qs.otherText.trim().length > 0;
107530
+ return hasSelection || hasOther;
107531
+ }
107532
+ function canSubmit(state4) {
107533
+ return state4.questions.every(
107534
+ (q, i) => isQuestionAnswered(q, state4.questionStates[i])
107535
+ );
107536
+ }
107537
+ function buildAskChoiceResult(state4) {
107538
+ if (state4.phase === "cancelled") return { kind: "cancelled" };
107539
+ if (state4.phase !== "submitted") return { kind: "cancelled" };
107540
+ const answers = state4.questions.map((q, i) => {
107541
+ const qs = state4.questionStates[i];
107542
+ const selectedIds = q.multiSelect ? qs.selectedIds : qs.pickedId ? [qs.pickedId] : [];
107543
+ const otherText = qs.otherText.trim();
107544
+ const parts = [];
107545
+ for (const id of selectedIds) {
107546
+ const choice = q.choices.find((c) => c.id === id);
107547
+ if (choice) {
107548
+ parts.push(choice.userMessage || choice.label);
107549
+ }
107550
+ }
107551
+ if (otherText) {
107552
+ parts.push(otherText);
107553
+ }
107554
+ return {
107555
+ questionId: q.id,
107556
+ selectedIds,
107557
+ otherText,
107558
+ userMessage: parts.join("\n")
107559
+ };
107560
+ });
107561
+ return { kind: "submitted", answers };
107562
+ }
107563
+ function clamp3(value, min, max) {
107564
+ return Math.max(min, Math.min(max, value));
107565
+ }
107566
+ var init_askChoiceState = __esm({
107567
+ "packages/ai/tools/askChoiceState.ts"() {
107568
+ "use strict";
107569
+ }
107570
+ });
107571
+
107572
+ // packages/cli/tui/askChoiceDialog.ts
107573
+ function renderTabBar(questions, activeIndex, colorEnabled) {
107574
+ const tabs = questions.map((q, i) => {
107575
+ const label = `Q${i + 1}`;
107576
+ if (i === activeIndex) {
107577
+ return colorEnabled ? `${themeColorSequence("accent")}\x1B[1m ${label} \x1B[0m` : `[${label}]`;
107578
+ }
107579
+ return colorEnabled ? themeText(` ${label} `, "muted", colorEnabled) : ` ${label} `;
107580
+ });
107581
+ const submitLabel = "Submit";
107582
+ return tabs.join(" ") + " " + (colorEnabled ? themeText(submitLabel, "chrome", colorEnabled) : submitLabel);
107583
+ }
107584
+ function renderFooter(colorEnabled) {
107585
+ const hints = "type answer \u21B5save tab switch s submit esc cancel";
107586
+ return colorEnabled ? themeText(` ${hints}`, "chrome", colorEnabled) : ` ${hints}`;
107587
+ }
107588
+ function renderAskChoiceFrame(state4) {
107589
+ const colorEnabled = resolveCliColorEnabled();
107590
+ const q = state4.questions[state4.activeIndex];
107591
+ const qs = state4.questionStates[state4.activeIndex];
107592
+ const lines = [];
107593
+ lines.push(renderDialogTitle("question"));
107594
+ lines.push("");
107595
+ if (state4.questions.length > 1) {
107596
+ lines.push(renderTabBar(state4.questions, state4.activeIndex, colorEnabled));
107597
+ lines.push("");
107598
+ }
107599
+ lines.push(
107600
+ colorEnabled ? `${themeColorSequence("accent")}? ${q.question}\x1B[0m` : `? ${q.question}`
107601
+ );
107602
+ if (q.multiSelect) {
107603
+ lines.push(
107604
+ colorEnabled ? themeText(" Space to toggle, Enter to confirm selection.", "muted", colorEnabled) : " Space to toggle, Enter to confirm selection."
107605
+ );
107606
+ } else {
107607
+ lines.push(
107608
+ colorEnabled ? themeText(" Type your answer, then press Enter to save.", "muted", colorEnabled) : " Type your answer, then press Enter to save."
107609
+ );
107610
+ }
107611
+ lines.push("");
107612
+ const totalRows = q.choices.length + (q.allowOther ? 1 : 0);
107613
+ const window2 = computeVisibleWindow({
107614
+ selectedIndex: qs.cursorIndex,
107615
+ total: totalRows
107616
+ });
107617
+ if (window2.start > 0) {
107618
+ lines.push(
107619
+ colorEnabled ? themeText(` \u2191 ${window2.start} more`, "chrome", colorEnabled) : ` \u2191 ${window2.start} more`
107620
+ );
107621
+ }
107622
+ for (let i = window2.start; i < window2.end; i++) {
107623
+ if (i < q.choices.length) {
107624
+ const choice = q.choices[i];
107625
+ const focused = qs.cursorIndex === i;
107626
+ const checkbox = q.multiSelect ? qs.selectedIds.includes(choice.id) ? DIALOG_CHECKED : DIALOG_UNCHECKED : qs.pickedId === choice.id ? DIALOG_CHECKED : void 0;
107627
+ lines.push(
107628
+ renderDialogRow({
107629
+ label: `[${i + 1}] ${choice.label}`,
107630
+ ...choice.detail ? { detail: choice.detail } : {},
107631
+ focused,
107632
+ ...checkbox ? { checkbox } : {}
107633
+ })
107634
+ );
107635
+ } else {
107636
+ const focused = qs.cursorIndex === i;
107637
+ const marker = focused ? DIALOG_CURSOR : " ";
107638
+ const otherContent = qs.otherFocused ? `${qs.otherText}\u2588` : qs.otherText || "";
107639
+ const row = `${marker} [${i + 1}] Other: ${otherContent}`;
107640
+ if (focused && colorEnabled) {
107641
+ lines.push(
107642
+ `\x1B[1m${themeColorSequence("accent")}${row}\x1B[0m`
107643
+ );
107644
+ } else {
107645
+ lines.push(row);
107646
+ }
107647
+ }
107648
+ }
107649
+ if (window2.end < totalRows) {
107650
+ lines.push(
107651
+ colorEnabled ? themeText(` \u2193 ${totalRows - window2.end} more`, "chrome", colorEnabled) : ` \u2193 ${totalRows - window2.end} more`
107652
+ );
107653
+ }
107654
+ lines.push("");
107655
+ lines.push(renderFooter(colorEnabled));
107656
+ return lines.join("\n");
107657
+ }
107658
+ async function runAskChoiceDialog(args2) {
107659
+ const { request } = args2;
107660
+ const normalized = normalizeAskChoiceArgs({
107661
+ question: request.question,
107662
+ choices: request.choices,
107663
+ questions: request.questions,
107664
+ blocking: request.blocking
107665
+ });
107666
+ if (normalized.questions.length === 0) {
107667
+ return { kind: "cancelled" };
107668
+ }
107669
+ const q0 = normalized.questions[0];
107670
+ if (normalized.questions.length === 1 && !q0.multiSelect && !q0.allowOther) {
107671
+ }
107672
+ let state4 = createInitialAskChoiceState(normalized.questions);
107673
+ const output2 = args2.output ?? process.stdout;
107674
+ const input2 = args2.input ?? process.stdin;
107675
+ const readKey = args2.readKey ?? createRawKeyReader(input2);
107676
+ const wasRaw = Boolean(input2.isTTY && input2.isRaw);
107677
+ let renderedLineCount = 0;
107678
+ const bottomAnchored = Boolean(args2.bottomAnchored && args2.bottomRow);
107679
+ const resolveBottomRow = () => Math.max(
107680
+ 1,
107681
+ typeof args2.bottomRow === "function" ? args2.bottomRow() : args2.bottomRow ?? 0
107682
+ );
107683
+ let lastBottomRow = 0;
107684
+ const paint = () => {
107685
+ const frame = renderAskChoiceFrame(state4);
107686
+ const lines = frame.split("\n");
107687
+ const lineCount = lines.length;
107688
+ const canPosition = outputIsTty(output2) && typeof output2.write === "function";
107689
+ if (bottomAnchored && canPosition) {
107690
+ const anchorRow = resolveBottomRow();
107691
+ clearAnchoredLines(
107692
+ output2,
107693
+ lastBottomRow > 0 ? lastBottomRow : anchorRow,
107694
+ renderedLineCount
107695
+ );
107696
+ for (let i = 0; i < lines.length; i++) {
107697
+ const row = anchorRow - (lines.length - 1 - i);
107698
+ if (row < 1) break;
107699
+ output2.write(`\x1B[${row};1H\x1B[2K${lines[i]}`);
107700
+ }
107701
+ lastBottomRow = anchorRow;
107702
+ renderedLineCount = lineCount;
107703
+ return;
107704
+ }
107705
+ if (canPosition) {
107706
+ for (let i = 0; i < renderedLineCount; i++) {
107707
+ output2.write("\x1B[1A\x1B[2K");
107708
+ }
107709
+ output2.write(`${frame}
107710
+ `);
107711
+ renderedLineCount = lineCount;
107712
+ return;
107713
+ }
107714
+ if (typeof output2.write === "function") {
107715
+ output2.write(`${frame}
107716
+ `);
107717
+ }
107718
+ renderedLineCount = lineCount;
107719
+ };
107720
+ const resizeTarget = output2;
107721
+ const onOutputResize = () => paint();
107722
+ if (input2.isTTY && !wasRaw) {
107723
+ input2.setRawMode?.(true);
107724
+ }
107725
+ if (bottomAnchored && outputIsTty(output2)) {
107726
+ resizeTarget.on?.("resize", onOutputResize);
107727
+ }
107728
+ paint();
107729
+ try {
107730
+ while (state4.phase === "active") {
107731
+ const sequence = await readKey();
107732
+ if (sequence == null) {
107733
+ state4 = askChoiceReducer(state4, { type: "CANCEL" });
107734
+ break;
107735
+ }
107736
+ let action3 = null;
107737
+ if (isCancel(sequence)) {
107738
+ action3 = { type: "CANCEL" };
107739
+ } else if (sequence === CSI_TAB) {
107740
+ action3 = { type: "NEXT_TAB" };
107741
+ } else if (sequence === CSI_SHIFT_TAB) {
107742
+ action3 = { type: "PREV_TAB" };
107743
+ } else if (isArrowUp(sequence)) {
107744
+ action3 = { type: "MOVE_CURSOR", delta: -1 };
107745
+ } else if (isArrowDown(sequence)) {
107746
+ action3 = { type: "MOVE_CURSOR", delta: 1 };
107747
+ } else if (sequence === CSI_SPACE) {
107748
+ action3 = { type: "TOGGLE_AT_CURSOR" };
107749
+ } else if (sequence === "s" && !state4.questionStates[state4.activeIndex].otherFocused && canSubmit(state4)) {
107750
+ action3 = { type: "SUBMIT" };
107751
+ } else if (isSubmit(sequence)) {
107752
+ const qs = state4.questionStates[state4.activeIndex];
107753
+ const q = state4.questions[state4.activeIndex];
107754
+ const isOtherRow = qs.cursorIndex >= q.choices.length;
107755
+ if (qs.otherFocused) {
107756
+ action3 = { type: "BLUR_OTHER" };
107757
+ } else if (isOtherRow && !qs.otherFocused) {
107758
+ action3 = { type: "FOCUS_OTHER" };
107759
+ } else {
107760
+ action3 = { type: "SELECT_AT_CURSOR" };
107761
+ }
107762
+ } else if (sequence === CSI_BACKSPACE || sequence === CSI_BACKSPACE_ALT) {
107763
+ const qs = state4.questionStates[state4.activeIndex];
107764
+ if (qs.otherFocused && qs.otherText.length > 0) {
107765
+ action3 = {
107766
+ type: "SET_OTHER_TEXT",
107767
+ text: qs.otherText.slice(0, -1)
107768
+ };
107769
+ }
107770
+ } else if (sequence.length === 1 && sequence.charCodeAt(0) >= 32) {
107771
+ const qs = state4.questionStates[state4.activeIndex];
107772
+ if (qs.otherFocused) {
107773
+ action3 = {
107774
+ type: "SET_OTHER_TEXT",
107775
+ text: qs.otherText + sequence
107776
+ };
107777
+ }
107778
+ }
107779
+ if (action3) {
107780
+ const prev = state4;
107781
+ state4 = askChoiceReducer(state4, action3);
107782
+ if (state4.phase !== "active") break;
107783
+ if (action3.type === "BLUR_OTHER" && state4.questions.length === 1 && canSubmit(state4)) {
107784
+ state4 = askChoiceReducer(state4, { type: "SUBMIT" });
107785
+ break;
107786
+ }
107787
+ paint();
107788
+ }
107789
+ }
107790
+ } finally {
107791
+ resizeTarget.off?.("resize", onOutputResize);
107792
+ readKey.dispose?.();
107793
+ if (input2.isTTY) {
107794
+ drainInputBuffer(input2);
107795
+ if (!wasRaw) input2.setRawMode?.(false);
107796
+ if (bottomAnchored) {
107797
+ clearAnchoredLines(
107798
+ output2,
107799
+ lastBottomRow > 0 ? lastBottomRow : resolveBottomRow(),
107800
+ renderedLineCount
107801
+ );
107802
+ } else {
107803
+ for (let i = 0; i < renderedLineCount; i++) {
107804
+ output2.write("\x1B[1A\x1B[2K");
107805
+ }
107806
+ }
107807
+ renderedLineCount = 0;
107808
+ }
107809
+ }
107810
+ const result = buildAskChoiceResult(state4);
107811
+ if (result.kind === "cancelled") {
107812
+ return { kind: "cancelled" };
107813
+ }
107814
+ if (result.answers.length === 1) {
107815
+ const a = result.answers[0];
107816
+ return {
107817
+ kind: "selected",
107818
+ userMessage: a.userMessage,
107819
+ label: a.selectedIds.map((id) => {
107820
+ const q = normalized.questions[0];
107821
+ return q.choices.find((c) => c.id === id)?.label ?? "";
107822
+ }).join(", ") || a.otherText || ""
107823
+ };
107824
+ }
107825
+ return {
107826
+ kind: "multi-submitted",
107827
+ answers: result.answers,
107828
+ userMessage: result.answers.map((a) => a.userMessage).filter(Boolean).join("\n\n")
107829
+ };
107830
+ }
107831
+ var CSI_TAB, CSI_SHIFT_TAB, CSI_BACKSPACE, CSI_BACKSPACE_ALT, CSI_SPACE;
107832
+ var init_askChoiceDialog = __esm({
107833
+ "packages/cli/tui/askChoiceDialog.ts"() {
107834
+ "use strict";
107835
+ init_askChoiceState();
107836
+ init_dialogFrame();
107837
+ init_selectDialog();
107838
+ init_terminalStyles();
107839
+ init_theme();
107840
+ CSI_TAB = " ";
107841
+ CSI_SHIFT_TAB = "\x1B[Z";
107842
+ CSI_BACKSPACE = "\x7F";
107843
+ CSI_BACKSPACE_ALT = "\b";
107844
+ CSI_SPACE = " ";
107845
+ }
107846
+ });
107847
+
107260
107848
  // packages/cli/tui/dialogHost.ts
107261
107849
  function resolveTtyRows(output2) {
107262
107850
  if (typeof output2 === "object" && output2 !== null && "rows" in output2 && typeof output2.rows === "number") {
@@ -107294,6 +107882,92 @@ var init_dialogHost = __esm({
107294
107882
  }
107295
107883
  });
107296
107884
 
107885
+ // packages/cli/tui/activityIndicator.ts
107886
+ function createActivityIndicator(deps) {
107887
+ const now = deps.now ?? (() => Date.now());
107888
+ const frameIntervalMs = deps.frameIntervalMs ?? ACTIVITY_FRAME_INTERVAL_MS;
107889
+ const fallbackDelayMs = deps.fallbackDelayMs ?? ACTIVITY_FALLBACK_DELAY_MS;
107890
+ const setIntervalFn = deps.setIntervalFn ?? ((cb, ms) => setInterval(cb, ms));
107891
+ const clearIntervalFn = deps.clearIntervalFn ?? ((handle) => clearInterval(handle));
107892
+ let explicitLabel = null;
107893
+ let explicitStartedAt = 0;
107894
+ let fallbackActive = false;
107895
+ let fallbackStartedAt = 0;
107896
+ let lastActivityAt = 0;
107897
+ let frameIndex = 0;
107898
+ let timer = null;
107899
+ const elapsedSecFrom = (startedAt) => startedAt > 0 ? Math.max(0, Math.floor((now() - startedAt) / 1e3)) : 0;
107900
+ const tick = () => {
107901
+ frameIndex += 1;
107902
+ if (explicitLabel === null && !fallbackActive && deps.isTurnActive() && lastActivityAt > 0 && now() - lastActivityAt >= fallbackDelayMs) {
107903
+ fallbackActive = true;
107904
+ fallbackStartedAt = now();
107905
+ }
107906
+ deps.onRepaint();
107907
+ };
107908
+ const ensureTimer = () => {
107909
+ if (timer === null) {
107910
+ timer = setIntervalFn(tick, frameIntervalMs);
107911
+ }
107912
+ };
107913
+ const report = (label) => {
107914
+ lastActivityAt = now();
107915
+ if (label !== null) {
107916
+ explicitLabel = label;
107917
+ if (explicitStartedAt === 0) explicitStartedAt = lastActivityAt;
107918
+ fallbackActive = false;
107919
+ fallbackStartedAt = 0;
107920
+ } else {
107921
+ explicitLabel = null;
107922
+ explicitStartedAt = 0;
107923
+ fallbackActive = false;
107924
+ fallbackStartedAt = 0;
107925
+ }
107926
+ ensureTimer();
107927
+ deps.onRepaint();
107928
+ };
107929
+ const getView = () => {
107930
+ if (!deps.isTurnActive()) return null;
107931
+ const frame = ACTIVITY_FRAMES[frameIndex % ACTIVITY_FRAMES.length];
107932
+ if (explicitLabel !== null) {
107933
+ return {
107934
+ frame,
107935
+ label: explicitLabel,
107936
+ elapsedSec: elapsedSecFrom(explicitStartedAt)
107937
+ };
107938
+ }
107939
+ if (fallbackActive) {
107940
+ return {
107941
+ frame,
107942
+ label: deps.fallbackLabel(),
107943
+ elapsedSec: elapsedSecFrom(fallbackStartedAt)
107944
+ };
107945
+ }
107946
+ return null;
107947
+ };
107948
+ const stop = () => {
107949
+ if (timer !== null) {
107950
+ clearIntervalFn(timer);
107951
+ timer = null;
107952
+ }
107953
+ explicitLabel = null;
107954
+ explicitStartedAt = 0;
107955
+ fallbackActive = false;
107956
+ fallbackStartedAt = 0;
107957
+ lastActivityAt = 0;
107958
+ };
107959
+ return { report, getView, stop };
107960
+ }
107961
+ var ACTIVITY_FRAMES, ACTIVITY_FRAME_INTERVAL_MS, ACTIVITY_FALLBACK_DELAY_MS;
107962
+ var init_activityIndicator = __esm({
107963
+ "packages/cli/tui/activityIndicator.ts"() {
107964
+ "use strict";
107965
+ ACTIVITY_FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
107966
+ ACTIVITY_FRAME_INTERVAL_MS = 150;
107967
+ ACTIVITY_FALLBACK_DELAY_MS = 1200;
107968
+ }
107969
+ });
107970
+
107297
107971
  // packages/ai/agent/utils/sortUtils.ts
107298
107972
  function sortAgentsFavoriteOwnedPublic(items) {
107299
107973
  const map = /* @__PURE__ */ new Map();
@@ -108103,51 +108777,76 @@ function renderStatusLine(state4) {
108103
108777
  if (!surface) return body;
108104
108778
  return `${surface} ${body} \x1B[49m`;
108105
108779
  }
108106
- function buildPlainScene(isDark) {
108107
- const sky = isDark ? "\u263E" : "\u2600";
108780
+ function buildPlainScene(isDark, frame = 0, maxFrames = 0) {
108781
+ let skyLine = ` ${isDark ? "\u{1F319}" : "\u2600"}`;
108782
+ if (isDark) {
108783
+ const star1 = frame % 6 < 3 ? "\u2726" : "\u22C6";
108784
+ const star2 = frame % 4 < 2 ? "\u22C6" : "\xB7";
108785
+ const star3 = frame % 5 < 2 ? "\xB7" : "\u2726";
108786
+ skyLine = ` ${star1} \u{1F319} ${star2} ${star3}`;
108787
+ }
108788
+ const wavePattern = "_.~^~.";
108789
+ const longWave = wavePattern.repeat(10);
108790
+ const offset = frame % wavePattern.length;
108791
+ const wave2 = longWave.slice(offset, offset + 25);
108108
108792
  return [
108109
- ` ${sky}`,
108110
- isDark ? " \u2571\u2572 \xB7" : " \u2571\u2572",
108111
- " \u2571 \u2572",
108112
- isDark ? " \u2571 \u2572 \u2726" : " \u2571 \u2572",
108113
- " \u2571 \u2660 \u2660 \u2572",
108114
- " \u2581\u2581\u2581\u2581\u2581\u2581\u2571 \u2660\u2660 \u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581",
108115
- " \u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584",
108116
- " \u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
108117
- " \u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580",
108118
- " \u2570\u256E~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~\u2570\u256E\u2248~"
108793
+ skyLine,
108794
+ " \u2571\u2572 \u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584",
108795
+ " \u2571 \u2572 \u2571\u2572 \u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588",
108796
+ " \u2571 \u2572\u2571 \u2572 \u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580",
108797
+ ` \u2571 \u2660 \u2572 ${wave2}`,
108798
+ " \u2581\u2581\u2581\u2581\u2581\u2581\u2571 \u2660 \u2660 \u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581"
108119
108799
  ].join("\n");
108120
108800
  }
108121
- function buildColoredScene(isDark) {
108801
+ function buildColoredScene(isDark, frame = 0, maxFrames = 0) {
108122
108802
  const pk = themeColorSequence("chrome");
108123
108803
  const sk = themeColorSequence("warning");
108124
- const st = themeColorSequence("muted");
108125
108804
  const tr = themeColorSequence("success");
108126
108805
  const wv = themeColorSequence("info");
108127
108806
  const ac = themeColorSequence("accent");
108128
- const cr = themeColorSequence("chrome");
108807
+ const mu = themeColorSequence("muted");
108129
108808
  const r = "\x1B[39m";
108130
108809
  const b = "\x1B[1m";
108131
108810
  const rs = "\x1B[0m";
108132
- const sky = isDark ? "\u263E" : "\u2600";
108811
+ let skyLine = ` ${sk}${isDark ? "\u{1F319}" : "\u2600"}${r}`;
108812
+ if (isDark) {
108813
+ const star1 = frame % 6 < 3 ? "\u2726" : "\u22C6";
108814
+ const star2 = frame % 4 < 2 ? "\u22C6" : "\xB7";
108815
+ const star3 = frame % 5 < 2 ? "\xB7" : "\u2726";
108816
+ skyLine = ` ${mu}${star1}${r} ${sk}\u{1F319}${r} ${mu}${star2}${r} ${mu}${star3}${r}`;
108817
+ }
108818
+ const wavePattern = "_.~^~.";
108819
+ const longWave = wavePattern.repeat(10);
108820
+ const offset = frame % wavePattern.length;
108821
+ const wave2 = longWave.slice(offset, offset + 25);
108822
+ const waveStr = `${wv}${wave2.slice(0, 20)}${r}${mu}${wave2.slice(20, 25)}${r}`;
108823
+ const line1 = "\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584";
108824
+ const line2 = "\u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588";
108825
+ const line3 = "\u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580";
108826
+ const sweepEndFrame = maxFrames > 3 ? maxFrames - 3 : maxFrames;
108827
+ const sweepLen = maxFrames === 0 ? 21 : Math.min(21, Math.floor(frame / sweepEndFrame * 21));
108828
+ const colorLine = (str, bold) => {
108829
+ const active = str.slice(0, sweepLen);
108830
+ const dimmed = str.slice(sweepLen);
108831
+ return `${bold ? b : ""}${ac}${active}${r}${mu}${dimmed}${r}${bold ? rs : ""}`;
108832
+ };
108833
+ const nolo1 = colorLine(line1, true);
108834
+ const nolo2 = colorLine(line2, false);
108835
+ const nolo3 = colorLine(line3, false);
108133
108836
  return [
108134
- ` ${sk}${sky}${r}`,
108135
- isDark ? ` ${pk}\u2571\u2572${r} ${st}\xB7${r}` : ` ${pk}\u2571\u2572${r}`,
108136
- ` ${pk}\u2571 \u2572${r}`,
108137
- isDark ? ` ${pk}\u2571 \u2572${r} ${st}\u2726${r}` : ` ${pk}\u2571 \u2572${r}`,
108138
- ` ${pk}\u2571${r} ${tr}\u2660 \u2660${r} ${pk}\u2572${r}`,
108139
- ` ${pk}\u2581\u2581\u2581\u2581\u2581\u2581\u2571${r} ${tr}\u2660\u2660${r} ${pk}\u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581${r}`,
108140
- ` ${b}${ac}\u2588\u2584 \u2588 \u2584\u2580\u2580\u2584 \u2588 \u2584\u2580\u2580\u2584${rs}`,
108141
- ` ${ac}\u2588 \u2580\u2588 \u2588 \u2588 \u2588 \u2588 \u2588${r}`,
108142
- ` ${cr}\u2580 \u2580 \u2580\u2580 \u2580\u2580\u2580\u2580 \u2580\u2580${r}`,
108143
- ` ${wv}\u2570\u256E~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~~\u2248~\u2570\u256E\u2248~${r}`
108837
+ skyLine,
108838
+ ` ${pk}\u2571\u2572${r} ${nolo1}`,
108839
+ ` ${pk}\u2571 \u2572 \u2571\u2572${r} ${nolo2}`,
108840
+ ` ${pk}\u2571 \u2572\u2571 \u2572${r} ${nolo3}`,
108841
+ ` ${pk}\u2571${r} ${tr}\u2660${r} ${pk}\u2572${r} ${waveStr}`,
108842
+ ` ${pk}\u2581\u2581\u2581\u2581\u2581\u2581\u2571${r} ${tr}\u2660${r} ${tr}\u2660${r} ${pk}\u2572\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581${r}`
108144
108843
  ].join("\n");
108145
108844
  }
108146
- function renderWelcome(state4) {
108845
+ function renderWelcome(state4, frame = 0, maxFrames = 0) {
108147
108846
  const colorEnabled = resolveCliColorEnabled();
108148
108847
  const brightness = resolveTuiBrightness();
108149
108848
  const isDark = brightness === "dark";
108150
- const sceneArt = colorEnabled ? buildColoredScene(isDark) : buildPlainScene(isDark);
108849
+ const sceneArt = colorEnabled ? buildColoredScene(isDark, frame, maxFrames) : buildPlainScene(isDark, frame, maxFrames);
108151
108850
  const versionLine = colorEnabled ? `\x1B[1m${themeColorSequence("accent")}nolo\x1B[0m ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |") : `nolo ${state4.cliVersion ?? ""} | server ${state4.serverUrl}`.replace(" |", " |");
108152
108851
  return [
108153
108852
  sceneArt,
@@ -110639,49 +111338,35 @@ async function startTuiWorkspace(options) {
110639
111338
  stdout: output2
110640
111339
  });
110641
111340
  if (detected) setActiveBrightness(detected);
110642
- output2.write(renderWelcome(state4));
111341
+ const frames = 15;
111342
+ output2.write("\x1B[?25l");
111343
+ for (let i = 0; i < frames; i++) {
111344
+ output2.write(renderWelcome(state4, i, frames));
111345
+ if (i < frames - 1) {
111346
+ await new Promise((resolve9) => setTimeout(resolve9, 100));
111347
+ output2.write("\r\x1B[8A\x1B[0J");
111348
+ }
111349
+ }
111350
+ output2.write("\x1B[?25h");
110643
111351
  let fixedInput = createNoopFixedInput();
110644
111352
  let buffer = "";
110645
111353
  let cursorPos = 0;
110646
111354
  let activeTurnAbort = null;
110647
- let activityLabel = null;
110648
- let activityStartedAt = 0;
110649
- let activityFrameIndex = 0;
110650
- let activityTimer = null;
110651
- const stopActivity = () => {
110652
- if (activityTimer !== null) {
110653
- clearInterval(activityTimer);
110654
- activityTimer = null;
110655
- }
110656
- activityLabel = null;
110657
- activityStartedAt = 0;
110658
- };
110659
- const activityReporter = (label) => {
110660
- if (label !== null) {
110661
- activityLabel = label;
110662
- if (activityStartedAt === 0) {
110663
- activityStartedAt = Date.now();
110664
- }
110665
- if (activityTimer === null) {
110666
- activityTimer = setInterval(() => {
110667
- activityFrameIndex += 1;
110668
- if (fixedInput.active && !fixedInput.isPaused()) {
110669
- output2.write("\x1B[?2026h\x1B[?25l");
110670
- try {
110671
- fixedInput.repaint(buffer);
110672
- } finally {
110673
- output2.write("\x1B[?25h\x1B[?2026l");
110674
- }
110675
- }
110676
- }, 150);
110677
- }
110678
- } else {
110679
- stopActivity();
111355
+ const activityIndicator = createActivityIndicator({
111356
+ isTurnActive: () => activeTurnAbort !== null,
111357
+ fallbackLabel: () => `${state4.agentName} -> working`,
111358
+ onRepaint: () => {
110680
111359
  if (fixedInput.active && !fixedInput.isPaused()) {
110681
- fixedInput.repaint(buffer);
111360
+ output2.write("\x1B[?2026h\x1B[?25l");
111361
+ try {
111362
+ fixedInput.repaint(buffer);
111363
+ } finally {
111364
+ output2.write("\x1B[?25h\x1B[?2026l");
111365
+ }
110682
111366
  }
110683
111367
  }
110684
- };
111368
+ });
111369
+ const activityReporter = (label) => activityIndicator.report(label);
110685
111370
  let explicitAgentSwitch = false;
110686
111371
  let copyViewExitResolver = null;
110687
111372
  const history = createTurnHistory();
@@ -110780,25 +111465,15 @@ ${t("copyViewHint")}
110780
111465
  scheduleRender();
110781
111466
  }) : output2;
110782
111467
  const requestUserChoice = isInteractiveInput(input2) && dialogHost ? async (req) => {
110783
- const items = req.choices.map((c) => ({
110784
- label: c.label
110785
- }));
110786
111468
  try {
110787
- const pickResult = await dialogHost.run(
110788
- (anchor) => runSelectDialog({
110789
- items,
110790
- title: req.question,
111469
+ return await dialogHost.run(
111470
+ (anchor) => runAskChoiceDialog({
111471
+ request: req,
110791
111472
  input: input2,
110792
111473
  output: output2,
110793
111474
  ...anchor
110794
111475
  })
110795
111476
  );
110796
- if (pickResult.kind === "selected") {
110797
- const choice = req.choices[pickResult.index];
110798
- const userMessage = choice?.userMessage?.trim() || choice?.label || "";
110799
- return { kind: "selected", userMessage, label: choice?.label ?? "" };
110800
- }
110801
- return { kind: "cancelled" };
110802
111477
  } catch {
110803
111478
  return { kind: "cancelled" };
110804
111479
  }
@@ -110851,7 +111526,7 @@ ${t("copyViewHint")}
110851
111526
  }
110852
111527
  return { ok: !wasAborted, aborted: wasAborted };
110853
111528
  } finally {
110854
- stopActivity();
111529
+ activityIndicator.stop();
110855
111530
  activeTurnAbort = null;
110856
111531
  explicitAgentSwitch = false;
110857
111532
  }
@@ -111210,17 +111885,15 @@ ${err.message}` : ""}`
111210
111885
  return base;
111211
111886
  },
111212
111887
  getActivityLine: () => {
111213
- if (activityLabel === null) return null;
111888
+ const view = activityIndicator.getView();
111889
+ if (view === null) return null;
111214
111890
  const colorEnabled = resolveCliColorEnabled();
111215
- const FRAMES = ["\xB7", "~", "\u2248", "\u223F", "\u2248", "~"];
111216
- const frame = FRAMES[activityFrameIndex % FRAMES.length];
111217
- const elapsedSec = activityStartedAt > 0 ? Math.max(0, Math.floor((Date.now() - activityStartedAt) / 1e3)) : 0;
111218
- const elapsed = formatElapsedSeconds(elapsedSec);
111891
+ const elapsed = formatElapsedSeconds(view.elapsedSec);
111219
111892
  const stopHint = t("stopHint");
111220
111893
  if (!colorEnabled) {
111221
- return `${frame} ${activityLabel} (${elapsed}) \xB7 ${stopHint}`;
111894
+ return `${view.frame} ${view.label} (${elapsed}) \xB7 ${stopHint}`;
111222
111895
  }
111223
- return themeText(frame, "accent") + " " + themeText(activityLabel, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111896
+ return themeText(view.frame, "accent") + " " + themeText(view.label, "muted") + themeText(` (${elapsed})`, "chrome") + themeText(` \xB7 ${stopHint}`, "chrome");
111224
111897
  },
111225
111898
  getQueueLines: () => {
111226
111899
  if (!chatQueueBinding || chatQueueBinding.queueLength() === 0) return [];
@@ -111309,12 +111982,27 @@ ${err.message}` : ""}`
111309
111982
  if (busyLock) {
111310
111983
  const submittedText2 = result.submit;
111311
111984
  const trimmedText = submittedText2.trim();
111312
- if (trimmedText === "/context" || trimmedText === "/ctx") {
111985
+ const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
111986
+ const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch";
111987
+ if (isBusyLocalSlash) {
111988
+ const beforeAgentKey = state4.agentKey;
111313
111989
  const res = handleTuiInput(submittedText2, state4);
111314
- state4 = res.nextState;
111315
- if (res.output) {
111316
- output2.write(`${res.output}
111990
+ if (res.action) {
111991
+ output2.write(
111992
+ "Model picker isn't available while a reply is running. Use `/switch <name>` to switch now (takes effect on the next turn), or wait for the reply to finish.\n"
111993
+ );
111994
+ } else {
111995
+ state4 = res.nextState;
111996
+ let msg = res.output;
111997
+ if (busySlashCommand === "/switch" && res.nextState.agentKey !== beforeAgentKey) {
111998
+ const hint = "Note: the new model takes effect on the next turn. Switching models may consume more tokens because the conversation context is re-sent to the new model.";
111999
+ msg = msg ? `${msg}
112000
+ ${hint}` : hint;
112001
+ }
112002
+ if (msg) {
112003
+ output2.write(`${msg}
111317
112004
  `);
112005
+ }
111318
112006
  }
111319
112007
  buffer = "";
111320
112008
  cursorPos2 = 0;
@@ -111537,8 +112225,9 @@ var init_readlineWorkspace = __esm({
111537
112225
  init_updateCommands();
111538
112226
  init_processSpawn();
111539
112227
  init_confirmDialog();
111540
- init_selectDialog();
112228
+ init_askChoiceDialog();
111541
112229
  init_dialogHost();
112230
+ init_activityIndicator();
111542
112231
  init_agentPicker();
111543
112232
  init_agentCatalog();
111544
112233
  init_dialogPicker();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nolo-cli",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first terminal workspace for Nolo",
6
6
  "bin": {