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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,31 +72,31 @@ Once the npm package is published, run without installing:
npx code-life-balance --username octocat --timezone Europe/Helsinki
```

For repository development, or before the npm package is published, use `pnpm cli --`.
Until the npm registry package is published, the web configurator uses the dependency-free CLI tarball attached to the stable GitHub release so its generated local command can run without cloning this repository. Repository contributors can still use `pnpm cli --`.

Authenticate with GitHub CLI:

```bash
gh auth login
```

Then run:
Then run the current stable CLI release directly:

```bash
pnpm cli -- --username octocat --timezone Europe/Helsinki
npx --yes https://github.com/alihd-tech/CodeLifeBalance/releases/download/v1.2.0/code-life-balance-1.2.0.tgz --username octocat --timezone Europe/Helsinki
```

Private mode stays on the local machine:

```bash
pnpm cli -- --include-private --timezone Europe/Helsinki
npx --yes https://github.com/alihd-tech/CodeLifeBalance/releases/download/v1.2.0/code-life-balance-1.2.0.tgz --include-private --timezone Europe/Helsinki
```

The CLI prefers `GITHUB_TOKEN`, then `gh auth token`. Public analysis can run without credentials when a username is supplied.

### Web configurator

Open `/configure` in the web app to choose timezone, work hours, theme, card style, output formats, schedule, and private/public mode. The page only generates configuration and does not request a GitHub token.
Open `/configure` in the web app to choose timezone, work hours, theme, card style, output formats, and private/public mode. The generated GitHub Actions workflow is manual (`workflow_dispatch`) and the page does not request a GitHub token.

## Architecture

Expand Down
63 changes: 15 additions & 48 deletions components/privacy-configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
type ReactNode,
} from "react"
import {
CalendarClock,
Check,
Clock3,
Code2,
Expand Down Expand Up @@ -95,10 +94,6 @@ export function PrivacyConfigurator() {

const currentPreview = previewMode === "workflow" ? workflow : cli
const privateMode = config.includePrivate
const scheduleLabel =
config.schedule === "manual"
? "Manual"
: config.schedule.charAt(0).toUpperCase() + config.schedule.slice(1)

return (
<div className="grid gap-4 lg:grid-cols-[minmax(0,0.9fr)_minmax(420px,1.1fr)] xl:grid-cols-[minmax(0,0.82fr)_minmax(520px,1.18fr)]">
Expand Down Expand Up @@ -281,40 +276,6 @@ export function PrivacyConfigurator() {
</div>
</ConfigSection>

<ConfigSection
icon={CalendarClock}
title="Automation"
description="Control when GitHub refreshes the generated report."
>
<ChoiceGroup
label="Schedule"
value={config.schedule}
options={[
{ value: "manual", label: "Manual" },
{ value: "daily", label: "Daily" },
{ value: "weekly", label: "Weekly" },
{ value: "monthly", label: "Monthly" },
{ value: "custom", label: "Custom" },
]}
onChange={(value) =>
update("schedule", value as PrivacyConfig["schedule"])
}
wrap
/>

{config.schedule === "custom" && (
<div className="mt-3">
<Field label="Custom cron" hint="GitHub Actions schedules run in UTC.">
<input
value={config.customCron}
onChange={(event) => update("customCron", event.target.value)}
placeholder="17 3 * * 1"
className={fieldClass}
/>
</Field>
</div>
)}
</ConfigSection>
</div>
</section>

Expand Down Expand Up @@ -358,17 +319,12 @@ export function PrivacyConfigurator() {
</div>
</div>

<div className="grid grid-cols-2 gap-px border-b border-border/70 bg-border sm:grid-cols-4">
<div className="grid grid-cols-3 gap-px border-b border-border/70 bg-border">
<SummaryItem
icon={Clock3}
label="Hours"
value={`${String(config.workdayStartHour).padStart(2, "0")}:00–${config.workdayEndHour === 24 ? "24:00" : `${String(config.workdayEndHour).padStart(2, "0")}:00`}`}
/>
<SummaryItem
icon={CalendarClock}
label="Schedule"
value={scheduleLabel}
/>
<SummaryItem
icon={FileCode2}
label="Formats"
Expand All @@ -386,8 +342,19 @@ export function PrivacyConfigurator() {
<div className="flex items-start gap-2 text-xs leading-relaxed text-muted-foreground">
<LockKeyhole className="mt-0.5 h-3.5 w-3.5 shrink-0 text-primary" />
<span>
Private mode expects a fine-grained GitHub token stored as{" "}
<code className="font-semibold text-primary">CODE_LIFE_TOKEN</code>.
{previewMode === "workflow" ? (
<>
Private mode expects a fine-grained GitHub token stored as{" "}
<code className="font-semibold text-primary">CODE_LIFE_TOKEN</code>.
</>
) : (
<>
Private CLI mode uses{" "}
<code className="font-semibold text-primary">GITHUB_TOKEN</code> or your
existing <code className="font-semibold text-primary">gh auth login</code>{" "}
session.
</>
)}
</span>
</div>
</div>
Expand All @@ -413,7 +380,7 @@ export function PrivacyConfigurator() {
<TrustNote
icon={Code2}
title="Portable setup"
text="Generated config is plain YAML and CLI arguments."
text="Generated config is plain YAML and a directly runnable CLI command."
/>
</div>
</aside>
Expand Down
38 changes: 7 additions & 31 deletions lib/privacy-config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export type ReportFormat = "svg" | "json" | "markdown"
export type ReportTheme = "dark" | "light"
export type CardStyle = "detailed" | "compact"
export type SchedulePreset = "manual" | "daily" | "weekly" | "monthly" | "custom"

export interface PrivacyConfig {
username: string
Expand All @@ -12,8 +11,6 @@ export interface PrivacyConfig {
cardStyle: CardStyle
formats: ReportFormat[]
outputDir: string
schedule: SchedulePreset
customCron: string
commit: boolean
includePrivate: boolean
}
Expand All @@ -27,52 +24,29 @@ export const defaultPrivacyConfig: PrivacyConfig = {
cardStyle: "detailed",
formats: ["svg", "json", "markdown"],
outputDir: "code-life-balance",
schedule: "weekly",
customCron: "17 3 * * 1",
commit: true,
includePrivate: false,
}

const CLI_RELEASE_PACKAGE =
"https://github.com/alihd-tech/CodeLifeBalance/releases/download/v1.2.0/code-life-balance-1.2.0.tgz"

function quoted(value: string) {
return JSON.stringify(value)
}

function cronFor(config: PrivacyConfig) {
switch (config.schedule) {
case "daily":
return "17 3 * * *"
case "weekly":
return "17 3 * * 1"
case "monthly":
return "17 3 1 * *"
case "custom":
return config.customCron.trim() || "17 3 * * 1"
default:
return null
}
}

function workflowExpression(value: string) {
return "$" + "{{ " + value + " }}"
}

export function generateWorkflow(config: PrivacyConfig) {
const cron = cronFor(config)
const permissions = config.commit ? "write" : "read"
const token = config.includePrivate
? workflowExpression("secrets.CODE_LIFE_TOKEN")
: workflowExpression("secrets.GITHUB_TOKEN")
const username = config.username.trim() || workflowExpression("github.repository_owner")
const formats = config.formats.length ? config.formats.join(",") : "svg"

const triggers = cron
? `on:
workflow_dispatch:
schedule:
- cron: ${quoted(cron)}`
: `on:
workflow_dispatch:`

const upload = config.commit
? ""
: `
Expand All @@ -85,7 +59,8 @@ export function generateWorkflow(config: PrivacyConfig) {

return `name: Code Life Balance

${triggers}
on:
workflow_dispatch:

permissions:
contents: ${permissions}
Expand Down Expand Up @@ -117,7 +92,8 @@ jobs:

export function generateCliCommand(config: PrivacyConfig) {
const args = [
"pnpm cli --",
"npx --yes",
CLI_RELEASE_PACKAGE,
config.username.trim() ? `--username ${shellQuote(config.username.trim())}` : "",
`--timezone ${shellQuote(config.timeZone)}`,
`--workday-start ${config.workdayStartHour}`,
Expand Down
Loading