@yegor256/jinka 0.0.8 → 0.0.9

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/src/event.js CHANGED
@@ -1,105 +1,99 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Event {
5
- constructor(line, count) {
6
- this.line = line;
7
- this.count = count;
8
- }
9
- text() {
10
- try {
11
- return this.#phrase(JSON.parse(this.line));
12
- } catch {
13
- return this.line.trim();
4
+ export const event = (line, count) => {
5
+ const first = (text) => text.split('\n').map((l) => l.trim()).find((l) => l !== '') ?? '';
6
+ const program = (command) => `\x1b[38;2;0;204;204m${command.trim().split(/\s+/)[0].split('/').at(-1)}\x1b[39m (${count} lines)`;
7
+ const plain = (name, input) => {
8
+ const phrases = {
9
+ Bash: () => input.description,
10
+ Agent: () => input.description,
11
+ Task: () => input.description,
12
+ Read: () => input.file_path && `reading ${input.file_path}`,
13
+ Edit: () => input.file_path && `editing ${input.file_path}`,
14
+ MultiEdit: () => input.file_path && `editing ${input.file_path}`,
15
+ Write: () => input.file_path && `writing ${input.file_path}`,
16
+ Grep: () => input.pattern && `searching for "${input.pattern}"`,
17
+ Glob: () => input.pattern && `looking for files "${input.pattern}"`,
18
+ WebFetch: () => input.url && `fetching ${input.url}`,
19
+ WebSearch: () => input.query && `searching the web for "${input.query}"`,
20
+ Skill: () => input.skill && `using the skill ${input.skill}`
21
+ };
22
+ return Object.hasOwn(phrases, name) ? phrases[name]() || undefined : undefined;
23
+ };
24
+ const part = (piece) => {
25
+ if (piece.type === 'text') {
26
+ return first(piece.text);
14
27
  }
15
- }
16
- summary() {
17
- try {
18
- return this.#first(this.#final(JSON.parse(this.line)));
19
- } catch {
20
- return '';
28
+ if (piece.type === 'tool_use') {
29
+ const input = piece.input ?? {};
30
+ const phrase = plain(piece.name, input);
31
+ if (phrase !== undefined) {
32
+ return first(phrase);
33
+ }
34
+ if (piece.name === 'Bash' && input.command !== undefined) {
35
+ return program(String(input.command));
36
+ }
37
+ return `\x1b[38;2;0;204;204m${
38
+ [piece.name, input.command ?? input.file_path ?? input.pattern ?? input.url ?? input.query ?? input.skill ?? input.description]
39
+ .filter((p) => p !== undefined)
40
+ .map((p) => first(String(p)))
41
+ .join(': ')
42
+ }\x1b[39m`;
21
43
  }
22
- }
23
- link() {
24
- try {
25
- return (this.#final(JSON.parse(this.line)).match(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:issues|pull)\/\d+/) ?? [''])[0];
26
- } catch {
27
- return '';
44
+ return '';
45
+ };
46
+ const final = (json) => {
47
+ if (json.type === 'result') {
48
+ return String(json.result ?? '');
28
49
  }
29
- }
30
- #phrase(json) {
50
+ if (json.type === 'item.completed' && json.item?.type === 'agent_message') {
51
+ return String(json.item.text ?? '');
52
+ }
53
+ return '';
54
+ };
55
+ const phrase = (json) => {
31
56
  if (json.type === 'assistant') {
32
- return (json.message?.content ?? []).map((part) => this.#part(part)).filter((p) => p !== '').join(' ');
57
+ return (json.message?.content ?? []).map((p) => part(p)).filter((p) => p !== '').join(' ');
33
58
  }
34
59
  if (json.type === 'result') {
35
- return this.#first(String(json.result ?? json.subtype ?? ''));
60
+ return first(String(json.result ?? json.subtype ?? ''));
36
61
  }
37
62
  if (json.type === 'item.completed' && json.item?.type === 'agent_message') {
38
- return this.#first(String(json.item.text ?? ''));
63
+ return first(String(json.item.text ?? ''));
39
64
  }
40
65
  if (json.type === 'item.started' && json.item?.type === 'command_execution') {
41
- return this.#program(String(json.item.command ?? ''));
66
+ return program(String(json.item.command ?? ''));
42
67
  }
43
68
  if (json.type === 'item.completed' && json.item?.type === 'file_change') {
44
69
  return `editing ${(json.item.changes ?? []).map((c) => c.path).join(', ')}`;
45
70
  }
46
71
  if (json.type === 'turn.failed') {
47
- return this.#first(String(json.error?.message ?? ''));
72
+ return first(String(json.error?.message ?? ''));
48
73
  }
49
74
  return '';
50
- }
51
- #final(json) {
52
- if (json.type === 'result') {
53
- return String(json.result ?? '');
54
- }
55
- if (json.type === 'item.completed' && json.item?.type === 'agent_message') {
56
- return String(json.item.text ?? '');
57
- }
58
- return '';
59
- }
60
- #part(part) {
61
- if (part.type === 'text') {
62
- return this.#first(part.text);
63
- }
64
- if (part.type === 'tool_use') {
65
- const input = part.input ?? {};
66
- const phrase = this.#plain(part.name, input);
67
- if (phrase !== undefined) {
68
- return this.#first(phrase);
75
+ };
76
+ return Object.freeze({
77
+ text: () => {
78
+ try {
79
+ return phrase(JSON.parse(line));
80
+ } catch {
81
+ return line.trim();
69
82
  }
70
- if (part.name === 'Bash' && input.command !== undefined) {
71
- return this.#program(String(input.command));
83
+ },
84
+ summary: () => {
85
+ try {
86
+ return first(final(JSON.parse(line)));
87
+ } catch {
88
+ return '';
89
+ }
90
+ },
91
+ link: () => {
92
+ try {
93
+ return (final(JSON.parse(line)).match(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:issues|pull)\/\d+/) ?? [''])[0];
94
+ } catch {
95
+ return '';
72
96
  }
73
- return `\x1b[38;2;0;204;204m${
74
- [part.name, input.command ?? input.file_path ?? input.pattern ?? input.url ?? input.query ?? input.skill ?? input.description]
75
- .filter((p) => p !== undefined)
76
- .map((p) => this.#first(String(p)))
77
- .join(': ')
78
- }\x1b[39m`;
79
97
  }
80
- return '';
81
- }
82
- #program(command) {
83
- return `\x1b[38;2;0;204;204m${command.trim().split(/\s+/)[0].split('/').at(-1)}\x1b[39m (${this.count} lines)`;
84
- }
85
- #plain(name, input) {
86
- const phrases = {
87
- Bash: () => input.description,
88
- Agent: () => input.description,
89
- Task: () => input.description,
90
- Read: () => input.file_path && `reading ${input.file_path}`,
91
- Edit: () => input.file_path && `editing ${input.file_path}`,
92
- MultiEdit: () => input.file_path && `editing ${input.file_path}`,
93
- Write: () => input.file_path && `writing ${input.file_path}`,
94
- Grep: () => input.pattern && `searching for "${input.pattern}"`,
95
- Glob: () => input.pattern && `looking for files "${input.pattern}"`,
96
- WebFetch: () => input.url && `fetching ${input.url}`,
97
- WebSearch: () => input.query && `searching the web for "${input.query}"`,
98
- Skill: () => input.skill && `using the skill ${input.skill}`
99
- };
100
- return Object.hasOwn(phrases, name) ? phrases[name]() || undefined : undefined;
101
- }
102
- #first(text) {
103
- return text.split('\n').map((l) => l.trim()).find((l) => l !== '') ?? '';
104
- }
105
- }
98
+ });
99
+ };
package/src/freeze.js CHANGED
@@ -1,46 +1,41 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Freeze {
5
- constructor(crew, notice, quit) {
6
- this.crew = crew;
7
- this.notice = notice;
8
- this.quit = quit;
9
- this.state = { frozen: false, idle: 0 };
10
- this.waiters = [];
11
- }
12
- on() {
13
- this.state.frozen = true;
14
- this.notice.warn('finishing, now we wait for all jobs to finish and then quit');
15
- this.#check();
16
- }
17
- off() {
18
- this.state.frozen = false;
19
- this.notice.warn('continuing, new jobs will start again');
20
- this.waiters.splice(0).forEach((w) => w());
21
- }
22
- active() {
23
- return this.state.frozen;
24
- }
25
- enter() {
26
- this.state.idle += 1;
27
- this.#check();
28
- }
29
- leave() {
30
- this.state.idle -= 1;
31
- }
32
- async thaw(signal) {
33
- signal.throwIfAborted();
34
- if (this.state.frozen) {
35
- await new Promise((resolve, reject) => {
36
- this.waiters.push(resolve);
37
- signal.addEventListener('abort', () => reject(signal.reason), { once: true });
38
- });
4
+ export const freeze = (crew, notice, quit) => {
5
+ const state = { frozen: false, idle: 0 };
6
+ const waiters = [];
7
+ const check = () => {
8
+ if (state.frozen && state.idle === crew.size()) {
9
+ quit();
39
10
  }
40
- }
41
- #check() {
42
- if (this.state.frozen && this.state.idle === this.crew.size()) {
43
- this.quit();
11
+ };
12
+ return Object.freeze({
13
+ on: () => {
14
+ state.frozen = true;
15
+ notice.warn('finishing, now we wait for all jobs to finish and then quit');
16
+ check();
17
+ },
18
+ off: () => {
19
+ state.frozen = false;
20
+ notice.warn('continuing, new jobs will start again');
21
+ waiters.splice(0).forEach((w) => w());
22
+ },
23
+ active: () => state.frozen,
24
+ enter: () => {
25
+ state.idle += 1;
26
+ check();
27
+ },
28
+ leave: () => {
29
+ state.idle -= 1;
30
+ },
31
+ thaw: async (signal) => {
32
+ signal.throwIfAborted();
33
+ if (state.frozen) {
34
+ await new Promise((resolve, reject) => {
35
+ waiters.push(resolve);
36
+ signal.addEventListener('abort', () => reject(signal.reason), { once: true });
37
+ });
38
+ }
44
39
  }
45
- }
46
- }
40
+ });
41
+ };
package/src/frozen.js CHANGED
@@ -1,26 +1,24 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Frozen {
5
- constructor(origin, freeze) {
6
- this.origin = origin;
7
- this.freeze = freeze;
8
- }
9
- async next(signal) {
10
- this.freeze.enter();
11
- try {
12
- return await this.#wish(signal);
13
- } finally {
14
- this.freeze.leave();
4
+ export const frozen = (origin, freeze) => {
5
+ const wish = async (signal) => {
6
+ await freeze.thaw(signal);
7
+ const found = await origin.next(signal);
8
+ if (freeze.active()) {
9
+ found.free();
10
+ return wish(signal);
15
11
  }
16
- }
17
- async #wish(signal) {
18
- await this.freeze.thaw(signal);
19
- const wish = await this.origin.next(signal);
20
- if (this.freeze.active()) {
21
- wish.free();
22
- return this.#wish(signal);
12
+ return found;
13
+ };
14
+ return Object.freeze({
15
+ next: async (signal) => {
16
+ freeze.enter();
17
+ try {
18
+ return await wish(signal);
19
+ } finally {
20
+ freeze.leave();
21
+ }
23
22
  }
24
- return wish;
25
- }
26
- }
23
+ });
24
+ };
package/src/genie.js CHANGED
@@ -2,49 +2,44 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
4
  import { createInterface } from 'node:readline';
5
- import { Event } from './event.js';
6
- import { Tokens } from './tokens.js';
7
- import { Model } from './model.js';
5
+ import { event } from './event.js';
6
+ import { tokens } from './tokens.js';
7
+ import { model } from './model.js';
8
8
 
9
- export class Genie {
10
- constructor(engine, disk, meter) {
11
- this.engine = engine;
12
- this.disk = disk;
13
- this.meter = meter;
14
- }
15
- async run(wish, row, signal) {
9
+ export const genie = (engine, disk, meter) => Object.freeze({
10
+ run: async (wish, row, signal) => {
16
11
  const prompt = [
17
12
  'Make this wish come true, working on your own without asking any questions:',
18
13
  '',
19
14
  await wish.text(),
20
15
  '',
21
- ...this.disk.lines(wish),
22
- ...this.engine.lines(),
16
+ ...disk.lines(wish),
17
+ ...engine.lines(),
23
18
  'When you finish, end with a single plain sentence that says what you did.',
24
19
  'On the next line, write the full URL of the GitHub issue or pull request you worked on, if there was one.'
25
20
  ].join('\n');
26
- const log = await this.disk.log(wish);
27
- const child = this.engine.start(this.disk.args(), signal);
21
+ const log = await disk.log(wish);
22
+ const child = engine.start(disk.args(), signal);
28
23
  child.stdin.on('error', () => {});
29
24
  child.stdin.end(prompt);
30
25
  child.stdout.pipe(log, { end: false });
31
26
  child.stderr.pipe(log, { end: false });
32
27
  let summary = '';
33
28
  let link = '';
34
- const usage = new Tokens();
35
- const model = new Model();
29
+ const usage = tokens();
30
+ const m = model();
36
31
  let count = 0;
37
32
  createInterface({ input: child.stderr }).on('line', () => {
38
33
  count += 1;
39
34
  });
40
35
  createInterface({ input: child.stdout }).on('line', (line) => {
41
36
  count += 1;
42
- this.meter.add(line);
37
+ meter.add(line);
43
38
  usage.add(line);
44
- model.add(line);
45
- summary = new Event(line, count).summary() || summary;
46
- link = new Event(line, count).link() || link;
47
- const text = new Event(line, count).text();
39
+ m.add(line);
40
+ summary = event(line, count).summary() || summary;
41
+ link = event(line, count).link() || link;
42
+ const text = event(line, count).text();
48
43
  if (text !== '') {
49
44
  row.show(text);
50
45
  }
@@ -55,7 +50,7 @@ export class Genie {
55
50
  reject(new Error(`Cannot start the agent "${child.spawnfile}": ${e.message}`));
56
51
  }
57
52
  });
58
- child.on('close', (code) => log.end(() => resolve({ code: code ?? 1, summary, link, tokens: usage.text(), model: model.text() })));
53
+ child.on('close', (code) => log.end(() => resolve({ code: code ?? 1, summary, link, tokens: usage.text(), model: m.text() })));
59
54
  });
60
55
  }
61
- }
56
+ });
package/src/jinka.js CHANGED
@@ -1,20 +1,14 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- import { Wand } from './wand.js';
4
+ import { wand } from './wand.js';
5
5
 
6
- export class Jinka {
7
- constructor(wishes, agent, screen, crew) {
8
- this.wishes = wishes;
9
- this.agent = agent;
10
- this.screen = screen;
11
- this.crew = crew;
12
- }
13
- async run(signal) {
14
- await this.crew.run(
15
- this.wishes,
16
- (i, wishes, s) => new Wand(wishes, this.agent, this.screen.row(i), this.screen.notice()).run(s),
6
+ export const jinka = (wishes, agent, screen, crew) => Object.freeze({
7
+ run: async (signal) => {
8
+ await crew.run(
9
+ wishes,
10
+ (i, w, s) => wand(w, agent, screen.row(i), screen.notice()).run(s),
17
11
  signal
18
12
  );
19
13
  }
20
- }
14
+ });
package/src/jobs.js CHANGED
@@ -1,19 +1,16 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Jobs {
5
- constructor(agent) {
6
- this.agent = agent;
7
- this.count = { done: 0 };
8
- }
9
- async run(wish, row, signal) {
10
- const result = await this.agent.run(wish, row, signal);
11
- if (result.code === 0) {
12
- this.count.done += 1;
13
- }
14
- return result;
15
- }
16
- text() {
17
- return `${this.count.done}`;
18
- }
19
- }
4
+ export const jobs = (agent) => {
5
+ const count = { done: 0 };
6
+ return Object.freeze({
7
+ run: async (wish, row, signal) => {
8
+ const result = await agent.run(wish, row, signal);
9
+ if (result.code === 0) {
10
+ count.done += 1;
11
+ }
12
+ return result;
13
+ },
14
+ text: () => `${count.done}`
15
+ });
16
+ };
package/src/keys.js CHANGED
@@ -1,28 +1,24 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Keys {
5
- constructor(stream, freeze, crew, interrupt) {
6
- this.stream = stream;
7
- this.freeze = freeze;
8
- this.crew = crew;
9
- this.interrupt = interrupt;
10
- }
11
- listen() {
12
- if (this.stream.isTTY) {
13
- this.stream.setRawMode(true);
14
- }
15
- this.stream.on('data', (chunk) => [...chunk.toString()].forEach((k) => this.#press(k)));
16
- }
17
- #press(key) {
4
+ export const keys = (stream, freeze, crew, interrupt) => {
5
+ const press = (key) => {
18
6
  ({
19
- f: () => this.freeze.on(),
20
- F: () => this.freeze.on(),
21
- c: () => this.freeze.off(),
22
- C: () => this.freeze.off(),
23
- '+': () => this.crew.more(),
24
- '-': () => this.crew.less(),
25
- '\x03': () => this.interrupt()
7
+ f: () => freeze.on(),
8
+ F: () => freeze.on(),
9
+ c: () => freeze.off(),
10
+ C: () => freeze.off(),
11
+ '+': () => crew.more(),
12
+ '-': () => crew.less(),
13
+ '\x03': () => interrupt()
26
14
  })[key]?.();
27
- }
28
- }
15
+ };
16
+ return Object.freeze({
17
+ listen: () => {
18
+ if (stream.isTTY) {
19
+ stream.setRawMode(true);
20
+ }
21
+ stream.on('data', (chunk) => [...chunk.toString()].forEach((k) => press(k)));
22
+ }
23
+ });
24
+ };
package/src/legend.js CHANGED
@@ -3,17 +3,13 @@
3
3
 
4
4
  import { styleText } from 'node:util';
5
5
 
6
- export class Legend {
7
- constructor(freeze, version) {
8
- this.freeze = freeze;
9
- this.version = version;
10
- }
11
- text() {
12
- return `${this.#keys()}\t${this.version}`;
13
- }
14
- #keys() {
15
- return this.freeze.active()
6
+ export const legend = (freeze, release) => {
7
+ const keys = () => (
8
+ freeze.active()
16
9
  ? `keys: ${styleText('bgGray', 'C', { validateStream: false })} - to continue`
17
- : `keys: ${styleText('bgGray', 'F', { validateStream: false })} - to finish and quit, ${styleText('bgGray', '+/-', { validateStream: false })} to add threads`;
18
- }
19
- }
10
+ : `keys: ${styleText('bgGray', 'F', { validateStream: false })} - to finish and quit, ${styleText('bgGray', '+/-', { validateStream: false })} to add threads`
11
+ );
12
+ return Object.freeze({
13
+ text: () => `${keys()}\t${release.text()}`
14
+ });
15
+ };
package/src/limited.js CHANGED
@@ -1,19 +1,16 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Limited {
5
- constructor(origin, limit, freeze) {
6
- this.origin = origin;
7
- this.limit = limit;
8
- this.freeze = freeze;
9
- this.state = { given: 0 };
10
- }
11
- async next(signal) {
12
- const wish = await this.origin.next(signal);
13
- this.state.given += 1;
14
- if (this.state.given >= this.limit) {
15
- this.freeze.on();
4
+ export const limited = (origin, limit, freeze) => {
5
+ const state = { given: 0 };
6
+ return Object.freeze({
7
+ next: async (signal) => {
8
+ const wish = await origin.next(signal);
9
+ state.given += 1;
10
+ if (state.given >= limit) {
11
+ freeze.on();
12
+ }
13
+ return wish;
16
14
  }
17
- return wish;
18
- }
19
- }
15
+ });
16
+ };
package/src/log.js CHANGED
@@ -1,21 +1,12 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- import { stripVTControlCharacters } from 'node:util';
5
- import { Row } from './row.js';
6
- import { Protocol } from './protocol.js';
4
+ import { protocol } from './protocol.js';
7
5
 
8
- export class Log {
9
- constructor(stream) {
10
- this.stream = stream;
11
- }
12
- row(index) {
13
- return new Row({ push: ([i, text]) => this.stream.write(`[${i + 1}] ${stripVTControlCharacters(text).replace('\t', ' ')}\n`) }, index);
14
- }
15
- notice() {
16
- return new Protocol({ push: ([, text]) => this.stream.write(`${new Date().toTimeString().slice(0, 8)} ${text}\n`) });
17
- }
18
- up() {}
19
- down() {}
20
- close() {}
21
- }
6
+ export const log = (stream) => Object.freeze({
7
+ row: () => Object.freeze({ show: () => {} }),
8
+ notice: () => protocol({ push: ([, text]) => stream.write(`${new Date().toTimeString().slice(0, 8)} ${text}\n`) }),
9
+ up: () => {},
10
+ down: () => {},
11
+ close: () => {}
12
+ });
package/src/logo.js CHANGED
@@ -1,8 +1,6 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Logo {
5
- lines() {
6
- return ['▚▄▄▙▄▄▄▄▗▄▖ ', '▞▀▀▛▀▀▀▛▀▀▀▀'].map((l) => `\x1b[38;2;215;119;87m${l}\x1b[39m`);
7
- }
8
- }
4
+ export const logo = () => Object.freeze({
5
+ lines: () => ['▚▄▄▙▄▄▄▄▗▄▖ ', '▞▀▀▛▀▀▀▛▀▀▀▀'].map((l) => `\x1b[38;2;215;119;87m${l}\x1b[39m`)
6
+ });
package/src/meters.js CHANGED
@@ -1,11 +1,6 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Meters {
5
- constructor(meters) {
6
- this.meters = meters;
7
- }
8
- add(line) {
9
- this.meters.forEach((meter) => meter.add(line));
10
- }
11
- }
4
+ export const meters = (m) => Object.freeze({
5
+ add: (line) => m.forEach((meter) => meter.add(line))
6
+ });
package/src/model.js CHANGED
@@ -1,25 +1,25 @@
1
1
  // SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
2
2
  // SPDX-License-Identifier: MIT
3
3
 
4
- export class Model {
5
- constructor() {
6
- this.names = [];
7
- }
8
- add(line) {
9
- try {
10
- const json = JSON.parse(line);
11
- if (json.type === 'system' && json.subtype === 'init' && typeof json.model === 'string') {
12
- this.names.push(json.model);
4
+ export const model = () => {
5
+ const names = [];
6
+ return Object.freeze({
7
+ add: (line) => {
8
+ try {
9
+ const json = JSON.parse(line);
10
+ if (json.type === 'system' && json.subtype === 'init' && typeof json.model === 'string') {
11
+ names.push(json.model);
12
+ }
13
+ } catch {
14
+ return;
13
15
  }
14
- } catch {
15
- return;
16
+ },
17
+ text: () => {
18
+ const parts = (names.at(-1) ?? '').replace(/^claude-/, '').replace(/-\d{8}$/, '').split('-').filter((p) => p !== '');
19
+ return [
20
+ ...parts.filter((p) => !/^\d+$/.test(p)).map((p) => `${p[0].toUpperCase()}${p.slice(1)}`),
21
+ parts.filter((p) => /^\d+$/.test(p)).join('.')
22
+ ].join('');
16
23
  }
17
- }
18
- text() {
19
- const parts = (this.names.at(-1) ?? '').replace(/^claude-/, '').replace(/-\d{8}$/, '').split('-').filter((p) => p !== '');
20
- return [
21
- ...parts.filter((p) => !/^\d+$/.test(p)).map((p) => `${p[0].toUpperCase()}${p.slice(1)}`),
22
- parts.filter((p) => /^\d+$/.test(p)).join('.')
23
- ].join('');
24
- }
25
- }
24
+ });
25
+ };