Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,34 @@

# CHANGELOG

## [v3.2.97] - 2026-04-27 — S1 起動時 ~/パス修正 + statusline.js 自動デプロイ

### 🎯 概要
S1 起動(SSH モード)でのテンプレートデプロイに2つの問題を修正。

### 🐛 根本原因

| バグ | 原因 | 修正 |
|---|---|---|
| `~/.claudeos/cron-launcher.sh` が `~/~/.cloudeos/` に誤配置 | bash double-quoteの中では `~` は展開されない | `~` → `$HOME` に変換するロジック追加 |
| S1 起動時に `statusline.js` が Linux に自動デプロイされない | デプロイリストに含まれていなかった | `scripts/templates/statusline.js` を追加・`$HOME/.claude/statusline.js` として InitializeOnly でデプロイ |

### 🔧 変更対象

| ファイル | 変更内容 |
|---|---|
| `scripts/main/Start-ClaudeCode.ps1` | `New-RemoteTemplateDeployScript` で `~` → `$HOME` 変換追加 / `statusline.js` デプロイ追加 |
| `scripts/templates/statusline.js` | statusline.js テンプレートとして新規追加 |

### ✅ 修正後の動作

```
S1 起動 → プロジェクト選択
→ ~/.claudeos/cron-launcher.sh が /home/kensan/.claudeos/ に正しく配置 ✅
→ ~/.claude/statusline.js が /home/kensan/.claude/ に InitializeOnly でデプロイ ✅
→ 次回 S1 からは statusline が確実に動作
```

## [v3.2.96] - 2026-04-27 — statusline.js Linux パス修正・自動デプロイ対応

### 🎯 概要
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
> **📌 v3.1.0 で Claude Code 専用ツールに整理**
> v3.1.0 より、Codex CLI / GitHub Copilot CLI の起動メニュー (S2/S3/L2/L3) は削除されました。本ツールは **Claude Code 専用の自律開発ランチャー** として位置づけを明確化し、Linux crontab 連携・セッション情報タブ・Statusline グローバル適用などの新機能に投資が集中しています。

> **🐛 v3.2.96 — statusline.js Linux パス修正・自動デプロイ対応(Windows/Linux 両対応)**
> `Invoke-RemoteSettingsSync` の Windows 絶対パス → `~/` 自動変換 + `statusline.js` 自動コピー。詳細は [`CHANGELOG.md`](./CHANGELOG.md) を参照。
> **🐛 v3.2.97 — S1 起動時 `~` パス修正 + statusline.js 自動デプロイ**
> `New-RemoteTemplateDeployScript` の bash double-quote `~` 非展開バグ修正(`$HOME` 置換)+ S1 起動時に `statusline.js` を自動デプロイ。詳細は [`CHANGELOG.md`](./CHANGELOG.md) を参照。

> **📨 v3.2.0 — Cron HTML メールレポート (Visual Recap Mail)**
> Cron で起動された ClaudeCode セッションの完了時に、**HTML 形式のレポートメール** を Gmail SMTP 経由で送信。アイコン+色付き表組み+実行サマリ(Monitor/Development/Verify/Improvement の出現回数/エラー検出/STABLE 達成)+次フェーズ提案を含む。送信先は `CLAUDEOS_DEFAULT_TO`(未設定時 `CLAUDEOS_SMTP_USER`)で指定し、SMTP 認証情報は `~/.env-claudeos` の Linux 環境変数で管理(config.json には書かない設計)。詳細は [`docs/common/16_HTMLメールレポート設定.md`](./docs/common/16_HTMLメールレポート設定.md) を参照。
Expand All @@ -27,7 +27,7 @@

