prjct-cli 0.29.0 → 0.29.2

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.
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Changes component
3
+ # Displays lines added and removed
4
+
5
+ component_changes() {
6
+ component_enabled "changes" || return
7
+
8
+ # ADDED and REMOVED are set by parse_stdin in cache.sh
9
+ echo -e "${SUCCESS}+${ADDED}${NC} ${ERROR}-${REMOVED}${NC}"
10
+ }
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Context component
3
+ # Displays context window usage progress bar
4
+
5
+ component_context() {
6
+ component_enabled "context" || return
7
+
8
+ # CTX_PERCENT is set by parse_stdin in cache.sh
9
+ local bar_width="${CONFIG_CONTEXT_BAR_WIDTH:-10}"
10
+ local filled=$((CTX_PERCENT * bar_width / 100))
11
+ local empty=$((bar_width - filled))
12
+
13
+ # Determine color based on usage
14
+ local bar_color
15
+ if [[ "$CTX_PERCENT" -ge 80 ]]; then
16
+ bar_color="$ERROR"
17
+ elif [[ "$CTX_PERCENT" -ge 50 ]]; then
18
+ bar_color="$ACCENT"
19
+ else
20
+ bar_color="$SUCCESS"
21
+ fi
22
+
23
+ # Build progress bar
24
+ local bar="${bar_color}"
25
+ for ((i=0; i<filled; i++)); do bar+="█"; done
26
+ bar+="${MUTED}"
27
+ for ((i=0; i<empty; i++)); do bar+="░"; done
28
+ bar+="${NC}"
29
+
30
+ echo -e "${MUTED}ctx${NC} ${bar} ${MUTED}${CTX_PERCENT}%${NC}"
31
+ }
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Directory component
3
+ # Displays the current directory name
4
+
5
+ component_dir() {
6
+ component_enabled "dir" || return
7
+
8
+ local dir_name=$(basename "$CWD")
9
+
10
+ echo -e "${ACCENT}${ICON_DIR} ${dir_name}${NC}"
11
+ }
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Git component
3
+ # Displays the current branch and dirty status
4
+
5
+ component_git() {
6
+ component_enabled "git" || return
7
+
8
+ local cache_file="${CACHE_DIR}/git.cache"
9
+ local git_data=""
10
+
11
+ # Check cache first
12
+ if cache_valid "$cache_file" "$CONFIG_CACHE_TTL_GIT"; then
13
+ git_data=$(cat "$cache_file")
14
+ else
15
+ # Check if in git repo
16
+ if ! git -C "$CWD" rev-parse --git-dir > /dev/null 2>&1; then
17
+ return
18
+ fi
19
+
20
+ # Get branch name
21
+ local branch=$(git -C "$CWD" branch --show-current 2>/dev/null)
22
+ [[ -z "$branch" ]] && return
23
+
24
+ # Check for dirty state
25
+ local dirty=""
26
+ if [[ -n $(git -C "$CWD" status --porcelain 2>/dev/null | head -1) ]]; then
27
+ dirty="*"
28
+ fi
29
+
30
+ git_data="${branch}|${dirty}"
31
+
32
+ # Cache the result
33
+ write_cache "$cache_file" "$git_data"
34
+ fi
35
+
36
+ [[ -z "$git_data" ]] && return
37
+
38
+ # Parse git data
39
+ local branch=$(echo "$git_data" | cut -d'|' -f1)
40
+ local dirty=$(echo "$git_data" | cut -d'|' -f2)
41
+
42
+ [[ -z "$branch" ]] && return
43
+
44
+ echo -e "${SECONDARY}${ICON_GIT} ${branch}${dirty}${NC}"
45
+ }
@@ -0,0 +1,74 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Linear integration component
3
+ # Displays the linked Linear issue ID and priority
4
+
5
+ component_linear() {
6
+ component_enabled "linear" || return
7
+ [[ "${CONFIG_LINEAR_ENABLED}" != "true" ]] && return
8
+
9
+ local cache_file="${CACHE_DIR}/linear.cache"
10
+ local issue_data=""
11
+
12
+ # Check cache first
13
+ if cache_valid "$cache_file" "$CONFIG_CACHE_TTL_LINEAR"; then
14
+ issue_data=$(cat "$cache_file")
15
+ else
16
+ # Get project ID
17
+ local project_id=$(get_project_id)
18
+ [[ -z "$project_id" ]] && return
19
+
20
+ local global_path="${HOME}/.prjct-cli/projects/${project_id}"
21
+ local state_file="${global_path}/storage/state.json"
22
+ local issues_file="${global_path}/storage/issues.json"
23
+
24
+ # Check if state file exists
25
+ [[ ! -f "$state_file" ]] && return
26
+
27
+ # Get linked issue from current task
28
+ local linked_issue=$(jq -r '.currentTask.linkedIssue // ""' "$state_file" 2>/dev/null)
29
+
30
+ # If no linked issue, try to get from issues.json (last worked)
31
+ if [[ -z "$linked_issue" ]] && [[ -f "$issues_file" ]]; then
32
+ # Get most recently updated issue that's in_progress
33
+ linked_issue=$(jq -r '
34
+ .issues // {} | to_entries
35
+ | map(select(.value.status == "in_progress"))
36
+ | sort_by(.value.updatedAt) | last
37
+ | .key // ""
38
+ ' "$issues_file" 2>/dev/null)
39
+ fi
40
+
41
+ [[ -z "$linked_issue" ]] && return
42
+
43
+ # Get issue details from issues cache
44
+ if [[ -f "$issues_file" ]]; then
45
+ issue_data=$(jq -r --arg id "$linked_issue" '
46
+ .issues[$id] // {} |
47
+ "\(.externalId // "")|\(.priority // "none")"
48
+ ' "$issues_file" 2>/dev/null)
49
+
50
+ # Cache the result
51
+ write_cache "$cache_file" "$issue_data"
52
+ fi
53
+ fi
54
+
55
+ # Return empty if no data
56
+ [[ -z "$issue_data" || "$issue_data" == "|" ]] && return
57
+
58
+ # Parse issue data
59
+ local issue_id=$(echo "$issue_data" | cut -d'|' -f1)
60
+ local priority=$(echo "$issue_data" | cut -d'|' -f2)
61
+
62
+ [[ -z "$issue_id" ]] && return
63
+
64
+ # Format output
65
+ local output="${ACCENT}${issue_id}${NC}"
66
+
67
+ # Add priority icon if enabled and priority is significant
68
+ if [[ "${CONFIG_LINEAR_SHOW_PRIORITY}" == "true" ]]; then
69
+ local priority_icon=$(get_priority_icon "$priority")
70
+ [[ -n "$priority_icon" ]] && output+="${priority_icon}"
71
+ fi
72
+
73
+ echo -e "$output"
74
+ }
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Model component
3
+ # Displays the current model icon
4
+
5
+ component_model() {
6
+ component_enabled "model" || return
7
+
8
+ # MODEL is set by parse_stdin in cache.sh
9
+ local icon=$(get_model_icon "$MODEL")
10
+
11
+ echo -e "$icon"
12
+ }
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+ # prjct statusline - prjct icon component
3
+ # Displays the prjct brand icon
4
+
5
+ component_prjct_icon() {
6
+ component_enabled "prjct_icon" || return
7
+
8
+ echo -e "${PRIMARY}${BOLD}${ICON_PRJCT}${NC}"
9
+ }
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Task component
3
+ # Displays the current prjct task
4
+
5
+ component_task() {
6
+ component_enabled "task" || return
7
+
8
+ local cache_file="${CACHE_DIR}/prjct_task.cache"
9
+ local task=""
10
+
11
+ # Check cache first
12
+ if cache_valid "$cache_file" "$CONFIG_CACHE_TTL_PRJCT"; then
13
+ task=$(cat "$cache_file")
14
+ else
15
+ # Get project ID
16
+ local project_id=$(get_project_id)
17
+ [[ -z "$project_id" ]] && return
18
+
19
+ # Get state file
20
+ local state_file="${HOME}/.prjct-cli/projects/${project_id}/storage/state.json"
21
+ [[ ! -f "$state_file" ]] && return
22
+
23
+ # Extract task title or description
24
+ task=$(jq -r '.currentTask.title // .currentTask.description // ""' "$state_file" 2>/dev/null)
25
+
26
+ # Truncate if too long
27
+ local max_len="${CONFIG_TASK_MAX_LENGTH:-25}"
28
+ if [[ ${#task} -gt $max_len ]]; then
29
+ task="${task:0:$((max_len - 3))}..."
30
+ fi
31
+
32
+ # Cache the result
33
+ write_cache "$cache_file" "$task"
34
+ fi
35
+
36
+ # Return empty if no task
37
+ [[ -z "$task" ]] && return
38
+
39
+ echo -e "${PURPLE}${task}${NC}"
40
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "version": 1,
3
+ "theme": "default",
4
+ "cacheTTL": {
5
+ "prjct": 30,
6
+ "git": 5,
7
+ "linear": 60
8
+ },
9
+ "components": {
10
+ "prjct_icon": {
11
+ "enabled": true,
12
+ "position": 0
13
+ },
14
+ "task": {
15
+ "enabled": true,
16
+ "position": 1,
17
+ "maxLength": 25
18
+ },
19
+ "linear": {
20
+ "enabled": true,
21
+ "position": 2,
22
+ "showPriority": true
23
+ },
24
+ "dir": {
25
+ "enabled": true,
26
+ "position": 3
27
+ },
28
+ "git": {
29
+ "enabled": true,
30
+ "position": 4
31
+ },
32
+ "changes": {
33
+ "enabled": true,
34
+ "position": 5
35
+ },
36
+ "context": {
37
+ "enabled": true,
38
+ "position": 6,
39
+ "barWidth": 10
40
+ },
41
+ "model": {
42
+ "enabled": true,
43
+ "position": 7
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,119 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Cache utilities
3
+ # Optimized caching for fast statusline rendering
4
+
5
+ # Cache directory
6
+ CACHE_DIR="/tmp/prjct_statusline"
7
+
8
+ # Ensure cache directory exists
9
+ ensure_cache_dir() {
10
+ [[ -d "$CACHE_DIR" ]] || mkdir -p "$CACHE_DIR"
11
+ }
12
+
13
+ # Check if cache file is still valid
14
+ # Usage: cache_valid "/path/to/cache" 30
15
+ # Returns: 0 if valid, 1 if expired/missing
16
+ cache_valid() {
17
+ local file="$1"
18
+ local ttl="${2:-30}"
19
+
20
+ [[ ! -f "$file" ]] && return 1
21
+
22
+ # Get file age in seconds (macOS and Linux compatible)
23
+ local mtime
24
+ if [[ "$OSTYPE" == "darwin"* ]]; then
25
+ mtime=$(stat -f %m "$file" 2>/dev/null)
26
+ else
27
+ mtime=$(stat -c %Y "$file" 2>/dev/null)
28
+ fi
29
+
30
+ [[ -z "$mtime" ]] && return 1
31
+
32
+ local now=$(date +%s)
33
+ local age=$((now - mtime))
34
+
35
+ [[ "$age" -lt "$ttl" ]]
36
+ }
37
+
38
+ # Read from cache if valid, otherwise return empty
39
+ # Usage: result=$(read_cache "/path/to/cache" 30)
40
+ read_cache() {
41
+ local file="$1"
42
+ local ttl="${2:-30}"
43
+
44
+ if cache_valid "$file" "$ttl"; then
45
+ cat "$file"
46
+ fi
47
+ }
48
+
49
+ # Write to cache
50
+ # Usage: write_cache "/path/to/cache" "content"
51
+ write_cache() {
52
+ local file="$1"
53
+ local content="$2"
54
+
55
+ ensure_cache_dir
56
+ echo "$content" > "$file"
57
+ }
58
+
59
+ # Parse all stdin data in a single jq call
60
+ # Sets global variables: MODEL, CWD, ADDED, REMOVED, CTX_SIZE, INPUT_TOKENS, etc.
61
+ parse_stdin() {
62
+ local input="$1"
63
+
64
+ # Single jq call to extract all values
65
+ local parsed
66
+ parsed=$(echo "$input" | jq -r '
67
+ [
68
+ (.model.display_name // "Claude"),
69
+ (.workspace.current_dir // "~"),
70
+ (.cost.total_lines_added // 0),
71
+ (.cost.total_lines_removed // 0),
72
+ (.context_window.context_window_size // 200000),
73
+ (.context_window.current_usage.input_tokens // 0),
74
+ (.context_window.current_usage.cache_creation_input_tokens // 0),
75
+ (.context_window.current_usage.cache_read_input_tokens // 0)
76
+ ] | @tsv
77
+ ' 2>/dev/null)
78
+
79
+ # Parse tab-separated values
80
+ IFS=$'\t' read -r MODEL CWD ADDED REMOVED CTX_SIZE INPUT_TOKENS CACHE_CREATE CACHE_READ <<< "$parsed"
81
+
82
+ # Set defaults if parsing failed
83
+ MODEL="${MODEL:-Claude}"
84
+ CWD="${CWD:-~}"
85
+ ADDED="${ADDED:-0}"
86
+ REMOVED="${REMOVED:-0}"
87
+ CTX_SIZE="${CTX_SIZE:-200000}"
88
+ INPUT_TOKENS="${INPUT_TOKENS:-0}"
89
+ CACHE_CREATE="${CACHE_CREATE:-0}"
90
+ CACHE_READ="${CACHE_READ:-0}"
91
+
92
+ # Calculate context percentage
93
+ TOTAL_USED=$((INPUT_TOKENS + CACHE_CREATE + CACHE_READ))
94
+ if [[ "$CTX_SIZE" -gt 0 ]] 2>/dev/null; then
95
+ CTX_PERCENT=$((TOTAL_USED * 100 / CTX_SIZE))
96
+ else
97
+ CTX_PERCENT=0
98
+ fi
99
+ [[ "$CTX_PERCENT" -gt 100 ]] && CTX_PERCENT=100
100
+ [[ "$CTX_PERCENT" -lt 0 ]] && CTX_PERCENT=0
101
+ }
102
+
103
+ # Get project ID from .prjct/prjct.config.json
104
+ # Usage: project_id=$(get_project_id)
105
+ get_project_id() {
106
+ local config_file="${CWD}/.prjct/prjct.config.json"
107
+
108
+ [[ ! -f "$config_file" ]] && return
109
+
110
+ jq -r '.projectId // ""' "$config_file" 2>/dev/null
111
+ }
112
+
113
+ # Get global path for project storage
114
+ # Usage: global_path=$(get_global_path "project-id")
115
+ get_global_path() {
116
+ local project_id="$1"
117
+ [[ -z "$project_id" ]] && return
118
+ echo "${HOME}/.prjct-cli/projects/${project_id}"
119
+ }
@@ -0,0 +1,147 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Configuration loading utilities
3
+ # Loads user preferences for statusline customization
4
+
5
+ # Config file location
6
+ CONFIG_FILE="${HOME}/.prjct-cli/statusline/config.json"
7
+
8
+ # Default configuration values
9
+ DEFAULT_THEME="default"
10
+ DEFAULT_CACHE_TTL_PRJCT=30
11
+ DEFAULT_CACHE_TTL_GIT=5
12
+ DEFAULT_CACHE_TTL_LINEAR=60
13
+ DEFAULT_TASK_MAX_LENGTH=25
14
+ DEFAULT_CONTEXT_BAR_WIDTH=10
15
+
16
+ # Component configuration (will be populated by load_config)
17
+ declare -A COMPONENT_ENABLED
18
+ declare -A COMPONENT_POSITION
19
+
20
+ # Load configuration from JSON file
21
+ # Sets global CONFIG_* variables
22
+ load_config() {
23
+ # Set defaults first
24
+ CONFIG_THEME="$DEFAULT_THEME"
25
+ CONFIG_CACHE_TTL_PRJCT="$DEFAULT_CACHE_TTL_PRJCT"
26
+ CONFIG_CACHE_TTL_GIT="$DEFAULT_CACHE_TTL_GIT"
27
+ CONFIG_CACHE_TTL_LINEAR="$DEFAULT_CACHE_TTL_LINEAR"
28
+ CONFIG_TASK_MAX_LENGTH="$DEFAULT_TASK_MAX_LENGTH"
29
+ CONFIG_CONTEXT_BAR_WIDTH="$DEFAULT_CONTEXT_BAR_WIDTH"
30
+ CONFIG_LINEAR_ENABLED="true"
31
+ CONFIG_LINEAR_SHOW_PRIORITY="true"
32
+
33
+ # Default component configuration
34
+ COMPONENT_ENABLED["prjct_icon"]="true"
35
+ COMPONENT_ENABLED["task"]="true"
36
+ COMPONENT_ENABLED["linear"]="true"
37
+ COMPONENT_ENABLED["dir"]="true"
38
+ COMPONENT_ENABLED["git"]="true"
39
+ COMPONENT_ENABLED["changes"]="true"
40
+ COMPONENT_ENABLED["context"]="true"
41
+ COMPONENT_ENABLED["model"]="true"
42
+
43
+ COMPONENT_POSITION["prjct_icon"]=0
44
+ COMPONENT_POSITION["task"]=1
45
+ COMPONENT_POSITION["linear"]=2
46
+ COMPONENT_POSITION["dir"]=3
47
+ COMPONENT_POSITION["git"]=4
48
+ COMPONENT_POSITION["changes"]=5
49
+ COMPONENT_POSITION["context"]=6
50
+ COMPONENT_POSITION["model"]=7
51
+
52
+ # Load config file if exists
53
+ [[ ! -f "$CONFIG_FILE" ]] && return
54
+
55
+ # Parse config in a single jq call
56
+ local config_data
57
+ config_data=$(jq -r '
58
+ [
59
+ (.theme // "default"),
60
+ (.cacheTTL.prjct // 30),
61
+ (.cacheTTL.git // 5),
62
+ (.cacheTTL.linear // 60),
63
+ (.components.task.maxLength // 25),
64
+ (.components.context.barWidth // 10),
65
+ (.components.linear.showPriority // true),
66
+ (.components.prjct_icon.enabled // true),
67
+ (.components.task.enabled // true),
68
+ (.components.linear.enabled // true),
69
+ (.components.dir.enabled // true),
70
+ (.components.git.enabled // true),
71
+ (.components.changes.enabled // true),
72
+ (.components.context.enabled // true),
73
+ (.components.model.enabled // true),
74
+ (.components.prjct_icon.position // 0),
75
+ (.components.task.position // 1),
76
+ (.components.linear.position // 2),
77
+ (.components.dir.position // 3),
78
+ (.components.git.position // 4),
79
+ (.components.changes.position // 5),
80
+ (.components.context.position // 6),
81
+ (.components.model.position // 7)
82
+ ] | @tsv
83
+ ' "$CONFIG_FILE" 2>/dev/null)
84
+
85
+ [[ -z "$config_data" ]] && return
86
+
87
+ # Parse tab-separated values
88
+ IFS=$'\t' read -r \
89
+ CONFIG_THEME \
90
+ CONFIG_CACHE_TTL_PRJCT CONFIG_CACHE_TTL_GIT CONFIG_CACHE_TTL_LINEAR \
91
+ CONFIG_TASK_MAX_LENGTH CONFIG_CONTEXT_BAR_WIDTH CONFIG_LINEAR_SHOW_PRIORITY \
92
+ E_PRJCT_ICON E_TASK E_LINEAR E_DIR E_GIT E_CHANGES E_CONTEXT E_MODEL \
93
+ P_PRJCT_ICON P_TASK P_LINEAR P_DIR P_GIT P_CHANGES P_CONTEXT P_MODEL \
94
+ <<< "$config_data"
95
+
96
+ # Set component enabled states
97
+ COMPONENT_ENABLED["prjct_icon"]="$E_PRJCT_ICON"
98
+ COMPONENT_ENABLED["task"]="$E_TASK"
99
+ COMPONENT_ENABLED["linear"]="$E_LINEAR"
100
+ COMPONENT_ENABLED["dir"]="$E_DIR"
101
+ COMPONENT_ENABLED["git"]="$E_GIT"
102
+ COMPONENT_ENABLED["changes"]="$E_CHANGES"
103
+ COMPONENT_ENABLED["context"]="$E_CONTEXT"
104
+ COMPONENT_ENABLED["model"]="$E_MODEL"
105
+
106
+ # Set component positions
107
+ COMPONENT_POSITION["prjct_icon"]="$P_PRJCT_ICON"
108
+ COMPONENT_POSITION["task"]="$P_TASK"
109
+ COMPONENT_POSITION["linear"]="$P_LINEAR"
110
+ COMPONENT_POSITION["dir"]="$P_DIR"
111
+ COMPONENT_POSITION["git"]="$P_GIT"
112
+ COMPONENT_POSITION["changes"]="$P_CHANGES"
113
+ COMPONENT_POSITION["context"]="$P_CONTEXT"
114
+ COMPONENT_POSITION["model"]="$P_MODEL"
115
+
116
+ # Update linear enabled based on component config
117
+ CONFIG_LINEAR_ENABLED="${COMPONENT_ENABLED["linear"]}"
118
+ }
119
+
120
+ # Check if a component is enabled
121
+ # Usage: if component_enabled "task"; then ...
122
+ component_enabled() {
123
+ local name="$1"
124
+ [[ "${COMPONENT_ENABLED[$name]}" == "true" ]]
125
+ }
126
+
127
+ # Get component position
128
+ # Usage: pos=$(component_position "task")
129
+ component_position() {
130
+ local name="$1"
131
+ echo "${COMPONENT_POSITION[$name]:-99}"
132
+ }
133
+
134
+ # Get sorted list of enabled components
135
+ # Usage: components=$(get_enabled_components)
136
+ get_enabled_components() {
137
+ local components=()
138
+
139
+ for name in "${!COMPONENT_ENABLED[@]}"; do
140
+ if [[ "${COMPONENT_ENABLED[$name]}" == "true" ]]; then
141
+ components+=("${COMPONENT_POSITION[$name]}:$name")
142
+ fi
143
+ done
144
+
145
+ # Sort by position and extract names
146
+ printf '%s\n' "${components[@]}" | sort -t: -k1 -n | cut -d: -f2
147
+ }
@@ -0,0 +1,169 @@
1
+ #!/bin/bash
2
+ # prjct statusline - Theme loading utilities
3
+ # Loads colors and icons from theme JSON files
4
+
5
+ # Default theme location
6
+ STATUSLINE_DIR="${HOME}/.prjct-cli/statusline"
7
+ THEME_DIR="${STATUSLINE_DIR}/themes"
8
+
9
+ # Default colors (ANSI 256) - used as fallback
10
+ DEFAULT_PRIMARY='\033[38;5;51m' # Cyan - prjct brand
11
+ DEFAULT_ACCENT='\033[38;5;220m' # Gold/Yellow
12
+ DEFAULT_SECONDARY='\033[38;5;147m' # Light purple
13
+ DEFAULT_MUTED='\033[38;5;242m' # Gray
14
+ DEFAULT_SUCCESS='\033[38;5;114m' # Green
15
+ DEFAULT_ERROR='\033[38;5;204m' # Pink/Red
16
+ DEFAULT_WARNING='\033[38;5;214m' # Orange
17
+ DEFAULT_PURPLE='\033[38;5;183m' # Purple
18
+
19
+ # Default icons
20
+ DEFAULT_ICON_PRJCT="⚡"
21
+ DEFAULT_ICON_DIR="󰉋"
22
+ DEFAULT_ICON_GIT=""
23
+ DEFAULT_ICON_SEPARATOR="│"
24
+ DEFAULT_ICON_OPUS="🎭"
25
+ DEFAULT_ICON_SONNET="📝"
26
+ DEFAULT_ICON_HAIKU="🍃"
27
+ DEFAULT_ICON_DEFAULT="🤖"
28
+ DEFAULT_ICON_PRIORITY_URGENT="🔴"
29
+ DEFAULT_ICON_PRIORITY_HIGH="🟠"
30
+ DEFAULT_ICON_PRIORITY_MEDIUM="🟡"
31
+
32
+ # Text formatting
33
+ BOLD='\033[1m'
34
+ DIM='\033[2m'
35
+ NC='\033[0m'
36
+
37
+ # Convert ANSI 256 color code to escape sequence
38
+ color_to_escape() {
39
+ local code="$1"
40
+ [[ -z "$code" ]] && return
41
+ echo "\\033[38;5;${code}m"
42
+ }
43
+
44
+ # Load theme from JSON file
45
+ # Sets global color and icon variables
46
+ load_theme() {
47
+ local theme_name="${CONFIG_THEME:-default}"
48
+ local theme_file="${THEME_DIR}/${theme_name}.json"
49
+
50
+ # Fallback to default theme
51
+ [[ ! -f "$theme_file" ]] && theme_file="${THEME_DIR}/default.json"
52
+
53
+ # If still no theme file, use defaults
54
+ if [[ ! -f "$theme_file" ]]; then
55
+ set_default_colors
56
+ set_default_icons
57
+ return
58
+ fi
59
+
60
+ # Load all colors and icons in a single jq call
61
+ local theme_data
62
+ theme_data=$(jq -r '
63
+ [
64
+ (.colors.primary // "51"),
65
+ (.colors.accent // "220"),
66
+ (.colors.secondary // "147"),
67
+ (.colors.muted // "242"),
68
+ (.colors.success // "114"),
69
+ (.colors.error // "204"),
70
+ (.colors.warning // "214"),
71
+ (.colors.purple // "183"),
72
+ (.icons.prjct // "⚡"),
73
+ (.icons.dir // "󰉋"),
74
+ (.icons.git // ""),
75
+ (.icons.separator // "│"),
76
+ (.icons.opus // "🎭"),
77
+ (.icons.sonnet // "📝"),
78
+ (.icons.haiku // "🍃"),
79
+ (.icons.default_model // "🤖"),
80
+ (.icons.priority_urgent // "🔴"),
81
+ (.icons.priority_high // "🟠"),
82
+ (.icons.priority_medium // "🟡")
83
+ ] | @tsv
84
+ ' "$theme_file" 2>/dev/null)
85
+
86
+ if [[ -z "$theme_data" ]]; then
87
+ set_default_colors
88
+ set_default_icons
89
+ return
90
+ fi
91
+
92
+ # Parse theme data
93
+ IFS=$'\t' read -r \
94
+ C_PRIMARY C_ACCENT C_SECONDARY C_MUTED C_SUCCESS C_ERROR C_WARNING C_PURPLE \
95
+ ICON_PRJCT ICON_DIR ICON_GIT ICON_SEPARATOR ICON_OPUS ICON_SONNET ICON_HAIKU ICON_DEFAULT ICON_PRIORITY_URGENT ICON_PRIORITY_HIGH ICON_PRIORITY_MEDIUM \
96
+ <<< "$theme_data"
97
+
98
+ # Convert color codes to escape sequences
99
+ PRIMARY=$(color_to_escape "$C_PRIMARY")
100
+ ACCENT=$(color_to_escape "$C_ACCENT")
101
+ SECONDARY=$(color_to_escape "$C_SECONDARY")
102
+ MUTED=$(color_to_escape "$C_MUTED")
103
+ SUCCESS=$(color_to_escape "$C_SUCCESS")
104
+ ERROR=$(color_to_escape "$C_ERROR")
105
+ WARNING=$(color_to_escape "$C_WARNING")
106
+ PURPLE=$(color_to_escape "$C_PURPLE")
107
+
108
+ # Set icons (use defaults for any missing)
109
+ ICON_PRJCT="${ICON_PRJCT:-$DEFAULT_ICON_PRJCT}"
110
+ ICON_DIR="${ICON_DIR:-$DEFAULT_ICON_DIR}"
111
+ ICON_GIT="${ICON_GIT:-$DEFAULT_ICON_GIT}"
112
+ ICON_SEPARATOR="${ICON_SEPARATOR:-$DEFAULT_ICON_SEPARATOR}"
113
+ ICON_OPUS="${ICON_OPUS:-$DEFAULT_ICON_OPUS}"
114
+ ICON_SONNET="${ICON_SONNET:-$DEFAULT_ICON_SONNET}"
115
+ ICON_HAIKU="${ICON_HAIKU:-$DEFAULT_ICON_HAIKU}"
116
+ ICON_DEFAULT="${ICON_DEFAULT:-$DEFAULT_ICON_DEFAULT}"
117
+ ICON_PRIORITY_URGENT="${ICON_PRIORITY_URGENT:-$DEFAULT_ICON_PRIORITY_URGENT}"
118
+ ICON_PRIORITY_HIGH="${ICON_PRIORITY_HIGH:-$DEFAULT_ICON_PRIORITY_HIGH}"
119
+ ICON_PRIORITY_MEDIUM="${ICON_PRIORITY_MEDIUM:-$DEFAULT_ICON_PRIORITY_MEDIUM}"
120
+ }
121
+
122
+ # Set default colors
123
+ set_default_colors() {
124
+ PRIMARY="$DEFAULT_PRIMARY"
125
+ ACCENT="$DEFAULT_ACCENT"
126
+ SECONDARY="$DEFAULT_SECONDARY"
127
+ MUTED="$DEFAULT_MUTED"
128
+ SUCCESS="$DEFAULT_SUCCESS"
129
+ ERROR="$DEFAULT_ERROR"
130
+ WARNING="$DEFAULT_WARNING"
131
+ PURPLE="$DEFAULT_PURPLE"
132
+ }
133
+
134
+ # Set default icons
135
+ set_default_icons() {
136
+ ICON_PRJCT="$DEFAULT_ICON_PRJCT"
137
+ ICON_DIR="$DEFAULT_ICON_DIR"
138
+ ICON_GIT="$DEFAULT_ICON_GIT"
139
+ ICON_SEPARATOR="$DEFAULT_ICON_SEPARATOR"
140
+ ICON_OPUS="$DEFAULT_ICON_OPUS"
141
+ ICON_SONNET="$DEFAULT_ICON_SONNET"
142
+ ICON_HAIKU="$DEFAULT_ICON_HAIKU"
143
+ ICON_DEFAULT="$DEFAULT_ICON_DEFAULT"
144
+ ICON_PRIORITY_URGENT="$DEFAULT_ICON_PRIORITY_URGENT"
145
+ ICON_PRIORITY_HIGH="$DEFAULT_ICON_PRIORITY_HIGH"
146
+ ICON_PRIORITY_MEDIUM="$DEFAULT_ICON_PRIORITY_MEDIUM"
147
+ }
148
+
149
+ # Get model icon based on model name
150
+ get_model_icon() {
151
+ local model="$1"
152
+ case "$model" in
153
+ *Opus*|*opus*) echo "$ICON_OPUS" ;;
154
+ *Sonnet*|*sonnet*) echo "$ICON_SONNET" ;;
155
+ *Haiku*|*haiku*) echo "$ICON_HAIKU" ;;
156
+ *) echo "$ICON_DEFAULT" ;;
157
+ esac
158
+ }
159
+
160
+ # Get priority icon
161
+ get_priority_icon() {
162
+ local priority="$1"
163
+ case "$priority" in
164
+ urgent) echo "$ICON_PRIORITY_URGENT" ;;
165
+ high) echo "$ICON_PRIORITY_HIGH" ;;
166
+ medium) echo "$ICON_PRIORITY_MEDIUM" ;;
167
+ *) echo "" ;;
168
+ esac
169
+ }
@@ -0,0 +1,121 @@
1
+ #!/bin/bash
2
+ # ============================================================================
3
+ # prjct statusline v2 for Claude Code
4
+ # Modular component system with graceful degradation
5
+ # ============================================================================
6
+
7
+ # Current CLI version (updated by postinstall/sync)
8
+ CLI_VERSION="0.29.0"
9
+
10
+ # Base paths
11
+ STATUSLINE_DIR="${HOME}/.prjct-cli/statusline"
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+
14
+ # Use installed location or script location for libs/components
15
+ if [[ -d "${STATUSLINE_DIR}/lib" ]]; then
16
+ LIB_DIR="${STATUSLINE_DIR}/lib"
17
+ COMPONENTS_DIR="${STATUSLINE_DIR}/components"
18
+ else
19
+ LIB_DIR="${SCRIPT_DIR}/lib"
20
+ COMPONENTS_DIR="${SCRIPT_DIR}/components"
21
+ fi
22
+
23
+ # Source libraries
24
+ source "${LIB_DIR}/cache.sh" 2>/dev/null || {
25
+ echo "prjct: lib/cache.sh missing"
26
+ exit 1
27
+ }
28
+ source "${LIB_DIR}/theme.sh" 2>/dev/null || {
29
+ echo "prjct: lib/theme.sh missing"
30
+ exit 1
31
+ }
32
+ source "${LIB_DIR}/config.sh" 2>/dev/null || {
33
+ echo "prjct: lib/config.sh missing"
34
+ exit 1
35
+ }
36
+
37
+ # Source all components
38
+ for component_file in "${COMPONENTS_DIR}"/*.sh; do
39
+ [[ -f "$component_file" ]] && source "$component_file" 2>/dev/null
40
+ done
41
+
42
+ # Read stdin (Claude Code passes session data)
43
+ INPUT=$(cat)
44
+
45
+ # Initialize
46
+ ensure_cache_dir
47
+ load_config
48
+ load_theme
49
+ parse_stdin "$INPUT"
50
+
51
+ # ============================================================================
52
+ # Version Check - Show update notification if needed
53
+ # ============================================================================
54
+ check_version_upgrade() {
55
+ local config_file="${CWD}/.prjct/prjct.config.json"
56
+
57
+ [[ ! -f "$config_file" ]] && return 1
58
+
59
+ local project_id=$(jq -r '.projectId // ""' "$config_file" 2>/dev/null)
60
+ [[ -z "$project_id" ]] && return 1
61
+
62
+ local project_json="${HOME}/.prjct-cli/projects/${project_id}/project.json"
63
+ [[ ! -f "$project_json" ]] && {
64
+ echo -e "${WARNING}prjct v${CLI_VERSION}${NC} ${MUTED}run p. sync${NC}"
65
+ return 0
66
+ }
67
+
68
+ local project_version=$(jq -r '.cliVersion // ""' "$project_json" 2>/dev/null)
69
+
70
+ if [[ -z "$project_version" ]] || [[ "$project_version" != "$CLI_VERSION" ]]; then
71
+ echo -e "${WARNING}prjct v${CLI_VERSION}${NC} ${MUTED}run p. sync${NC}"
72
+ return 0
73
+ fi
74
+
75
+ return 1
76
+ }
77
+
78
+ # Check for version upgrade first
79
+ VERSION_MSG=$(check_version_upgrade)
80
+ if [[ -n "$VERSION_MSG" ]]; then
81
+ echo -e "$VERSION_MSG"
82
+ exit 0
83
+ fi
84
+
85
+ # ============================================================================
86
+ # Build Statusline
87
+ # ============================================================================
88
+ build_statusline() {
89
+ local line=""
90
+ local sep=" ${MUTED}${ICON_SEPARATOR}${NC} "
91
+ local first=true
92
+
93
+ # Get sorted list of enabled components
94
+ local components=$(get_enabled_components)
95
+
96
+ for component_name in $components; do
97
+ # Call the component function
98
+ local func_name="component_${component_name}"
99
+
100
+ # Check if function exists
101
+ if declare -f "$func_name" > /dev/null 2>&1; then
102
+ local output=$($func_name)
103
+
104
+ # Skip empty outputs (graceful degradation)
105
+ [[ -z "$output" ]] && continue
106
+
107
+ # Add separator between components
108
+ if [[ "$first" == "true" ]]; then
109
+ line="$output"
110
+ first=false
111
+ else
112
+ line+="${sep}${output}"
113
+ fi
114
+ fi
115
+ done
116
+
117
+ echo -e "$line"
118
+ }
119
+
120
+ # Output the statusline
121
+ build_statusline
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "prjct-default",
3
+ "description": "Default prjct theme for Claude Code statusline",
4
+ "colors": {
5
+ "primary": "51",
6
+ "accent": "220",
7
+ "secondary": "147",
8
+ "muted": "242",
9
+ "success": "114",
10
+ "error": "204",
11
+ "warning": "214",
12
+ "purple": "183"
13
+ },
14
+ "icons": {
15
+ "prjct": "⚡",
16
+ "dir": "󰉋",
17
+ "git": "",
18
+ "separator": "│",
19
+ "opus": "🎭",
20
+ "sonnet": "📝",
21
+ "haiku": "🍃",
22
+ "default_model": "🤖",
23
+ "priority_urgent": "🔴",
24
+ "priority_high": "🟠",
25
+ "priority_medium": "🟡"
26
+ }
27
+ }
@@ -12508,7 +12508,7 @@ var require_package = __commonJS({
12508
12508
  "package.json"(exports, module) {
12509
12509
  module.exports = {
12510
12510
  name: "prjct-cli",
12511
- version: "0.28.0",
12511
+ version: "0.29.2",
12512
12512
  description: "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
12513
12513
  main: "core/index.ts",
12514
12514
  bin: {
@@ -12591,6 +12591,7 @@ var require_package = __commonJS({
12591
12591
  bun: ">=1.0.0"
12592
12592
  },
12593
12593
  files: [
12594
+ "assets/",
12594
12595
  "bin/",
12595
12596
  "core/",
12596
12597
  "dist/",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "Built for Claude - Ship fast, track progress, stay focused. Developer momentum tool for indie hackers.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {
@@ -83,6 +83,7 @@
83
83
  "bun": ">=1.0.0"
84
84
  },
85
85
  "files": [
86
+ "assets/",
86
87
  "bin/",
87
88
  "core/",
88
89
  "dist/",
@@ -25,7 +25,7 @@ If ARGUMENTS = "task fix the login bug":
25
25
 
26
26
  ## Available Commands
27
27
 
28
- `task` `done` `ship` `sync` `init` `idea` `dash` `next` `pause` `resume` `bug` `spec` `suggest` `git` `test` `cleanup` `design` `analyze` `undo` `redo`
28
+ `task` `done` `ship` `sync` `init` `idea` `dash` `next` `pause` `resume` `bug` `linear` `feature` `prd` `plan` `review` `merge` `git` `test` `cleanup` `design` `analyze` `history` `enrich`
29
29
 
30
30
  ## Action
31
31