juno-code 1.0.24 → 1.0.27
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/bin/cli.js +14760 -16208
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +14693 -16142
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/templates/scripts/kanban.sh +8 -2
- package/dist/templates/services/__pycache__/claude.cpython-38.pyc +0 -0
- package/dist/templates/services/claude.py +188 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -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
|
|
19
|
-
|
|
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
|
|
|
Binary file
|
|
@@ -22,6 +22,16 @@ class ClaudeService:
|
|
|
22
22
|
DEFAULT_PERMISSION_MODE = "default"
|
|
23
23
|
DEFAULT_AUTO_INSTRUCTION = """You are Claude Code, an AI coding assistant. Follow the instructions provided and generate high-quality code."""
|
|
24
24
|
|
|
25
|
+
# Model shorthand mappings (colon-prefixed names expand to full model IDs)
|
|
26
|
+
MODEL_SHORTHANDS = {
|
|
27
|
+
":claude-haiku-4-5": "claude-haiku-4-5-20251001",
|
|
28
|
+
":claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
|
|
29
|
+
":claude-opus-4": "claude-opus-4-20250514",
|
|
30
|
+
":haiku": "claude-haiku-4-5-20251001",
|
|
31
|
+
":sonnet": "claude-sonnet-4-5-20250929",
|
|
32
|
+
":opus": "claude-opus-4-20250514",
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
def __init__(self):
|
|
26
36
|
self.model_name = self.DEFAULT_MODEL
|
|
27
37
|
self.permission_mode = self.DEFAULT_PERMISSION_MODE
|
|
@@ -31,6 +41,24 @@ class ClaudeService:
|
|
|
31
41
|
self.additional_args: List[str] = []
|
|
32
42
|
self.message_counter = 0
|
|
33
43
|
self.verbose = False
|
|
44
|
+
# User message truncation: -1 = no truncation, N = truncate to N lines
|
|
45
|
+
self.user_message_truncate = int(os.environ.get("CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE", "4"))
|
|
46
|
+
|
|
47
|
+
def expand_model_shorthand(self, model: str) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Expand model shorthand names to full model IDs.
|
|
50
|
+
|
|
51
|
+
If the model starts with ':', look it up in MODEL_SHORTHANDS.
|
|
52
|
+
Otherwise, return the model name as-is.
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
:claude-haiku-4-5 -> claude-haiku-4-5-20251001
|
|
56
|
+
:haiku -> claude-haiku-4-5-20251001
|
|
57
|
+
claude-sonnet-4-5-20250929 -> claude-sonnet-4-5-20250929 (unchanged)
|
|
58
|
+
"""
|
|
59
|
+
if model.startswith(':'):
|
|
60
|
+
return self.MODEL_SHORTHANDS.get(model, model)
|
|
61
|
+
return model
|
|
34
62
|
|
|
35
63
|
def check_claude_installed(self) -> bool:
|
|
36
64
|
"""Check if claude CLI is installed and available"""
|
|
@@ -54,15 +82,18 @@ class ClaudeService:
|
|
|
54
82
|
Examples:
|
|
55
83
|
%(prog)s -p "Write a hello world function"
|
|
56
84
|
%(prog)s -pp prompt.txt --cd /path/to/project
|
|
57
|
-
%(prog)s -p "Add tests" -m
|
|
85
|
+
%(prog)s -p "Add tests" -m :opus --tool "Bash Edit"
|
|
86
|
+
%(prog)s -p "Quick task" -m :haiku
|
|
87
|
+
%(prog)s -p "Complex task" -m claude-opus-4-20250514
|
|
58
88
|
|
|
59
89
|
Environment Variables:
|
|
60
|
-
CLAUDE_PROJECT_PATH
|
|
61
|
-
CLAUDE_MODEL
|
|
62
|
-
CLAUDE_AUTO_INSTRUCTION
|
|
63
|
-
CLAUDE_PERMISSION_MODE
|
|
64
|
-
CLAUDE_PRETTY
|
|
65
|
-
CLAUDE_VERBOSE
|
|
90
|
+
CLAUDE_PROJECT_PATH Project path (default: current directory)
|
|
91
|
+
CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)
|
|
92
|
+
CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt
|
|
93
|
+
CLAUDE_PERMISSION_MODE Permission mode (default: default)
|
|
94
|
+
CLAUDE_PRETTY Pretty print JSON output (default: true)
|
|
95
|
+
CLAUDE_VERBOSE Enable verbose output (default: false)
|
|
96
|
+
CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)
|
|
66
97
|
"""
|
|
67
98
|
)
|
|
68
99
|
|
|
@@ -90,7 +121,7 @@ Environment Variables:
|
|
|
90
121
|
"-m", "--model",
|
|
91
122
|
type=str,
|
|
92
123
|
default=os.environ.get("CLAUDE_MODEL", self.DEFAULT_MODEL),
|
|
93
|
-
help=f"Model name (e.g
|
|
124
|
+
help=f"Model name. Supports shorthand (e.g., ':haiku', ':sonnet', ':opus', ':claude-haiku-4-5') or full model ID (e.g., 'claude-haiku-4-5-20251001'). Default: {self.DEFAULT_MODEL} (env: CLAUDE_MODEL)"
|
|
94
125
|
)
|
|
95
126
|
|
|
96
127
|
parser.add_argument(
|
|
@@ -214,10 +245,20 @@ Environment Variables:
|
|
|
214
245
|
"""
|
|
215
246
|
Format JSON line for pretty output.
|
|
216
247
|
For type=assistant: show datetime, message content, and counter
|
|
248
|
+
For type=user: show datetime, message content (truncated based on env var), and counter
|
|
217
249
|
For other types: show full message with datetime and counter
|
|
218
250
|
Returns None if line should be skipped
|
|
219
251
|
|
|
220
252
|
IMPORTANT: Always preserve the 'type' field so shell backend can parse events
|
|
253
|
+
|
|
254
|
+
MULTI-LINE HANDLING: When content/result fields contain \\n escape sequences,
|
|
255
|
+
the output shows the JSON metadata on one line, then the actual content/result
|
|
256
|
+
value is printed below with newlines properly rendered (similar to jq -r or @text).
|
|
257
|
+
This keeps JSON structure compact while making multi-line strings readable.
|
|
258
|
+
|
|
259
|
+
USER MESSAGE TRUNCATION: User messages are truncated based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
|
|
260
|
+
environment variable (default: 4 lines, -1: no truncation). When truncated, a [Truncated...]
|
|
261
|
+
indicator is added. This only applies to user messages in pretty mode.
|
|
221
262
|
"""
|
|
222
263
|
try:
|
|
223
264
|
data = json.loads(json_line)
|
|
@@ -226,8 +267,53 @@ Environment Variables:
|
|
|
226
267
|
# Get current datetime in readable format
|
|
227
268
|
now = datetime.now().strftime("%I:%M:%S %p")
|
|
228
269
|
|
|
270
|
+
# For user messages, show simplified output with truncation
|
|
271
|
+
if data.get("type") == "user":
|
|
272
|
+
message = data.get("message", {})
|
|
273
|
+
content_list = message.get("content", [])
|
|
274
|
+
|
|
275
|
+
# Extract text content
|
|
276
|
+
text_content = ""
|
|
277
|
+
for item in content_list:
|
|
278
|
+
if isinstance(item, dict) and item.get("type") == "text":
|
|
279
|
+
text_content = item.get("text", "")
|
|
280
|
+
break
|
|
281
|
+
|
|
282
|
+
# Apply truncation for user messages based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
|
|
283
|
+
# -1 means no truncation, otherwise truncate to N lines
|
|
284
|
+
if self.user_message_truncate != -1:
|
|
285
|
+
lines = text_content.split('\n')
|
|
286
|
+
if len(lines) > self.user_message_truncate:
|
|
287
|
+
# Truncate to N lines and add indicator
|
|
288
|
+
text_content = '\n'.join(lines[:self.user_message_truncate]) + '\n[Truncated...]'
|
|
289
|
+
|
|
290
|
+
# Create simplified output with datetime, content, and counter
|
|
291
|
+
simplified = {
|
|
292
|
+
"type": "user",
|
|
293
|
+
"datetime": now,
|
|
294
|
+
"counter": f"#{self.message_counter}"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# Check if content has newlines after potential truncation
|
|
298
|
+
if '\n' in text_content:
|
|
299
|
+
# Multi-line content: print JSON metadata, then raw content
|
|
300
|
+
metadata = {
|
|
301
|
+
"type": "user",
|
|
302
|
+
"datetime": now,
|
|
303
|
+
"counter": f"#{self.message_counter}"
|
|
304
|
+
}
|
|
305
|
+
# Print metadata as compact JSON on first line
|
|
306
|
+
output = json.dumps(metadata, ensure_ascii=False)
|
|
307
|
+
# Then print content label and raw multi-line text
|
|
308
|
+
output += "\ncontent:\n" + text_content
|
|
309
|
+
return output
|
|
310
|
+
else:
|
|
311
|
+
# Single-line content: normal JSON
|
|
312
|
+
simplified["content"] = text_content
|
|
313
|
+
return json.dumps(simplified, ensure_ascii=False)
|
|
314
|
+
|
|
229
315
|
# For assistant messages, show simplified output
|
|
230
|
-
|
|
316
|
+
elif data.get("type") == "assistant":
|
|
231
317
|
message = data.get("message", {})
|
|
232
318
|
content_list = message.get("content", [])
|
|
233
319
|
|
|
@@ -258,21 +344,82 @@ Environment Variables:
|
|
|
258
344
|
|
|
259
345
|
# Add either content or tool_use data
|
|
260
346
|
if tool_use_data:
|
|
261
|
-
|
|
347
|
+
# Check if prompt field in tool_use.input has multi-line content
|
|
348
|
+
tool_input = tool_use_data.get("input", {})
|
|
349
|
+
prompt_field = tool_input.get("prompt", "")
|
|
350
|
+
|
|
351
|
+
if isinstance(prompt_field, str) and '\n' in prompt_field:
|
|
352
|
+
# Multi-line prompt: extract it and render separately
|
|
353
|
+
# Create a copy of tool_use_data with prompt removed
|
|
354
|
+
tool_use_copy = {
|
|
355
|
+
"name": tool_use_data.get("name", ""),
|
|
356
|
+
"input": {k: v for k, v in tool_input.items() if k != "prompt"}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
simplified["tool_use"] = tool_use_copy
|
|
360
|
+
|
|
361
|
+
# Print metadata as compact JSON on first line
|
|
362
|
+
output = json.dumps(simplified, ensure_ascii=False)
|
|
363
|
+
# Then print prompt label and raw multi-line text
|
|
364
|
+
output += "\nprompt:\n" + prompt_field
|
|
365
|
+
return output
|
|
366
|
+
else:
|
|
367
|
+
# No multi-line prompt: normal JSON output for tool_use
|
|
368
|
+
simplified["tool_use"] = tool_use_data
|
|
369
|
+
return json.dumps(simplified, ensure_ascii=False)
|
|
262
370
|
else:
|
|
263
|
-
# For
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
371
|
+
# For content, check if it has newlines
|
|
372
|
+
if '\n' in text_content:
|
|
373
|
+
# Multi-line content: print JSON metadata, then raw content
|
|
374
|
+
metadata = {
|
|
375
|
+
"type": "assistant",
|
|
376
|
+
"datetime": now,
|
|
377
|
+
"counter": f"#{self.message_counter}"
|
|
378
|
+
}
|
|
379
|
+
# Print metadata as compact JSON on first line
|
|
380
|
+
output = json.dumps(metadata, ensure_ascii=False)
|
|
381
|
+
# Then print content label and raw multi-line text
|
|
382
|
+
output += "\ncontent:\n" + text_content
|
|
383
|
+
return output
|
|
271
384
|
else:
|
|
385
|
+
# Single-line content: normal JSON
|
|
272
386
|
simplified["content"] = text_content
|
|
273
|
-
|
|
274
|
-
return json.dumps(simplified)
|
|
387
|
+
return json.dumps(simplified, ensure_ascii=False)
|
|
275
388
|
else:
|
|
389
|
+
# For other message types, check if there's nested content to flatten
|
|
390
|
+
message = data.get("message", {})
|
|
391
|
+
content_list = message.get("content", [])
|
|
392
|
+
|
|
393
|
+
# Check if this is a message with nested tool_result or similar content
|
|
394
|
+
if content_list and isinstance(content_list, list) and len(content_list) > 0:
|
|
395
|
+
nested_item = content_list[0]
|
|
396
|
+
if isinstance(nested_item, dict) and nested_item.get("type") in ["tool_result"]:
|
|
397
|
+
# Flatten the nested structure by pulling nested fields to top level
|
|
398
|
+
flattened = {
|
|
399
|
+
"datetime": now,
|
|
400
|
+
"counter": f"#{self.message_counter}",
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# Add tool_use_id if present
|
|
404
|
+
if "tool_use_id" in nested_item:
|
|
405
|
+
flattened["tool_use_id"] = nested_item["tool_use_id"]
|
|
406
|
+
|
|
407
|
+
# Add type from nested item
|
|
408
|
+
flattened["type"] = nested_item["type"]
|
|
409
|
+
|
|
410
|
+
# Handle content field with multiline support
|
|
411
|
+
nested_content = nested_item.get("content", "")
|
|
412
|
+
if isinstance(nested_content, str) and '\n' in nested_content:
|
|
413
|
+
# Multi-line content: separate metadata from content
|
|
414
|
+
# Print metadata as compact JSON
|
|
415
|
+
metadata_json = json.dumps(flattened, ensure_ascii=False)
|
|
416
|
+
# Then print content label and raw multi-line text
|
|
417
|
+
return metadata_json + "\ncontent:\n" + nested_content
|
|
418
|
+
else:
|
|
419
|
+
# Single-line content: normal JSON
|
|
420
|
+
flattened["content"] = nested_content
|
|
421
|
+
return json.dumps(flattened, ensure_ascii=False)
|
|
422
|
+
|
|
276
423
|
# For other message types, show full message with datetime and counter
|
|
277
424
|
# Type field is already present in data, so it's preserved
|
|
278
425
|
output = {
|
|
@@ -280,7 +427,18 @@ Environment Variables:
|
|
|
280
427
|
"counter": f"#{self.message_counter}",
|
|
281
428
|
**data
|
|
282
429
|
}
|
|
283
|
-
|
|
430
|
+
|
|
431
|
+
# Check if 'result' field has multi-line content
|
|
432
|
+
if "result" in output and isinstance(output["result"], str) and '\n' in output["result"]:
|
|
433
|
+
# Multi-line result: separate metadata from content
|
|
434
|
+
result_value = output.pop("result")
|
|
435
|
+
# Print metadata as compact JSON
|
|
436
|
+
metadata_json = json.dumps(output, ensure_ascii=False)
|
|
437
|
+
# Then print result label and raw multi-line text
|
|
438
|
+
return metadata_json + "\nresult:\n" + result_value
|
|
439
|
+
else:
|
|
440
|
+
# Normal JSON output
|
|
441
|
+
return json.dumps(output, ensure_ascii=False)
|
|
284
442
|
|
|
285
443
|
except json.JSONDecodeError:
|
|
286
444
|
# If not valid JSON, return as-is
|
|
@@ -368,12 +526,13 @@ Environment Variables:
|
|
|
368
526
|
)
|
|
369
527
|
print("\nRun 'claude.py --help' for usage information.", file=sys.stderr)
|
|
370
528
|
print("\nAvailable Environment Variables:", file=sys.stderr)
|
|
371
|
-
print(" CLAUDE_PROJECT_PATH
|
|
372
|
-
print(" CLAUDE_MODEL
|
|
373
|
-
print(" CLAUDE_AUTO_INSTRUCTION
|
|
374
|
-
print(" CLAUDE_PERMISSION_MODE
|
|
375
|
-
print(" CLAUDE_PRETTY
|
|
376
|
-
print(" CLAUDE_VERBOSE
|
|
529
|
+
print(" CLAUDE_PROJECT_PATH Project path (default: current directory)", file=sys.stderr)
|
|
530
|
+
print(" CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)", file=sys.stderr)
|
|
531
|
+
print(" CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt", file=sys.stderr)
|
|
532
|
+
print(" CLAUDE_PERMISSION_MODE Permission mode (default: default)", file=sys.stderr)
|
|
533
|
+
print(" CLAUDE_PRETTY Pretty print JSON output (default: true)", file=sys.stderr)
|
|
534
|
+
print(" CLAUDE_VERBOSE Enable verbose output (default: false)", file=sys.stderr)
|
|
535
|
+
print(" CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)", file=sys.stderr)
|
|
377
536
|
return 1
|
|
378
537
|
|
|
379
538
|
# Check if claude is installed
|
|
@@ -390,7 +549,8 @@ Environment Variables:
|
|
|
390
549
|
|
|
391
550
|
# Set configuration from arguments
|
|
392
551
|
self.project_path = os.path.abspath(args.cd)
|
|
393
|
-
|
|
552
|
+
# Expand model shorthand (e.g., :haiku -> claude-haiku-4-5-20251001)
|
|
553
|
+
self.model_name = self.expand_model_shorthand(args.model)
|
|
394
554
|
self.auto_instruction = args.auto_instruction
|
|
395
555
|
|
|
396
556
|
# Get prompt from file or argument
|