Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
8d505bd
test(update): add coverage for update-check version, state and releas…
AbdelrahmanHafez Jul 28, 2026
9d25b72
feat(update): add update-check module for versions, state and release…
AbdelrahmanHafez Jul 28, 2026
84400bb
test(update): add coverage for updater strategies, throttle and apply…
AbdelrahmanHafez Jul 28, 2026
d98867b
feat(update): add updater orchestration with Squirrel and download st…
AbdelrahmanHafez Jul 28, 2026
1fda1db
test(update): add UpdateBanner rendering and interaction coverage
AbdelrahmanHafez Jul 28, 2026
f57fda9
feat(update): render the update banner states and actions
AbdelrahmanHafez Jul 28, 2026
5ce273d
test(update): surface forced update-check failures to the caller
AbdelrahmanHafez Jul 28, 2026
00e4bc3
fix(update): rethrow forced update-check failures after logging
AbdelrahmanHafez Jul 28, 2026
f879e0a
feat(update): wire daily update checks, banner IPC and the updates me…
AbdelrahmanHafez Jul 28, 2026
e432c60
test(cli): add coverage for the cached update notice
AbdelrahmanHafez Jul 28, 2026
a815840
feat(cli): print a cached update notice on launch and --version
AbdelrahmanHafez Jul 28, 2026
2586d32
test(update): cover concurrent applies, openPath failures and dismiss…
AbdelrahmanHafez Jul 28, 2026
2588730
fix(update): serialize applies, surface openPath failures, protect di…
AbdelrahmanHafez Jul 29, 2026
6c710d9
test(update): cover out-of-order check completions and mid-flight dis…
AbdelrahmanHafez Jul 29, 2026
788db17
fix(update): discard stale check completions and keep mid-flight dism…
AbdelrahmanHafez Jul 29, 2026
a8290de
test(update): cover check completions during active applies and stale…
AbdelrahmanHafez Jul 29, 2026
b1d0b6e
fix(update): keep active applies and drop stale failures on check com…
AbdelrahmanHafez Jul 29, 2026
77d0c16
test(update): keep apply failures visible across check completions
AbdelrahmanHafez Jul 29, 2026
89bfebe
fix(update): guard check completions with a status generation counter
AbdelrahmanHafez Jul 29, 2026
c310658
test(update): keep identical retry failures visible across check comp…
AbdelrahmanHafez Jul 29, 2026
bcf1a90
fix(update): enter the updating phase before configuring the Squirrel…
AbdelrahmanHafez Jul 29, 2026
a87015f
test(update): specify serialized checks with caller-owned failures
AbdelrahmanHafez Jul 29, 2026
43e2937
fix(update): serialize update checks behind a single in-flight request
AbdelrahmanHafez Jul 29, 2026
f4e3a1f
test(update): pin queued checks to their enqueue-time context
AbdelrahmanHafez Jul 29, 2026
1f3819e
fix(update): pin checks to enqueue-time action generation and dismiss…
AbdelrahmanHafez Jul 29, 2026
160f892
test(update): keep terminal apply outcomes through throttled checks
AbdelrahmanHafez Jul 29, 2026
4cc6b7f
fix(update): treat throttled checks as read-only and installerReady a…
AbdelrahmanHafez Jul 29, 2026
fd237a7
test(update): cover applyLatest and the apply-update launch flag
AbdelrahmanHafez Jul 29, 2026
04bc1a1
feat(update): add applyLatest and the apply-update launch flag
AbdelrahmanHafez Jul 29, 2026
1db42a5
test(cli): cover the codiff update command decision and runner
AbdelrahmanHafez Jul 29, 2026
b289bb9
feat(cli): implement the codiff update decision and runner
AbdelrahmanHafez Jul 29, 2026
301e336
feat(cli): add the codiff update command across dev, brew and app ins…
AbdelrahmanHafez Jul 29, 2026
ee33cb3
test(update): surface applyLatest check failures and version-less pro…
AbdelrahmanHafez Jul 29, 2026
cab9295
fix(update): turn applyLatest check failures into the error banner state
AbdelrahmanHafez Jul 29, 2026
81bee42
test(update): keep applies alive when a concurrent applyLatest check …
AbdelrahmanHafez Jul 29, 2026
7913c30
fix(update): let mid-flight actions outlive a failed applyLatest check
AbdelrahmanHafez Jul 29, 2026
99e0f22
test(update): let a newer applyLatest outrank an older failed one
AbdelrahmanHafez Jul 29, 2026
56f439f
fix(update): sequence applyLatest so newer requests own the outcome
AbdelrahmanHafez Jul 29, 2026
3926b23
test(update): give the newest applyLatest ownership of successful app…
AbdelrahmanHafez Jul 29, 2026
6938699
fix(update): defer older successful applyLatest requests to newer ones
AbdelrahmanHafez Jul 29, 2026
1306e4f
test(cli): expect the installed wrapper to print the cached update no…
AbdelrahmanHafez Jul 29, 2026
e4e0553
test(cli): expect the update notice to mention codiff update
AbdelrahmanHafez Jul 29, 2026
485958e
fix(cli): print the cached update notice from the installed wrapper
AbdelrahmanHafez Jul 29, 2026
4c5f4b4
test(cli): expect the wrapper notice to survive corrupt cache input
AbdelrahmanHafez Jul 29, 2026
df3edcf
fix(cli): keep the wrapper update notice silent on corrupt cache input
AbdelrahmanHafez Jul 29, 2026
58e40ca
test(ui): describe the corner update pill with a details popover
AbdelrahmanHafez Jul 29, 2026
5e5d2c7
feat(ui): replace the update banner with a corner pill and details po…
AbdelrahmanHafez Jul 29, 2026
f1424b7
style(ui): dock the update pill into the sidebar footer
AbdelrahmanHafez Jul 29, 2026
90db1b6
test(cli): expect the wrapper notice to reject unparseable timestamps
AbdelrahmanHafez Jul 29, 2026
4119cc4
fix(cli): validate the cached timestamp like Date.parse before noticing
AbdelrahmanHafez Jul 29, 2026
c3804ab
style(ui): raise the light update accent to meet text contrast
AbdelrahmanHafez Jul 29, 2026
c90b2bc
test(ui): expect a corner close button on the update popover
AbdelrahmanHafez Jul 29, 2026
f695229
feat(ui): add a corner close button to the update popover
AbdelrahmanHafez Jul 29, 2026
403c25d
docs(cli): correct the day-overflow note on is_iso_timestamp
AbdelrahmanHafez Jul 29, 2026
83cd99a
test(ui): describe the one-click update pill
AbdelrahmanHafez Jul 29, 2026
0dc21f0
feat(ui): apply the update directly from the pill
AbdelrahmanHafez Jul 29, 2026
541d3e7
test(ui): expect platform-neutral install copy on the update pill
AbdelrahmanHafez Jul 29, 2026
a4be715
fix(ui): drop the restart promise from the update tooltip
AbdelrahmanHafez Jul 29, 2026
12a95af
style(ui): center the update pill and size it to its label
AbdelrahmanHafez Jul 29, 2026
6a6f1f6
test(update): expect strategy-aware tooltips on the update pill
AbdelrahmanHafez Jul 29, 2026
8fdf69f
fix(updater): expose the update strategy so the pill can describe the…
AbdelrahmanHafez Jul 29, 2026
75dac17
Merge branch 'main' into feat/update-notifications
AbdelrahmanHafez Jul 29, 2026
bcd3dee
test(updater): expect Linux assets matched to the running architecture
AbdelrahmanHafez Jul 29, 2026
4c1f8f3
fix(updater): match Linux release assets to the running architecture
AbdelrahmanHafez Jul 29, 2026
e76f4fd
test(update): expect plain Windows installs to hand off to the releas…
AbdelrahmanHafez Jul 29, 2026
5074f31
fix(updater): hand plain Windows installs off to the release page
AbdelrahmanHafez Jul 29, 2026
d99f97d
test(update): expect downloaded installers verified against release d…
AbdelrahmanHafez Jul 29, 2026
362a58e
fix(updater): stream downloads and verify them against the release di…
AbdelrahmanHafez Jul 29, 2026
a2deba0
test(update): expect codiff update to hand brew installs to the app
AbdelrahmanHafez Jul 29, 2026
b46d9ae
fix(update): drop the brew special case from codiff update
AbdelrahmanHafez Jul 29, 2026
94ef066
test(ui): expect the update pill to use the shared button styles
AbdelrahmanHafez Jul 29, 2026
8b9d86a
fix(ui): render the update pill as a shared button with a solid accent
AbdelrahmanHafez Jul 29, 2026
eb44229
test(ui): expect the update pill styles to stay out of the web styles…
AbdelrahmanHafez Jul 29, 2026
849a50c
fix(ui): keep the update pill styles out of the shared web stylesheet
AbdelrahmanHafez Jul 29, 2026
57aed65
test(updater): expect a manual retry to recover once the page opens
AbdelrahmanHafez Jul 29, 2026
d605851
fix(updater): recover from a failed manual hand-off on retry
AbdelrahmanHafez Jul 29, 2026
8bff687
style(ui): match the update accent to the Codex button blue
AbdelrahmanHafez Jul 29, 2026
2ff68ad
test(updater): expect manual applies serialized and yielding to dismi…
AbdelrahmanHafez Jul 29, 2026
cd63a21
fix(updater): serialize manual applies and yield to newer actions
AbdelrahmanHafez Jul 29, 2026
1191b3e
style(ui): darken the update accent and hover to clear WCAG AA
AbdelrahmanHafez Jul 29, 2026
4a7cc20
test(updater): expect applyLatest to supersede a pending manual hand-off
AbdelrahmanHafez Jul 29, 2026
24ce5ec
fix(updater): let newer manual requests supersede a pending hand-off
AbdelrahmanHafez Jul 29, 2026
524ac39
test(updater): expect a dismissal during a queued manual hand-off to win
AbdelrahmanHafez Jul 29, 2026
57f707f
fix(updater): capture ownership before waiting on a superseded hand-off
AbdelrahmanHafez Jul 29, 2026
39da78e
test(ui): expect a text-only Update pill while an update is available
AbdelrahmanHafez Jul 29, 2026
275b834
fix(ui): drop the sparkle icon from the update pill
AbdelrahmanHafez Jul 29, 2026
433df2f
test(updater): expect fresh requests not to share an invalidated hand…
AbdelrahmanHafez Jul 29, 2026
3a1143e
fix(updater): key shared manual hand-offs to their action generation
AbdelrahmanHafez Jul 29, 2026
231853c
test(ui): drop the icon-free pill assertion
AbdelrahmanHafez Jul 29, 2026
42addcf
test(ui): drop the stylesheet-placement assertion
AbdelrahmanHafez Jul 29, 2026
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
1 change: 1 addition & 0 deletions bin/arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ const usageExamples = [
description: 'Review the open pull request for a branch.',
},
{ command: 'codiff mr 75', description: 'Review GitLab merge request !75.' },
{ command: 'codiff update', description: 'Update Codiff to the latest release.' },
{ command: 'codiff --plan plan.md', description: 'Edit a plan and wait for handoff.' },
{ command: 'codiff --plan plan.md --share', description: 'Share a Markdown plan.' },
{ command: 'codiff -w', description: 'Walk through local changes, or HEAD when clean.' },
Expand Down
112 changes: 101 additions & 11 deletions bin/codiff-app
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,80 @@ codiff_version() {
sed -n 's/^ "version": "\([^"]*\)".*/\1/p' "$script_dir/../package.json"
}