| 項目 | 状態 |
|------|------|
| バージョン | **v3.2.96** (statusline.js Linux パス修正・自動デプロイ) — 旧: v3.2.95 |
| バージョン | **v3.2.97** (S1 起動 `~` パス修正 + statusline.js 自動デプロイ) — 旧: v3.2.96 |
| テスト | **776件** — Pester (Unit 21 / Integration 11 / Smoke 1) |
| CI | ✅ SUCCESS |
| ClaudeOS (Claude Code 専用) | v8 (Opus 4.7 最適化 / Token 1.35x 補正 / Agent Teams 並列 spawn / `/compact` 事前発動 / `task_budget` / 1H cache / `/ultrareview` / PreCompact hook / `/recap` fallback / Push Notification / Effort 動的切替) |
Expand Down
6 changes: 6 additions & 0 deletions scripts/main/Start-ClaudeCode.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ function New-RemoteTemplateDeployScript {

$content = Get-Content -Path $TemplatePath -Raw -Encoding UTF8
$base64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($content))
# bash double-quote does not expand ~ — replace with $HOME for correct expansion
$normalizedTargetPath = $TargetPath.Replace('\', '/')
if ($normalizedTargetPath -match '^~/') {
$normalizedTargetPath = '$HOME/' + $normalizedTargetPath.Substring(2)
}
$mkdir = ""
if ($EnsureParentDirectory) {
$mkdir = "mkdir -p `"`$(dirname `"$normalizedTargetPath`")`"`n"
Expand Down Expand Up @@ -442,6 +446,8 @@ try {
(New-RemoteTemplateDeployScript -TemplatePath (Join-Path $ScriptRoot 'Claude\templates\claudeos\commands\session-info.md') -TargetPath "$linuxProject/.claude/commands/session-info.md" -Label '.claude/commands/session-info.md' -EnsureParentDirectory)
# v3.1.0: cron-launcher.sh を ~/.claudeos/ に配布
(New-RemoteTemplateDeployScript -TemplatePath (Join-Path $ScriptRoot 'Claude\templates\linux\cron-launcher.sh') -TargetPath "~/.claudeos/cron-launcher.sh" -Label '~/.claudeos/cron-launcher.sh' -EnsureParentDirectory)
# v3.2.97: statusline.js を ~/.claude/ に配布 (InitializeOnly: 存在時は上書きしない)
(New-RemoteTemplateDeployScript -TemplatePath (Join-Path $ScriptRoot 'scripts\templates\statusline.js') -TargetPath "~/.claude/statusline.js" -Label '~/.claude/statusline.js' -EnsureParentDirectory -InitializeOnly)
@"
cat > $(ConvertTo-BashSingleQuoted -Value $remoteBootstrap) <<'EOF'
#!/usr/bin/env bash
Expand Down
145 changes: 145 additions & 0 deletions scripts/templates/statusline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Claude Code status line script (Windows / Node.js)
// Reads JSON from stdin, outputs formatted multi-line status bar with ANSI colors.
const { execSync } = require("child_process");
const os = require("os");

// ANSI color codes
const C = {
cyan: "\x1b[36m",
green: "\x1b[32m",
yellow: "\x1b[33m",
magenta: "\x1b[35m",
blue: "\x1b[34m",
white: "\x1b[97m",
red: "\x1b[31m",
bold: "\x1b[1m",
r: "\x1b[0m",
};

function getGitBranch(cwd) {
try {
return execSync("git rev-parse --abbrev-ref HEAD", {
cwd,
encoding: "utf8",
stdio: ["pipe", "pipe", "ignore"],
}).trim();
} catch {
return "?";
}
}

function progressBar(pct, width = 10) {
const filled = Math.round((pct / 100) * width);
const empty = width - filled;
// Green for filled, dim gray for empty
let color = C.green;
if (pct >= 80) color = C.red;
else if (pct >= 50) color = C.yellow;
return color + "\u25b0".repeat(filled) + C.cyan + "\u25b1".repeat(empty) + C.r;
Comment on lines +34 to +38
}

function formatDuration(ms) {
const totalSec = Math.floor(ms / 1000);
const hours = Math.floor(totalSec / 3600);
const minutes = Math.floor((totalSec % 3600) / 60);
if (hours > 0) return `${hours}h ${String(minutes).padStart(2, "0")}m`;
return `${minutes}m`;
}

function formatResetTime(epoch) {
if (!epoch) return "";
const dt = new Date(epoch * 1000);
const now = new Date();
const timeStr = dt
.toLocaleString("en-US", {
hour: "numeric",
hour12: true,
timeZone: "Asia/Tokyo",
})
.toLowerCase();
const sameDay =
dt.toLocaleDateString("en-US", { timeZone: "Asia/Tokyo" }) ===
now.toLocaleDateString("en-US", { timeZone: "Asia/Tokyo" });
if (sameDay) return `Resets ${timeStr} (Asia/Tokyo)`;
const dateStr = dt.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
timeZone: "Asia/Tokyo",
});
return `Resets ${dateStr} at ${timeStr} (Asia/Tokyo)`;
}

