@yegor256/jinka 0.0.9 → 0.0.10
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 +2 -2
- package/bin/jinka.js +9 -1
- package/package.json +1 -1
- package/src/agent.js +2 -1
- package/src/claude.js +15 -14
- package/src/codex.js +18 -17
- package/src/genie.js +1 -2
- package/src/hello.js +29 -0
- package/src/model.js +5 -1
- package/src/recorded.js +34 -0
- package/src/tty.js +15 -6
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
First, you create `~/.jinka/wishes/Make-Me-Happy.md` file:
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
|
-
I wish my GitHub
|
|
10
|
+
I wish my GitHub repository to be in good shape: `foo/bar`.
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Then, you install `jinka`:
|
|
@@ -16,7 +16,7 @@ Then, you install `jinka`:
|
|
|
16
16
|
npm install -g @yegor256/jinka
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Finally, you run it
|
|
19
|
+
Finally, you run it:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
jinka
|
package/bin/jinka.js
CHANGED
|
@@ -20,6 +20,8 @@ import { log } from '../src/log.js';
|
|
|
20
20
|
import { status } from '../src/status.js';
|
|
21
21
|
import { quota } from '../src/quota.js';
|
|
22
22
|
import { rollouts } from '../src/rollouts.js';
|
|
23
|
+
import { recorded } from '../src/recorded.js';
|
|
24
|
+
import { model } from '../src/model.js';
|
|
23
25
|
import { meters } from '../src/meters.js';
|
|
24
26
|
import { freeze } from '../src/freeze.js';
|
|
25
27
|
import { frozen } from '../src/frozen.js';
|
|
@@ -35,6 +37,7 @@ import { jobs } from '../src/jobs.js';
|
|
|
35
37
|
import { summary } from '../src/summary.js';
|
|
36
38
|
import { summarized } from '../src/summarized.js';
|
|
37
39
|
import { semaphore } from '../src/semaphore.js';
|
|
40
|
+
import { hello } from '../src/hello.js';
|
|
38
41
|
|
|
39
42
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
40
43
|
const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
@@ -97,7 +100,10 @@ const a = values.agent === '' ? engine : values.agent;
|
|
|
97
100
|
const t = tokens();
|
|
98
101
|
const q = quota();
|
|
99
102
|
const c = crew(values.threads, limit, { warn: (text) => screen.notice().warn(text) });
|
|
100
|
-
const
|
|
103
|
+
const sessions = join(process.env.CODEX_HOME ?? join(homedir(), '.codex'), 'sessions');
|
|
104
|
+
const e = (engine === 'codex' ? codex : claude)(agent(a), values.model, values.effort, resolve(values.skill));
|
|
105
|
+
const d = disk(dir, resolve(values.logs), resolve(values.memory), c);
|
|
106
|
+
const j = jobs(genie(e, d, meters([t, engine === 'codex' ? rollouts(q, sessions) : q]), engine === 'codex' ? () => recorded(model(), sessions) : model));
|
|
101
107
|
const f = freeze(c, { warn: (text) => screen.notice().warn(text) }, () => quit(0));
|
|
102
108
|
const r = release(pkg.version, 'https://registry.npmjs.org/@yegor256/jinka/latest');
|
|
103
109
|
const start = Date.now();
|
|
@@ -118,6 +124,7 @@ process.on('exit', () => screen.close());
|
|
|
118
124
|
if (tui) {
|
|
119
125
|
r.check();
|
|
120
126
|
}
|
|
127
|
+
screen.notice().warn(`every agent starts as: ${e.command(d.args())}`);
|
|
121
128
|
const quit = (code) => {
|
|
122
129
|
screen.close();
|
|
123
130
|
process.stdout.write('', () => process.stderr.write('', () => process.exit(code)));
|
|
@@ -133,6 +140,7 @@ process.on('SIGINT', interrupt);
|
|
|
133
140
|
keys(process.stdin, f, c, interrupt).listen();
|
|
134
141
|
wheel(process.stdin, screen).listen();
|
|
135
142
|
try {
|
|
143
|
+
await hello(e, screen.notice()).run(controller.signal);
|
|
136
144
|
await jinka(
|
|
137
145
|
limited(frozen(throttled(wishes(dir, values.interval, values.archive === '' ? '' : resolve(values.archive), values.pause, { warn: (text) => screen.notice().warn(text) }), q, values.throttle, 15 * 60 * 1000, { warn: (text) => screen.notice().warn(text) }), f), values.limit, f),
|
|
138
146
|
j,
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
package/src/claude.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
export const claude = (agent, model, effort, plugin) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
signal
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
4
|
+
export const claude = (agent, model, effort, plugin) => {
|
|
5
|
+
const full = (args) => [
|
|
6
|
+
'-p', '--output-format', 'stream-json', '--verbose',
|
|
7
|
+
'--plugin-dir', plugin,
|
|
8
|
+
...args,
|
|
9
|
+
'--dangerously-skip-permissions',
|
|
10
|
+
...(model === '' ? [] : ['--model', model]),
|
|
11
|
+
...(effort === '' ? [] : ['--effort', effort])
|
|
12
|
+
];
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
start: (args, signal) => agent.start(full(args), signal),
|
|
15
|
+
command: (args) => agent.command(full(args)),
|
|
16
|
+
lines: () => []
|
|
17
|
+
});
|
|
18
|
+
};
|
package/src/codex.js
CHANGED
|
@@ -3,20 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
|
|
6
|
-
export const codex = (agent, model, effort, plugin) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
signal
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
6
|
+
export const codex = (agent, model, effort, plugin) => {
|
|
7
|
+
const full = (args) => [
|
|
8
|
+
'exec', '--json',
|
|
9
|
+
'--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check',
|
|
10
|
+
...args,
|
|
11
|
+
...(model === '' ? [] : ['--model', model]),
|
|
12
|
+
...(effort === '' ? [] : ['--config', `model_reasoning_effort=${effort}`]),
|
|
13
|
+
'-'
|
|
14
|
+
];
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
start: (args, signal) => agent.start(full(args), signal),
|
|
17
|
+
command: (args) => agent.command(full(args)),
|
|
18
|
+
lines: () => [
|
|
19
|
+
`Your skills live in the directory ${join(plugin, 'skills')}, one skill per subdirectory, each described in its SKILL.md file.`,
|
|
20
|
+
'Before you start, read the descriptions of these skills, and whenever a skill fits your work, read its SKILL.md file and follow it.'
|
|
21
|
+
]
|
|
22
|
+
});
|
|
23
|
+
};
|
package/src/genie.js
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
import { createInterface } from 'node:readline';
|
|
5
5
|
import { event } from './event.js';
|
|
6
6
|
import { tokens } from './tokens.js';
|
|
7
|
-
import { model } from './model.js';
|
|
8
7
|
|
|
9
|
-
export const genie = (engine, disk, meter) => Object.freeze({
|
|
8
|
+
export const genie = (engine, disk, meter, model) => Object.freeze({
|
|
10
9
|
run: async (wish, row, signal) => {
|
|
11
10
|
const prompt = [
|
|
12
11
|
'Make this wish come true, working on your own without asking any questions:',
|
package/src/hello.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { event } from './event.js';
|
|
6
|
+
|
|
7
|
+
export const hello = (engine, notice) => Object.freeze({
|
|
8
|
+
run: (signal) => new Promise((resolve, reject) => {
|
|
9
|
+
const child = engine.start([], signal);
|
|
10
|
+
const answer = [''];
|
|
11
|
+
child.stdin.on('error', () => {});
|
|
12
|
+
child.stdin.end('Tell me, who you are, in a very short single sentence.');
|
|
13
|
+
child.stderr.resume();
|
|
14
|
+
createInterface({ input: child.stdout }).on('line', (line) => {
|
|
15
|
+
answer[0] = event(line, 0).summary() || answer[0];
|
|
16
|
+
});
|
|
17
|
+
child.on('error', (e) => (signal.aborted ? resolve() : reject(new Error(`Cannot start the agent "${child.spawnfile}": ${e.message}`))));
|
|
18
|
+
child.on('close', (code) => {
|
|
19
|
+
if (signal.aborted) {
|
|
20
|
+
resolve();
|
|
21
|
+
} else if (code === 0) {
|
|
22
|
+
notice.info(`the agent says: ${answer[0]}`);
|
|
23
|
+
resolve();
|
|
24
|
+
} else {
|
|
25
|
+
reject(new Error(`The agent "${child.spawnfile}" exited with code ${code} while telling who it is: ${answer[0]}`));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
})
|
|
29
|
+
});
|
package/src/model.js
CHANGED
|
@@ -15,7 +15,11 @@ export const model = () => {
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
text: () => {
|
|
18
|
-
const
|
|
18
|
+
const name = names.at(-1) ?? '';
|
|
19
|
+
if (!name.startsWith('claude-')) {
|
|
20
|
+
return name;
|
|
21
|
+
}
|
|
22
|
+
const parts = name.replace(/^claude-/, '').replace(/-\d{8}$/, '').split('-').filter((p) => p !== '');
|
|
19
23
|
return [
|
|
20
24
|
...parts.filter((p) => !/^\d+$/.test(p)).map((p) => `${p[0].toUpperCase()}${p.slice(1)}`),
|
|
21
25
|
parts.filter((p) => /^\d+$/.test(p)).join('.')
|
package/src/recorded.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026 Yegor Bugayenko
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export const recorded = (origin, dir) => {
|
|
8
|
+
let thread = '';
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
add: (line) => {
|
|
11
|
+
origin.add(line);
|
|
12
|
+
try {
|
|
13
|
+
const json = JSON.parse(line);
|
|
14
|
+
if (json.type === 'thread.started' && typeof json.thread_id === 'string') {
|
|
15
|
+
thread = json.thread_id;
|
|
16
|
+
}
|
|
17
|
+
if (json.type === 'turn.completed' && thread !== '' && existsSync(dir)) {
|
|
18
|
+
readdirSync(dir, { recursive: true })
|
|
19
|
+
.map(String)
|
|
20
|
+
.filter((path) => path.endsWith(`-${thread}.jsonl`))
|
|
21
|
+
.flatMap((path) => readFileSync(join(dir, path), 'utf8').split('\n'))
|
|
22
|
+
.filter((text) => text.includes('"turn_context"'))
|
|
23
|
+
.map((text) => JSON.parse(text).payload?.model)
|
|
24
|
+
.filter((name) => typeof name === 'string')
|
|
25
|
+
.slice(-1)
|
|
26
|
+
.forEach((name) => origin.add(JSON.stringify({ type: 'system', subtype: 'init', model: name })));
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
text: () => origin.text()
|
|
33
|
+
});
|
|
34
|
+
};
|
package/src/tty.js
CHANGED
|
@@ -17,9 +17,18 @@ export const tty = (stream, crew, status, legend) => {
|
|
|
17
17
|
const [left, right] = text.split('\t');
|
|
18
18
|
return `${left}${' '.repeat(Math.max(1, width - 1 - stripVTControlCharacters(left).length - stripVTControlCharacters(right).length))}${right}`;
|
|
19
19
|
};
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const cut = (text, width) => {
|
|
21
|
+
const plain = stripVTControlCharacters(text);
|
|
22
|
+
const space = plain.lastIndexOf(' ', width);
|
|
23
|
+
return space > 2 && plain.slice(space + 1).split(' ')[0].length <= width - 2 ? [space, space + 1] : [width, width];
|
|
24
|
+
};
|
|
25
|
+
const wrap = (text, width) => {
|
|
26
|
+
if (stripVTControlCharacters(text).length <= width || width < 3) {
|
|
27
|
+
return [text];
|
|
28
|
+
}
|
|
29
|
+
const [end, start] = cut(text, width);
|
|
30
|
+
return [sliceAnsi(text, 0, end), ...wrap(` ${sliceAnsi(text, start)}`, width)];
|
|
31
|
+
};
|
|
23
32
|
const draw = () => {
|
|
24
33
|
const rows = stream.rows || 24;
|
|
25
34
|
const wands = crew.indexes().map((i) => [i, lines.get(i) ?? { head: '', tail: '', start: Date.now() }]);
|
|
@@ -63,9 +72,9 @@ export const tty = (stream, crew, status, legend) => {
|
|
|
63
72
|
lines.set(index, { head, tail, start: head === line.head ? line.start : Date.now() });
|
|
64
73
|
paint();
|
|
65
74
|
};
|
|
66
|
-
const note = (text) => {
|
|
75
|
+
const note = (level, text) => {
|
|
67
76
|
notes.push(
|
|
68
|
-
`${new Date().toTimeString().slice(0, 8)} ${text.replace(/https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/(?:issues|pull)\/(\d+)/g, (url, repo, number) => `\x1b]8;;${url}\x07\x1b[38;2;106;113;247m${repo}#${number}\x1b[39m\x1b]8;;\x07`)}`
|
|
77
|
+
`${level === 'warn' ? `\x1b[38;2;255;135;0m${new Date().toTimeString().slice(0, 8)}\x1b[39m` : new Date().toTimeString().slice(0, 8)} ${text.replace(/https:\/\/github\.com\/([^/\s]+\/[^/\s]+)\/(?:issues|pull)\/(\d+)/g, (url, repo, number) => `\x1b]8;;${url}\x07\x1b[38;2;106;113;247m${repo}#${number}\x1b[39m\x1b]8;;\x07`)}`
|
|
69
78
|
);
|
|
70
79
|
if (view.depth > 0) {
|
|
71
80
|
view.depth += wrap(notes.at(-1), stream.columns || 80).length;
|
|
@@ -74,7 +83,7 @@ export const tty = (stream, crew, status, legend) => {
|
|
|
74
83
|
};
|
|
75
84
|
return Object.freeze({
|
|
76
85
|
row: (index) => row({ push: ([i, text]) => update(i, text) }, index),
|
|
77
|
-
notice: () => protocol({ push: ([level, text]) => note(level
|
|
86
|
+
notice: () => protocol({ push: ([level, text]) => note(level, text) }),
|
|
78
87
|
up: () => {
|
|
79
88
|
view.depth += 1;
|
|
80
89
|
redraw();
|