juno-code 1.0.24 → 1.0.26

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/dist/index.js CHANGED
@@ -75,7 +75,7 @@ var __export = (target, all) => {
75
75
  exports.version = void 0;
76
76
  var init_version = __esm({
77
77
  "src/version.ts"() {
78
- exports.version = "1.0.24";
78
+ exports.version = "1.0.26";
79
79
  }
80
80
  });
81
81
  function isHeadlessEnvironment() {
package/dist/index.mjs CHANGED
@@ -44,7 +44,7 @@ var __export = (target, all) => {
44
44
  var version;
45
45
  var init_version = __esm({
46
46
  "src/version.ts"() {
47
- version = "1.0.24";
47
+ version = "1.0.26";
48
48
  }
49
49
  });
50
50
  function isHeadlessEnvironment() {
@@ -10,13 +10,19 @@
10
10
  # Usage: ./.juno_task/scripts/kanban.sh [juno-kanban arguments]
11
11
  # Example: ./.juno_task/scripts/kanban.sh list --limit 5
12
12
  #
13
+ # Environment Variables:
14
+ # JUNO_DEBUG=true - Show [DEBUG] diagnostic messages
15
+ # JUNO_VERBOSE=true - Show [KANBAN] informational messages
16
+ # (Both default to false for silent operation)
17
+ #
13
18
  # Created by: juno-code init command
14
19
  # Date: Auto-generated during project initialization
15
20
 
16
21
  set -euo pipefail # Exit on error, undefined variable, or pipe failure
17
22
 
18
- # DEBUG OUTPUT: Show that kanban.sh is being executed (only if JUNO_VERBOSE=true)
19
- if [ "${JUNO_VERBOSE:-false}" = "true" ]; then
23
+ # DEBUG OUTPUT: Show that kanban.sh is being executed (only if JUNO_DEBUG=true)
24
+ # Note: JUNO_DEBUG is separate from JUNO_VERBOSE for fine-grained control
25
+ if [ "${JUNO_DEBUG:-false}" = "true" ]; then
20
26
  echo "[DEBUG] kanban.sh is being executed from: $(pwd)" >&2
21
27
  fi
22
28
 
@@ -31,6 +31,8 @@ class ClaudeService:
31
31
  self.additional_args: List[str] = []
32
32
  self.message_counter = 0
33
33
  self.verbose = False
34
+ # User message truncation: -1 = no truncation, N = truncate to N lines
35
+ self.user_message_truncate = int(os.environ.get("CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE", "4"))
34
36
 
35
37
  def check_claude_installed(self) -> bool:
36
38
  """Check if claude CLI is installed and available"""
@@ -57,12 +59,13 @@ Examples:
57
59
  %(prog)s -p "Add tests" -m claude-opus-4-20250514 --tool "Bash Edit"
58
60
 
59
61
  Environment Variables:
60
- CLAUDE_PROJECT_PATH Project path (default: current directory)
61
- CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)
62
- CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt
63
- CLAUDE_PERMISSION_MODE Permission mode (default: default)
64
- CLAUDE_PRETTY Pretty print JSON output (default: true)
65
- CLAUDE_VERBOSE Enable verbose output (default: false)
62
+ CLAUDE_PROJECT_PATH Project path (default: current directory)
63
+ CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)
64
+ CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt
65
+ CLAUDE_PERMISSION_MODE Permission mode (default: default)
66
+ CLAUDE_PRETTY Pretty print JSON output (default: true)
67
+ CLAUDE_VERBOSE Enable verbose output (default: false)
68
+ CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)
66
69
  """
67
70
  )
68
71
 
@@ -214,10 +217,20 @@ Environment Variables:
214
217
  """
215
218
  Format JSON line for pretty output.
216
219
  For type=assistant: show datetime, message content, and counter
220
+ For type=user: show datetime, message content (truncated based on env var), and counter
217
221
  For other types: show full message with datetime and counter
218
222
  Returns None if line should be skipped
219
223
 
220
224
  IMPORTANT: Always preserve the 'type' field so shell backend can parse events
225
+
226
+ MULTI-LINE HANDLING: When content/result fields contain \\n escape sequences,
227
+ the output shows the JSON metadata on one line, then the actual content/result
228
+ value is printed below with newlines properly rendered (similar to jq -r or @text).
229
+ This keeps JSON structure compact while making multi-line strings readable.
230
+
231
+ USER MESSAGE TRUNCATION: User messages are truncated based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
232
+ environment variable (default: 4 lines, -1: no truncation). When truncated, a [Truncated...]
233
+ indicator is added. This only applies to user messages in pretty mode.
221
234
  """
222
235
  try:
223
236
  data = json.loads(json_line)
@@ -226,8 +239,53 @@ Environment Variables:
226
239
  # Get current datetime in readable format
227
240
  now = datetime.now().strftime("%I:%M:%S %p")
228
241
 
242
+ # For user messages, show simplified output with truncation
243
+ if data.get("type") == "user":
244
+ message = data.get("message", {})
245
+ content_list = message.get("content", [])
246
+
247
+ # Extract text content
248
+ text_content = ""
249
+ for item in content_list:
250
+ if isinstance(item, dict) and item.get("type") == "text":
251
+ text_content = item.get("text", "")
252
+ break
253
+
254
+ # Apply truncation for user messages based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
255
+ # -1 means no truncation, otherwise truncate to N lines
256
+ if self.user_message_truncate != -1:
257
+ lines = text_content.split('\n')
258
+ if len(lines) > self.user_message_truncate:
259
+ # Truncate to N lines and add indicator
260
+ text_content = '\n'.join(lines[:self.user_message_truncate]) + '\n[Truncated...]'
261
+
262
+ # Create simplified output with datetime, content, and counter
263
+ simplified = {
264
+ "type": "user",
265
+ "datetime": now,
266
+ "counter": f"#{self.message_counter}"
267
+ }
268
+
269
+ # Check if content has newlines after potential truncation
270
+ if '\n' in text_content:
271
+ # Multi-line content: print JSON metadata, then raw content
272
+ metadata = {
273
+ "type": "user",
274
+ "datetime": now,
275
+ "counter": f"#{self.message_counter}"
276
+ }
277
+ # Print metadata as compact JSON on first line
278
+ output = json.dumps(metadata, ensure_ascii=False)
279
+ # Then print content label and raw multi-line text
280
+ output += "\ncontent:\n" + text_content
281
+ return output
282
+ else:
283
+ # Single-line content: normal JSON
284
+ simplified["content"] = text_content
285
+ return json.dumps(simplified, ensure_ascii=False)
286
+
229
287
  # For assistant messages, show simplified output
230
- if data.get("type") == "assistant":
288
+ elif data.get("type") == "assistant":
231
289
  message = data.get("message", {})
232
290
  content_list = message.get("content", [])
233
291
 
@@ -259,20 +317,61 @@ Environment Variables:
259
317
  # Add either content or tool_use data
260
318
  if tool_use_data:
261
319
  simplified["tool_use"] = tool_use_data
320
+ # Normal JSON output for tool_use
321
+ return json.dumps(simplified, ensure_ascii=False)
262
322
  else:
263
- # For multi-line content, render it in a readable format
264
- # Check if content contains newline characters
265
- if text_content and '\n' in text_content:
266
- # Split into lines and create a readable multi-line representation
267
- simplified["content"] = text_content
268
- # Return a custom formatted output for multi-line content
269
- # that preserves the structure but makes it readable
270
- return json.dumps(simplified, indent=2, ensure_ascii=False)
323
+ # For content, check if it has newlines
324
+ if '\n' in text_content:
325
+ # Multi-line content: print JSON metadata, then raw content
326
+ metadata = {
327
+ "type": "assistant",
328
+ "datetime": now,
329
+ "counter": f"#{self.message_counter}"
330
+ }
331
+ # Print metadata as compact JSON on first line
332
+ output = json.dumps(metadata, ensure_ascii=False)
333
+ # Then print content label and raw multi-line text
334
+ output += "\ncontent:\n" + text_content
335
+ return output
271
336
  else:
337
+ # Single-line content: normal JSON
272
338
  simplified["content"] = text_content
273
-
274
- return json.dumps(simplified)
339
+ return json.dumps(simplified, ensure_ascii=False)
275
340
  else:
341
+ # For other message types, check if there's nested content to flatten
342
+ message = data.get("message", {})
343
+ content_list = message.get("content", [])
344
+
345
+ # Check if this is a message with nested tool_result or similar content
346
+ if content_list and isinstance(content_list, list) and len(content_list) > 0:
347
+ nested_item = content_list[0]
348
+ if isinstance(nested_item, dict) and nested_item.get("type") in ["tool_result"]:
349
+ # Flatten the nested structure by pulling nested fields to top level
350
+ flattened = {
351
+ "datetime": now,
352
+ "counter": f"#{self.message_counter}",
353
+ }
354
+
355
+ # Add tool_use_id if present
356
+ if "tool_use_id" in nested_item:
357
+ flattened["tool_use_id"] = nested_item["tool_use_id"]
358
+
359
+ # Add type from nested item
360
+ flattened["type"] = nested_item["type"]
361
+
362
+ # Handle content field with multiline support
363
+ nested_content = nested_item.get("content", "")
364
+ if isinstance(nested_content, str) and '\n' in nested_content:
365
+ # Multi-line content: separate metadata from content
366
+ # Print metadata as compact JSON
367
+ metadata_json = json.dumps(flattened, ensure_ascii=False)
368
+ # Then print content label and raw multi-line text
369
+ return metadata_json + "\ncontent:\n" + nested_content
370
+ else:
371
+ # Single-line content: normal JSON
372
+ flattened["content"] = nested_content
373
+ return json.dumps(flattened, ensure_ascii=False)
374
+
276
375
  # For other message types, show full message with datetime and counter
277
376
  # Type field is already present in data, so it's preserved
278
377
  output = {
@@ -280,7 +379,18 @@ Environment Variables:
280
379
  "counter": f"#{self.message_counter}",
281
380
  **data
282
381
  }
283
- return json.dumps(output)
382
+
383
+ # Check if 'result' field has multi-line content
384
+ if "result" in output and isinstance(output["result"], str) and '\n' in output["result"]:
385
+ # Multi-line result: separate metadata from content
386
+ result_value = output.pop("result")
387
+ # Print metadata as compact JSON
388
+ metadata_json = json.dumps(output, ensure_ascii=False)
389
+ # Then print result label and raw multi-line text
390
+ return metadata_json + "\nresult:\n" + result_value
391
+ else:
392
+ # Normal JSON output
393
+ return json.dumps(output, ensure_ascii=False)
284
394
 
285
395
  except json.JSONDecodeError:
286
396
  # If not valid JSON, return as-is
@@ -368,12 +478,13 @@ Environment Variables:
368
478
  )
369
479
  print("\nRun 'claude.py --help' for usage information.", file=sys.stderr)
370
480
  print("\nAvailable Environment Variables:", file=sys.stderr)
371
- print(" CLAUDE_PROJECT_PATH Project path (default: current directory)", file=sys.stderr)
372
- print(" CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)", file=sys.stderr)
373
- print(" CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt", file=sys.stderr)
374
- print(" CLAUDE_PERMISSION_MODE Permission mode (default: default)", file=sys.stderr)
375
- print(" CLAUDE_PRETTY Pretty print JSON output (default: true)", file=sys.stderr)
376
- print(" CLAUDE_VERBOSE Enable verbose output (default: false)", file=sys.stderr)
481
+ print(" CLAUDE_PROJECT_PATH Project path (default: current directory)", file=sys.stderr)
482
+ print(" CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)", file=sys.stderr)
483
+ print(" CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt", file=sys.stderr)
484
+ print(" CLAUDE_PERMISSION_MODE Permission mode (default: default)", file=sys.stderr)
485
+ print(" CLAUDE_PRETTY Pretty print JSON output (default: true)", file=sys.stderr)
486
+ print(" CLAUDE_VERBOSE Enable verbose output (default: false)", file=sys.stderr)
487
+ print(" CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)", file=sys.stderr)
377
488
  return 1
378
489
 
379
490
  # Check if claude is installed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juno-code",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "TypeScript CLI tool for AI subagent orchestration with code automation",
5
5
  "keywords": [
6
6
  "ai",