wendkeep 0.23.0 → 0.25.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.
- package/CHANGELOG.md +24 -0
- package/README.md +13 -1
- package/bin/wendkeep.mjs +9 -2
- package/package.json +1 -1
- package/src/cost.mjs +108 -3
- package/src/init.mjs +2 -2
- package/src/stats.mjs +45 -0
- package/src/taxonomy.mjs +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ 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.25.0] — 2026-07-08
|
|
8
|
+
|
|
9
|
+
Cost trend/projection + shareable stats + launch assets.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`wendkeep cost --trend [day|week|month]`** — cost bucketed over time plus a run-rate
|
|
13
|
+
**projection** (recent-window daily average × horizon). `wendkeep cost --write` generates a
|
|
14
|
+
`00-Custo.md` trend note in the vault (by-month table + projection + top models). `src/cost.mjs`.
|
|
15
|
+
- **`wendkeep stats`** — one shareable line: sessions · prompts · spend · date span · models
|
|
16
|
+
(`--json` too). For the npm page, a README badge line, or a post. `src/stats.mjs`.
|
|
17
|
+
- **Launch assets** (`docs/`): README hero (tagline, badges, quickstart, screenshot slot),
|
|
18
|
+
Show HN / r/ObsidianMD / X post drafts (`docs/20-launch-posts.md`), and a repeatable
|
|
19
|
+
graph-screenshot guide (`docs/21-graph-screenshot.md`).
|
|
20
|
+
|
|
21
|
+
## [0.24.0] — 2026-07-08
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **No companion is pre-selected anymore.** `context-mode` was pre-checked (and the
|
|
25
|
+
non-interactive default); wendkeep is a neutral harness and should not presume a third-party
|
|
26
|
+
plugin. The interactive picker now starts with **nothing checked**, `init --yes` (and any
|
|
27
|
+
non-interactive run) installs **no** companions, and `resolveCompanions({})` returns `[]`.
|
|
28
|
+
Opt in explicitly — interactively (Space) or `--companions context-mode`. `src/taxonomy.mjs`.
|
|
29
|
+
- Prompt/help/README text updated to reflect the empty default.
|
|
30
|
+
|
|
7
31
|
## [0.23.0] — 2026-07-08
|
|
8
32
|
|
|
9
33
|
Vault structure — generated views + housekeeping (audit wave 2).
|
package/README.md
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
# wendkeep
|
|
2
2
|
|
|
3
|
+
> **Your AI coding agent forgets every session. wendkeep makes it remember — in the Obsidian vault you already use.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/wendkeep)
|
|
3
6
|

|
|
7
|
+

|
|
8
|
+