# major.minor.patch integer compare; any other shape is "not newer".
is_newer_version() {
case "$1$2" in *[!0-9.]*|'') return 1 ;; esac
case ".$1." in *..*) return 1 ;; esac
case ".$2." in *..*) return 1 ;; esac
old_ifs="$IFS"
IFS=.
# shellcheck disable=SC2086
set -- $1 -- $2
IFS="$old_ifs"
[ "$#" -eq 7 ] && [ "$4" = "--" ] || return 1
# sh integer tests overflow beyond int64; nothing that large is a release.
for version_component in "$1" "$2" "$3" "$5" "$6" "$7"; do
[ "${#version_component}" -le 9 ] || return 1
done
latest_major="$1" latest_minor="$2" latest_patch="$3"
current_major="$5" current_minor="$6" current_patch="$7"
if [ "$latest_major" -ne "$current_major" ]; then
[ "$latest_major" -gt "$current_major" ]
return $?
fi
if [ "$latest_minor" -ne "$current_minor" ]; then
[ "$latest_minor" -gt "$current_minor" ]
return $?
fi
[ "$latest_patch" -gt "$current_patch" ]
}

# Mirror of the Node notice's Date.parse gate, restricted to the ISO shape
# the app writes: YYYY-MM-DDTHH:MM:SS with in-range components, optionally
# followed by a .mmm fraction and Z. Anything else, including timezone
# offsets Date.parse would accept, stays silent. Day-in-month overflow such
# as February 30th passes, matching the Date.parse rollover behavior.
is_iso_timestamp() {
case "$1" in
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-5][0-9]:[0-5][0-9]*) ;;
*) return 1 ;;
esac
iso_month="${1#????-}"
iso_month="${iso_month%%-*}"
iso_day="${1#????-??-}"
iso_day="${iso_day%%T*}"
iso_hour="${1#????-??-??T}"
iso_hour="${iso_hour%%:*}"
iso_tail="${1#????-??-??T??:??:??}"
case "$iso_month" in 0[1-9]|1[0-2]) ;; *) return 1 ;; esac
case "$iso_day" in 0[1-9]|[12][0-9]|3[01]) ;; *) return 1 ;; esac
case "$iso_hour" in [01][0-9]|2[0-3]) ;; *) return 1 ;; esac
case "$iso_tail" in ''|Z|.[0-9][0-9][0-9]|.[0-9][0-9][0-9]Z) ;; *) return 1 ;; esac
}

