chorus-cli 0.4.2 → 0.4.4
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/index.js +57 -4
- package/package.json +1 -1
- package/tools/coder.py +19 -9
- package/tools/qa.py +5 -1
package/index.js
CHANGED
|
@@ -18,6 +18,43 @@ const execPromise = util.promisify(exec);
|
|
|
18
18
|
const execFilePromise = util.promisify(execFile);
|
|
19
19
|
const fs = require('fs').promises;
|
|
20
20
|
|
|
21
|
+
// Returns a stable hardware UUID for this machine, with a persistent fallback
|
|
22
|
+
async function getMachineId() {
|
|
23
|
+
try {
|
|
24
|
+
if (process.platform === 'darwin') {
|
|
25
|
+
const { stdout } = await execPromise(
|
|
26
|
+
"ioreg -rd1 -c IOPlatformExpertDevice | awk -F'\"' '/IOPlatformUUID/{print $4}'"
|
|
27
|
+
);
|
|
28
|
+
if (stdout.trim()) return stdout.trim();
|
|
29
|
+
} else if (process.platform === 'linux') {
|
|
30
|
+
const fsp = require('fs').promises;
|
|
31
|
+
const id = (await fsp.readFile('/etc/machine-id', 'utf8')).trim();
|
|
32
|
+
if (id) return id;
|
|
33
|
+
} else if (process.platform === 'win32') {
|
|
34
|
+
const { stdout } = await execPromise('wmic csproduct get UUID');
|
|
35
|
+
const lines = stdout.trim().split('\n');
|
|
36
|
+
if (lines.length > 1) {
|
|
37
|
+
const uuid = lines[1].trim();
|
|
38
|
+
if (uuid && uuid !== 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF') return uuid;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch { /* fall through to persistent fallback */ }
|
|
42
|
+
|
|
43
|
+
// Persistent fallback: generate and cache a random UUID
|
|
44
|
+
const configDir = path.join(os.homedir(), '.config', 'chorus');
|
|
45
|
+
const idPath = path.join(configDir, 'machine-id');
|
|
46
|
+
try {
|
|
47
|
+
const existing = await fs.readFile(idPath, 'utf8');
|
|
48
|
+
if (existing.trim()) return existing.trim();
|
|
49
|
+
} catch { /* no file yet */ }
|
|
50
|
+
|
|
51
|
+
const { randomUUID } = require('crypto');
|
|
52
|
+
const newId = randomUUID();
|
|
53
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
54
|
+
await fs.writeFile(idPath, newId + '\n');
|
|
55
|
+
return newId;
|
|
56
|
+
}
|
|
57
|
+
|
|
21
58
|
// Run coder.py with real-time stderr streaming so progress is visible
|
|
22
59
|
function runCoder(prompt) {
|
|
23
60
|
return new Promise((resolve, reject) => {
|
|
@@ -26,6 +63,9 @@ function runCoder(prompt) {
|
|
|
26
63
|
env.CHORUS_API_KEY = CONFIG.ai.chorusApiKey;
|
|
27
64
|
env.CHORUS_API_URL = CONFIG.ai.chorusApiUrl;
|
|
28
65
|
}
|
|
66
|
+
if (CONFIG.ai.machineId) {
|
|
67
|
+
env.CHORUS_MACHINE_ID = CONFIG.ai.machineId;
|
|
68
|
+
}
|
|
29
69
|
const proc = spawn(CONFIG.ai.venvPython, [CONFIG.ai.coderPath, '--prompt', prompt], {
|
|
30
70
|
cwd: process.cwd(),
|
|
31
71
|
env,
|
|
@@ -77,6 +117,9 @@ function runQAChat(issue, enrichedDetails, qaName, useSuper = false) {
|
|
|
77
117
|
env.CHORUS_API_KEY = CONFIG.ai.chorusApiKey;
|
|
78
118
|
env.CHORUS_API_URL = CONFIG.ai.chorusApiUrl;
|
|
79
119
|
}
|
|
120
|
+
if (CONFIG.ai.machineId) {
|
|
121
|
+
env.CHORUS_MACHINE_ID = CONFIG.ai.machineId;
|
|
122
|
+
}
|
|
80
123
|
if (CONFIG.messenger === 'slack' && CONFIG.slack.botToken) {
|
|
81
124
|
env.SLACK_BOT_TOKEN = CONFIG.slack.botToken;
|
|
82
125
|
}
|
|
@@ -194,10 +237,14 @@ IMPORTANT: Output ONLY the message above. Do not include any preamble, thinking
|
|
|
194
237
|
if (!CONFIG.ai.chorusApiKey) {
|
|
195
238
|
throw new Error('CHORUS_API_KEY environment variable is required. Run "chorus setup" to configure.');
|
|
196
239
|
}
|
|
197
|
-
const
|
|
240
|
+
const openaiOpts = {
|
|
198
241
|
apiKey: CONFIG.ai.chorusApiKey,
|
|
199
242
|
baseURL: CONFIG.ai.chorusApiUrl,
|
|
200
|
-
}
|
|
243
|
+
};
|
|
244
|
+
if (CONFIG.ai.machineId) {
|
|
245
|
+
openaiOpts.defaultHeaders = { 'X-Machine-Id': CONFIG.ai.machineId };
|
|
246
|
+
}
|
|
247
|
+
const openai = new OpenAI(openaiOpts);
|
|
201
248
|
|
|
202
249
|
const response = await openai.chat.completions.create({
|
|
203
250
|
model: 'anthropic/claude-opus-4',
|
|
@@ -506,6 +553,9 @@ async function processTicket(issueArg, { useSuper = false, skipQA = false, qaNam
|
|
|
506
553
|
try {
|
|
507
554
|
console.log('🚀 Starting ticket processing...\n');
|
|
508
555
|
|
|
556
|
+
// Resolve machine fingerprint for per-machine usage tracking
|
|
557
|
+
CONFIG.ai.machineId = await getMachineId();
|
|
558
|
+
|
|
509
559
|
// 0. Ensure Python venv exists and has required dependencies
|
|
510
560
|
const reqFile = path.join(__dirname, 'tools', 'requirements.txt');
|
|
511
561
|
const { execFileSync: efs } = require('child_process');
|
|
@@ -869,6 +919,9 @@ async function setupProxyAuth() {
|
|
|
869
919
|
|
|
870
920
|
const baseUrl = CONFIG.ai.chorusApiUrl.replace(/\/v1\/?$/, '');
|
|
871
921
|
|
|
922
|
+
// Resolve machine fingerprint so the server can track per-machine usage
|
|
923
|
+
const machineId = await getMachineId();
|
|
924
|
+
|
|
872
925
|
// Try register first, fall back to login if already registered
|
|
873
926
|
let apiKey;
|
|
874
927
|
try {
|
|
@@ -895,11 +948,11 @@ async function setupProxyAuth() {
|
|
|
895
948
|
});
|
|
896
949
|
|
|
897
950
|
console.log(' Registering...');
|
|
898
|
-
let res = await doPost(`${baseUrl}/auth/register`, { email, password });
|
|
951
|
+
let res = await doPost(`${baseUrl}/auth/register`, { email, password, machineId });
|
|
899
952
|
|
|
900
953
|
if (res.status === 409 || (res.body && res.body.error && res.body.error.includes('already'))) {
|
|
901
954
|
console.log(' Account exists, logging in...');
|
|
902
|
-
res = await doPost(`${baseUrl}/auth/login`, { email, password });
|
|
955
|
+
res = await doPost(`${baseUrl}/auth/login`, { email, password, machineId });
|
|
903
956
|
}
|
|
904
957
|
|
|
905
958
|
if (res.status >= 400) {
|
package/package.json
CHANGED
package/tools/coder.py
CHANGED
|
@@ -50,23 +50,29 @@ def is_token_limit_error(err):
|
|
|
50
50
|
return "token limit exceeded" in msg or "rate_limit_error" in msg
|
|
51
51
|
|
|
52
52
|
SYSTEM_PROMPT = """\
|
|
53
|
-
You are a coding agent running
|
|
53
|
+
You are a coding agent running inside a CLI tool called Chorus.
|
|
54
|
+
Your output goes straight to a terminal. There is no browser, no rich renderer.
|
|
54
55
|
Working directory: {cwd}
|
|
55
56
|
|
|
56
57
|
You help with software engineering tasks: writing code, debugging, refactoring, \
|
|
57
58
|
explaining code, running commands, and managing files.
|
|
58
59
|
|
|
59
60
|
Formatting:
|
|
60
|
-
-
|
|
61
|
-
- No ## headers, **bold**, *italic*, [links](url), or bullet symbols like -.
|
|
61
|
+
- Plain text only. Never use markdown.
|
|
62
|
+
- No ## headers, **bold**, *italic*, `backticks`, [links](url), or bullet symbols like -.
|
|
62
63
|
- Use blank lines, indentation, and CAPS for emphasis or section labels.
|
|
63
64
|
- Use plain numbered lists (1. 2. 3.) when listing things.
|
|
64
|
-
-
|
|
65
|
-
|
|
65
|
+
- Refer to code identifiers by name directly (e.g. myFunction, not `myFunction`).
|
|
66
|
+
|
|
67
|
+
Communication style:
|
|
68
|
+
- Be terse. Say what you are doing and why in one or two lines, then do it.
|
|
69
|
+
- No greetings, preambles, encouragement, or sign-offs.
|
|
70
|
+
- No "Great question!", "Let me", "Sure!", "I'll now", or similar filler.
|
|
71
|
+
- When explaining your plan, use short declarative sentences. Skip obvious reasoning.
|
|
72
|
+
- After completing work, state what changed and nothing else.
|
|
66
73
|
|
|
67
74
|
{approach}Guidelines:
|
|
68
|
-
-
|
|
69
|
-
- Always use your tools. If a question can be answered by running a command (git, ls, etc.), use the bash tool — never guess.
|
|
75
|
+
- Always use your tools. If a question can be answered by running a command (git, ls, etc.), use the bash tool. Never guess.
|
|
70
76
|
- Always read a file before editing it.
|
|
71
77
|
- If edit_file fails with "old_string not found", re-read the file to get the actual current content before retrying. Never guess at file contents.
|
|
72
78
|
- Use edit_file for targeted changes. Use write_file for new files or complete rewrites.
|
|
@@ -692,7 +698,7 @@ def run_prompt(client, prompt, system):
|
|
|
692
698
|
|
|
693
699
|
plan_messages = [
|
|
694
700
|
{"role": "system", "content": system},
|
|
695
|
-
{"role": "user", "content": f"{prompt}\n\nBefore making any code changes,
|
|
701
|
+
{"role": "user", "content": f"{prompt}\n\nBefore making any code changes, briefly state:\n1. The goal\n2. Files to examine\n3. Files to modify and how\n\nKeep it short. Do NOT write any code yet."}
|
|
696
702
|
]
|
|
697
703
|
|
|
698
704
|
try:
|
|
@@ -924,7 +930,11 @@ def main():
|
|
|
924
930
|
sys.exit(1)
|
|
925
931
|
|
|
926
932
|
base_url = os.environ.get("CHORUS_API_URL", "https://chorus-bad0f.web.app/v1")
|
|
927
|
-
|
|
933
|
+
machine_id = os.environ.get("CHORUS_MACHINE_ID")
|
|
934
|
+
client_kwargs = {"api_key": api_key, "base_url": base_url}
|
|
935
|
+
if machine_id:
|
|
936
|
+
client_kwargs["default_headers"] = {"X-Machine-Id": machine_id}
|
|
937
|
+
client = OpenAI(**client_kwargs)
|
|
928
938
|
system = SYSTEM_PROMPT.format(cwd=os.getcwd(), approach=APPROACH_BLOCK)
|
|
929
939
|
|
|
930
940
|
# Load codebase map if available
|
package/tools/qa.py
CHANGED
|
@@ -403,7 +403,11 @@ Write a clear numbered list of requirements. Each requirement should be specific
|
|
|
403
403
|
def run_qa_chat(issue_context, messenger, qa_name):
|
|
404
404
|
api_key = os.environ.get("CHORUS_API_KEY")
|
|
405
405
|
base_url = os.environ.get("CHORUS_API_URL", "https://chorus-bad0f.web.app/v1")
|
|
406
|
-
|
|
406
|
+
machine_id = os.environ.get("CHORUS_MACHINE_ID")
|
|
407
|
+
client_kwargs = {"api_key": api_key, "base_url": base_url}
|
|
408
|
+
if machine_id:
|
|
409
|
+
client_kwargs["default_headers"] = {"X-Machine-Id": machine_id}
|
|
410
|
+
client = OpenAI(**client_kwargs)
|
|
407
411
|
conversation = []
|
|
408
412
|
raw_responses = []
|
|
409
413
|
|