wendkeep 0.9.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.1] — 2026-07-06
8
+
9
+ Interactive install UX: language first.
10
+
11
+ ### Added
12
+ - **`wendkeep init` asks the vault language first** on an interactive TTY (when `--locale`
13
+ isn't passed): `[1] Português [2] English`. The answer drives the folders, scaffold and
14
+ skills — and the remaining prompts (vault path, companion selection) now render in the
15
+ chosen locale instead of always Portuguese. `--yes`, `--locale` and non-TTY are unchanged.
16
+
7
17
  ## [0.9.0] — 2026-07-06
8
18
 
9
19
  Engineering debt: sensor editing + i18n coherence for auto-generated notes.
@@ -213,6 +223,7 @@ Initial release — the capture engine, extracted from a system in daily product
213
223
  - `wendkeep init` (cross-platform installer) + optional `@bitbonsai/mcpvault` MCP server.
214
224
 
215
225
  <!-- Only v0.4.0+ is tagged in git (history starts here); older versions link to npm. -->
226
+ [0.9.1]: https://github.com/rogersialves/wendkeep/releases/tag/v0.9.1
216
227
  [0.9.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.9.0
217
228
  [0.8.1]: https://github.com/rogersialves/wendkeep/releases/tag/v0.8.1
218
229
  [0.8.0]: https://github.com/rogersialves/wendkeep/releases/tag/v0.8.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "A persistent-memory harness for AI coding agents on your Obsidian vault: turn-by-turn session capture plus a native, zero-dependency spec→change→verify→archive loop (sensor-gated, independent verdict, mutation discrimination). Local-first, agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,6 +4,7 @@
4
4
  import readline from 'node:readline';
5
5
 
6
6
  const HINT = 'Espaço marca/desmarca · ↑/↓ move · a=todos · n=nenhum · Enter confirma';
7
+ const HEADER = 'Companions';
7
8
 
8
9
  export function initialCompanionMenu(companions) {
9
10
  return {
@@ -35,8 +36,8 @@ export function reduceCompanionMenu(state, action) {
35
36
  }
36
37
  }
37
38
 
38
- export function renderCompanionMenu(state) {
39
- const lines = [`Companions — ${HINT}:`];
39
+ export function renderCompanionMenu(state, { hint = HINT, header = HEADER } = {}) {
40
+ const lines = [`${header} — ${hint}:`];
40
41
  state.items.forEach((it, i) => {
41
42
  const cursor = i === state.cursor ? '>' : ' ';
42
43
  const box = it.checked ? '[x]' : '[ ]';
@@ -63,7 +64,7 @@ export function canInteractiveSelect(input = process.stdin, output = process.std
63
64
 
64
65
  // Run the raw-TTY checkbox menu; resolves with the selected ids. Caller must check
65
66
  // canInteractiveSelect() first (otherwise use the text fallback).
66
- export function selectCompanionsInteractive(companions, { input = process.stdin, output = process.stdout } = {}) {
67
+ export function selectCompanionsInteractive(companions, { input = process.stdin, output = process.stdout, labels } = {}) {
67
68
  return new Promise((resolve) => {
68
69
  let state = initialCompanionMenu(companions);
69
70
  let drawnLines = 0;
@@ -77,7 +78,7 @@ export function selectCompanionsInteractive(companions, { input = process.stdin,
77
78
  readline.moveCursor(output, 0, -drawnLines);
78
79
  readline.clearScreenDown(output);
79
80
  }
80
- const text = renderCompanionMenu(state);
81
+ const text = renderCompanionMenu(state, labels);
81
82
  output.write(`${text}\n`);
82
83
  drawnLines = text.split('\n').length;
83
84
  };
package/src/init.mjs CHANGED
@@ -192,17 +192,55 @@ function installVaultColors(vaultPath) {
192
192
 
193
193
  // --- main -------------------------------------------------------------------
194
194
 
195
+ // Interactive prompt strings by locale. The language question itself is bilingual (asked
196
+ // before the locale is known); everything after follows the answer.
197
+ const PROMPTS = {
198
+ 'pt-BR': {
199
+ vault: (f) => `Caminho do vault Obsidian (Enter aceita o padrão, ou digite outro)\n [${f}]\n> `,
200
+ companionsHeader: '\nCompanions (plugins/MCP opcionais — context-mode é a principal):',
201
+ companionsAsk: (def) => `Digite os ids separados por vírgula (Enter aceita [${def}], "none" p/ nenhum): `,
202
+ menu: { hint: 'Espaço marca/desmarca · ↑/↓ move · a=todos · n=nenhum · Enter confirma', header: 'Companions' },
203
+ },
204
+ en: {
205
+ vault: (f) => `Obsidian vault path (Enter for the default, or type another)\n [${f}]\n> `,
206
+ companionsHeader: '\nCompanions (optional plugins/MCP — context-mode is the main one):',
207
+ companionsAsk: (def) => `Enter ids comma-separated (Enter for [${def}], "none" for none): `,
208
+ menu: { hint: 'Space toggles · ↑/↓ move · a=all · n=none · Enter confirms', header: 'Companions' },
209
+ },
210
+ };
211
+
212
+ export function promptStrings(localeId) {
213
+ return PROMPTS[localeId] || PROMPTS['pt-BR'];
214
+ }
215
+
216
+ // Map the language answer to a locale id. 2/en → en; 1/pt/empty/unknown → pt-BR.
217
+ export function parseLocaleAnswer(ans) {
218
+ const a = String(ans || '').trim().toLowerCase();
219
+ if (a === '2' || a === 'en' || a === 'english') return 'en';
220
+ return 'pt-BR';
221
+ }
222
+
195
223
  export async function runInit(argv) {
196
224
  const args = parseArgs(argv);
197
225
  const projectPath = resolve(args.project || process.cwd());
198
226
  const log = (s) => process.stdout.write(`${s}\n`);
199
227
 
228
+ // Language first (i18n): an interactive TTY without --locale is asked the vault language;
229
+ // folders, prompts and scaffold all follow the answer.
230
+ if (!args.locale && process.stdin.isTTY && !args.yes) {
231
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
232
+ const ans = await rl.question('Idioma do vault / Vault language:\n [1] Português (pt-BR) [2] English (en)\n> ');
233
+ rl.close();
234
+ args.locale = parseLocaleAnswer(ans);
235
+ }
236
+ const P = promptStrings(args.locale && LOCALES[args.locale] ? args.locale : DEFAULT_LOCALE);
237
+
200
238
  let vaultPath = args.vault;
201
239
  if (!vaultPath) {
202
240
  const fallback = join(projectPath, deriveVaultDirName(projectPath));
203
241
  if (process.stdin.isTTY && !args.yes) {
204
242
  const rl = createInterface({ input: process.stdin, output: process.stdout });
205
- const ans = (await rl.question(`Obsidian vault path (Enter aceita o padrão, ou digite outro caminho)\n [${fallback}]\n> `)).trim();
243
+ const ans = (await rl.question(P.vault(fallback))).trim();
206
244
  rl.close();
207
245
  vaultPath = ans || fallback;
208
246
  } else {
@@ -222,16 +260,14 @@ export async function runInit(argv) {
222
260
  } else if (process.stdin.isTTY && !args.yes) {
223
261
  if (canInteractiveSelect()) {
224
262
  log(''); // the checkbox menu renders its own header
225
- companions = await selectCompanionsInteractive(COMPANIONS);
263
+ companions = await selectCompanionsInteractive(COMPANIONS, { labels: P.menu });
226
264
  } else {
227
265
  // Text fallback (no raw-mode TTY): list + comma input with clear instructions.
228
- log('\nCompanions (plugins/MCP opcionais — context-mode é a principal):');
266
+ log(P.companionsHeader);
229
267
  for (const c of COMPANIONS) log(` ${c.default ? '[x]' : '[ ]'} ${c.label}`);
230
268
  const def = resolveCompanions({}).join(',');
231
269
  const rl = createInterface({ input: process.stdin, output: process.stdout });
232
- const ans = (await rl.question(
233
- `Digite os ids separados por vírgula (Enter aceita [${def}], "none" p/ nenhum): `,
234
- )).trim();
270
+ const ans = (await rl.question(P.companionsAsk(def))).trim();
235
271
  rl.close();
236
272
  companions = ans.toLowerCase() === 'none' ? [] : resolveCompanions({ companionsFlag: ans || def });
237
273
  }