# Mirror of bin/update-notice.js for the wrapper-owned code paths: read the
# state cached by the app's daily check and never touch the network. The
# notice is best effort: a missing, unreadable, or corrupt cache must stay
# silent and must never change the wrapped command's exit status.
print_update_notice() {
[ -n "${HOME:-}" ] || return 0
state_file="$HOME/.codiff/update-state.json"
[ -f "$state_file" ] && [ -r "$state_file" ] || return 0
# tail -n 1 mirrors JSON duplicate-key last-wins resolution.
checked_at="$(sed -n 's/.*"lastCheckedAt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$state_file" 2>/dev/null | tail -n 1)"
latest="$(sed -n 's/.*"latestVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$state_file" 2>/dev/null | tail -n 1)"
dismissed="$(sed -n 's/.*"dismissedVersion"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$state_file" 2>/dev/null | tail -n 1)"
is_iso_timestamp "$checked_at" || return 0
[ -n "$latest" ] || return 0
[ "$latest" = "$dismissed" ] && return 0
current="$(codiff_version)"
is_newer_version "$latest" "$current" || return 0
# The backticks are literal command quoting, not a missed expansion.
# shellcheck disable=SC2016
printf 'A new version of Codiff is available (v%s -> v%s). Run `codiff update` to update.\n' \
"$current" "$latest" >&2
}

