{
  "name": "Claude Status Checker (Real ccusage)",
  "nodes": [
    {
      "parameters": {},
      "id": "manual-trigger",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "command": "timeout 10s claude status || echo 'TIMEOUT_OR_ERROR'"
      },
      "id": "claude-status-check",
      "name": "Check Claude Status",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process Claude status output and determine if it's working or out of tokens\nconst statusOutput = $json;\n\n// Check if command timed out or failed\nif (statusOutput.exitCode !== 0 || \n    statusOutput.stdout.includes('TIMEOUT_OR_ERROR') || \n    statusOutput.stderr.includes('timeout') ||\n    !statusOutput.stdout.trim()) {\n  \n  return [{\n    json: {\n      claudeStatus: 'unavailable',\n      needsTokenCheck: true,\n      message: 'Claude is not responding - likely out of tokens'\n    }\n  }];\n}\n\n// Check for common Claude error patterns that indicate token issues\nconst tokenErrorPatterns = [\n  'rate limit',\n  'quota exceeded',\n  'no tokens',\n  'insufficient tokens',\n  'credit limit',\n  'usage limit'\n];\n\nconst statusText = statusOutput.stdout.toLowerCase();\nconst hasTokenError = tokenErrorPatterns.some(pattern => statusText.includes(pattern));\n\nif (hasTokenError) {\n  return [{\n    json: {\n      claudeStatus: 'token_exhausted',\n      needsTokenCheck: true,\n      message: 'Claude reports token/quota issues'\n    }\n  }];\n}\n\n// Claude is responding normally\nreturn [{\n  json: {\n    claudeStatus: 'available',\n    needsTokenCheck: false,\n    message: 'Claude is available and responding',\n    statusDetails: statusOutput.stdout\n  }\n}];"
      },
      "id": "process-status",
      "name": "Process Status Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "condition1",
              "leftValue": "={{ $json.needsTokenCheck }}",
              "rightValue": null,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-if-tokens-needed",
      "name": "Check If Token Info Needed",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "command": "CLAUDE_CONFIG_DIR=/home/node/.claude ccusage blocks"
      },
      "id": "check-usage",
      "name": "Check Claude Usage",
      "type": "n8n-nodes-base.executeCommand",
      "typeVersion": 1,
      "position": [
        1120,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// Parse real ccusage blocks output to get exact block timing\nconst usageOutput = $('Check Claude Usage').item.json;\nconst statusInfo = $('Process Status Response').item.json;\n\nif (usageOutput.exitCode !== 0) {\n  const errorResponse = {\n    status: 'error',\n    claudeStatus: statusInfo.claudeStatus,\n    message: '\ud83d\udea8 **Claude Status Check Failed**\\n\\nClaude is not responding and I couldn\\'t check usage information.\\n\\nPlease wait a few minutes and try again.',\n    recommendation: 'Wait 5-10 minutes before retrying',\n    timestamp: new Date().toISOString()\n  };\n  \n  return [{ json: errorResponse }];\n}\n\nlet usageText = usageOutput.stdout;\nconst now = new Date();\n\n// Parse ccusage blocks output to find the current ACTIVE block\nfunction parseActiveBlock(output) {\n  const lines = output.split('\\n');\n  let activeBlockInfo = null;\n  let remainingInfo = null;\n  \n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i];\n    \n    // Look for ACTIVE block line - format is split across two lines\n    if (line.includes('ACTIVE')) {\n      // Extract block start time from current line\n      const blockStartMatch = line.match(/([0-9]+\\/[0-9]+\\/[0-9]+,\\s+[0-9]+:[0-9]+:[0-9]+\\s+[AP]M)/);\n      const usageMatch = line.match(/([0-9]+\\.[0-9]+)%/);\n      \n      // Look for remaining time in the next line\n      let remainingTimeStr = null;\n      if (i + 1 < lines.length) {\n        const nextLine = lines[i + 1];\n        const remainingMatch = nextLine.match(/([0-9]+m)\\s+remaining/);\n        if (remainingMatch) {\n          const fullRemainingMatch = nextLine.match(/([0-9]+h\\s+[0-9]+m)\\s+remaining/);\n          remainingTimeStr = fullRemainingMatch ? fullRemainingMatch[1] : remainingMatch[1];\n        }\n      }\n      \n      if (blockStartMatch && remainingTimeStr) {\n        const blockStartStr = blockStartMatch[1];\n        const blockStartTime = new Date(blockStartStr);\n        \n        // Calculate next block start (5 hours after current block start)\n        const nextBlockStart = new Date(blockStartTime.getTime() + 5 * 60 * 60 * 1000);\n        \n        // Parse remaining time\n        const timeParseMatch = remainingTimeStr.match(/(?:([0-9]+)h\\s+)?([0-9]+)m/);\n        if (timeParseMatch) {\n          const hoursRemaining = timeParseMatch[1] ? parseInt(timeParseMatch[1]) : 0;\n          const minutesRemaining = parseInt(timeParseMatch[2]);\n          const totalMinutesRemaining = hoursRemaining * 60 + minutesRemaining;\n          \n          activeBlockInfo = {\n            blockStart: blockStartStr,\n            blockStartTime: blockStartTime,\n            remainingTime: remainingTimeStr,\n            nextBlockStart: nextBlockStart,\n            hoursRemaining: hoursRemaining,\n            minutesRemaining: minutesRemaining,\n            totalMinutesRemaining: totalMinutesRemaining,\n            usagePercent: usageMatch ? parseFloat(usageMatch[1]) : null\n          };\n        }\n      }\n    }\n    \n    // Look for REMAINING tokens info\n    if (line.includes('REMAINING')) {\n      const tokensMatch = line.match(/([0-9,]+)/);\n      const percentMatch = line.match(/([0-9]+\\.[0-9]+)%/);\n      if (tokensMatch && percentMatch) {\n        remainingInfo = {\n          remainingTokens: tokensMatch[1],\n          remainingPercent: parseFloat(percentMatch[1])\n        };\n      }\n    }\n  }\n  \n  return { activeBlockInfo, remainingInfo };\n}\n\n// Parse the block information\nconst { activeBlockInfo, remainingInfo } = parseActiveBlock(usageText);\n\nif (!activeBlockInfo) {\n  // Fallback if no active block found\n  const errorResponse = {\n    status: 'error',\n    claudeStatus: statusInfo.claudeStatus,\n    message: '\ud83d\udea8 **No Active Block Found**\\n\\nCannot determine current Claude block status. This might mean:\\n\\n\u2022 No recent Claude usage found\\n\u2022 Claude session may have expired\\n\u2022 Try using Claude once to initialize a session\\n\\n**Raw output:**\\n```\\n' + usageText.substring(0, 500) + '\\n```',\n    recommendation: 'Use Claude to start a new session, then check again',\n    timestamp: now.toISOString()\n  };\n  \n  return [{ json: errorResponse }];\n}\n\n// Format wait time\nlet waitTimeText;\nif (activeBlockInfo.hoursRemaining > 0) {\n  waitTimeText = `${activeBlockInfo.hoursRemaining}h ${activeBlockInfo.minutesRemaining}m`;\n} else {\n  waitTimeText = `${activeBlockInfo.minutesRemaining}m`;\n}\n\nconst nextBlockFormatted = activeBlockInfo.nextBlockStart.toLocaleString();\n\n// Extract key usage info from the blocks output\nlet recentUsageInfo = '';\nconst lines = usageText.split('\\n');\nfor (const line of lines) {\n  if (line.includes('\u2502') && (line.includes('ACTIVE') || line.includes('sonnet-4') || line.includes('opus-4') || line.includes('%'))) {\n    recentUsageInfo += line + '\\n';\n  }\n}\n\n// Build comprehensive response message\nlet message = `\u23f3 **Claude Token Block Status** (Live Data)\\n\\n`;\nmessage += `**Current Status:** ${statusInfo.message}\\n\\n`;\nmessage += `**\ud83d\udd50 Real Block Info:**\\n`;\nmessage += `\u2022 Current block started: **${activeBlockInfo.blockStart}**\\n`;\nmessage += `\u2022 Block usage: **${activeBlockInfo.usagePercent || 'N/A'}%** used\\n`;\nmessage += `\u2022 Time remaining: **${waitTimeText}**\\n`;\nmessage += `\u2022 Next block starts: **${nextBlockFormatted}**\\n`;\nmessage += `\u2022 Current time: ${now.toLocaleString()}\\n\\n`;\n\nif (remainingInfo) {\n  message += `**\ud83d\udcca Token Status:**\\n`;\n  message += `\u2022 Remaining tokens: ${remainingInfo.remainingTokens} (${remainingInfo.remainingPercent}%)\\n\\n`;\n}\n\nif (recentUsageInfo.trim()) {\n  message += `**\ud83d\udcc8 Block Details:**\\n\\`\\`\\`\\n${recentUsageInfo.trim()}\\n\\`\\`\\`\\n\\n`;\n}\n\n// Smart recommendations based on remaining time\nif (activeBlockInfo.totalMinutesRemaining <= 15) {\n  message += `**\ud83c\udfc3\u200d\u2642\ufe0f Very Short Wait:** Next block starts very soon!\\n\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 60) {\n  message += `**\u23f0 Short Wait:** Next block starts in ${waitTimeText}\\n\\n`;\n} else {\n  message += `**\u23f3 Medium Wait:** Next block starts in ${waitTimeText}\\n\\n`;\n}\n\nmessage += `**\ud83d\udca1 Smart Recommendations:**\\n`;\nif (activeBlockInfo.totalMinutesRemaining <= 15) {\n  message += `\u2022 \ud83c\udfc3\u200d\u2642\ufe0f Very short wait - try again in ${waitTimeText}\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 60) {\n  message += `\u2022 \u23f0 Short wait - set a timer for ${nextBlockFormatted}\\n`;\n} else if (activeBlockInfo.totalMinutesRemaining <= 180) {\n  message += `\u2022 \ud83d\udcc5 Medium wait - check back at ${nextBlockFormatted}\\n`;\n} else {\n  message += `\u2022 \ud83d\udd50 Longer wait - set a reminder for ${nextBlockFormatted}\\n`;\n}\nmessage += `\u2022 \ud83d\udcdd Use the time to plan your next Claude tasks\\n`;\nmessage += `\u2022 \ud83d\udd04 Current block is ${activeBlockInfo.usagePercent || 'N/A'}% used\\n\\n`;\n\nmessage += `---\\n*Live status at ${now.toLocaleString()} based on real ccusage blocks data*`;\n\nconst response = {\n  status: 'token_exhausted',\n  claudeStatus: statusInfo.claudeStatus,\n  message: message,\n  waitTime: waitTimeText,\n  nextBlockStart: nextBlockFormatted,\n  minutesUntilNextBlock: activeBlockInfo.totalMinutesRemaining,\n  blockInfo: {\n    currentTime: now.toISOString(),\n    blockStartTime: activeBlockInfo.blockStart,\n    nextBlockStart: activeBlockInfo.nextBlockStart.toISOString(),\n    hoursRemaining: activeBlockInfo.hoursRemaining,\n    minutesRemaining: activeBlockInfo.minutesRemaining,\n    usagePercent: activeBlockInfo.usagePercent || null,\n    remainingTokens: remainingInfo?.remainingTokens || null,\n    remainingPercent: remainingInfo?.remainingPercent || null\n  },\n  rawUsageOutput: usageText,\n  timestamp: now.toISOString()\n};\n\nreturn [{ json: response }];"
      },
      "id": "process-usage-info",
      "name": "Process Real Block Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        200
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build success response for when Claude is available\nconst statusInfo = $('Process Status Response').item.json;\n\nconst message = `\u2705 **Claude is Available**\\n\\n${statusInfo.message}\\n\\n${statusInfo.statusDetails ? `**Status Details:**\\n\\`\\`\\`\\n${statusInfo.statusDetails}\\n\\`\\`\\`\\n\\n` : ''}`;\nmessage += `**\ud83d\udca1 Status:** Ready to process requests\\n\\n`;\nmessage += `---\\n*Status checked at ${new Date().toLocaleString()}*`;\n\nconst response = {\n  status: 'available',\n  claudeStatus: statusInfo.claudeStatus,\n  message: message,\n  timestamp: new Date().toISOString()\n};\n\nreturn [{ json: response }];"
      },
      "id": "build-success-response",
      "name": "Build Success Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        400
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Check Claude Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Claude Status": {
      "main": [
        [
          {
            "node": "Process Status Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Status Response": {
      "main": [
        [
          {
            "node": "Check If Token Info Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check If Token Info Needed": {
      "main": [
        [
          {
            "node": "Check Claude Usage",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Claude Usage": {
      "main": [
        [
          {
            "node": "Process Real Block Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Real Block Data": {
      "main": [
        []
      ]
    },
    "Build Success Response": {
      "main": [
        []
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "tags": [
    {
      "name": "Claude Status",
      "id": "claude-status"
    },
    {
      "name": "Real ccusage",
      "id": "real-ccusage"
    },
    {
      "name": "Token Management",
      "id": "token-management"
    },
    {
      "name": "AI Automation",
      "id": "ai-automation"
    }
  ]
}