ucode-agent 1.7.0 → 1.8.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/package.json +1 -1
- package/src/core/login.js +59 -0
- package/src/ui/screen.js +1252 -1253
- package/src/ui/theme.js +339 -278
- package/ucode.js +132 -124
package/ucode.js
CHANGED
|
@@ -1,124 +1,132 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* ucode.js — the command.
|
|
4
|
-
*
|
|
5
|
-
* Parses the arguments, builds an Agent, and gets out of the way. Everything
|
|
6
|
-
* of substance is under src/.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import path from 'node:path';
|
|
10
|
-
import process from 'node:process';
|
|
11
|
-
import { readFile } from 'node:fs/promises';
|
|
12
|
-
import { realpathSync } from 'node:fs';
|
|
13
|
-
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
14
|
-
|
|
15
|
-
import { Agent } from './src/core/loop.js';
|
|
16
|
-
import { setModel, modelName, MODELS, DEFAULT_MODEL, ENV_FILE } from './src/core/provider.js';
|
|
17
|
-
import { VERSION } from './src/core/version.js';
|
|
18
|
-
import { Plain } from './src/ui/plain.js';
|
|
19
|
-
import { blue, dim, sky } from './src/ui/theme.js';
|
|
20
|
-
|
|
21
|
-
function parseArgs(argv) {
|
|
22
|
-
const args = { debug: false, model: null, cwd: process.cwd(), help: false, plan: false, version: false };
|
|
23
|
-
|
|
24
|
-
for (let i = 0; i < argv.length; i++) {
|
|
25
|
-
const a = argv[i];
|
|
26
|
-
if (a === '--debug') args.debug = true;
|
|
27
|
-
else if (a === '--plan') args.plan = true;
|
|
28
|
-
else if (a === '--model' || a === '-m') args.model = argv[++i];
|
|
29
|
-
else if (a === '--cwd' || a === '-C') args.cwd = path.resolve(argv[++i]);
|
|
30
|
-
else if (a === '--help' || a === '-h') args.help = true;
|
|
31
|
-
else if (a === '--version' || a === '-v') args.version = true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return args;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function usage() {
|
|
38
|
-
const entries = Object.entries(MODELS);
|
|
39
|
-
const width = Math.max(...entries.map(([, m]) => m.name.length));
|
|
40
|
-
const models = entries
|
|
41
|
-
.map(([id, m]) => ` ${m.name.padEnd(width)} ${dim(id)}`)
|
|
42
|
-
.join('\n');
|
|
43
|
-
|
|
44
|
-
process.stdout.write(
|
|
45
|
-
`\n ${blue('ucode')} — a terminal coding agent\n\n` +
|
|
46
|
-
' ucode [options]\n\n' +
|
|
47
|
-
` -m, --model <id> which model to use (default: ${modelName(DEFAULT_MODEL)})\n` +
|
|
48
|
-
' -C, --cwd <dir> work in another directory\n' +
|
|
49
|
-
' --plan start in plan mode: read and research, change nothing\n' +
|
|
50
|
-
' --debug print stack traces when something breaks\n' +
|
|
51
|
-
' -v, --version print the version\n' +
|
|
52
|
-
' -h, --help this message\n' +
|
|
53
|
-
' doctor check that everything ucode needs is working\n
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ucode.js — the command.
|
|
4
|
+
*
|
|
5
|
+
* Parses the arguments, builds an Agent, and gets out of the way. Everything
|
|
6
|
+
* of substance is under src/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import process from 'node:process';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { realpathSync } from 'node:fs';
|
|
13
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
import { Agent } from './src/core/loop.js';
|
|
16
|
+
import { setModel, modelName, MODELS, DEFAULT_MODEL, ENV_FILE } from './src/core/provider.js';
|
|
17
|
+
import { VERSION } from './src/core/version.js';
|
|
18
|
+
import { Plain } from './src/ui/plain.js';
|
|
19
|
+
import { blue, dim, sky } from './src/ui/theme.js';
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const args = { debug: false, model: null, cwd: process.cwd(), help: false, plan: false, version: false };
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < argv.length; i++) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a === '--debug') args.debug = true;
|
|
27
|
+
else if (a === '--plan') args.plan = true;
|
|
28
|
+
else if (a === '--model' || a === '-m') args.model = argv[++i];
|
|
29
|
+
else if (a === '--cwd' || a === '-C') args.cwd = path.resolve(argv[++i]);
|
|
30
|
+
else if (a === '--help' || a === '-h') args.help = true;
|
|
31
|
+
else if (a === '--version' || a === '-v') args.version = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return args;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function usage() {
|
|
38
|
+
const entries = Object.entries(MODELS);
|
|
39
|
+
const width = Math.max(...entries.map(([, m]) => m.name.length));
|
|
40
|
+
const models = entries
|
|
41
|
+
.map(([id, m]) => ` ${m.name.padEnd(width)} ${dim(id)}`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
|
|
44
|
+
process.stdout.write(
|
|
45
|
+
`\n ${blue('ucode')} — a terminal coding agent\n\n` +
|
|
46
|
+
' ucode [options]\n\n' +
|
|
47
|
+
` -m, --model <id> which model to use (default: ${modelName(DEFAULT_MODEL)})\n` +
|
|
48
|
+
' -C, --cwd <dir> work in another directory\n' +
|
|
49
|
+
' --plan start in plan mode: read and research, change nothing\n' +
|
|
50
|
+
' --debug print stack traces when something breaks\n' +
|
|
51
|
+
' -v, --version print the version\n' +
|
|
52
|
+
' -h, --help this message\n' +
|
|
53
|
+
' doctor check that everything ucode needs is working\n' +
|
|
54
|
+
' login <key> save your key for every folder on this machine\n\n' +
|
|
55
|
+
` ${sky('Models')}\n${models}\n\n` +
|
|
56
|
+
` Needs UCODE_API_KEY in the environment or in ${ENV_FILE}\n` +
|
|
57
|
+
' Free keys: https://openrouter.ai/keys\n\n'
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
const args = parseArgs(process.argv.slice(2));
|
|
63
|
+
|
|
64
|
+
if (args.help) return usage();
|
|
65
|
+
|
|
66
|
+
if (process.argv[2] === 'login') {
|
|
67
|
+
const { saveKey } = await import('./src/core/login.js');
|
|
68
|
+
process.stdout.write(`${await saveKey(process.argv[3])}
|
|
69
|
+
`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (process.argv[2] === 'doctor') {
|
|
74
|
+
const { runDoctor } = await import('./src/core/doctor.js');
|
|
75
|
+
process.stdout.write(`${(await runDoctor()).join('\n')}\n`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (args.version) {
|
|
80
|
+
process.stdout.write(`${VERSION}\n`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (args.model) {
|
|
85
|
+
try {
|
|
86
|
+
setModel(args.model);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
new Plain({ cwd: args.cwd }).error(err);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const agent = new Agent({ cwd: args.cwd, debug: args.debug });
|
|
95
|
+
if (args.plan) agent.ui.mode = 'plan';
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await agent.start();
|
|
99
|
+
} catch (err) {
|
|
100
|
+
agent.ui.error(err, { debug: args.debug });
|
|
101
|
+
await agent.persist().catch(() => {});
|
|
102
|
+
agent.ui.close();
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Was this file launched, or imported?
|
|
109
|
+
*
|
|
110
|
+
* Importing it — which another front end would do to reuse Agent — must not
|
|
111
|
+
* open a terminal session. The obvious check is comparing `import.meta.url`
|
|
112
|
+
* with argv[1], and it is wrong: after `npm link`, argv[1] arrives as the path
|
|
113
|
+
* through the symlink in the global node_modules while `import.meta.url` has
|
|
114
|
+
* already been resolved to the real file. The two never match, so the command
|
|
115
|
+
* starts, matches nothing, and exits successfully having done absolutely
|
|
116
|
+
* nothing — which is a great deal harder to diagnose than a crash.
|
|
117
|
+
*
|
|
118
|
+
* Comparing real paths is what actually answers the question.
|
|
119
|
+
*/
|
|
120
|
+
function launchedDirectly() {
|
|
121
|
+
const entry = process.argv[1];
|
|
122
|
+
if (!entry) return false;
|
|
123
|
+
|
|
124
|
+
const self = fileURLToPath(import.meta.url);
|
|
125
|
+
try {
|
|
126
|
+
return realpathSync(entry) === realpathSync(self);
|
|
127
|
+
} catch {
|
|
128
|
+
return pathToFileURL(entry).href === import.meta.url;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (launchedDirectly()) main();
|