diff --git a/dsr-input/README.md b/dsr-input/README.md new file mode 100644 index 0000000..ce51d7f --- /dev/null +++ b/dsr-input/README.md @@ -0,0 +1,19 @@ +# Data Gathering for the @ReachableCEO daily stakeholder report + +## Introduction + +I'm trying to life a fully instrumented CTO/founder life and #buildInPublic. And unlike most of the founders doing so, I'm actually publishing daily reports and live streaming my workstation all dya. + +I want to automate the instrumentation/reporting process as much as possible to get the highest fidelity information for stakeholders consumption. + +## Data sources + +- Apple Health (exporting via AutoHealthExport) +- Workout (evaluating a cou +- Nutrition +- Daily habit tracking +- ActivityWatch +- WakaAPI + + +## Scripts \ No newline at end of file diff --git a/dsr-input/dsr-gather-gitea.sh b/dsr-input/dsr-gather-gitea.sh new file mode 100644 index 0000000..f352cea --- /dev/null +++ b/dsr-input/dsr-gather-gitea.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Script to query Gitea API for a user's activity on a specific date + +######################################################################################################## +#Obtain gitea api key from bitwarden +######################################################################################################## + +#################################### +## Step 0: Set to use tsys server +#################################### +bw logout + +echo "Setting cli to use tsys bitwarden server..." +bw config server https://pwvault.turnsys.com + +#################################### +## Step 1: login to bitwarden +#################################### + +# From: https://bitwarden.com/help/cli/#using-an-api-key + +### Set apikey environment varaible + +echo "Sourcing clientid/apikey data..." +source D:/tsys/secrets/bitwarden/data/apikey-bitwarden-reachableceo + +### Login to vault using apikey... + +echo "Logging in..." +bw login --apikey $BW_CLIENTID $BW_CLIENTSECRET + +### Step 1.1: unlock / save session id + +echo "Unlocking..." +export BW_SESSION="$(bw unlock --passwordenv TSYS_BW_PASSWORD_REACHABLECEO --raw)" + + +### Step 2: retrive a value into an environment variable + +export GITEA_APIKEY="$(bw get password APIKEY-Gitea)" + +######################################################################################################## +# Accrss gitea data +######################################################################################################## + +# Script to query Gitea API for a user's activity on a specific date + +# Usage: ./get_gitea_user_activity.sh [GITEA_URL] [TOKEN] + +# Set username, date, and default Gitea URL +USERNAME="${1:-reachableceo}" # Default to "reachableceo" if not provided +DATE="${2:-$(date +%Y-%m-%d)}" # Default to today's date if not provided +GITEA_URL="${3:-https://git.knownelement.com}" # Default Gitea URL if not provided +TOKEN="${GITEA_APIKEY}" # Use APIKEY-GItea or passed argument + +# API Endpoint for user activities +API_ENDPOINT="$GITEA_URL/api/v1/users/$USERNAME/timeline" + +# Make the API call +if [ -n "$TOKEN" ]; then + # If token is provided, use it in the Authorization header + RESPONSE=$(curl -s -H "Authorization: token $TOKEN" "$API_ENDPOINT") +else + # If no token is provided, make an unauthenticated request + RESPONSE=$(curl -s "$API_ENDPOINT") +fi + +# Check for API errors +if [[ "$RESPONSE" == "Not found" || -z "$RESPONSE" ]]; then + echo "Error: User '$USERNAME' not found, or endpoint is incorrect." + exit 1 +fi + +# Validate JSON response +if ! echo "$RESPONSE" | jq empty >/dev/null 2>&1; then + echo "Error: Invalid JSON response from Gitea API." + echo "Response: $RESPONSE" + exit 1 +fi + +# Filter the activity by date using jq +echo "$RESPONSE" | jq --arg date "$DATE" '[.[] | select(.created_at | startswith($date))]' \ No newline at end of file diff --git a/dsr-input/export-joplin-md.sh b/dsr-input/export-joplin-md.sh new file mode 100644 index 0000000..943d39a --- /dev/null +++ b/dsr-input/export-joplin-md.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Functions + +# Search for a note by title and return its ID +search_note() { + local title="$1" + echo "Searching for note with title: $title" + note_id=$(joplin search "$title" --fields id --limit 1 --json | jq -r '.[0].id') + + if [[ -z "$note_id" || "$note_id" == "null" ]]; then + echo "Error: Note with title '$title' not found." + exit 1 + fi + + echo "Found note with ID: $note_id" + echo "$note_id" +} + +# Export the note by ID to the specified directory +export_note() { + local note_id="$1" + local output_dir="$2" + + echo "Exporting note ID: $note_id to directory: $output_dir" + mkdir -p "$output_dir" || { + echo "Error: Unable to create directory $output_dir" + exit 1 + } + + # Export the note with attachments to the specified directory + joplin export "$note_id" --format md --output "$output_dir" || { + echo "Error: Failed to export note ID $note_id" + exit 1 + } + + echo "Note exported successfully to $output_dir" +} + +# Main function +main() { + if [[ $# -lt 2 ]]; then + echo "Usage: $0 " + exit 1 + fi + + local note_title="$1" + local output_dir="$2" + + # Search for the note and get its ID + local note_id + note_id=$(search_note "$note_title") + + # Export the note + export_note "$note_id" "$output_dir" +} + +# Run the main function with all script arguments +main "$@" diff --git a/dsr-joplin-create/dsr-new.sh b/dsr-joplin-create/dsr-new.sh new file mode 100644 index 0000000..61f8018 --- /dev/null +++ b/dsr-joplin-create/dsr-new.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +# Use the Joplin CLI and create a new note for today. Let me pass in the notebook folder path. Use bash functions. + +# Created by chatgpt +# https://chatgpt.com/share/6750c7df-6b2c-8005-a91c-1bc5b3875170 + + +# shellcheck disable=SC1090 + +# Bash3 Boilerplate Setup +set -o errexit +set -o nounset +set -o pipefail +IFS=$'\n\t' + +# Constants +readonly SCRIPT_NAME=$(basename "$0") +readonly SCRIPT_VERSION="1.0" +readonly SCRIPT_AUTHOR="Charles N Wyble" +readonly SCRIPT_DESC="Create a new Daily Stakeholder Report note in Joplin" + +# Logging and Debugging (from bash3boilerplate) +readonly LOG_FILE="/tmp/${SCRIPT_NAME}.log" +readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') +info() { echo "[INFO] [$TIMESTAMP] $*"; } +error() { echo "[ERROR] [$TIMESTAMP] $*" >&2; } + +# Default Exit Codes +readonly ERR_JOPLIN_NOT_INSTALLED=10 +readonly ERR_NOTEBOOK_NOT_FOUND=20 + +# Function: Usage Instructions +usage() { + cat < + +Example: + $SCRIPT_NAME "Stakeholder Reports" + +Options: + -h, --help Display this help message. +EOF +} + +# Check dependencies +require_joplin() { + if ! command -v joplin &>/dev/null; then + error "Joplin CLI is not installed or not in PATH. Please install it and try again." + exit $ERR_JOPLIN_NOT_INSTALLED + fi +} + +# Check if notebook exists in Joplin +notebook_exists() { + local notebook="$1" + if ! joplin use "$notebook" >/dev/null 2>&1; then + error "Notebook '$notebook' does not exist. Please create it first." + exit $ERR_NOTEBOOK_NOT_FOUND + fi +} + +# Create a new note in the specified notebook +create_note() { + local notebook="$1" + local today_date=$(date '+%d-%m-%Y') # Format: YYYY-MM-DD + local title="Daily Stakeholder Report - $date" + local content="## Daily Stakeholder Report\n\n**Date:** $date\n\n---\n\n### Key Updates\n\n- \n\n### Issues\n\n- \n\n### Next Steps\n\n- \n" + + joplin create note --title "$title" --body "$content" >/dev/null + info "Note created successfully in notebook '$notebook'." +} + +# Main Function +main() { + local notebook_path="$1" + + # Ensure Joplin CLI is present + require_joplin + + # Verify notebook existence + notebook_exists "$notebook_path" + + # Create a new note + create_note "$notebook_path" +} + +# Argument Parsing +if [[ $# -lt 1 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + usage + exit 0 +fi + +main "$1" diff --git a/dsr-joplin-create/dsr-populate-objectives.sh b/dsr-joplin-create/dsr-populate-objectives.sh new file mode 100644 index 0000000..cd8d069 --- /dev/null +++ b/dsr-joplin-create/dsr-populate-objectives.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +# Written by ChatGPT +# https://chatgpt.com/share/6750c7df-6b2c-8005-a91c-1bc5b3875170 + +# Prompt: +# Pull any issues from Redmine that have a due date of today via API + +# shellcheck disable=SC1090 + +# Bash3 Boilerplate Setup +set -o errexit +set -o nounset +set -o pipefail +IFS=$'\n\t' + +# Constants +readonly SCRIPT_NAME=$(basename "$0") +readonly SCRIPT_VERSION="1.0" +readonly SCRIPT_AUTHOR="Charles N Wyble" +readonly SCRIPT_DESC="Pull Redmine issues with a due date of today and output in Markdown" + +# Logging and Debugging +readonly LOG_FILE="/tmp/${SCRIPT_NAME}.log" +readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') +info() { echo "[INFO] [$TIMESTAMP] $*" | tee -a "$LOG_FILE"; } +error() { echo "[ERROR] [$TIMESTAMP] $*" >&2 | tee -a "$LOG_FILE"; } + +# Default Exit Codes +readonly ERR_CURL_NOT_INSTALLED=10 +readonly ERR_API_FAILURE=20 + +# Configuration +readonly REDMINE_INSTANCE="projects.knownelement.com" +readonly REDMINE_API_URL="https://${REDMINE_INSTANCE}" +readonly REDMINE_API_KEY="your_api_key_here" +readonly DATE=$(date '+%Y-%m-%d') + +# Function: Usage Instructions +usage() { + cat < + +Options: + -h, --help Display this help message. + +Example: + $SCRIPT_NAME 123 +EOF +} + +# Check dependencies +require_curl() { + if ! command -v curl &>/dev/null; then + error "curl is not installed or not in PATH. Please install it and try again." + exit $ERR_CURL_NOT_INSTALLED + fi +} + +# Fetch issues from Redmine API +fetch_issues() { + local project_id="$1" + local url="${REDMINE_API_URL}/issues.json?key=${REDMINE_API_KEY}&project_id=${project_id}&due_date=${DATE}" + + info "Fetching issues with due date ${DATE} for project ID ${project_id}..." + response=$(curl -s -w "%{http_code}" -o /tmp/redmine_response.json "$url") + http_code=$(tail -n1 <<<"$response") + + if [[ "$http_code" -ne 200 ]]; then + error "Failed to fetch issues from Redmine API. HTTP Code: $http_code" + exit $ERR_API_FAILURE + fi + + info "Issues fetched successfully. Parsing and converting to Markdown..." + generate_markdown /tmp/redmine_response.json +} + +# Generate Markdown output from JSON response +generate_markdown() { + local json_file="$1" + local markdown_output="/tmp/redmine_issues.md" + + echo "# Redmine Issues for ${DATE}" > "$markdown_output" + echo "" >> "$markdown_output" + + jq -r --arg base_url "$REDMINE_API_URL" '.issues[] | "## [\(.subject)](\($base_url)/issues/\(.id))\n- **ID**: \(.id)\n- **Status**: \(.status.name)\n- **Due Date**: \(.due_date)\n\n---"' \ + "$json_file" >> "$markdown_output" + + info "Markdown output written to $markdown_output" + cat "$markdown_output" +} + +# Main Function +main() { + local project_id="$1" + + # Ensure curl is installed + require_curl + + # Fetch issues for the project + fetch_issues "$project_id" +} + +# Argument Parsing +if [[ $# -lt 1 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + usage + exit 0 +fi + +main "$1" + diff --git a/dsr-publish/2025Plan.yml b/dsr-publish/2025Plan.yml new file mode 100644 index 0000000..13cd678 --- /dev/null +++ b/dsr-publish/2025Plan.yml @@ -0,0 +1,12 @@ +title: ReachableCEO 2025 Plan +titlepage: true +toc: true +toc-own-page: true +header-left: "\\hspace{1cm}" +header-center: "\\leftmark" +header-right: "Page \\thepage" +footer-left: "Charles N Wyble" +footer-center: "Tenacity. Velocity. Focus." +page-background: "./background5.pdf" +titlepage-logo: "D:/tsys/@ReachableCEO/ReachableCEO.png" +urlcolor: blue diff --git a/dsr-publish/background5.pdf b/dsr-publish/background5.pdf new file mode 100644 index 0000000..f7839c3 --- /dev/null +++ b/dsr-publish/background5.pdf @@ -0,0 +1,70 @@ +%PDF-1.5 +% +3 0 obj +<< /Length 4 0 R + /Filter /FlateDecode +>> +stream +xm;@ D=/18 K삢D +rƯ0f|%߅ 1Dg,7 +!ijҔP /chpxr‰1W^Qd%q!9ZDj UѪApr$&]x_ʑȽIW?fr)6EU +endstream +endobj +4 0 obj + 168 +endobj +2 0 obj +<< + /ExtGState << + /a0 << /CA 1 /ca 1 >> + >> +>> +endobj +5 0 obj +<< /Type /Page + /Parent 1 0 R + /MediaBox [ 0 0 595.275574 841.889771 ] + /Contents 3 0 R + /Group << + /Type /Group + /S /Transparency + /I true + /CS /DeviceRGB + >> + /Resources 2 0 R +>> +endobj +1 0 obj +<< /Type /Pages + /Kids [ 5 0 R ] + /Count 1 +>> +endobj +6 0 obj +<< /Creator (cairo 1.14.8 (http://cairographics.org)) + /Producer (cairo 1.14.8 (http://cairographics.org)) +>> +endobj +7 0 obj +<< /Type /Catalog + /Pages 1 0 R +>> +endobj +xref +0 8 +0000000000 65535 f +0000000582 00000 n +0000000282 00000 n +0000000015 00000 n +0000000260 00000 n +0000000354 00000 n +0000000647 00000 n +0000000774 00000 n +trailer +<< /Size 8 + /Root 7 0 R + /Info 6 0 R +>> +startxref +826 +%%EOF diff --git a/dsr-publish/create-2025Plan.sh b/dsr-publish/create-2025Plan.sh new file mode 100644 index 0000000..ed6d1b4 --- /dev/null +++ b/dsr-publish/create-2025Plan.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +INPUT_FILE="../dsr-build-temp/@DailyStakeholderReports/StrategicPlans/2025-ReachableCEO-Plan.md" +OUTPUT_FILE="../dsr-build-output/ReachableCEO2025Plan.pdf" +METADATA_FILE="2025Plan.yml" +TEMPLATE="eisvogel" + +pandoc \ +$INPUT_FILE \ +--template $TEMPLATE \ +--metadata-file=$METADATA_FILE \ +--from markdown \ +--to=pdf \ +--output $OUTPUT_FILE diff --git a/dsr-publish/create-dsr-pdf.sh b/dsr-publish/create-dsr-pdf.sh new file mode 100644 index 0000000..fbddd5f --- /dev/null +++ b/dsr-publish/create-dsr-pdf.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +TODAY_DATE=$(date +%m-%d-%Y) +INPUT_FILE="DSR-$TODAY_DATE.md" +OUTPUT_FILE="/d/tsys/@ReachableCEO/DailyStakeholderReport-Pipeline/dsr-build-output/DSR-$TODAY_DATE.pdf" +METADATA_FILE="/d/tsys/@ReachableCEO/DailyStakeholderReport-Pipeline/dsr-publish/daily-stakeholder-report.yml" +TEMPLATE="eisvogel" + +cd /d/tsys/@ReachableCEO/DailyStakeholderReport-Pipeline/dsr-build-temp/@DailyStakeholderReports/Today || exit + +pandoc \ +$INPUT_FILE \ +--template $TEMPLATE \ +--metadata-file=$METADATA_FILE \ +--from markdown \ +--to=pdf \ +--output $OUTPUT_FILE diff --git a/dsr-publish/daily-stakeholder-report.yml b/dsr-publish/daily-stakeholder-report.yml new file mode 100644 index 0000000..1d8971a --- /dev/null +++ b/dsr-publish/daily-stakeholder-report.yml @@ -0,0 +1,11 @@ +title: Daily Stakeholder Report - \today +urlcolor: blue +titlepage: true +header-left: "\\hspace{1cm}" +header-center: "\\leftmark" +header-right: "Page \\thepage" +footer-left: "Charles N Wyble" +footer-center: "Tenacity. Velocity. Focus." +footer-right: "[Source code](https://git.knownelement.com/reachableceo/DailyStakeholderReport-Pipeline/src/branch/main/dsr-publish/create-dsr-pdf.sh)" +page-background: "D:/tsys/@ReachableCEO/DailyStakeholderReport-Pipeline/dsr-publish/background5.pdf" +titlepage-logo: "D:/tsys/@ReachableCEO/ReachableCEO.png" diff --git a/dsr-publish/publish-dsr.sh b/dsr-publish/publish-dsr.sh new file mode 100644 index 0000000..a439478 --- /dev/null +++ b/dsr-publish/publish-dsr.sh @@ -0,0 +1,131 @@ +#!/bin/bash + +secrets_manager() +{ + +echo "Obtaining discourse api key..." + +bw logout + +#################################### +## Step 0: Set to use tsys server +#################################### +bw config server https://pwvault.turnsys.com + +#################################### +## Step 1: login to bitwarden +#################################### + +# From: https://bitwarden.com/help/cli/#using-an-api-key + +### Set apikey environment varaible + +source D:/tsys/secrets/bitwarden/data/apikey-bitwarden-reachableceo + +### Login to vault using apikey... + +bw login --apikey $BW_CLIENTID $BW_CLIENTSECRET + +### Step 1.1: unlock / save session id + +export BW_SESSION="$(bw unlock --passwordenv TSYS_BW_PASSWORD_REACHABLECEO --raw)" + +### Step 2: retrive a value into an environment variable + +export DISCOURSE_APIKEY="$(bw get password APIKEY-discourse)" + +} + +post_dsr() + +{ + +TODAY_DATE=$(date +%m-%d-%Y) + +echo "Posting DSR..." + +DISCOURSE_URL="https://community.turnsys.com" # e.g., https://forum.example.com +API_KEY="$DISCOURSE_APIKEY" # Your API key +API_USERNAME="reachableceo" # API username or admin account + +# The category ID to post to (update to match your forum's categories) +CATEGORY_ID=61 + +# The title for the post (generated here; customize as needed) +TITLE="Daily Stakeholder Report - $TODAY_DATE" + +# The content of the post +CONTENT="Please use the link below to download today's stakeholder report." + +# The file to upload (from the second argument or auto-generated based on date) +FILE_PATH="../dsr-build-output/DSR-$TODAY_DATE.pdf" + +# Check if the file exists +if [ ! -f "$FILE_PATH" ]; then + echo "File not found: $FILE_PATH" + exit 1 +fi + +# Upload the file +echo "Uploading file..." +upload_response=$(curl -s -X POST "$DISCOURSE_URL/uploads.json" \ + -H "Content-Type: multipart/form-data" \ + -H "Api-Key: $API_KEY" \ + -H "Api-Username: $API_USERNAME" \ + -F "file=@$FILE_PATH;type=application/pdf" \ + -F "type=composer") + +echo "Upload Response: $upload_response" + +# Extract the short_url from the response +short_url=$(echo "$upload_response" | /mingw64/bin/jq -r '.short_url') + +# Check if the short_url was returned +if [ "$short_url" == "null" ]; then + echo "Failed to extract short_url. Response:" + echo "$upload_response" + exit 1 +fi + +echo "File uploaded successfully. Short URL: $short_url" + +# Append the file link to the post content (Markdown format) +CONTENT="$CONTENT\n\n[Download todays report in PDF format]($short_url)" + +# Create the new topic +echo "Creating new topic..." +post_response=$(curl -s -X POST "$DISCOURSE_URL/posts.json" \ + -H "Content-Type: application/json" \ + -H "Api-Key: $API_KEY" \ + -H "Api-Username: $API_USERNAME" \ + -d @- <