|
|
4
9
|
|
|
5
10
|
**A persistent‑memory harness for AI coding agents, built on your Obsidian vault.** Every Claude Code / Codex session is captured turn‑by‑turn into local Markdown — with token/cost tracking, auto‑extracted decisions, bugs and learnings, and a curated memory layer injected back at the start of the next session. On top of that memory core sits a native, zero‑dependency **change lifecycle** (spec → change → TDD → sensor‑gated archive) that keeps intent, work and proof wikilinked in one graph. 100% local, open‑core.
|
|
6
11
|
|
|
12
|
+
```bash
|
|
13
|
+
npm i -D wendkeep && npx wendkeep init # captures from the next session on
|
|
14
|
+
npx wendkeep import # backfill past Claude + Codex sessions
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
<!-- SCREENSHOT: Obsidian graph of a wendkeep vault (sessions ↔ decisions ↔ bugs ↔ changes). Drop a PNG/GIF at docs/assets/graph.png and reference it here. -->
|
|
18
|
+
|
|
7
19
|
> Extracted from a system in daily production use: the capture engine, cost tracking and graph wiring are battle‑tested; the cross‑platform installer (`wendkeep init`) and the native change loop are the newer parts. See [`docs/`](https://github.com/rogersialves/wendkeep/tree/main/docs) for the project's strategy and decision log.
|
|
8
20
|
|
|
9
21
|
---
|
|
@@ -65,7 +77,7 @@ npx wendkeep init --no-companions --no-mcp --yes # zero companions,
|
|
|
65
77
|
| `--vault <path>` | Vault folder. Default `<project>/.<project-name>-vault`; interactive init asks. Point it at an existing vault to install into it. |
|
|
66
78
|
| `--project <path>` | Project root to wire (default: current directory). |
|
|
67
79
|
| `--locale <pt-BR\|en>` | Vault language — folder names, scaffold, skills. Interactive init asks; locked at init. |
|
|
68
|
-
| `--companions <csv>` | Companions to pin: `context-mode,caveman,understand-anything` (default:
|
|
80
|
+
| `--companions <csv>` | Companions to pin: `context-mode,caveman,understand-anything` (default: **none** — opt in explicitly; `dotcontext` is legacy). |
|
|
69
81
|
| `--no-companions` | Pin no companions. |
|
|
70
82
|
| `--no-mcp` | Skip **wendkeep's own** vault MCP (`wendkeep-vault`). Companion MCPs still follow `--companions`. |
|
|
71
83
|
| `--no-colors` | Skip the Obsidian color system (`.obsidian` snippet + graph groups). |
|
package/bin/wendkeep.mjs
CHANGED
|
@@ -30,7 +30,7 @@ Usage:
|
|
|
30
30
|
--project <path> Project root to wire (default: current directory).
|
|
31
31
|
--no-mcp Do not add the mcpvault MCP server to .mcp.json.
|
|
32
32
|
--companions <csv> Companion plugins/MCP to pin: context-mode,caveman,understand-anything
|
|
33
|
-
(default:
|
|
33
|
+
(default: none — opt in explicitly). dotcontext is legacy — the native a2 loop replaces it.
|
|
34
34
|
--no-companions Skip companion plugins/MCP entirely.
|
|
35
35
|
--no-colors Skip the Obsidian color system (.obsidian snippet + graph groups).
|
|
36
36
|
--dotcontext-mcp <v> dotcontext MCP placement: auto (default; skip project entry
|
|
@@ -48,7 +48,9 @@ Usage:
|
|
|
48
48
|
wendkeep spec <sub> Living specs: list | show <capability>.
|
|
49
49
|
wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
|
|
50
50
|
wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
|
|
51
|
-
--since <date> · --top [N] (priciest
|
|
51
|
+
--since <date> · --top [N] (priciest) · --trend [day|week|month]
|
|
52
|
+
(+ run-rate projection) · --write (generate 00-Custo.md) · --json.
|
|
53
|
+
wendkeep stats [--vault P] One shareable line: sessions · prompts · spend · span · models (--json).
|
|
52
54
|
wendkeep import [opts] Backfill: import this project's past Claude + Codex sessions into
|
|
53
55
|
the vault (deduped by session_id). --source all|claude|codex (default
|
|
54
56
|
all) · --stamp-ids (backfill session_id in existing notes) ·
|
|
@@ -144,6 +146,11 @@ async function main() {
|
|
|
144
146
|
runCost(rest);
|
|
145
147
|
break;
|
|
146
148
|
}
|
|
149
|
+
case 'stats': {
|
|
150
|
+
const { runStats } = await import('../src/stats.mjs');
|
|
151
|
+
runStats(rest);
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
147
154
|
case 'import': {
|
|
148
155
|
const { runImportCli } = await import('../src/import.mjs');
|
|
149
156
|
runImportCli(rest);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
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": {
|
package/src/cost.mjs
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// `wendkeep cost` — aggregate AI-coding spend across every session note in the vault.
|
|
2
2
|
// Each session note carries cost in its frontmatter (main + subagents since 0.10.0); this
|
|
3
3
|
// rolls the whole vault up: total, by day, by model. Pure aggregation + a thin CLI.
|
|
4
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { getLocale } from '../hooks/locale.mjs';
|
|
7
7
|
|
|
8
8
|
const round4 = (n) => Math.round((Number(n) || 0) * 10000) / 10000;
|
|
9
9
|
const usd = (n) => `$${(Number(n) || 0).toFixed(4)}`;
|
|
10
|
+
const today = () => {
|
|
11
|
+
const d = new Date();
|
|
12
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
function fmValue(content, key) {
|
|
12
16
|
const m = content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
@@ -24,6 +28,7 @@ export function parseSessionCost(content) {
|
|
|
24
28
|
wasted: Number(fmValue(content, 'subagents_wasted_usd')) || 0,
|
|
25
29
|
tokens: Number(fmValue(content, 'tokens_total')) || 0,
|
|
26
30
|
subTokens: Number(fmValue(content, 'subagents_tokens_total')) || 0,
|
|
31
|
+
prompts: Number(fmValue(content, 'prompts')) || 0,
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
|
|
@@ -35,8 +40,9 @@ export function aggregateCosts(entries) {
|
|
|
35
40
|
let wasted = 0;
|
|
36
41
|
let tokens = 0;
|
|
37
42
|
let subTokens = 0;
|
|
43
|
+
let prompts = 0;
|
|
38
44
|
for (const e of entries) {
|
|
39
|
-
main += e.mainCost; sub += e.subCost; wasted += e.wasted || 0; tokens += e.tokens; subTokens += e.subTokens;
|
|
45
|
+
main += e.mainCost; sub += e.subCost; wasted += e.wasted || 0; tokens += e.tokens; subTokens += e.subTokens; prompts += e.prompts || 0;
|
|
40
46
|
const d = e.date || '?';
|
|
41
47
|
(byDay[d] = byDay[d] || { cost: 0, count: 0 }).cost += e.mainCost + e.subCost;
|
|
42
48
|
byDay[d].count += 1;
|
|
@@ -48,12 +54,93 @@ export function aggregateCosts(entries) {
|
|
|
48
54
|
count: entries.length,
|
|
49
55
|
main: round4(main), sub: round4(sub), total: round4(total), wasted: round4(wasted),
|
|
50
56
|
avg: round4(entries.length ? total / entries.length : 0),
|
|
51
|
-
tokens, subTokens,
|
|
57
|
+
tokens, subTokens, prompts,
|
|
52
58
|
byDay: Object.entries(byDay).sort().map(([date, v]) => ({ date, cost: round4(v.cost), count: v.count })),
|
|
53
59
|
byModel: Object.entries(byModel).sort((a, b) => b[1].cost - a[1].cost).map(([model, v]) => ({ model, cost: round4(v.cost), count: v.count })),
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
// Shift a 'YYYY-MM-DD' string by `delta` days (UTC, pure).
|
|
64
|
+
function shiftDate(dateStr, delta) {
|
|
65
|
+
const d = new Date(`${dateStr}T00:00:00Z`);
|
|
66
|
+
d.setUTCDate(d.getUTCDate() + delta);
|
|
67
|
+
return d.toISOString().slice(0, 10);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ISO-ish week key 'YYYY-Www' for a 'YYYY-MM-DD' string (pure).
|
|
71
|
+
function isoWeek(dateStr) {
|
|
72
|
+
const d = new Date(`${dateStr}T00:00:00Z`);
|
|
73
|
+
const day = (d.getUTCDay() + 6) % 7; // Mon=0
|
|
74
|
+
d.setUTCDate(d.getUTCDate() - day + 3); // nearest Thursday
|
|
75
|
+
const firstThu = new Date(Date.UTC(d.getUTCFullYear(), 0, 4));
|
|
76
|
+
const week = 1 + Math.round(((d - firstThu) / 86400000 - 3 + ((firstThu.getUTCDay() + 6) % 7)) / 7);
|
|
77
|
+
return `${d.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Group the per-day cost series (agg.byDay) into day|week|month buckets. Pure.
|
|
81
|
+
export function trendBuckets(byDay, bucket = 'month') {
|
|
82
|
+
const keyOf = (date) => (bucket === 'day' ? date : bucket === 'week' ? isoWeek(date) : String(date).slice(0, 7));
|
|
83
|
+
const map = {};
|
|
84
|
+
for (const d of byDay || []) {
|
|
85
|
+
if (!d.date || d.date === '?') continue;
|
|
86
|
+
const k = keyOf(d.date);
|
|
87
|
+
(map[k] = map[k] || { period: k, cost: 0, count: 0 }).cost += d.cost;
|
|
88
|
+
map[k].count += d.count;
|
|
89
|
+
}
|
|
90
|
+
return Object.values(map).sort((a, b) => a.period.localeCompare(b.period)).map((b) => ({ ...b, cost: round4(b.cost) }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Run-rate projection from the last `windowDays` of activity. Pure; nowStr = 'YYYY-MM-DD'
|
|
94
|
+
// (defaults to the latest day seen). Honest: a flat run-rate, not a fitted forecast.
|
|
95
|
+
export function projectSpend(byDay, { nowStr, windowDays = 30, horizonDays = 30 } = {}) {
|
|
96
|
+
const days = (byDay || []).filter((d) => d.date && d.date !== '?');
|
|
97
|
+
if (!days.length) return { dailyRate: 0, projected: 0, windowDays, horizonDays, basisDays: 0, basisTotal: 0 };
|
|
98
|
+
const now = nowStr || days[days.length - 1].date;
|
|
99
|
+
const cutoff = shiftDate(now, -windowDays);
|
|
100
|
+
const recent = days.filter((d) => d.date > cutoff);
|
|
101
|
+
const basisTotal = recent.reduce((s, d) => s + d.cost, 0);
|
|
102
|
+
const dailyRate = basisTotal / windowDays;
|
|
103
|
+
return { dailyRate: round4(dailyRate), projected: round4(dailyRate * horizonDays), windowDays, horizonDays, basisDays: recent.length, basisTotal: round4(basisTotal) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// A generated vault note: cost by month + projection + top models. Overwrites (it is generated).
|
|
107
|
+
export function renderTrendNote(agg, proj, dateStr) {
|
|
108
|
+
const rows = trendBuckets(agg.byDay, 'month').map((b) => `| ${b.period} | ${usd(b.cost)} | ${b.count} |`).join('\n');
|
|
109
|
+
const models = agg.byModel.slice(0, 8).map((m) => `| ${m.model} | ${usd(m.cost)} | ${m.count} |`).join('\n');
|
|
110
|
+
return `---
|
|
111
|
+
type: cost-trend
|
|
112
|
+
date: ${dateStr}
|
|
113
|
+
cssclasses:
|
|
114
|
+
- topic-dashboard
|
|
115
|
+
tags:
|
|
116
|
+
- custo
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# Custo — tendência
|
|
120
|
+
|
|
121
|
+
> Gerado por \`wendkeep cost --write\`. ${agg.count} sessão(ões) · total ${usd(agg.total)} · ${usd(agg.avg)}/sessão.
|
|
122
|
+
|
|
123
|
+
## Por mês
|
|
124
|
+
|
|
125
|
+
| Mês | Custo | Sessões |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
${rows || '| — | — | — |'}
|
|
128
|
+
|
|
129
|
+
## Projeção (run-rate ${proj.windowDays}d)
|
|
130
|
+
|
|
131
|
+
Base: ${usd(proj.basisTotal)} nos últimos ${proj.windowDays} dias (${proj.basisDays} dia(s) com atividade) → **${usd(proj.dailyRate)}/dia**.
|
|
132
|
+
Projeção próximos ${proj.horizonDays} dias: **${usd(proj.projected)}**.
|
|
133
|
+
|
|
134
|
+
> Run-rate simples (média diária × horizonte), não previsão ajustada.
|
|
135
|
+
|
|
136
|
+
## Por modelo
|
|
137
|
+
|
|
138
|
+
| Modelo | Custo | Sessões |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
${models || '| — | — | — |'}
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
|
|
57
144
|
function walkNotes(dir) {
|
|
58
145
|
const out = [];
|
|
59
146
|
let names;
|
|
@@ -111,6 +198,24 @@ export function runCost(argv) {
|
|
|
111
198
|
process.exit(0);
|
|
112
199
|
}
|
|
113
200
|
|
|
201
|
+
const trendIdx = argv.indexOf('--trend');
|
|
202
|
+
if (trendIdx >= 0) {
|
|
203
|
+
const bucket = ['day', 'week', 'month'].includes(argv[trendIdx + 1]) ? argv[trendIdx + 1] : 'month';
|
|
204
|
+
const proj = projectSpend(agg.byDay);
|
|
205
|
+
process.stdout.write(`Tendência de custo (por ${bucket}):\n`);
|
|
206
|
+
for (const b of trendBuckets(agg.byDay, bucket)) process.stdout.write(` ${b.period} ${usd(b.cost).padStart(11)} (${b.count})\n`);
|
|
207
|
+
process.stdout.write(`\nRun-rate ${proj.windowDays}d: ${usd(proj.dailyRate)}/dia · projeção ${proj.horizonDays}d: ${usd(proj.projected)} (base ${usd(proj.basisTotal)} em ${proj.basisDays} dia(s))\n`);
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (argv.includes('--write')) {
|
|
212
|
+
const proj = projectSpend(agg.byDay);
|
|
213
|
+
const rel = '00-Custo.md';
|
|
214
|
+
writeFileSync(join(vaultBase, rel), renderTrendNote(agg, proj, today()), 'utf8');
|
|
215
|
+
process.stdout.write(`cost --write: ${rel} (nota de tendência gerada)\n`);
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
218
|
+
|
|
114
219
|
process.stdout.write(`Custo total (vault): ${usd(agg.total)} — ${agg.count} sessão(ões) · ${usd(agg.avg)}/sessão\n`);
|
|
115
220
|
process.stdout.write(` main: ${usd(agg.main)} · subagents: ${usd(agg.sub)}\n`);
|
|
116
221
|
if (agg.wasted) process.stdout.write(` desperdiçado (runs killed/failed): ${usd(agg.wasted)}\n`);
|
package/src/init.mjs
CHANGED
|
@@ -207,13 +207,13 @@ function installVaultColors(vaultPath) {
|
|
|
207
207
|
const PROMPTS = {
|
|
208
208
|
'pt-BR': {
|
|
209
209
|
vault: (f) => `Caminho do vault Obsidian (Enter aceita o padrão, ou digite outro)\n [${f}]\n> `,
|
|
210
|
-
companionsHeader: '\nCompanions (plugins/MCP opcionais —
|
|
210
|
+
companionsHeader: '\nCompanions (plugins/MCP opcionais — nenhum vem pré-marcado):',
|
|
211
211
|
companionsAsk: (def) => `Digite os ids separados por vírgula (Enter aceita [${def}], "none" p/ nenhum): `,
|
|
212
212
|
menu: { hint: 'Espaço marca/desmarca · ↑/↓ move · a=todos · n=nenhum · Enter confirma', header: 'Companions' },
|
|
213
213
|
},
|
|
214
214
|
en: {
|
|
215
215
|
vault: (f) => `Obsidian vault path (Enter for the default, or type another)\n [${f}]\n> `,
|
|
216
|
-
companionsHeader: '\nCompanions (optional plugins/MCP —
|
|
216
|
+
companionsHeader: '\nCompanions (optional plugins/MCP — none pre-selected):',
|
|
217
217
|
companionsAsk: (def) => `Enter ids comma-separated (Enter for [${def}], "none" for none): `,
|
|
218
218
|
menu: { hint: 'Space toggles · ↑/↓ move · a=all · n=none · Enter confirms', header: 'Companions' },
|
|
219
219
|
},
|
package/src/stats.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// `wendkeep stats` — one shareable line about what the vault has captured. For the npm page,
|
|
2
|
+
// a README badge line, or a tweet. Read-only; reuses the cost aggregation.
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
5
|
+
import { collectVaultCost } from './cost.mjs';
|
|
6
|
+
|
|
7
|
+
const usd = (n) => `$${(Number(n) || 0).toFixed(2)}`;
|
|
8
|
+
const num = (n) => String(Math.round(Number(n) || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
9
|
+
|
|
10
|
+
// Pure: derive the shareable stats from a collectVaultCost aggregate.
|
|
11
|
+
export function statsFrom(agg) {
|
|
12
|
+
const days = (agg.byDay || []).map((d) => d.date).filter((d) => d && d !== '?').sort();
|
|
13
|
+
return {
|
|
14
|
+
sessions: agg.count,
|
|
15
|
+
prompts: agg.prompts || 0,
|
|
16
|
+
cost: agg.total,
|
|
17
|
+
models: (agg.byModel || []).length,
|
|
18
|
+
firstDay: days[0] || '',
|
|
19
|
+
lastDay: days[days.length - 1] || '',
|
|
20
|
+
spanDays: days.length,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function statsLine(s) {
|
|
25
|
+
const span = s.firstDay && s.lastDay ? ` · ${s.spanDays} dia(s) (${s.firstDay}→${s.lastDay})` : '';
|
|
26
|
+
return `wendkeep: ${num(s.sessions)} sessão(ões) · ${num(s.prompts)} prompts · ${usd(s.cost)} capturado${span} · ${s.models} modelo(s)`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function opt(argv, name) {
|
|
30
|
+
const i = argv.indexOf(name);
|
|
31
|
+
if (i >= 0) return argv[i + 1];
|
|
32
|
+
const eq = argv.find((a) => a.startsWith(`${name}=`));
|
|
33
|
+
return eq ? eq.slice(name.length + 1) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function runStats(argv) {
|
|
37
|
+
const vaultRaw = opt(argv, '--vault') || process.env.OBSIDIAN_VAULT_PATH;
|
|
38
|
+
if (!vaultRaw) { process.stderr.write('wendkeep stats: no vault (--vault or OBSIDIAN_VAULT_PATH).\n'); process.exit(2); }
|
|
39
|
+
const vaultBase = isAbsolute(vaultRaw) ? vaultRaw : resolve(process.cwd(), vaultRaw);
|
|
40
|
+
if (!existsSync(vaultBase)) { process.stderr.write(`wendkeep stats: vault not found: ${vaultBase}\n`); process.exit(2); }
|
|
41
|
+
const s = statsFrom(collectVaultCost(vaultBase));
|
|
42
|
+
if (argv.includes('--json')) { process.stdout.write(`${JSON.stringify(s, null, 2)}\n`); process.exit(0); }
|
|
43
|
+
process.stdout.write(`${statsLine(s)}\n`);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
package/src/taxonomy.mjs
CHANGED
|
@@ -102,8 +102,10 @@ export function hookCommand(name) {
|
|
|
102
102
|
export const COMPANIONS = [
|
|
103
103
|
{
|
|
104
104
|
id: 'context-mode',
|
|
105
|
-
label: 'context-mode — otimizador de contexto + memória FTS5 (
|
|
106
|
-
default:
|
|
105
|
+
label: 'context-mode — otimizador de contexto + memória FTS5 (opcional)',
|
|
106
|
+
// Not a default: wendkeep is a neutral harness and does not presume a third-party plugin.
|
|
107
|
+
// Opt in interactively or with `--companions context-mode`.
|
|
108
|
+
default: false,
|
|
107
109
|
marketplace: { source: 'git', url: 'https://github.com/mksglu/context-mode.git' },
|
|
108
110
|
plugin: 'context-mode',
|
|
109
111
|
// Agent-agnostic: MCP server, self-updating via unpinned npx.
|