axis-crm 1.0.2 → 1.1.0
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/cli/index.js +6 -4
- package/package.json +1 -1
- package/template/CLAUDE.md +161 -27
- package/template/scripts/new-lead.sh +32 -21
- package/template/scripts/new-project.sh +14 -9
- package/template/scripts/setup.sh +115 -0
- package/template/scripts/validate.sh +69 -18
package/cli/index.js
CHANGED
|
@@ -95,12 +95,14 @@ countFiles(targetDir);
|
|
|
95
95
|
console.log(` Done! ${fileCount} files created.\n`);
|
|
96
96
|
console.log(" Next steps:\n");
|
|
97
97
|
console.log(` cd ${targetName}`);
|
|
98
|
-
console.log("
|
|
99
|
-
console.log('
|
|
100
|
-
console.log(
|
|
98
|
+
console.log(" ./scripts/setup.sh");
|
|
99
|
+
console.log(' ./scripts/new-lead.sh "First Lead Inc" --source manual');
|
|
100
|
+
console.log(" ./scripts/validate.sh");
|
|
101
101
|
console.log("");
|
|
102
|
+
console.log(
|
|
103
|
+
" Open CLAUDE.md to use with AI coding agents (Claude Code, Cursor).",
|
|
104
|
+
);
|
|
102
105
|
console.log(" Open in Obsidian for dashboards (install Dataview plugin).");
|
|
103
|
-
console.log(" Open CLAUDE.md to use with AI coding agents.");
|
|
104
106
|
console.log("");
|
|
105
107
|
console.log(" Docs: https://github.com/PatrikSchick-AI/axis-crm");
|
|
106
108
|
console.log("");
|
package/package.json
CHANGED
package/template/CLAUDE.md
CHANGED
|
@@ -1,38 +1,172 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Axis CRM — AI Agent Instructions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Git-based CRM for freelancers and small teams.
|
|
4
|
+
> You (the AI agent) are the primary interface. Users send you context and you manage the CRM.
|
|
4
5
|
|
|
5
|
-
##
|
|
6
|
+
## Quick Reference
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- `leads/` — each lead = a single .md file
|
|
9
|
-
- `templates/` — templates for all record types
|
|
10
|
-
- `rules/` — lifecycle, scoring, field definitions, folder structure
|
|
11
|
-
- `scripts/` — new-lead.sh, convert-to-client.sh, new-project.sh, validate.sh
|
|
12
|
-
- `automations/` — n8n workflow JSON exports
|
|
13
|
-
- `dashboards/` — Obsidian Dataview dashboards
|
|
8
|
+
All scripts are in `scripts/`. They are non-interactive (no prompts, all input via flags).
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
### Create lead
|
|
11
|
+
```bash
|
|
12
|
+
./scripts/new-lead.sh "Company Name" \
|
|
13
|
+
--source referral \
|
|
14
|
+
--city Prague \
|
|
15
|
+
--industry consulting \
|
|
16
|
+
--email info@example.com \
|
|
17
|
+
--phone "+420123456789" \
|
|
18
|
+
--web https://example.com
|
|
19
|
+
```
|
|
20
|
+
All flags are optional. Creates `leads/company-name.md`.
|
|
21
|
+
|
|
22
|
+
### Convert lead to client
|
|
23
|
+
```bash
|
|
24
|
+
./scripts/convert-to-client.sh company-name
|
|
25
|
+
```
|
|
26
|
+
Reads lead file, creates full client folder structure, deletes lead (history in git).
|
|
27
|
+
|
|
28
|
+
### Create project for client
|
|
29
|
+
```bash
|
|
30
|
+
./scripts/new-project.sh client-slug "Project Name"
|
|
31
|
+
```
|
|
32
|
+
Creates `clients/client-slug/projects/project-name/` with README.md + brief.md.
|
|
33
|
+
|
|
34
|
+
### Validate data
|
|
35
|
+
```bash
|
|
36
|
+
./scripts/validate.sh # check all
|
|
37
|
+
./scripts/validate.sh leads # leads only
|
|
38
|
+
./scripts/validate.sh clients # clients only
|
|
39
|
+
./scripts/validate.sh --fix # show actionable fix commands
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Setup check
|
|
43
|
+
```bash
|
|
44
|
+
./scripts/setup.sh # verify environment
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common Operations
|
|
48
|
+
|
|
49
|
+
### Add a contact to a client
|
|
50
|
+
Create a markdown file in `clients/{slug}/contacts/`:
|
|
51
|
+
```yaml
|
|
52
|
+
---
|
|
53
|
+
title: "Jane Smith"
|
|
54
|
+
created: "2026-04-14"
|
|
55
|
+
tags: [contact]
|
|
56
|
+
type: contact
|
|
57
|
+
role: "CEO"
|
|
58
|
+
email: "jane@example.com"
|
|
59
|
+
phone: "+420123456789"
|
|
60
|
+
linkedin: ""
|
|
61
|
+
last_contact: ""
|
|
62
|
+
notes: ""
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Notes
|
|
66
|
+
|
|
67
|
+
Key decision maker. Prefers email communication.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Add a meeting note
|
|
71
|
+
Create a file in `clients/{slug}/meetings/`:
|
|
72
|
+
```yaml
|
|
73
|
+
---
|
|
74
|
+
title: "Kickoff meeting"
|
|
75
|
+
created: "2026-04-14"
|
|
76
|
+
tags: [meeting]
|
|
77
|
+
type: meeting
|
|
78
|
+
attendees: [Jane Smith, John Doe]
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Agenda
|
|
82
|
+
|
|
83
|
+
1. Project scope review
|
|
84
|
+
2. Timeline discussion
|
|
85
|
+
|
|
86
|
+
## Notes
|
|
87
|
+
|
|
88
|
+
- Agreed on 3-month timeline
|
|
89
|
+
- Budget confirmed at 50k CZK/month
|
|
16
90
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
91
|
+
## Action Items
|
|
92
|
+
|
|
93
|
+
- [ ] Send proposal by Friday
|
|
94
|
+
- [ ] Schedule follow-up for next week
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Add a communication entry
|
|
98
|
+
Create a file in `clients/{slug}/communication/`:
|
|
99
|
+
```yaml
|
|
100
|
+
---
|
|
101
|
+
title: "Price agreed"
|
|
102
|
+
created: "2026-04-14"
|
|
103
|
+
tags: [communication, decision]
|
|
104
|
+
type: communication
|
|
105
|
+
channel: email
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Summary
|
|
109
|
+
|
|
110
|
+
Agreed on hourly rate of 2000 CZK. Starting next Monday.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Update client rates
|
|
114
|
+
Edit `clients/{slug}/README.md` frontmatter:
|
|
115
|
+
```yaml
|
|
116
|
+
hourly_rate: 2000
|
|
117
|
+
manday_rate: 15000
|
|
118
|
+
currency: "CZK"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Score a lead
|
|
122
|
+
Review `rules/scoring.md` for criteria, then update `leads/{slug}.md`:
|
|
123
|
+
```yaml
|
|
124
|
+
score: 75
|
|
125
|
+
```
|
|
126
|
+
Thresholds: 0-39 = low, 40-69 = medium, 70-100 = high priority.
|
|
127
|
+
|
|
128
|
+
## File Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
clients/ — each client = a folder
|
|
132
|
+
{slug}/
|
|
133
|
+
README.md — overview, rates, status, frontmatter
|
|
134
|
+
contacts/ — key people (.md per person)
|
|
135
|
+
meetings/ — meeting notes (.md per meeting)
|
|
136
|
+
projects/ — project folders (README.md + brief.md)
|
|
137
|
+
communication/ — key decisions, emails (.md per entry)
|
|
138
|
+
files/ — contracts/, proposals/, invoices/
|
|
139
|
+
|
|
140
|
+
leads/ — each lead = a single .md file
|
|
141
|
+
{slug}.md — frontmatter with all lead data
|
|
142
|
+
|
|
143
|
+
templates/ — templates for all record types
|
|
144
|
+
rules/ — lifecycle, scoring, field definitions
|
|
145
|
+
scripts/ — new-lead.sh, convert-to-client.sh, new-project.sh, validate.sh, setup.sh
|
|
146
|
+
automations/ — n8n workflow JSON exports
|
|
147
|
+
dashboards/ — Obsidian Dataview dashboards
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Lead Lifecycle
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
new -> enriched -> qualified -> contacted -> responded -> opportunity -> client
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Each stage has required fields. Run `./scripts/validate.sh` to check.
|
|
21
157
|
|
|
22
158
|
## Rules
|
|
23
159
|
|
|
24
160
|
- A lead is a file. A client is a folder.
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Client rates: `hourly_rate`, `manday_rate`, `currency` fields in client README.md
|
|
161
|
+
- All scripts are non-interactive — designed for AI agent usage
|
|
162
|
+
- All flags have argument guards — missing value = clear error message
|
|
163
|
+
- Slug = lowercase, hyphens, ASCII only (diacritics stripped)
|
|
164
|
+
- Never commit .env, API keys, or credentials
|
|
165
|
+
- Never use emoji as icons in any output
|
|
31
166
|
|
|
32
|
-
##
|
|
167
|
+
## Integration with Axis Tasks
|
|
33
168
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
169
|
+
If [Axis Tasks](https://github.com/PatrikSchick-AI/Axis-Tasks) is installed as a sibling directory (`../tasks/`):
|
|
170
|
+
- Meeting action items can be converted to tasks
|
|
171
|
+
- Client slug from CRM is used as `--client` in task creation
|
|
172
|
+
- Time reports can be filtered per client
|
|
@@ -4,15 +4,24 @@ set -euo pipefail
|
|
|
4
4
|
# new-lead.sh — Create a new lead from template
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# ./new-lead.sh "
|
|
8
|
-
# ./new-lead.sh "
|
|
7
|
+
# ./new-lead.sh "Company Name" --source firmy-cz --city Prague --industry accounting
|
|
8
|
+
# ./new-lead.sh "Company Name" --email info@example.com --web https://example.com
|
|
9
|
+
# ./new-lead.sh "Company Name" (creates with defaults, fill in later)
|
|
9
10
|
|
|
10
11
|
CRM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
11
12
|
TEMPLATE="$CRM_DIR/templates/lead.md"
|
|
12
13
|
LEADS_DIR="$CRM_DIR/leads"
|
|
13
14
|
|
|
14
15
|
if [ $# -lt 1 ]; then
|
|
15
|
-
echo "Usage: new-lead.sh \"
|
|
16
|
+
echo "Usage: new-lead.sh \"Company Name\" [flags]"
|
|
17
|
+
echo ""
|
|
18
|
+
echo "Flags:"
|
|
19
|
+
echo " --source Lead source (e.g. firmy-cz, apify, referral, manual)"
|
|
20
|
+
echo " --city City"
|
|
21
|
+
echo " --industry Industry"
|
|
22
|
+
echo " --email Email"
|
|
23
|
+
echo " --phone Phone"
|
|
24
|
+
echo " --web Website URL"
|
|
16
25
|
exit 1
|
|
17
26
|
fi
|
|
18
27
|
|
|
@@ -24,35 +33,36 @@ SLUG=$(echo "$TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed
|
|
|
24
33
|
TODAY=$(date +%Y-%m-%d)
|
|
25
34
|
OUTPUT="$LEADS_DIR/$SLUG.md"
|
|
26
35
|
|
|
36
|
+
if [ -z "$SLUG" ]; then
|
|
37
|
+
echo "Error: Cannot generate slug from title '$TITLE'"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
27
41
|
if [ -f "$OUTPUT" ]; then
|
|
28
42
|
echo "Error: Lead already exists: $OUTPUT"
|
|
29
43
|
exit 1
|
|
30
44
|
fi
|
|
31
45
|
|
|
32
|
-
# Parse optional flags
|
|
46
|
+
# Parse optional flags (with argument guards)
|
|
33
47
|
SOURCE="" CITY="" INDUSTRY="" EMAIL="" PHONE="" WEB=""
|
|
34
48
|
while [ $# -gt 0 ]; do
|
|
35
49
|
case "$1" in
|
|
36
|
-
--source)
|
|
37
|
-
|
|
38
|
-
--
|
|
39
|
-
|
|
40
|
-
--
|
|
41
|
-
|
|
50
|
+
--source) [ $# -ge 2 ] || { echo "Error: --source requires a value"; exit 1; }
|
|
51
|
+
SOURCE="$2"; shift 2 ;;
|
|
52
|
+
--city) [ $# -ge 2 ] || { echo "Error: --city requires a value"; exit 1; }
|
|
53
|
+
CITY="$2"; shift 2 ;;
|
|
54
|
+
--industry) [ $# -ge 2 ] || { echo "Error: --industry requires a value"; exit 1; }
|
|
55
|
+
INDUSTRY="$2"; shift 2 ;;
|
|
56
|
+
--email) [ $# -ge 2 ] || { echo "Error: --email requires a value"; exit 1; }
|
|
57
|
+
EMAIL="$2"; shift 2 ;;
|
|
58
|
+
--phone) [ $# -ge 2 ] || { echo "Error: --phone requires a value"; exit 1; }
|
|
59
|
+
PHONE="$2"; shift 2 ;;
|
|
60
|
+
--web) [ $# -ge 2 ] || { echo "Error: --web requires a value"; exit 1; }
|
|
61
|
+
WEB="$2"; shift 2 ;;
|
|
42
62
|
*) echo "Unknown flag: $1"; exit 1 ;;
|
|
43
63
|
esac
|
|
44
64
|
done
|
|
45
65
|
|
|
46
|
-
# If no flags provided, prompt interactively
|
|
47
|
-
if [ -z "$SOURCE" ] && [ -t 0 ]; then
|
|
48
|
-
read -p "Source (e.g. firmy-cz, apify, manual): " SOURCE
|
|
49
|
-
read -p "City: " CITY
|
|
50
|
-
read -p "Industry: " INDUSTRY
|
|
51
|
-
read -p "Email: " EMAIL
|
|
52
|
-
read -p "Phone: " PHONE
|
|
53
|
-
read -p "Web: " WEB
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
66
|
# Create lead from template
|
|
57
67
|
sed -e "s|^title: ''|title: \"$TITLE\"|" \
|
|
58
68
|
-e "s|^created: ''|created: \"$TODAY\"|" \
|
|
@@ -65,4 +75,5 @@ sed -e "s|^title: ''|title: \"$TITLE\"|" \
|
|
|
65
75
|
"$TEMPLATE" > "$OUTPUT"
|
|
66
76
|
|
|
67
77
|
echo "Created: $OUTPUT"
|
|
68
|
-
echo "Status: new
|
|
78
|
+
echo "Status: new | Source: ${SOURCE:-none}"
|
|
79
|
+
exit 0
|
|
@@ -4,18 +4,21 @@ set -uo pipefail
|
|
|
4
4
|
# new-project.sh — Create a new project folder for a client
|
|
5
5
|
#
|
|
6
6
|
# Usage:
|
|
7
|
-
# ./new-project.sh client-slug
|
|
7
|
+
# ./new-project.sh client-slug "Project Name"
|
|
8
8
|
# ./new-project.sh acme-consulting "Web Redesign"
|
|
9
9
|
|
|
10
10
|
CRM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
11
11
|
CLIENTS_DIR="$CRM_DIR/clients"
|
|
12
12
|
TEMPLATE_DIR="$CRM_DIR/templates/project"
|
|
13
13
|
|
|
14
|
+
# Portable sed -i
|
|
15
|
+
_sed_i() { if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "$@"; else sed -i "$@"; fi; }
|
|
16
|
+
|
|
14
17
|
if [ $# -lt 2 ]; then
|
|
15
18
|
echo "Usage: new-project.sh <client-slug> \"Project Name\""
|
|
16
19
|
echo ""
|
|
17
20
|
echo "Available clients:"
|
|
18
|
-
ls "$CLIENTS_DIR"
|
|
21
|
+
ls "$CLIENTS_DIR" 2>/dev/null || echo " (none)"
|
|
19
22
|
exit 1
|
|
20
23
|
fi
|
|
21
24
|
|
|
@@ -27,7 +30,7 @@ if [ ! -d "$CLIENT_DIR" ]; then
|
|
|
27
30
|
echo "Error: Client not found: $CLIENT_DIR"
|
|
28
31
|
echo ""
|
|
29
32
|
echo "Available clients:"
|
|
30
|
-
ls "$CLIENTS_DIR"
|
|
33
|
+
ls "$CLIENTS_DIR" 2>/dev/null || echo " (none)"
|
|
31
34
|
exit 1
|
|
32
35
|
fi
|
|
33
36
|
|
|
@@ -36,6 +39,11 @@ PROJECT_SLUG=$(echo "$PROJECT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0
|
|
|
36
39
|
PROJECT_DIR="$CLIENT_DIR/projects/$PROJECT_SLUG"
|
|
37
40
|
TODAY=$(date +%Y-%m-%d)
|
|
38
41
|
|
|
42
|
+
if [ -z "$PROJECT_SLUG" ]; then
|
|
43
|
+
echo "Error: Cannot generate slug from project name '$PROJECT_NAME'"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
39
47
|
if [ -d "$PROJECT_DIR" ]; then
|
|
40
48
|
echo "Error: Project already exists: $PROJECT_DIR"
|
|
41
49
|
exit 1
|
|
@@ -45,14 +53,14 @@ fi
|
|
|
45
53
|
cp -r "$TEMPLATE_DIR" "$PROJECT_DIR"
|
|
46
54
|
|
|
47
55
|
# Fill in README.md
|
|
48
|
-
|
|
56
|
+
_sed_i \
|
|
49
57
|
-e "s|^title: ''|title: '$PROJECT_NAME'|" \
|
|
50
58
|
-e "s|^created: ''|created: '$TODAY'|" \
|
|
51
59
|
-e "s|^start: ''|start: '$TODAY'|" \
|
|
52
60
|
"$PROJECT_DIR/README.md"
|
|
53
61
|
|
|
54
62
|
# Fill in brief.md
|
|
55
|
-
|
|
63
|
+
_sed_i \
|
|
56
64
|
-e "s|^title: 'Brief — '|title: 'Brief — $PROJECT_NAME'|" \
|
|
57
65
|
-e "s|^created: ''|created: '$TODAY'|" \
|
|
58
66
|
"$PROJECT_DIR/brief.md"
|
|
@@ -63,7 +71,4 @@ echo " README.md — scope, status, timeline"
|
|
|
63
71
|
echo " brief.md — client brief"
|
|
64
72
|
echo " deliverables/ — outputs"
|
|
65
73
|
echo " notes/ — ongoing notes"
|
|
66
|
-
|
|
67
|
-
echo "Next steps:"
|
|
68
|
-
echo " 1. Fill in brief.md with client requirements"
|
|
69
|
-
echo " 2. Add scope and timeline to README.md"
|
|
74
|
+
exit 0
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# setup.sh — Verify environment and initialize Axis CRM
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ./setup.sh # check + init
|
|
8
|
+
# ./setup.sh --check # check only, no changes
|
|
9
|
+
|
|
10
|
+
CRM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
11
|
+
CHECK_ONLY=false
|
|
12
|
+
[ "${1:-}" = "--check" ] && CHECK_ONLY=true
|
|
13
|
+
|
|
14
|
+
PASS=0
|
|
15
|
+
WARN=0
|
|
16
|
+
FAIL=0
|
|
17
|
+
|
|
18
|
+
ok() { echo " [OK] $1"; PASS=$((PASS + 1)); }
|
|
19
|
+
warn() { echo " [WARN] $1"; WARN=$((WARN + 1)); }
|
|
20
|
+
fail() { echo " [FAIL] $1"; FAIL=$((FAIL + 1)); }
|
|
21
|
+
|
|
22
|
+
echo ""
|
|
23
|
+
echo "=== Axis CRM — Setup Check ==="
|
|
24
|
+
echo ""
|
|
25
|
+
|
|
26
|
+
# --- Required ---
|
|
27
|
+
|
|
28
|
+
echo "Required:"
|
|
29
|
+
|
|
30
|
+
if command -v bash &>/dev/null; then
|
|
31
|
+
ok "bash $(bash --version | head -1 | sed 's/.*version //' | sed 's/ .*//')"
|
|
32
|
+
else
|
|
33
|
+
fail "bash not found"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if command -v git &>/dev/null; then
|
|
37
|
+
ok "git $(git --version | sed 's/git version //')"
|
|
38
|
+
else
|
|
39
|
+
fail "git not found"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
echo ""
|
|
43
|
+
echo "Optional:"
|
|
44
|
+
|
|
45
|
+
# Axis Tasks integration
|
|
46
|
+
TASKS_DIR=""
|
|
47
|
+
for candidate in "$CRM_DIR/../tasks" "$CRM_DIR/../10-tasks"; do
|
|
48
|
+
if [ -d "$candidate/scripts" ]; then
|
|
49
|
+
TASKS_DIR="$(cd "$candidate" && pwd)"
|
|
50
|
+
break
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
if [ -n "$TASKS_DIR" ]; then
|
|
55
|
+
ok "Axis Tasks found ($TASKS_DIR)"
|
|
56
|
+
echo " Task creation from meetings enabled"
|
|
57
|
+
else
|
|
58
|
+
warn "Axis Tasks not found — task integration disabled"
|
|
59
|
+
echo " Install: https://github.com/PatrikSchick-AI/Axis-Tasks"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo "Directories:"
|
|
64
|
+
|
|
65
|
+
DIRS="clients leads templates scripts rules dashboards automations"
|
|
66
|
+
for dir in $DIRS; do
|
|
67
|
+
if [ -d "$CRM_DIR/$dir" ]; then
|
|
68
|
+
ok "$dir/"
|
|
69
|
+
else
|
|
70
|
+
if [ "$CHECK_ONLY" = true ]; then
|
|
71
|
+
fail "$dir/ missing"
|
|
72
|
+
else
|
|
73
|
+
mkdir -p "$CRM_DIR/$dir"
|
|
74
|
+
ok "$dir/ (created)"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
echo ""
|
|
80
|
+
echo "Scripts:"
|
|
81
|
+
|
|
82
|
+
for script in new-lead.sh convert-to-client.sh new-project.sh validate.sh; do
|
|
83
|
+
SCRIPT_PATH="$CRM_DIR/scripts/$script"
|
|
84
|
+
if [ ! -f "$SCRIPT_PATH" ]; then
|
|
85
|
+
fail "$script missing"
|
|
86
|
+
elif [ -x "$SCRIPT_PATH" ]; then
|
|
87
|
+
ok "$script"
|
|
88
|
+
else
|
|
89
|
+
if [ "$CHECK_ONLY" = true ]; then
|
|
90
|
+
warn "$script not executable"
|
|
91
|
+
else
|
|
92
|
+
chmod +x "$SCRIPT_PATH"
|
|
93
|
+
ok "$script (made executable)"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
echo ""
|
|
99
|
+
|
|
100
|
+
TOTAL=$((PASS + WARN + FAIL))
|
|
101
|
+
echo "=== Summary: $PASS passed, $WARN warnings, $FAIL failed (of $TOTAL checks) ==="
|
|
102
|
+
|
|
103
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
104
|
+
echo ""
|
|
105
|
+
echo "Fix the failures above before using Axis CRM."
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
if [ "$WARN" -gt 0 ]; then
|
|
110
|
+
echo ""
|
|
111
|
+
echo "Axis CRM is ready. Warnings are optional enhancements."
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
echo ""
|
|
115
|
+
exit 0
|
|
@@ -7,7 +7,7 @@ set -uo pipefail
|
|
|
7
7
|
# ./validate.sh # Check everything
|
|
8
8
|
# ./validate.sh leads # Check only leads
|
|
9
9
|
# ./validate.sh clients # Check only clients
|
|
10
|
-
# ./validate.sh --fix # Show
|
|
10
|
+
# ./validate.sh --fix # Show actionable fix commands
|
|
11
11
|
|
|
12
12
|
CRM_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
13
|
LEADS_DIR="$CRM_DIR/leads"
|
|
@@ -16,16 +16,27 @@ CLIENTS_DIR="$CRM_DIR/clients"
|
|
|
16
16
|
ERRORS=0
|
|
17
17
|
WARNINGS=0
|
|
18
18
|
CHECKED=0
|
|
19
|
-
|
|
19
|
+
FIX_MODE=false
|
|
20
|
+
MODE="all"
|
|
21
|
+
|
|
22
|
+
# Parse arguments
|
|
23
|
+
for arg in "$@"; do
|
|
24
|
+
case "$arg" in
|
|
25
|
+
--fix) FIX_MODE=true ;;
|
|
26
|
+
leads|clients|all) MODE="$arg" ;;
|
|
27
|
+
*) echo "Unknown argument: $arg"; exit 1 ;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
20
30
|
|
|
21
31
|
RED='\033[0;31m'
|
|
22
32
|
YELLOW='\033[0;33m'
|
|
23
33
|
GREEN='\033[0;32m'
|
|
24
34
|
NC='\033[0m'
|
|
25
35
|
|
|
26
|
-
error() {
|
|
27
|
-
warn() {
|
|
28
|
-
ok() {
|
|
36
|
+
error() { printf " ${RED}ERROR${NC}: %s\n" "$1"; ERRORS=$((ERRORS + 1)); }
|
|
37
|
+
warn() { printf " ${YELLOW}WARN${NC}: %s\n" "$1"; WARNINGS=$((WARNINGS + 1)); }
|
|
38
|
+
ok() { printf " ${GREEN}OK${NC}: %s\n" "$1"; }
|
|
39
|
+
fix() { printf " ${GREEN}FIX${NC}: %s\n" "$1"; }
|
|
29
40
|
|
|
30
41
|
# Extract frontmatter value (handles quoted and unquoted)
|
|
31
42
|
get_field() {
|
|
@@ -51,13 +62,23 @@ check_lead() {
|
|
|
51
62
|
|
|
52
63
|
# Always required
|
|
53
64
|
for f in title created source; do
|
|
54
|
-
has_field "$file" "$f"
|
|
65
|
+
if ! has_field "$file" "$f"; then
|
|
66
|
+
error "$name: missing '$f' (required for all leads)"
|
|
67
|
+
if [ "$FIX_MODE" = true ]; then
|
|
68
|
+
fix "Add '$f' to frontmatter in leads/$name.md"
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
55
71
|
done
|
|
56
72
|
|
|
57
73
|
# Enriched+
|
|
58
74
|
if [[ "$status" =~ ^(enriched|qualified|contacted|responded|opportunity)$ ]]; then
|
|
59
75
|
for f in city industry ico web email; do
|
|
60
|
-
has_field "$file" "$f"
|
|
76
|
+
if ! has_field "$file" "$f"; then
|
|
77
|
+
error "$name: missing '$f' (required for status=$status)"
|
|
78
|
+
if [ "$FIX_MODE" = true ]; then
|
|
79
|
+
fix "Add '$f: \"value\"' to leads/$name.md frontmatter"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
61
82
|
done
|
|
62
83
|
fi
|
|
63
84
|
|
|
@@ -67,19 +88,29 @@ check_lead() {
|
|
|
67
88
|
score=$(get_field "$file" "score")
|
|
68
89
|
if [ -z "$score" ] || [ "$score" = "0" ]; then
|
|
69
90
|
warn "$name: score is 0 (expected > 40 for status=$status)"
|
|
91
|
+
if [ "$FIX_MODE" = true ]; then
|
|
92
|
+
fix "Run scoring: review rules/scoring.md and update 'score:' in leads/$name.md"
|
|
93
|
+
fi
|
|
70
94
|
fi
|
|
71
95
|
fi
|
|
72
96
|
|
|
73
97
|
# Contacted+
|
|
74
98
|
if [[ "$status" =~ ^(contacted|responded|opportunity)$ ]]; then
|
|
75
99
|
for f in campaign first_contact; do
|
|
76
|
-
has_field "$file" "$f"
|
|
100
|
+
if ! has_field "$file" "$f"; then
|
|
101
|
+
error "$name: missing '$f' (required for status=$status)"
|
|
102
|
+
if [ "$FIX_MODE" = true ]; then
|
|
103
|
+
fix "Add '$f: \"value\"' to leads/$name.md frontmatter"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
77
106
|
done
|
|
78
107
|
fi
|
|
79
108
|
|
|
80
109
|
# Opportunity
|
|
81
110
|
if [ "$status" = "opportunity" ]; then
|
|
82
|
-
has_field "$file" "priority"
|
|
111
|
+
if ! has_field "$file" "priority"; then
|
|
112
|
+
warn "$name: missing 'priority' (recommended for opportunity)"
|
|
113
|
+
fi
|
|
83
114
|
fi
|
|
84
115
|
}
|
|
85
116
|
|
|
@@ -92,17 +123,27 @@ check_client() {
|
|
|
92
123
|
|
|
93
124
|
if [ ! -f "$readme" ]; then
|
|
94
125
|
error "$name: missing README.md"
|
|
126
|
+
if [ "$FIX_MODE" = true ]; then
|
|
127
|
+
fix "cp templates/client/README.md clients/$name/README.md"
|
|
128
|
+
fi
|
|
95
129
|
return
|
|
96
130
|
fi
|
|
97
131
|
|
|
98
132
|
# Required fields
|
|
99
133
|
for f in title status; do
|
|
100
|
-
has_field "$readme" "$f"
|
|
134
|
+
if ! has_field "$readme" "$f"; then
|
|
135
|
+
error "$name: missing '$f' in README.md"
|
|
136
|
+
fi
|
|
101
137
|
done
|
|
102
138
|
|
|
103
139
|
# Check folder structure
|
|
104
140
|
for subdir in contacts projects meetings communication files; do
|
|
105
|
-
[ -d "$dir/$subdir" ]
|
|
141
|
+
if [ ! -d "$dir/$subdir" ]; then
|
|
142
|
+
warn "$name: missing $subdir/ directory"
|
|
143
|
+
if [ "$FIX_MODE" = true ]; then
|
|
144
|
+
fix "mkdir -p clients/$name/$subdir"
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
106
147
|
done
|
|
107
148
|
|
|
108
149
|
# Check for at least one contact
|
|
@@ -115,35 +156,45 @@ check_client() {
|
|
|
115
156
|
|
|
116
157
|
# Run checks
|
|
117
158
|
echo "=== Axis CRM Validation ==="
|
|
159
|
+
[ "$FIX_MODE" = true ] && echo "(fix mode — showing actionable suggestions)"
|
|
118
160
|
echo ""
|
|
119
161
|
|
|
120
162
|
if [ "$MODE" = "all" ] || [ "$MODE" = "leads" ]; then
|
|
121
163
|
echo "--- Leads ---"
|
|
164
|
+
HAS_LEADS=false
|
|
122
165
|
for f in "$LEADS_DIR"/*.md; do
|
|
123
|
-
[ -f "$f" ]
|
|
166
|
+
[ -f "$f" ] || continue
|
|
167
|
+
HAS_LEADS=true
|
|
168
|
+
check_lead "$f"
|
|
124
169
|
done
|
|
170
|
+
[ "$HAS_LEADS" = false ] && echo " (no leads found)"
|
|
125
171
|
echo ""
|
|
126
172
|
fi
|
|
127
173
|
|
|
128
174
|
if [ "$MODE" = "all" ] || [ "$MODE" = "clients" ]; then
|
|
129
175
|
echo "--- Clients ---"
|
|
176
|
+
HAS_CLIENTS=false
|
|
130
177
|
for d in "$CLIENTS_DIR"/*/; do
|
|
131
|
-
[ -d "$d" ]
|
|
178
|
+
[ -d "$d" ] || continue
|
|
179
|
+
HAS_CLIENTS=true
|
|
180
|
+
check_client "$d"
|
|
132
181
|
done
|
|
182
|
+
[ "$HAS_CLIENTS" = false ] && echo " (no clients found)"
|
|
133
183
|
echo ""
|
|
134
184
|
fi
|
|
135
185
|
|
|
136
186
|
# Summary
|
|
137
187
|
echo "=== Summary ==="
|
|
138
188
|
echo " Checked: $CHECKED"
|
|
139
|
-
|
|
140
|
-
|
|
189
|
+
printf " Errors: ${RED}%d${NC}\n" "$ERRORS"
|
|
190
|
+
printf " Warnings: ${YELLOW}%d${NC}\n" "$WARNINGS"
|
|
141
191
|
|
|
142
|
-
if [ "$ERRORS" -gt 0 ]; then
|
|
192
|
+
if [ "$ERRORS" -gt 0 ] && [ "$FIX_MODE" = false ]; then
|
|
143
193
|
echo ""
|
|
144
194
|
echo "Run with '--fix' flag for actionable suggestions."
|
|
145
195
|
exit 1
|
|
146
|
-
|
|
196
|
+
elif [ "$ERRORS" -eq 0 ]; then
|
|
147
197
|
echo ""
|
|
148
|
-
|
|
198
|
+
printf "${GREEN}All checks passed.${NC}\n"
|
|
149
199
|
fi
|
|
200
|
+
exit 0
|