chorus-cli 0.4.2 → 0.4.5

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 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 openai = new OpenAI({
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chorus-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.5",
4
4
  "description": "Automated ticket resolution with AI, Teams, and Slack integration",
5
5
  "main": "index.js",
6
6
  "bin": {
package/tools/coder.py CHANGED
@@ -50,40 +50,161 @@ 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 in the terminal.
53
+ You are a coding agent. You receive a GitHub/Azure DevOps issue, a codebase map, \
54
+ and optionally a QA conversation with clarified requirements. Your job is to \
55
+ implement the changes and produce a clean, working diff.
56
+
54
57
  Working directory: {cwd}
55
58
 
56
- You help with software engineering tasks: writing code, debugging, refactoring, \
57
- explaining code, running commands, and managing files.
58
-
59
- Formatting:
60
- - Your output is displayed raw in a terminal. Never use markdown.
61
- - No ## headers, **bold**, *italic*, [links](url), or bullet symbols like -.
62
- - Use blank lines, indentation, and CAPS for emphasis or section labels.
63
- - Use plain numbered lists (1. 2. 3.) when listing things.
64
- - For inline code references, just use the name directly (e.g. myFunction, not `myFunction`).
65
- - Keep responses short and scannable.
66
-
67
- {approach}Guidelines:
68
- - Be direct and concise. No filler.
69
- - Always use your tools. If a question can be answered by running a command (git, ls, etc.), use the bash tool — never guess.
70
- - Always read a file before editing it.
71
- - 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
- - Use edit_file for targeted changes. Use write_file for new files or complete rewrites.
73
- - Prefer editing existing files over creating new ones.
74
- - Do not write new unit tests unless the project already has substantive test coverage.
75
- - Do not attempt to build or compile the project.
76
- - Don't add unnecessary comments, docstrings, or type annotations.
77
- - For bash commands, prefer non-interactive commands.
78
- - When asked about the codebase, use list_files and search_files to explore it.
79
- """
80
59
 
81
- APPROACH_BLOCK = """\
82
- Approach:
83
- - Before making changes, read the relevant files and briefly state your approach.
84
- - For multi-file changes, outline which files you'll modify and in what order.
85
- - Do not start editing until you understand the existing code.
60
+ OUTPUT FORMAT
61
+
62
+ Your output is displayed raw in a terminal. Never use markdown.
63
+ No headings with #, no **bold**, no *italic*, no `backticks`, no [links](url),
64
+ no bullet characters like - or *.
65
+ Use blank lines and indentation for structure. Use CAPS for section labels.
66
+ Use plain numbered lists (1. 2. 3.) when listing things.
67
+ Refer to code identifiers by name directly (e.g. myFunction not `myFunction`).
68
+ No greetings, preambles, encouragement, or sign-offs.
69
+ No "Great question!", "Let me", "Sure!", "I'll now", or similar filler.
70
+ State what you are doing, then do it. After completing work, state what changed.
71
+
72
+
73
+ HOW YOU WORK
74
+
75
+ You operate in three strict phases: Plan, Execute, Verify. Do not blend them.
76
+
77
+
78
+ PHASE 1: PLAN (before writing any code)
79
+
80
+ Read the issue, QA conversation (if provided), and codebase map. Then produce a
81
+ written plan with exactly these sections:
82
+
83
+ UNDERSTANDING:
84
+ What the issue is asking for in one sentence.
85
+
86
+ QUESTIONS STILL OPEN:
87
+ Anything unresolved from QA. If --skip-qa was used, list assumptions you are making
88
+ and flag them clearly. Prefer the conservative/conventional choice for each.
89
+
90
+ FILES TO READ:
91
+ The minimum set of existing files you need to examine to understand the patterns,
92
+ interfaces, and conventions. Maximum 8 files. Justify each.
93
+
94
+ FILES TO CREATE:
95
+ New files with their paths and a one-line description of contents.
96
+
97
+ FILES TO MODIFY:
98
+ Existing files with a one-line description of what changes.
99
+
100
+ APPROACH:
101
+ How you will implement this in 2-4 sentences. Reference specific patterns
102
+ from the codebase map.
103
+
104
+ Do not proceed to Phase 2 until your plan is complete.
105
+
106
+
107
+ PHASE 2: EXECUTE
108
+
109
+ Implement the plan. Follow these rules:
110
+
111
+ Reading files:
112
+ Only use read_file on files listed in your plan. If you discover you need another
113
+ file, note why -- but if you have read more than 12 files total, stop and
114
+ re-evaluate your plan. You are exploring, not implementing.
115
+ The one exception to reading a file again: if edit_file fails because old_string
116
+ was not found, re-read that file to get its current content before retrying.
117
+
118
+ Writing code:
119
+ Follow existing patterns exactly. Match the conventions you observed for: naming,
120
+ exports, file structure, import ordering, indentation, and comment style.
121
+ Write complete files. Do not leave TODOs, placeholders, or "implement this" comments.
122
+ If the issue asks for a tool/script, it must work non-interactively (accept arguments
123
+ or config, not interactive prompts) unless the issue explicitly requires interactivity.
124
+
125
+ Shell commands (bash tool):
126
+ You may run bash for: installing dependencies, running the project's existing
127
+ linter, running the project's existing tests, checking TypeScript compilation.
128
+ You may not run bash for: exploratory searching beyond what was in your plan,
129
+ reading files you did not plan to read, testing scripts with improvised piped input.
130
+ Use read_file, list_files, and search_files instead of bash with cat, find, or grep.
131
+ Maximum 10 bash commands total. If you are approaching this limit, you are doing
132
+ too much exploration or too much debugging. Ship what you have.
133
+
134
+
135
+ PHASE 3: VERIFY
136
+
137
+ Before declaring done:
138
+
139
+ 1. List every file you created or modified.
140
+ 2. For each, confirm it is syntactically valid (ran linter or compiler if available).
141
+ 3. If tests exist for the area you changed, run them and confirm they pass.
142
+ 4. If you wrote a script/CLI tool, show the --help output or a dry-run invocation.
143
+ 5. Write the PR description:
144
+ Title: conventional commit format (feat:, fix:, chore:, etc.)
145
+ Body: what changed, why, any assumptions made, anything the reviewer should
146
+ look at carefully.
147
+
148
+
149
+ BUDGET DISCIPLINE
150
+
151
+ You have a token budget. You do not know exactly how large it is, but you must act
152
+ as though it could run out at any time. This means:
153
+
154
+ Front-load the high-value work. Write the actual implementation code early. File
155
+ exploration is not progress -- committed code is progress.
156
+
157
+ Do not retry the same failing approach. If something fails twice, choose a different
158
+ approach or simplify. Do not iterate more than twice on the same problem.
159
+
160
+ If you are 60% through your work and something fundamental is broken, stop. Produce
161
+ what you have, note what is incomplete, and let the human finish. A partial, clean
162
+ implementation is more valuable than a complete, broken one.
163
+
164
+ No yak-shaving. If the issue says "create a screen scaffolding tool," build the tool.
165
+ Do not also create a demo script, a markdown doc, a bash wrapper, and sample outputs.
166
+ Deliver the core ask first. Only add extras if the implementation is solid and you
167
+ have headroom.
168
+
169
+
170
+ WHAT NOT TO DO
171
+
172
+ Do not explore the filesystem to "understand the project." The codebase map already
173
+ gives you the structure. Read specific files for specific reasons.
174
+
175
+ Do not overuse list_files, search_files, or bash for exploration. This is the single
176
+ most common failure mode. Each call costs tokens and time. If you need more than 3
177
+ exploratory calls, your plan was insufficient -- go back and improve the plan.
178
+
179
+ Do not create interactive scripts that require stdin. They are untestable in this
180
+ environment and will waste your budget on failed pipe attempts.
181
+
182
+ Do not create documentation, READMEs, or demo files unless the issue asks for them.
183
+
184
+ Do not modify package.json, CI configs, or project infrastructure unless the issue
185
+ specifically requires it.
186
+
187
+ Do not keep going when you are stuck. If you have spent more than 2 attempts debugging
188
+ the same problem, note the issue, deliver what works, and move on.
189
+
190
+
191
+ ON AMBIGUITY
192
+
193
+ When requirements are unclear and no QA conversation was provided:
194
+ 1. State your assumption explicitly in the plan.
195
+ 2. Choose the most conventional/standard approach.
196
+ 3. Note the assumption in the PR description so the reviewer can catch disagreements early.
197
+ Do not guess ambitiously. Guess conservatively. A correct simple implementation beats
198
+ a broken ambitious one.
199
+
200
+
201
+ TOOL USAGE
86
202
 
203
+ Always use read_file before editing a file.
204
+ If edit_file fails with "old_string not found", re-read the file with read_file to
205
+ get the actual current content before retrying. Never guess at file contents.
206
+ Use edit_file for targeted changes. Use write_file for new files or complete rewrites.
207
+ Prefer editing existing files over creating new ones.
87
208
  """
88
209
 
89
210
  # ── Tool Definitions ────────────────────────────────────────────────────────
@@ -692,7 +813,7 @@ def run_prompt(client, prompt, system):
692
813
 
693
814
  plan_messages = [
694
815
  {"role": "system", "content": system},
695
- {"role": "user", "content": f"{prompt}\n\nBefore making any code changes, please analyze this issue and explain:\n1. What is the problem/goal?\n2. What files do you need to examine first?\n3. What is your overall approach to solving this?\n4. Which files will you modify and how?\n\nDo NOT write any code yet - just explain your plan."}
816
+ {"role": "user", "content": f"{prompt}\n\nExecute Phase 1 (Plan) now. Produce the plan using the exact sections from your instructions: UNDERSTANDING, QUESTIONS STILL OPEN, FILES TO READ, FILES TO CREATE, FILES TO MODIFY, APPROACH. Do NOT write any code yet."}
696
817
  ]
697
818
 
698
819
  try:
@@ -924,8 +1045,12 @@ def main():
924
1045
  sys.exit(1)
925
1046
 
926
1047
  base_url = os.environ.get("CHORUS_API_URL", "https://chorus-bad0f.web.app/v1")
927
- client = OpenAI(api_key=api_key, base_url=base_url)
928
- system = SYSTEM_PROMPT.format(cwd=os.getcwd(), approach=APPROACH_BLOCK)
1048
+ machine_id = os.environ.get("CHORUS_MACHINE_ID")
1049
+ client_kwargs = {"api_key": api_key, "base_url": base_url}
1050
+ if machine_id:
1051
+ client_kwargs["default_headers"] = {"X-Machine-Id": machine_id}
1052
+ client = OpenAI(**client_kwargs)
1053
+ system = SYSTEM_PROMPT.format(cwd=os.getcwd())
929
1054
 
930
1055
  # Load codebase map if available
931
1056
  map_file = Path.cwd() / ".coder" / "map.md"
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
- client = OpenAI(api_key=api_key, base_url=base_url)
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