show_help() {
version="$(codiff_version)"
blue_bold="$(printf '\033[1;34m')"
Expand All @@ -30,6 +104,9 @@ show_help() {
printf '\n'
printf '%s\n' "${blue_bold}Usage:${reset} ${gray}codiff [options] [<ref> | <pr> | <url>] [path]${reset}"
printf '\n'
printf '%s\n' "${blue_bold}Commands:${reset}"
printf ' %-38s%s%s%s\n' "update" "$gray" "Update Codiff to the latest release." "$reset"
printf '\n'
printf '%s\n' "${blue_bold}Options:${reset}"
printf ' %-38s%s%s%s\n' "--agent <codex|claude|opencode|pi>" "$gray" "Override the agent backend for this session." "$reset"
printf ' %-38s%s%s%s\n' "--branch <ref>" "$gray" "Review the current branch against a target branch." "$reset"
Expand Down Expand Up @@ -65,22 +142,32 @@ show_help() {
printf ' %-32s%s%s%s\n' "codiff --share HEAD" "$gray" "Share a walkthrough for a commit." "$reset"
}

resolve_runtime() {
runtime="${CODIFF_NODE_COMMAND:-}"
if [ -z "$runtime" ]; then
electron_exec="$(ls "$app_path/Contents/MacOS/" 2>/dev/null | head -n 1)"
if [ -n "$electron_exec" ]; then
runtime="$app_path/Contents/MacOS/$electron_exec"
fi
fi
if [ -z "$runtime" ] || [ ! -x "$runtime" ]; then
printf '%s\n' "codiff: could not find the bundled runtime for $1." >&2
exit 1
fi
}

# `codiff update` checks GitHub Releases and updates this install in place.
if [ "$#" -eq 1 ] && [ "$1" = "update" ]; then
resolve_runtime update
ELECTRON_RUN_AS_NODE=1 exec "$runtime" "$script_dir/codiff.js" update
fi

# Flags the Node entry owns: it does the work and prints the result, so this
# helper hands the whole command line over to the bundled runtime.
for arg in "$@"; do
case "$arg" in
--share|--public|--completions|--completions=*)
runtime="${CODIFF_NODE_COMMAND:-}"
if [ -z "$runtime" ]; then
electron_exec="$(ls "$app_path/Contents/MacOS/" 2>/dev/null | head -n 1)"
if [ -n "$electron_exec" ]; then
runtime="$app_path/Contents/MacOS/$electron_exec"
fi
fi
if [ -z "$runtime" ] || [ ! -x "$runtime" ]; then
printf '%s\n' "codiff: could not find the bundled runtime for $arg." >&2
exit 1
fi
resolve_runtime "$arg"
ELECTRON_RUN_AS_NODE=1 exec "$runtime" "$script_dir/codiff.js" "$@"
;;
esac
Expand Down Expand Up @@ -259,6 +346,7 @@ for arg in "$@"; do
;;
--version|-v)
printf 'codiff v%s\n' "$(codiff_version)"
print_update_notice
exit 0
;;
--walkthrough-guide)
Expand Down Expand Up @@ -487,6 +575,8 @@ open_codiff() {
open -n "$app_path" --args "$@"
}

