ai-usage-analyzer 0.1.0 → 0.2.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/README.md +38 -4
- package/bin/ai-usage.js +31 -6
- package/package.json +1 -1
- package/src/markdown.js +257 -0
package/README.md
CHANGED
|
@@ -76,10 +76,12 @@ npx -y github:adetxt/ai-usage-analyzer --help
|
|
|
76
76
|
## Usage
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
ai-usage
|
|
80
|
-
ai-usage --top 10
|
|
81
|
-
ai-usage --json
|
|
82
|
-
ai-usage --
|
|
79
|
+
ai-usage # default TUI
|
|
80
|
+
ai-usage --top 10 # show top 10 heaviest sessions
|
|
81
|
+
ai-usage --json # machine-readable JSON output
|
|
82
|
+
ai-usage --markdown # GitHub-flavored Markdown report
|
|
83
|
+
ai-usage --md > report.md # same, save to file
|
|
84
|
+
ai-usage -h # help (also --help)
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
### Environment overrides
|
|
@@ -179,6 +181,38 @@ ai-usage --json | jq '.summary'
|
|
|
179
181
|
# }
|
|
180
182
|
```
|
|
181
183
|
|
|
184
|
+
### Markdown (`--markdown` / `--md`)
|
|
185
|
+
|
|
186
|
+
GitHub-flavored markdown report with the same sections as the TUI (header,
|
|
187
|
+
detected tools, overview, token breakdown, per-project, per-month, per-week,
|
|
188
|
+
top sessions, notes). Designed to be pasted into GitHub issues, PRs, or
|
|
189
|
+
Notion pages.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
ai-usage --md > report.md
|
|
193
|
+
cat report.md # or just paste the output into a GitHub comment
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Distribution bars use Unicode block characters (`█░`) so they render in
|
|
197
|
+
plain markdown without colors. Sample output:
|
|
198
|
+
|
|
199
|
+
```markdown
|
|
200
|
+
# AI Token Usage Report
|
|
201
|
+
|
|
202
|
+
**Range**: 2026-04-01 → 2026-06-27
|
|
203
|
+
**Sessions**: 411
|
|
204
|
+
**Total tokens**: 1.58B
|
|
205
|
+
**Cost**: $29.40 (opencode only)
|
|
206
|
+
|
|
207
|
+
## Detected AI Tools
|
|
208
|
+
|
|
209
|
+
| Tool | Status | Path | Count | Tokens |
|
|
210
|
+
|---|---|---|---:|---|
|
|
211
|
+
| Codex | ✅ present | `~/.codex/sessions` | 94 | ✅ |
|
|
212
|
+
| OpenCode | ✅ present | `~/.local/share/opencode/opencode.db` | 328 | ✅ |
|
|
213
|
+
| ...
|
|
214
|
+
```
|
|
215
|
+
|
|
182
216
|
## License
|
|
183
217
|
|
|
184
218
|
MIT
|
package/bin/ai-usage.js
CHANGED
|
@@ -10,14 +10,18 @@ import {
|
|
|
10
10
|
renderPerProject, renderPerMonth, renderPerWeek,
|
|
11
11
|
renderTopSessions, renderNotes,
|
|
12
12
|
} from '../src/render.js';
|
|
13
|
+
import { renderMarkdown } from '../src/markdown.js';
|
|
13
14
|
|
|
14
15
|
const args = process.argv.slice(2);
|
|
15
|
-
const
|
|
16
|
-
const showHelp =
|
|
17
|
-
const jsonOut =
|
|
16
|
+
const hasFlag = (name) => args.includes(`--${name}`) || args.includes(`-${name}`);
|
|
17
|
+
const showHelp = hasFlag('help') || hasFlag('h');
|
|
18
|
+
const jsonOut = hasFlag('json');
|
|
19
|
+
const mdOut = hasFlag('markdown') || hasFlag('md');
|
|
18
20
|
const topN = (() => {
|
|
19
21
|
const i = args.indexOf('--top');
|
|
20
|
-
|
|
22
|
+
if (i < 0) return 5;
|
|
23
|
+
const v = parseInt(args[i + 1], 10);
|
|
24
|
+
return Number.isFinite(v) && v > 0 ? v : 5;
|
|
21
25
|
})();
|
|
22
26
|
|
|
23
27
|
if (showHelp) {
|
|
@@ -28,9 +32,16 @@ Usage:
|
|
|
28
32
|
ai-usage [options]
|
|
29
33
|
|
|
30
34
|
Options:
|
|
31
|
-
--top N Show top N heaviest sessions (default: 5)
|
|
32
|
-
--json Output machine-readable JSON instead of TUI
|
|
33
35
|
-h, --help Show this help
|
|
36
|
+
--json Output machine-readable JSON instead of TUI
|
|
37
|
+
--markdown, --md Output as a Markdown report (GitHub-flavored tables)
|
|
38
|
+
--top N Show top N heaviest sessions (default: 5)
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
ai-usage # default TUI
|
|
42
|
+
ai-usage --json | jq .summary # pipe to jq
|
|
43
|
+
ai-usage --md > report.md # save as markdown
|
|
44
|
+
ai-usage --top 20 # show top 20 sessions
|
|
34
45
|
|
|
35
46
|
Environment overrides (per-tool data path):
|
|
36
47
|
CLAUDE_HOME, CODEX_HOME, OPENCODE_HOME, MIMOCODE_HOME,
|
|
@@ -49,6 +60,11 @@ Supported tools:
|
|
|
49
60
|
process.exit(0);
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
if (jsonOut && mdOut) {
|
|
64
|
+
console.error('Error: --json and --markdown are mutually exclusive.');
|
|
65
|
+
process.exit(2);
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
async function main() {
|
|
53
69
|
const t0 = Date.now();
|
|
54
70
|
const detections = detectAll();
|
|
@@ -70,6 +86,15 @@ async function main() {
|
|
|
70
86
|
return;
|
|
71
87
|
}
|
|
72
88
|
|
|
89
|
+
if (mdOut) {
|
|
90
|
+
const out = renderMarkdown({
|
|
91
|
+
records, detections, errors,
|
|
92
|
+
dateRange: range, topN, generatedAt: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
process.stdout.write(out);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
// TUI render
|
|
74
99
|
const sections = [
|
|
75
100
|
renderHeader({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-usage-analyzer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TUI analyzer for local AI coding agent token usage. Auto-detects Claude Code, Codex, OpenCode, MimoCode, Copilot, Antigravity, and Gemini.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.33.0",
|
package/src/markdown.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Markdown report renderer for AI token usage analysis.
|
|
2
|
+
// Produces a self-contained GitHub-flavored markdown document that
|
|
3
|
+
// copies cleanly into issues, PRs, Notion, etc.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
perProject, perMonth, perWeek, perTool, overall, topSessions, tokenBreakdown,
|
|
7
|
+
MONTH_NAMES,
|
|
8
|
+
} from './aggregate.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Number formatting helpers (re-implemented locally to avoid TUI deps)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export function fmtInt(n) {
|
|
15
|
+
return Number(n || 0).toLocaleString('en-US');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function fmtCompact(n) {
|
|
19
|
+
const a = Math.abs(n);
|
|
20
|
+
if (a >= 1e9) return (n / 1e9).toFixed(2) + 'B';
|
|
21
|
+
if (a >= 1e6) return (n / 1e6).toFixed(2) + 'M';
|
|
22
|
+
if (a >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
23
|
+
return String(n || 0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function fmtCost(n) {
|
|
27
|
+
if (!n) return '—';
|
|
28
|
+
return '$' + Number(n).toFixed(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function compactHome(p) {
|
|
32
|
+
if (!p) return '—';
|
|
33
|
+
const home = process.env.HOME || '';
|
|
34
|
+
return p.startsWith(home) ? '~' + p.slice(home.length) : p;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function bar(value, max, width) {
|
|
38
|
+
if (!max || max <= 0) return '░'.repeat(width);
|
|
39
|
+
const pct = Math.max(0, Math.min(1, value / max));
|
|
40
|
+
const filled = Math.round(pct * width);
|
|
41
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pct(x) { return (x * 100).toFixed(1) + '%'; }
|
|
45
|
+
|
|
46
|
+
function mdEscape(s) {
|
|
47
|
+
if (s == null) return '';
|
|
48
|
+
return String(s).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function shortModel(m) {
|
|
52
|
+
if (!m) return '—';
|
|
53
|
+
if (typeof m === 'string' && m.trim().startsWith('{')) {
|
|
54
|
+
try {
|
|
55
|
+
const d = JSON.parse(m);
|
|
56
|
+
const id = d.id || d.model || m;
|
|
57
|
+
const prov = d.providerId || d.provider;
|
|
58
|
+
return id + (prov ? ` (${prov})` : '');
|
|
59
|
+
} catch { return m.slice(0, 30); }
|
|
60
|
+
}
|
|
61
|
+
return m;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function today() {
|
|
65
|
+
return new Date().toISOString().split('T')[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Public: renderMarkdown
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export function renderMarkdown({
|
|
73
|
+
records = [], detections = [], errors = [],
|
|
74
|
+
dateRange = [null, null], topN = 5, generatedAt = null,
|
|
75
|
+
}) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const tot = overall(records);
|
|
78
|
+
const breakdown = tokenBreakdown(tot);
|
|
79
|
+
const tools = perTool(records);
|
|
80
|
+
const hasData = records.length > 0;
|
|
81
|
+
|
|
82
|
+
// Header --------------------------------------------------------------
|
|
83
|
+
out.push(`# AI Token Usage Report`);
|
|
84
|
+
out.push(``);
|
|
85
|
+
out.push(`> Generated by [\`ai-usage-analyzer\`](https://github.com/adetxt/ai-usage-analyzer) on ${generatedAt ? generatedAt.split('T')[0] : today()}`);
|
|
86
|
+
out.push(``);
|
|
87
|
+
|
|
88
|
+
if (hasData) {
|
|
89
|
+
out.push(`**Range**: ${dateRange[0] ?? '—'} → ${dateRange[1] ?? '—'} `);
|
|
90
|
+
out.push(`**Sessions**: ${fmtInt(records.length)} `);
|
|
91
|
+
out.push(`**Total tokens**: ${fmtCompact(tot.tokensTotal)} `);
|
|
92
|
+
if (tot.cost > 0) {
|
|
93
|
+
out.push(`**Cost**: ${fmtCost(tot.cost)} (opencode only) `);
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
out.push(`**No session data available** — only detection status below.`);
|
|
97
|
+
}
|
|
98
|
+
out.push(``);
|
|
99
|
+
|
|
100
|
+
// Detected tools ------------------------------------------------------
|
|
101
|
+
out.push(`## Detected AI Tools`);
|
|
102
|
+
out.push(``);
|
|
103
|
+
out.push(`| Tool | Status | Path | Count | Tokens |`);
|
|
104
|
+
out.push(`|---|---|---|---:|---|`);
|
|
105
|
+
for (const d of detections) {
|
|
106
|
+
const status = d.status === 'present' ? '✅ present' : '❌ absent';
|
|
107
|
+
const path = d.path ? '`' + mdEscape(compactHome(d.path)) + '`' : '—';
|
|
108
|
+
const count = d.count ? fmtInt(d.count) : '—';
|
|
109
|
+
const tok = d.hasTokens
|
|
110
|
+
? (d.count ? '✅' : '—')
|
|
111
|
+
: 'n/a';
|
|
112
|
+
out.push(`| ${mdEscape(d.name)} | ${status} | ${path} | ${count} | ${tok} |`);
|
|
113
|
+
}
|
|
114
|
+
out.push(``);
|
|
115
|
+
|
|
116
|
+
if (!hasData) {
|
|
117
|
+
if (errors && errors.length > 0) {
|
|
118
|
+
out.push(`## Errors`);
|
|
119
|
+
out.push(``);
|
|
120
|
+
for (const e of errors) out.push(`- ${mdEscape(e)}`);
|
|
121
|
+
out.push(``);
|
|
122
|
+
}
|
|
123
|
+
return out.join('\n');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Overview ------------------------------------------------------------
|
|
127
|
+
out.push(`## Overview`);
|
|
128
|
+
out.push(``);
|
|
129
|
+
out.push(`### Per-tool Summary`);
|
|
130
|
+
out.push(``);
|
|
131
|
+
out.push(`| Tool | Sessions | Total | Avg/sess | Cost |`);
|
|
132
|
+
out.push(`|---|---:|---:|---:|---:|`);
|
|
133
|
+
for (const t of tools) {
|
|
134
|
+
out.push(`| ${mdEscape(t.tool)} | ${fmtInt(t.n)} | ${fmtCompact(t.tokensTotal)} | ${fmtCompact(t.avg)} | ${fmtCost(t.cost)} |`);
|
|
135
|
+
}
|
|
136
|
+
out.push(`| **TOTAL** | **${fmtInt(records.length)}** | **${fmtCompact(tot.tokensTotal)}** | **${fmtCompact(tot.avg)}** | **${fmtCost(tot.cost)}** |`);
|
|
137
|
+
out.push(``);
|
|
138
|
+
|
|
139
|
+
out.push(`### Token Breakdown`);
|
|
140
|
+
out.push(``);
|
|
141
|
+
out.push(`| Type | Tokens | Share |`);
|
|
142
|
+
out.push(`|---|---:|---:|`);
|
|
143
|
+
out.push(`| Input | ${fmtCompact(breakdown.input)} | ${pct(breakdown.ratios.input)} |`);
|
|
144
|
+
out.push(`| Output | ${fmtCompact(breakdown.output)} | ${pct(breakdown.ratios.output)} |`);
|
|
145
|
+
out.push(`| Cache Read | ${fmtCompact(breakdown.cacheRead)} | ${pct(breakdown.ratios.cacheRead)} |`);
|
|
146
|
+
out.push(`| Cache Write | ${fmtCompact(breakdown.cacheWrite)} | ${pct(breakdown.ratios.cacheWrite)} |`);
|
|
147
|
+
out.push(`| Reasoning | ${fmtCompact(breakdown.reasoning)} | ${pct(breakdown.ratios.reasoning)} |`);
|
|
148
|
+
out.push(`| **Total** | **${fmtCompact(breakdown.total)}** | **100.0%** |`);
|
|
149
|
+
out.push(``);
|
|
150
|
+
|
|
151
|
+
// Per Project ---------------------------------------------------------
|
|
152
|
+
const projects = perProject(records);
|
|
153
|
+
if (projects.length > 0) {
|
|
154
|
+
const max = projects[0].tokensTotal;
|
|
155
|
+
out.push(`## Per Project`);
|
|
156
|
+
out.push(``);
|
|
157
|
+
out.push(`| Tool | Project | Sessions | Input | Output | Cache | Total | Dist |`);
|
|
158
|
+
out.push(`|---|---|---:|---:|---:|---:|---:|---|`);
|
|
159
|
+
for (const p of projects) {
|
|
160
|
+
const cache = p.tokensCacheRead + p.tokensCacheWrite;
|
|
161
|
+
const proj = compactHome(p.project);
|
|
162
|
+
out.push(`| ${mdEscape(p.tool)} | \`${mdEscape(proj)}\` | ${fmtInt(p.n)} | ${fmtCompact(p.tokensInput)} | ${fmtCompact(p.tokensOutput)} | ${fmtCompact(cache)} | ${fmtCompact(p.tokensTotal)} | ${bar(p.tokensTotal, max, 20)} |`);
|
|
163
|
+
}
|
|
164
|
+
out.push(``);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Per Month -----------------------------------------------------------
|
|
168
|
+
const months = perMonth(records);
|
|
169
|
+
if (months.length > 0) {
|
|
170
|
+
const max = Math.max(...months.map(m => m.tokensTotal));
|
|
171
|
+
out.push(`## Per Month`);
|
|
172
|
+
out.push(``);
|
|
173
|
+
out.push(`| Month | Sessions | Input | Output | Total | OC | CX | MM | Dist |`);
|
|
174
|
+
out.push(`|---|---:|---:|---:|---:|---:|---:|---:|---|`);
|
|
175
|
+
for (const m of months) {
|
|
176
|
+
const yyyy = m.month.slice(0, 4);
|
|
177
|
+
const mm = m.month.slice(5, 7);
|
|
178
|
+
const label = `${MONTH_NAMES[mm] || mm} ${yyyy}`;
|
|
179
|
+
out.push(`| ${label} | ${fmtInt(m.n)} | ${fmtCompact(m.tokensInput)} | ${fmtCompact(m.tokensOutput)} | ${fmtCompact(m.tokensTotal)} | ${m.byTool.opencode ? fmtCompact(m.byTool.opencode) : '—'} | ${m.byTool.codex ? fmtCompact(m.byTool.codex) : '—'} | ${m.byTool.mimocode ? fmtCompact(m.byTool.mimocode) : '—'} | ${bar(m.tokensTotal, max, 20)} |`);
|
|
180
|
+
}
|
|
181
|
+
out.push(``);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Per Week ------------------------------------------------------------
|
|
185
|
+
const weeks = perWeek(records);
|
|
186
|
+
if (weeks.length > 0) {
|
|
187
|
+
const max = Math.max(...weeks.map(w => w.tokensTotal));
|
|
188
|
+
out.push(`## Per Week`);
|
|
189
|
+
out.push(``);
|
|
190
|
+
out.push(`| Week | Sessions | Input | Output | Total | OC | CX | MM | Dist |`);
|
|
191
|
+
out.push(`|---|---:|---:|---:|---:|---:|---:|---:|---|`);
|
|
192
|
+
for (const w of weeks) {
|
|
193
|
+
out.push(`| ${w.week} | ${fmtInt(w.n)} | ${fmtCompact(w.tokensInput)} | ${fmtCompact(w.tokensOutput)} | ${fmtCompact(w.tokensTotal)} | ${w.byTool.opencode ? fmtCompact(w.byTool.opencode) : '—'} | ${w.byTool.codex ? fmtCompact(w.byTool.codex) : '—'} | ${w.byTool.mimocode ? fmtCompact(w.byTool.mimocode) : '—'} | ${bar(w.tokensTotal, max, 18)} |`);
|
|
194
|
+
}
|
|
195
|
+
out.push(``);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Top N ---------------------------------------------------------------
|
|
199
|
+
const top = topSessions(records, topN);
|
|
200
|
+
if (top.length > 0) {
|
|
201
|
+
out.push(`## Top ${top.length} Heaviest Sessions`);
|
|
202
|
+
out.push(``);
|
|
203
|
+
out.push(`| # | Total | Input | Output | Cost | Model | Project | Title |`);
|
|
204
|
+
out.push(`|---:|---:|---:|---:|---:|---|---|---|`);
|
|
205
|
+
top.forEach((r, i) => {
|
|
206
|
+
const proj = compactHome(r.project);
|
|
207
|
+
out.push(`| ${i + 1} | ${fmtCompact(r.tokensTotal)} | ${fmtCompact(r.tokensInput)} | ${fmtCompact(r.tokensOutput)} | ${r.cost ? '$' + r.cost.toFixed(4) : '—'} | ${mdEscape(shortModel(r.model))} | \`${mdEscape(proj)}\` | ${mdEscape(r.title || '—')} |`);
|
|
208
|
+
});
|
|
209
|
+
out.push(``);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Notes ---------------------------------------------------------------
|
|
213
|
+
out.push(`## Notes`);
|
|
214
|
+
out.push(``);
|
|
215
|
+
out.push(`### Token Definitions`);
|
|
216
|
+
out.push(``);
|
|
217
|
+
out.push(`- **Input** — prompt tokens sent to the model`);
|
|
218
|
+
out.push(`- **Output** — completion tokens generated by the model`);
|
|
219
|
+
out.push(`- **Cache Read** — prompt tokens served from the provider's cache (cheap)`);
|
|
220
|
+
out.push(`- **Cache Write** — prompt tokens cached for future use (OpenCode only)`);
|
|
221
|
+
out.push(`- **Reasoning** — extended thinking / chain-of-thought tokens`);
|
|
222
|
+
out.push(``);
|
|
223
|
+
|
|
224
|
+
const withTokens = detections.filter(d => d.hasTokens && d.status === 'present');
|
|
225
|
+
if (withTokens.length > 0) {
|
|
226
|
+
out.push(`### Detected Tools With Token Data`);
|
|
227
|
+
out.push(``);
|
|
228
|
+
for (const d of withTokens) {
|
|
229
|
+
out.push(`- **${mdEscape(d.name)}** — \`${mdEscape(d.description)}\``);
|
|
230
|
+
}
|
|
231
|
+
out.push(``);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const noTokens = detections.filter(d => !d.hasTokens && d.status === 'present');
|
|
235
|
+
if (noTokens.length > 0) {
|
|
236
|
+
out.push(`### Detected Tools Without Token Data`);
|
|
237
|
+
out.push(``);
|
|
238
|
+
for (const d of noTokens) {
|
|
239
|
+
out.push(`- **${mdEscape(d.name)}** — \`${mdEscape(d.description)}\``);
|
|
240
|
+
}
|
|
241
|
+
out.push(``);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (errors && errors.length > 0) {
|
|
245
|
+
out.push(`### Errors`);
|
|
246
|
+
out.push(``);
|
|
247
|
+
for (const e of errors.slice(0, 10)) {
|
|
248
|
+
out.push(`- ${mdEscape(e)}`);
|
|
249
|
+
}
|
|
250
|
+
if (errors.length > 10) {
|
|
251
|
+
out.push(`- … and ${errors.length - 10} more`);
|
|
252
|
+
}
|
|
253
|
+
out.push(``);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return out.join('\n');
|
|
257
|
+
}
|