let raw = "";
process.stdin.on("data", (chunk) => (raw += chunk));
process.stdin.on("end", () => {
if (!raw.trim()) return;

let data;
try {
data = JSON.parse(raw);
} catch {
return;
}

const model = data.model || {};
const modelName = model.display_name || model.id || "?";

const cwd = data.cwd || "";
const project = cwd ? require("path").basename(cwd) : "?";

const branch = getGitBranch(cwd || ".");
const osName = os.platform() === "win32" ? "Windows" : os.platform();

const ctx = data.context_window || {};
const ctxPct = ctx.used_percentage || 0;

const cost = data.cost || {};
const linesAdded = cost.total_lines_added || 0;
const linesRemoved = cost.total_lines_removed || 0;
const durationMs = cost.total_duration_ms || 0;

const rateLimits = data.rate_limits || {};
const fiveHour = rateLimits.five_hour || {};
const sevenDay = rateLimits.seven_day || {};

const sep = ` ${C.blue}\u2502${C.r} `;

// Line 1: Model / Project / Branch / OS
const line1 = [
`${C.magenta}\ud83e\udd16 ${C.bold}${modelName}${C.r}`,
`${C.yellow}\ud83d\udcc1 ${project}${C.r}`,
`${C.green}\ud83c\udf3f ${branch}${C.r}`,
`${C.cyan}\ud83d\udda5 ${osName}${C.r}`,
].join(sep);
console.log(line1);

// Line 2: Context % / File changes / Duration
const ctxBar = progressBar(ctxPct);
const line2Parts = [
`${C.blue}\ud83d\udcca ${C.white}${Math.round(ctxPct)}%${C.r} ${ctxBar}`,
`${C.cyan}\u270f\ufe0f ${C.green}+${linesAdded}${C.r}/${C.red}-${linesRemoved}${C.r}`,
];
if (durationMs > 0)
line2Parts.push(`${C.blue}\u23f1 ${C.white}${formatDuration(durationMs)}${C.r}`);
console.log(line2Parts.join(sep));

// Line 3: 5-hour rate limit (Pro/Max only)
const fivePct = fiveHour.used_percentage;
if (fivePct != null) {
const fiveBar = progressBar(fivePct);
const fiveReset = formatResetTime(fiveHour.resets_at);
console.log(
`${C.blue}\u23f1 5h${C.r} ${fiveBar} ${C.white}${Math.round(fivePct)}%${C.r} ${C.cyan}${fiveReset}${C.r}`
);
}

// Line 4: 7-day rate limit (Pro/Max only)
const sevenPct = sevenDay.used_percentage;
if (sevenPct != null) {
const sevenBar = progressBar(sevenPct);
const sevenReset = formatResetTime(sevenDay.resets_at);
console.log(
`${C.blue}\ud83d\udcc5 7d${C.r} ${sevenBar} ${C.white}${Math.round(sevenPct)}%${C.r} ${C.cyan}\u5168\u30e2\u30c7\u30eb${C.r} ${C.cyan}${sevenReset}${C.r}`
);
}
});
Loading