print_update_notice

if [ -n "$plan_file_path" ]; then
if [ -z "$plan_result_file_path" ]; then
plan_result_directory="$(mktemp -d "${TMPDIR:-/tmp}/codiff-plan-result.XXXXXX")"
Expand Down
34 changes: 34 additions & 0 deletions bin/codiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from './arguments.js';
import { completionShells, generateCompletionScript } from './completions.js';
import { waitForPlanResult } from './plan-result.js';
import { runUpdateCommand } from './update-command.js';
import { getUpdateNotice } from './update-notice.js';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const require = createRequire(import.meta.url);
Expand Down Expand Up @@ -105,7 +107,30 @@ const buildWalkthroughGuide = () => {
return `${guide}\n\n\`\`\`json\n${JSON.stringify(narrativeWalkthroughSchema, null, 2)}\n\`\`\`\n`;
};

const runCodiffUpdate = () => {
const appBundleMatch = process.platform === 'darwin' ? root.match(/^(.*\.app)\//) : null;
return runUpdateCommand({
currentVersion: packageJson.version,
isSourceCheckout: existsSync(resolve(root, '.git')),
log: (line) => process.stdout.write(`${line}\n`),
openApp: appBundleMatch
? () => {
spawn('open', ['-n', appBundleMatch[1], '--args', '--apply-update'], {
detached: true,
stdio: 'ignore',
}).unref();
}
: null,
});
};

const run = async () => {
const rawArguments = process.argv.slice(2);
if (rawArguments.length === 1 && rawArguments[0] === 'update') {
process.exitCode = await runCodiffUpdate();
return;
}

const parsedArguments = parseArguments(process.argv.slice(2));

if (parsedArguments.help) {
Expand All @@ -115,6 +140,10 @@ const run = async () => {

if (parsedArguments.version) {
process.stdout.write(`codiff v${packageJson.version}\n`);
const notice = getUpdateNotice({ currentVersion: packageJson.version });
if (notice) {
process.stderr.write(`${notice}\n`);
}
return;
}

Expand Down Expand Up @@ -296,6 +325,11 @@ const run = async () => {
delete childEnv.ELECTRON_RUN_AS_NODE;
delete childEnv.ELECTRON_NO_ATTACH_CONSOLE;

const updateNotice = getUpdateNotice({ currentVersion: packageJson.version });
if (updateNotice) {
process.stderr.write(`${updateNotice}\n`);
}

const { default: electron } = await import('electron');

const child = spawn(electron, [root], {
Expand Down
88 changes: 88 additions & 0 deletions bin/update-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const {
fetchLatestRelease,
isNewerVersion,
releasePageUrl,
} = require('../electron/update-check.cjs');

/**
* Decide what `codiff update` should do for this install.
*
* Homebrew-owned installs are not special-cased: the cask is marked
* auto_updates, so the app updating itself in place is the supported path and
* stays ahead of a tap that may lag behind the newest release.
*
* @param {{
* currentVersion: string;
* isSourceCheckout: boolean;
* latestVersion: string;
* }} options
* @returns {{ kind: 'up-to-date' } | { kind: 'open-app' | 'source-checkout'; version: string }}
*/
export function resolveUpdateAction({ currentVersion, isSourceCheckout, latestVersion }) {
if (!isNewerVersion(latestVersion, currentVersion)) {
return { kind: 'up-to-date' };
}

return isSourceCheckout
? { kind: 'source-checkout', version: latestVersion }
: { kind: 'open-app', version: latestVersion };
}

/**
* Check GitHub Releases and perform the platform-appropriate update.
*
* @param {{
* currentVersion: string;
* isSourceCheckout: boolean;
* log: (line: string) => void;
* openApp: (() => void) | null;
* releaseUrl?: string;
* }} options
* @returns {Promise<number>}
*/
export async function runUpdateCommand({
currentVersion,
isSourceCheckout,
log,
openApp,
releaseUrl,
}) {
let latestVersion;
try {
latestVersion = (await fetchLatestRelease(releaseUrl)).version;
} catch (error) {
log(
`codiff: could not check for updates: ${
error instanceof Error ? error.message : String(error)
}`,
);
return 1;
}

const action = resolveUpdateAction({ currentVersion, isSourceCheckout, latestVersion });

switch (action.kind) {
case 'up-to-date':
log(`codiff v${currentVersion} is up to date.`);
return 0;
case 'source-checkout':
log(
`Codiff v${action.version} is available, but this is a source checkout. Run git pull and rebuild instead.`,
);
return 0;
case 'open-app':
if (openApp) {
log(`Opening Codiff to install v${action.version}…`);
openApp();
return 0;
}

log(
`Codiff v${action.version} is available. Download it from ${releasePageUrl(action.version)} or open the app and use the update banner.`,
);
return 1;
}
}
18 changes: 18 additions & 0 deletions bin/update-notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const { getAvailableUpdate, readUpdateState } = require('../electron/update-check.cjs');

/**
* Build a one-line update notice from the state cached by the app's daily
* check. Never performs network requests so CLI startup stays instant.
*
* @param {{ configDir?: string; currentVersion: string }} options
* @returns {string | null}
*/
export function getUpdateNotice({ configDir, currentVersion }) {
const update = getAvailableUpdate(readUpdateState(configDir), currentVersion);
return update
? `A new version of Codiff is available (v${currentVersion} -> v${update.version}). Run \`codiff update\` to update.`
: null;
}
1 change: 1 addition & 0 deletions config/defaults.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"settings": {
"agentBackend": "codex",
"checkForUpdates": true,
"claudeModel": "claude-sonnet-4-6",
"codeFontFamily": "",
"codeFontSize": 13,
Expand Down
Loading
Loading