@sayknow-cli/coding-agent 0.2.3 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ Sayknow-CLI is a rebranded fork of [gajae-code](https://github.com/Yeachan-Heo/g
4
4
  This file tracks the **fork's own releases**; upstream's full feature history lives
5
5
  in that project. Each release notes the upstream version it is built on.
6
6
 
7
+ ## [0.2.4] — 2026-06-19
8
+
9
+ ### Added
10
+
11
+ - The model selector is now fully localized — the preset list ("Model presets",
12
+ "Create custom preset", "Browse all models"), the apply/default scope menu,
13
+ action menus, and hints follow the interface language. Model and provider names
14
+ (Claude, Codex, …) stay verbatim.
15
+
16
+ ### Fixed
17
+
18
+ - Release builds no longer fail spuriously on a timing-flaky pi-shell
19
+ process-reaping test: the Rust test runner now retries flaky tests
20
+ (`cargo nextest --retries 2`).
21
+
7
22
  ## [0.2.3] — 2026-06-19
8
23
 
9
24
  ### Fixed
@@ -62,5 +62,19 @@ export declare const en: {
62
62
  readonly "cmd.session": "Session management commands";
63
63
  readonly "cmd.clear": "Clear the conversation";
64
64
  readonly "cmd.quit": "Exit Sayknow-CLI";
65
+ readonly "modelSelector.presets": "Model presets";
66
+ readonly "modelSelector.createCustom": "Create custom preset";
67
+ readonly "modelSelector.browseAll": "Browse all models";
68
+ readonly "modelSelector.applySession": "Apply for this session";
69
+ readonly "modelSelector.setDefault": "Set as default";
70
+ readonly "modelSelector.pressEnterApply": "Press Enter to apply this preset";
71
+ readonly "modelSelector.presetPreview": "Preset preview: {name}";
72
+ readonly "modelSelector.showingScoped": "Showing models from --models scope";
73
+ readonly "modelSelector.modelsTab": "Models";
74
+ readonly "modelSelector.noMatching": "No matching models.";
75
+ readonly "modelSelector.modelName": "Model Name: {value}";
76
+ readonly "modelSelector.actionFor": "Action for: {id}";
77
+ readonly "modelSelector.reasoningFor": "Reasoning for {target}: {id}";
78
+ readonly "modelSelector.temporaryModel": "temporary model";
65
79
  };
66
80
  export type MsgKey = keyof typeof en;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@sayknow-cli/coding-agent",
4
- "version": "0.2.3",
4
+ "version": "0.2.4",
5
5
  "description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/jaybeyond/Sayknow_CLI",
7
7
  "author": "jaybeyond",
@@ -51,12 +51,12 @@
51
51
  "@agentclientprotocol/sdk": "0.21.0",
52
52
  "@babel/parser": "^7.29.3",
53
53
  "@mozilla/readability": "^0.6.0",
54
- "@sayknow-cli/stats": "0.2.3",
55
- "@sayknow-cli/agent-core": "0.2.3",
56
- "@sayknow-cli/ai": "0.2.3",
57
- "@sayknow-cli/natives": "0.2.3",
58
- "@sayknow-cli/tui": "0.2.3",
59
- "@sayknow-cli/utils": "0.2.3",
54
+ "@sayknow-cli/stats": "0.2.4",
55
+ "@sayknow-cli/agent-core": "0.2.4",
56
+ "@sayknow-cli/ai": "0.2.4",
57
+ "@sayknow-cli/natives": "0.2.4",
58
+ "@sayknow-cli/tui": "0.2.4",
59
+ "@sayknow-cli/utils": "0.2.4",
60
60
  "@puppeteer/browsers": "^2.13.0",
61
61
  "@types/turndown": "5.0.6",
62
62
  "@xterm/headless": "^6.0.0",
@@ -62,4 +62,18 @@ export const de: Partial<Record<MsgKey, string>> = {
62
62
  "cmd.session": "Befehle zur Sitzungsverwaltung",
63
63
  "cmd.clear": "Unterhaltung löschen",
64
64
  "cmd.quit": "Sayknow-CLI beenden",
65
+ "modelSelector.presets": "Modell-Presets",
66
+ "modelSelector.createCustom": "Eigenes Preset erstellen",
67
+ "modelSelector.browseAll": "Alle Modelle durchsuchen",
68
+ "modelSelector.applySession": "Für diese Sitzung anwenden",
69
+ "modelSelector.setDefault": "Als Standard festlegen",
70
+ "modelSelector.pressEnterApply": "Enter drücken, um dieses Preset anzuwenden",
71
+ "modelSelector.presetPreview": "Preset-Vorschau: {name}",
72
+ "modelSelector.showingScoped": "Zeige Modelle aus dem --models-Bereich",
73
+ "modelSelector.modelsTab": "Modelle",
74
+ "modelSelector.noMatching": "Keine passenden Modelle.",
75
+ "modelSelector.modelName": "Modellname: {value}",
76
+ "modelSelector.actionFor": "Aktion für: {id}",
77
+ "modelSelector.reasoningFor": "Reasoning für {target}: {id}",
78
+ "modelSelector.temporaryModel": "temporäres Modell",
65
79
  };
@@ -81,6 +81,20 @@ export const en = {
81
81
  "cmd.session": "Session management commands",
82
82
  "cmd.clear": "Clear the conversation",
83
83
  "cmd.quit": "Exit Sayknow-CLI",
84
+ "modelSelector.presets": "Model presets",
85
+ "modelSelector.createCustom": "Create custom preset",
86
+ "modelSelector.browseAll": "Browse all models",
87
+ "modelSelector.applySession": "Apply for this session",
88
+ "modelSelector.setDefault": "Set as default",
89
+ "modelSelector.pressEnterApply": "Press Enter to apply this preset",
90
+ "modelSelector.presetPreview": "Preset preview: {name}",
91
+ "modelSelector.showingScoped": "Showing models from --models scope",
92
+ "modelSelector.modelsTab": "Models",
93
+ "modelSelector.noMatching": "No matching models.",
94
+ "modelSelector.modelName": "Model Name: {value}",
95
+ "modelSelector.actionFor": "Action for: {id}",
96
+ "modelSelector.reasoningFor": "Reasoning for {target}: {id}",
97
+ "modelSelector.temporaryModel": "temporary model",
84
98
  } as const;
85
99
 
86
100
  export type MsgKey = keyof typeof en;
@@ -62,4 +62,18 @@ export const es: Partial<Record<MsgKey, string>> = {
62
62
  "cmd.session": "Comandos de gestión de sesiones",
63
63
  "cmd.clear": "Borrar la conversación",
64
64
  "cmd.quit": "Salir de Sayknow-CLI",
65
+ "modelSelector.presets": "Preajustes de modelo",
66
+ "modelSelector.createCustom": "Crear preajuste personalizado",
67
+ "modelSelector.browseAll": "Ver todos los modelos",
68
+ "modelSelector.applySession": "Aplicar en esta sesión",
69
+ "modelSelector.setDefault": "Establecer como predeterminado",
70
+ "modelSelector.pressEnterApply": "Pulsa Enter para aplicar este preajuste",
71
+ "modelSelector.presetPreview": "Vista previa del preajuste: {name}",
72
+ "modelSelector.showingScoped": "Mostrando modelos del ámbito --models",
73
+ "modelSelector.modelsTab": "Modelos",
74
+ "modelSelector.noMatching": "No hay modelos coincidentes.",
75
+ "modelSelector.modelName": "Nombre del modelo: {value}",
76
+ "modelSelector.actionFor": "Acción para: {id}",
77
+ "modelSelector.reasoningFor": "Razonamiento para {target}: {id}",
78
+ "modelSelector.temporaryModel": "modelo temporal",
65
79
  };
@@ -62,4 +62,18 @@ export const fr: Partial<Record<MsgKey, string>> = {
62
62
  "cmd.session": "Commandes de gestion de session",
63
63
  "cmd.clear": "Effacer la conversation",
64
64
  "cmd.quit": "Quitter Sayknow-CLI",
65
+ "modelSelector.presets": "Préréglages de modèle",
66
+ "modelSelector.createCustom": "Créer un préréglage personnalisé",
67
+ "modelSelector.browseAll": "Parcourir tous les modèles",
68
+ "modelSelector.applySession": "Appliquer pour cette session",
69
+ "modelSelector.setDefault": "Définir par défaut",
70
+ "modelSelector.pressEnterApply": "Appuyez sur Entrée pour appliquer ce préréglage",
71
+ "modelSelector.presetPreview": "Aperçu du préréglage : {name}",
72
+ "modelSelector.showingScoped": "Affichage des modèles de la portée --models",
73
+ "modelSelector.modelsTab": "Modèles",
74
+ "modelSelector.noMatching": "Aucun modèle correspondant.",
75
+ "modelSelector.modelName": "Nom du modèle : {value}",
76
+ "modelSelector.actionFor": "Action pour : {id}",
77
+ "modelSelector.reasoningFor": "Raisonnement pour {target} : {id}",
78
+ "modelSelector.temporaryModel": "modèle temporaire",
65
79
  };
@@ -66,4 +66,18 @@ export const ja: Partial<Record<MsgKey, string>> = {
66
66
  "cmd.session": "セッション管理コマンド",
67
67
  "cmd.clear": "会話をクリア",
68
68
  "cmd.quit": "Sayknow-CLI を終了",
69
+ "modelSelector.presets": "モデルプリセット",
70
+ "modelSelector.createCustom": "カスタムプリセットを作成",
71
+ "modelSelector.browseAll": "すべてのモデルを表示",
72
+ "modelSelector.applySession": "このセッションのみ適用",
73
+ "modelSelector.setDefault": "デフォルトに設定",
74
+ "modelSelector.pressEnterApply": "Enter キーでこのプリセットを適用",
75
+ "modelSelector.presetPreview": "プリセットのプレビュー: {name}",
76
+ "modelSelector.showingScoped": "--models スコープのモデルを表示中",
77
+ "modelSelector.modelsTab": "モデル",
78
+ "modelSelector.noMatching": "一致するモデルがありません。",
79
+ "modelSelector.modelName": "モデル名: {value}",
80
+ "modelSelector.actionFor": "操作対象: {id}",
81
+ "modelSelector.reasoningFor": "{target} の推論レベル: {id}",
82
+ "modelSelector.temporaryModel": "一時モデル",
69
83
  };
@@ -66,4 +66,18 @@ export const ko: Partial<Record<MsgKey, string>> = {
66
66
  "cmd.session": "세션 관리 명령",
67
67
  "cmd.clear": "대화 지우기",
68
68
  "cmd.quit": "Sayknow-CLI 종료",
69
+ "modelSelector.presets": "모델 프리셋",
70
+ "modelSelector.createCustom": "커스텀 프리셋 만들기",
71
+ "modelSelector.browseAll": "전체 모델 보기",
72
+ "modelSelector.applySession": "이 세션에만 적용",
73
+ "modelSelector.setDefault": "기본값으로 설정",
74
+ "modelSelector.pressEnterApply": "Enter를 눌러 이 프리셋 적용",
75
+ "modelSelector.presetPreview": "프리셋 미리보기: {name}",
76
+ "modelSelector.showingScoped": "--models 범위의 모델만 표시 중",
77
+ "modelSelector.modelsTab": "모델",
78
+ "modelSelector.noMatching": "일치하는 모델이 없습니다.",
79
+ "modelSelector.modelName": "모델 이름: {value}",
80
+ "modelSelector.actionFor": "작업 대상: {id}",
81
+ "modelSelector.reasoningFor": "{target} 추론 수준: {id}",
82
+ "modelSelector.temporaryModel": "임시 모델",
69
83
  };
@@ -66,4 +66,18 @@ export const zh: Partial<Record<MsgKey, string>> = {
66
66
  "cmd.session": "会话管理命令",
67
67
  "cmd.clear": "清空对话",
68
68
  "cmd.quit": "退出 Sayknow-CLI",
69
+ "modelSelector.presets": "模型预设",
70
+ "modelSelector.createCustom": "创建自定义预设",
71
+ "modelSelector.browseAll": "浏览全部模型",
72
+ "modelSelector.applySession": "仅本次会话应用",
73
+ "modelSelector.setDefault": "设为默认",
74
+ "modelSelector.pressEnterApply": "按 Enter 应用此预设",
75
+ "modelSelector.presetPreview": "预设预览:{name}",
76
+ "modelSelector.showingScoped": "正在显示 --models 范围内的模型",
77
+ "modelSelector.modelsTab": "模型",
78
+ "modelSelector.noMatching": "没有匹配的模型。",
79
+ "modelSelector.modelName": "模型名称:{value}",
80
+ "modelSelector.actionFor": "操作对象:{id}",
81
+ "modelSelector.reasoningFor": "{target} 的推理级别:{id}",
82
+ "modelSelector.temporaryModel": "临时模型",
69
83
  };
@@ -29,6 +29,7 @@ import {
29
29
  type ScopedModelSelection,
30
30
  } from "../../config/model-resolver";
31
31
  import type { Settings } from "../../config/settings";
32
+ import { t } from "../../i18n";
32
33
  import { type ThemeColor, theme } from "../../modes/theme/theme";
33
34
  import { formatModelOnboardingInlineHint } from "../../setup/model-onboarding-guidance";
34
35
  import { formatClampedModelSelector, getThinkingLevelMetadata, parseThinkingLevel } from "../../thinking";
@@ -190,7 +191,7 @@ const PROFILE_ROLE_PREVIEW_ORDER: SkcModelAssignmentTargetId[] = [
190
191
  "critic",
191
192
  "architect",
192
193
  ];
193
- const PRESET_SCOPE_LABELS = ["Apply for this session", "Set as default"];
194
+ const presetScopeLabels = (): readonly string[] => [t("modelSelector.applySession"), t("modelSelector.setDefault")];
194
195
 
195
196
  function isPrintableCharacter(keyData: string): boolean {
196
197
  return keyData.length === 1 && keyData >= " " && keyData !== "\x7f";
@@ -290,8 +291,7 @@ export class ModelSelectorComponent extends Container {
290
291
  this.addChild(new Spacer(1));
291
292
 
292
293
  // Add hint about model filtering
293
- const hintText =
294
- scopedModels.length > 0 ? "Showing models from --models scope" : formatModelOnboardingInlineHint();
294
+ const hintText = scopedModels.length > 0 ? t("modelSelector.showingScoped") : formatModelOnboardingInlineHint();
295
295
  this.addChild(new Text(theme.fg("warning", hintText), 0, 0));
296
296
  this.addChild(new Spacer(1));
297
297
 
@@ -594,7 +594,7 @@ export class ModelSelectorComponent extends Container {
594
594
  this.#headerContainer.clear();
595
595
 
596
596
  const tabs: Tab[] = this.#providers.map(provider => ({ id: provider.id, label: provider.label }));
597
- const tabBar = new TabBar("Models", tabs, getTabBarTheme(), this.#activeTabIndex);
597
+ const tabBar = new TabBar(t("modelSelector.modelsTab"), tabs, getTabBarTheme(), this.#activeTabIndex);
598
598
  tabBar.onTabChange = (_tab, index) => {
599
599
  this.#activeTabIndex = index;
600
600
  this.#selectedIndex = 0;
@@ -830,19 +830,19 @@ export class ModelSelectorComponent extends Container {
830
830
  this.#headerContainer.clear();
831
831
  this.#tabBar = null;
832
832
  this.#listContainer.clear();
833
- this.#headerContainer.addChild(new Text(theme.fg("accent", "Model presets"), 0, 0));
833
+ this.#headerContainer.addChild(new Text(theme.fg("accent", t("modelSelector.presets")), 0, 0));
834
834
  const rows = this.#getPresetRows();
835
835
  for (let i = 0; i < rows.length; i++) {
836
836
  const row = rows[i];
837
837
  const selected = i === this.#presetCursor;
838
838
  const prefix = selected ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
839
839
  if (row.kind === "create") {
840
- const label = "Create custom preset";
840
+ const label = t("modelSelector.createCustom");
841
841
  this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
842
842
  continue;
843
843
  }
844
844
  if (row.kind === "browse") {
845
- const label = "Browse all models";
845
+ const label = t("modelSelector.browseAll");
846
846
  this.#listContainer.addChild(new Text(`${prefix}${selected ? theme.fg("accent", label) : label}`, 0, 0));
847
847
  continue;
848
848
  }
@@ -872,7 +872,14 @@ export class ModelSelectorComponent extends Container {
872
872
  #renderPresetPreview(profile: ModelProfileDefinition): void {
873
873
  this.#listContainer.addChild(new Spacer(1));
874
874
  this.#listContainer.addChild(
875
- new Text(theme.fg("muted", ` Preset preview: ${getModelProfilePresentation(profile).displayName}`), 0, 0),
875
+ new Text(
876
+ theme.fg(
877
+ "muted",
878
+ ` ${t("modelSelector.presetPreview", { name: getModelProfilePresentation(profile).displayName })}`,
879
+ ),
880
+ 0,
881
+ 0,
882
+ ),
876
883
  );
877
884
  for (const role of PROFILE_ROLE_PREVIEW_ORDER) {
878
885
  const selector = profile.modelMapping[role];
@@ -889,15 +896,15 @@ export class ModelSelectorComponent extends Container {
889
896
  }
890
897
  this.#listContainer.addChild(new Spacer(1));
891
898
  if (this.#presetScopeMenuOpen) {
892
- for (let i = 0; i < PRESET_SCOPE_LABELS.length; i++) {
893
- const label = PRESET_SCOPE_LABELS[i] ?? "";
899
+ for (let i = 0; i < presetScopeLabels().length; i++) {
900
+ const label = presetScopeLabels()[i] ?? "";
894
901
  const prefix = i === this.#presetScopeIndex ? theme.fg("accent", `${theme.nav.cursor} `) : " ";
895
902
  this.#listContainer.addChild(
896
903
  new Text(`${prefix}${i === this.#presetScopeIndex ? theme.fg("accent", label) : label}`, 0, 0),
897
904
  );
898
905
  }
899
906
  } else {
900
- this.#listContainer.addChild(new Text(theme.fg("muted", " Press Enter to apply this preset"), 0, 0));
907
+ this.#listContainer.addChild(new Text(theme.fg("muted", ` ${t("modelSelector.pressEnterApply")}`), 0, 0));
901
908
  }
902
909
  }
903
910
 
@@ -1050,7 +1057,10 @@ export class ModelSelectorComponent extends Container {
1050
1057
  const statusMessage = this.#getProviderEmptyStateMessage();
1051
1058
  this.#listContainer.addChild(
1052
1059
  new Text(
1053
- theme.fg("muted", statusMessage ?? ` No matching models. ${formatModelOnboardingInlineHint()}`),
1060
+ theme.fg(
1061
+ "muted",
1062
+ statusMessage ?? ` ${t("modelSelector.noMatching")} ${formatModelOnboardingInlineHint()}`,
1063
+ ),
1054
1064
  0,
1055
1065
  0,
1056
1066
  ),
@@ -1065,7 +1075,11 @@ export class ModelSelectorComponent extends Container {
1065
1075
  ? ` (${selected.model.provider}/${selected.model.id}, ${(selected as CanonicalModelItem).variantCount} variants)`
1066
1076
  : "";
1067
1077
  this.#listContainer.addChild(
1068
- new Text(theme.fg("muted", ` Model Name: ${selected.model.name}${suffix}`), 0, 0),
1078
+ new Text(
1079
+ theme.fg("muted", ` ${t("modelSelector.modelName", { value: selected.model.name })}${suffix}`),
1080
+ 0,
1081
+ 0,
1082
+ ),
1069
1083
  );
1070
1084
  if (this.#pendingThinkingChoice) {
1071
1085
  this.#renderThinkingMenu(this.#pendingThinkingChoice);
@@ -1077,7 +1091,9 @@ export class ModelSelectorComponent extends Container {
1077
1091
 
1078
1092
  #renderActionMenu(item: ModelItem | CanonicalModelItem): void {
1079
1093
  this.#listContainer.addChild(new Spacer(1));
1080
- this.#listContainer.addChild(new Text(theme.fg("muted", ` Action for: ${item.model.id}`), 0, 0));
1094
+ this.#listContainer.addChild(
1095
+ new Text(theme.fg("muted", ` ${t("modelSelector.actionFor", { id: item.model.id })}`), 0, 0),
1096
+ );
1081
1097
  this.#listContainer.addChild(new Spacer(1));
1082
1098
  const actionCount = this.#getActionCount(item.model);
1083
1099
  for (let i = 0; i < actionCount; i++) {
@@ -1091,10 +1107,18 @@ export class ModelSelectorComponent extends Container {
1091
1107
  }
1092
1108
 
1093
1109
  #renderThinkingMenu(choice: PendingThinkingChoice): void {
1094
- const targetLabel = choice.role === null ? "temporary model" : SKC_MODEL_ASSIGNMENT_TARGETS[choice.role].name;
1110
+ const targetLabel =
1111
+ choice.role === null ? t("modelSelector.temporaryModel") : SKC_MODEL_ASSIGNMENT_TARGETS[choice.role].name;
1095
1112
  this.#listContainer.addChild(new Spacer(1));
1096
1113
  this.#listContainer.addChild(
1097
- new Text(theme.fg("muted", ` Reasoning for ${targetLabel}: ${choice.item.model.id}`), 0, 0),
1114
+ new Text(
1115
+ theme.fg(
1116
+ "muted",
1117
+ ` ${t("modelSelector.reasoningFor", { target: targetLabel, id: choice.item.model.id })}`,
1118
+ ),
1119
+ 0,
1120
+ 0,
1121
+ ),
1098
1122
  );
1099
1123
  this.#listContainer.addChild(new Spacer(1));
1100
1124
  for (let i = 0; i < choice.levels.length; i++) {
@@ -1188,7 +1212,7 @@ export class ModelSelectorComponent extends Container {
1188
1212
  if (rows.length === 0) return;
1189
1213
  if (this.#presetScopeMenuOpen) {
1190
1214
  this.#presetScopeIndex =
1191
- this.#presetScopeIndex === 0 ? PRESET_SCOPE_LABELS.length - 1 : this.#presetScopeIndex - 1;
1215
+ this.#presetScopeIndex === 0 ? presetScopeLabels().length - 1 : this.#presetScopeIndex - 1;
1192
1216
  } else {
1193
1217
  this.#presetCursor = this.#presetCursor === 0 ? rows.length - 1 : this.#presetCursor - 1;
1194
1218
  this.#previewProfileName = undefined;
@@ -1202,7 +1226,7 @@ export class ModelSelectorComponent extends Container {
1202
1226
  const rows = this.#getPresetRows();
1203
1227
  if (rows.length === 0) return;
1204
1228
  if (this.#presetScopeMenuOpen) {
1205
- this.#presetScopeIndex = (this.#presetScopeIndex + 1) % PRESET_SCOPE_LABELS.length;
1229
+ this.#presetScopeIndex = (this.#presetScopeIndex + 1) % presetScopeLabels().length;
1206
1230
  } else {
1207
1231
  this.#presetCursor = (this.#presetCursor + 1) % rows.length;
1208
1232
  this.#previewProfileName = undefined;