@dotdotdash/afterhours 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # afterhours
2
+
3
+ **afterhours** is an autonomous, schedulable system that keeps your Node.js / web projects up-to-date while you sleep. It audits dependencies, applies agent-driven code optimizations, verifies everything still works, opens a pull request, and (optionally) merges and deploys — all hands-free.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Dependency audit** — finds vulnerable and outdated packages, upgrades safe deps automatically, uses an LLM agent to fix any breakages
10
+ - **Code optimization** — runs an LLM agent to find and apply code quality improvements, verifies each change with your build + test suite
11
+ - **Deployment** — pushes Docker images, publishes npm packages, creates GitHub Releases, deploys to Vercel or Render
12
+ - **Platform integrations** — GitHub, GitLab, Azure Repos, AWS CodeCommit, Google Cloud Source (PR/MR creation, auto-merge, commenting)
13
+ - **Notifications** — PR comment, email, or webhook on every run
14
+ - **Scheduler adapters** — GitHub Actions, Docker + cron, or bring your own
15
+
16
+ ---
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @dotdotdash/afterhours
22
+ ```
23
+
24
+ Or use `npx @dotdotdash/afterhours` without installing globally. The CLI command is `afterhours`.
25
+
26
+ ---
27
+
28
+ ## Quick start
29
+
30
+ ```bash
31
+ # 1. Scaffold config
32
+ cd /your/project
33
+ afterhours init --scheduler github
34
+
35
+ # 2. Add secrets to .env (never commit it)
36
+ cp .env.example .env
37
+ # Fill in GITHUB_TOKEN, ANTHROPIC_API_KEY, etc.
38
+
39
+ # 3. Verify the setup
40
+ afterhours doctor
41
+
42
+ # 4. Do a dry run
43
+ afterhours run --dry-run
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Configuration
49
+
50
+ `afterhours init` creates `.afterhours/config.yml`. The full annotated reference is in [`templates/config.yml`](templates/config.yml).
51
+
52
+ ### Minimal config
53
+
54
+ ```yaml
55
+ version: 1
56
+
57
+ project:
58
+ type: auto # auto-detected
59
+ packageManager: auto
60
+
61
+ platform:
62
+ provider: github
63
+ baseBranch: main
64
+ autoMerge: false
65
+
66
+ llm:
67
+ provider: anthropic
68
+ model: claude-sonnet-4-5
69
+ apiKeyEnv: ANTHROPIC_API_KEY
70
+
71
+ tasks:
72
+ dependencyAudit:
73
+ enabled: true
74
+ codeOptimization:
75
+ enabled: true
76
+ deployment:
77
+ enabled: false
78
+ targets: []
79
+ issueTriage:
80
+ enabled: false # resolve GitHub issues automatically (branch, PR, close on success)
81
+ allowedAuthors:
82
+ - "*" # or specific usernames, e.g. ["octocat", "hubot"]
83
+
84
+ notifications:
85
+ - type: github
86
+ ```
87
+
88
+ Issue triage (`tasks.issueTriage`) requires a platform provider that supports issues (currently GitHub). When enabled, afterhours lists open issues, filters them to `allowedAuthors`, and for each match creates a branch, runs the agent to attempt a fix, and:
89
+
90
+ - On success: verifies the change, commits, opens a PR (auto-merging if `platform.autoMerge` is set), and closes the issue with a comment.
91
+ - On failure (no changes made, or verify gate fails): leaves the issue open and adds a comment explaining what happened.
92
+
93
+ ### Using a GitHub Copilot seat for the agent
94
+
95
+ Set `llm.provider: copilot` to drive the agent with a GitHub Copilot subscription instead of a direct LLM API key:
96
+
97
+ ```yaml
98
+ llm:
99
+ provider: copilot
100
+ model: gpt-4o # any Copilot-supported chat model
101
+ apiKeyEnv: COPILOT_TOKEN # GitHub token for the Copilot-licensed account
102
+ ```
103
+
104
+ The Copilot account is fully independent from `platform` (repo access) — `apiKeyEnv` here can point to a token for a different GitHub account than `GITHUB_TOKEN`. This lets an org member's Copilot seat drive automated work against a repo (e.g. a client-owned repo) where only a separate PAT has push/PR access. Note that this relies on GitHub's internal Copilot chat-completions API (the same mechanism used by third-party editor integrations); confirm it's compatible with your Copilot license terms before enabling it for unattended/agentic use.
105
+
106
+ ---
107
+
108
+ ## Environment variables
109
+
110
+ All secrets are read from environment variables — never from config files. The canonical list is in [`.env.example`](.env.example).
111
+
112
+ | Variable | Used for |
113
+ |---|---|
114
+ | `GITHUB_TOKEN` | GitHub API + auto-merge + notifications |
115
+ | `ANTHROPIC_API_KEY` | LLM agent (Anthropic) |
116
+ | `OPENAI_API_KEY` | LLM agent (OpenAI) |
117
+ | `GEMINI_API_KEY` | LLM agent (Google Gemini) |
118
+ | `COPILOT_TOKEN` | LLM agent (GitHub Copilot); can belong to a different GitHub account than `GITHUB_TOKEN` |
119
+ | `NPM_TOKEN` | npm publish |
120
+ | `REGISTRY_USER` / `REGISTRY_PASS` | Docker registry login |
121
+ | `VERCEL_TOKEN` / `VERCEL_PROJECT_ID` / `VERCEL_ORG_ID` | Vercel deploy |
122
+ | `RENDER_API_KEY` / `RENDER_SERVICE_ID` | Render deploy |
123
+
124
+ ---
125
+
126
+ ## Security
127
+
128
+ - Secrets are **never** written to disk or logged. All log output is redacted against known env values.
129
+ - Agent file-system tools enforce path-traversal checks (`assertSafePath`).
130
+ - Agent shell-tool has a denylist for dangerous patterns (`rm -rf /`, `dd if=`, etc.).
131
+ - LLM prompts include "minimal changes" and no-secrets guardrails.
132
+ - Dependency upgrades are split into **safe** (patch/minor, non-deprecated) and **risky** (major, deprecated) and the risky set is skipped unless `allowMajor: true`.
133
+ - Every change must pass the full verify gate (build + tests) before it is committed.
134
+ - The PR is opened without auto-merge by default.
135
+
136
+ ### OWASP Top 10 mitigations
137
+
138
+ | Risk | Mitigation |
139
+ |---|---|
140
+ | Injection | Command denylist; no shell=true; args passed as arrays |
141
+ | Broken access control | Path traversal guard on all agent FS tools |
142
+ | Sensitive data exposure | Secrets redacted from all log/output; env-only secret resolution |
143
+ | Insecure deps | Core task: `npm audit`; automated upgrades with gate |
144
+
145
+ ---
146
+
147
+ ## Scheduler adapters
148
+
149
+ ### GitHub Actions (recommended)
150
+
151
+ ```bash
152
+ afterhours init --scheduler github
153
+ ```
154
+
155
+ Creates `.github/workflows/afterhours.yml`. Add `ANTHROPIC_API_KEY` (or equivalent) as a repository secret.
156
+
157
+ ### Docker + cron
158
+
159
+ ```bash
160
+ afterhours init --scheduler docker-cron
161
+ ```
162
+
163
+ Creates `.afterhours/docker-cron/` — a `Dockerfile` + `crontab`. Build and run:
164
+
165
+ ```bash
166
+ cd .afterhours/docker-cron
167
+ docker build -t afterhours-cron .
168
+ docker run -d --env-file ../../.env -v $(pwd)/../../:/workspace afterhours-cron
169
+ ```
170
+
171
+ ---
172
+
173
+ ## CLI reference
174
+
175
+ ```
176
+ afterhours init [--scheduler github|docker-cron|none] [--force]
177
+ afterhours run [--dry-run] [--task <id>] [--no-deploy]
178
+ afterhours doctor [--platform]
179
+ afterhours report [runId]
180
+ afterhours audit # (planned)
181
+ afterhours optimize # (planned)
182
+ afterhours deploy # (planned)
183
+ ```
184
+
185
+ ---
186
+
187
+ ## License
188
+
189
+ MIT
@@ -0,0 +1,56 @@
1
+ // src/report/index.ts
2
+ import { mkdirSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ function renderMarkdown(report) {
5
+ const lines = [
6
+ `# afterhours Run Report (${report.id})`,
7
+ "",
8
+ `**Started:** ${report.startedAt} `,
9
+ report.completedAt ? `**Completed:** ${report.completedAt}` : "",
10
+ "",
11
+ `**Project:** ${report.projectType} / ${report.projectPackageManager} `,
12
+ `**Branch:** ${report.branch} `,
13
+ `**Base:** ${report.baseBranch}`,
14
+ "",
15
+ "## Tasks",
16
+ ""
17
+ ];
18
+ for (const task of report.tasks) {
19
+ const icon = task.status === "changed" ? "\u2705" : task.status === "noop" ? "\u23ED" : task.status === "failed" ? "\u274C" : "\u26A0\uFE0F";
20
+ lines.push(`### ${icon} ${task.title} (${task.status})`);
21
+ if (task.summary) lines.push(`> ${task.summary}`);
22
+ if (task.commit) lines.push(`**Commit:** ${task.commit}`);
23
+ if (task.outstandingReason) lines.push(`**Outstanding:** ${task.outstandingReason}`);
24
+ lines.push("");
25
+ }
26
+ if (report.outstandingWork.length > 0) {
27
+ lines.push("## Outstanding Work");
28
+ for (const item of report.outstandingWork) {
29
+ lines.push(`- ${item}`);
30
+ }
31
+ lines.push("");
32
+ }
33
+ if (report.pr) {
34
+ lines.push(`## Pull Request`);
35
+ lines.push(`[${report.pr.id}](${report.pr.url}) \u2014 ${report.pr.merged ? "merged" : "open"}`);
36
+ lines.push("");
37
+ }
38
+ return lines.filter((l) => l !== void 0).join("\n");
39
+ }
40
+ function renderJson(report) {
41
+ return JSON.stringify(report, null, 2);
42
+ }
43
+ function writeReports(report, reportsDir) {
44
+ mkdirSync(reportsDir, { recursive: true });
45
+ const jsonPath = join(reportsDir, `${report.id}.json`);
46
+ const mdPath = join(reportsDir, `${report.id}.md`);
47
+ writeFileSync(jsonPath, renderJson(report), "utf8");
48
+ writeFileSync(mdPath, renderMarkdown(report), "utf8");
49
+ return { jsonPath, mdPath };
50
+ }
51
+
52
+ export {
53
+ renderMarkdown,
54
+ renderJson,
55
+ writeReports
56
+ };
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node