devgrowth 1.0.0 → 1.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 +135 -90
- package/package.json +52 -48
- package/src/commands/config.js +54 -50
- package/src/commands/dashboard.js +13 -0
- package/src/commands/history.js +75 -73
- package/src/commands/log.js +40 -36
- package/src/commands/login.js +127 -0
- package/src/commands/logout.js +26 -0
- package/src/commands/milestone.js +54 -51
- package/src/commands/review.js +62 -57
- package/src/commands/start.js +97 -91
- package/src/commands/status.js +55 -53
- package/src/commands/sync.js +29 -0
- package/src/core/apiClient.js +46 -0
- package/src/core/auth.js +34 -0
- package/src/core/banner.js +293 -293
- package/src/core/configManager.js +57 -70
- package/src/core/eventLog.js +112 -0
- package/src/core/logger.js +62 -62
- package/src/core/milestones.js +62 -72
- package/src/core/prompt.js +58 -0
- package/src/core/rebuild.js +37 -0
- package/src/core/schedule.js +1 -70
- package/src/core/stats.js +43 -130
- package/src/core/storage.js +52 -33
- package/src/core/sync.js +101 -0
- package/src/index.js +221 -152
package/README.md
CHANGED
|
@@ -1,90 +1,135 @@
|
|
|
1
|
-
# DevGrowth CLI
|
|
2
|
-
|
|
3
|
-
Zero-friction developer growth CLI for Saw Eh Doh Wah.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
devgrowth
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
devgrowth
|
|
36
|
-
devgrowth
|
|
37
|
-
devgrowth
|
|
38
|
-
devgrowth
|
|
39
|
-
devgrowth
|
|
40
|
-
devgrowth
|
|
41
|
-
devgrowth
|
|
42
|
-
devgrowth
|
|
43
|
-
devgrowth
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
1
|
+
# DevGrowth CLI
|
|
2
|
+
|
|
3
|
+
Zero-friction developer growth CLI for Saw Eh Doh Wah.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the published package globally so the `devgrowth` command is
|
|
8
|
+
available anywhere:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g devgrowth
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Developing locally
|
|
15
|
+
|
|
16
|
+
If you're working from a clone of this repo instead of the published
|
|
17
|
+
package, use `npm link` to symlink your local checkout into the global npm
|
|
18
|
+
bin:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Windows Notification Setup
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
# Requires devgrowth to be globally available (via npm link or installed globally)
|
|
29
|
+
.\scripts\install-windows.ps1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
devgrowth # Splash banner + command list
|
|
36
|
+
devgrowth banner # Banner with current stats
|
|
37
|
+
devgrowth banner --compact # Single-line banner
|
|
38
|
+
devgrowth init # Initialize configuration
|
|
39
|
+
devgrowth start # Start tonight's session
|
|
40
|
+
devgrowth start --light # Low-energy mode (skip timer)
|
|
41
|
+
devgrowth start --no-banner # Skip the startup banner
|
|
42
|
+
devgrowth log "..." # Write session log
|
|
43
|
+
devgrowth log "..." --light # Mark as light session
|
|
44
|
+
devgrowth status # Show weekly progress
|
|
45
|
+
devgrowth review # Weekly review (auto-saves review section)
|
|
46
|
+
devgrowth milestone list # Show achievement grid
|
|
47
|
+
devgrowth milestone check laravel m1 formRequests # Check off milestone
|
|
48
|
+
devgrowth milestone progress laravel # Show milestone progress
|
|
49
|
+
devgrowth history # Browse current week logs
|
|
50
|
+
devgrowth history --week 32 # View a specific week
|
|
51
|
+
devgrowth history --week 1 --year 2027 # View week across year boundaries
|
|
52
|
+
devgrowth history --skill laravel # Filter by skill
|
|
53
|
+
devgrowth history --search "Form Request" # Full-text search
|
|
54
|
+
devgrowth config show # View configuration
|
|
55
|
+
devgrowth config set <key> <value> # Update configuration
|
|
56
|
+
devgrowth config edit # Open config in default editor
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Sync across devices
|
|
60
|
+
|
|
61
|
+
devgrowth works fully offline. To share your sessions, streaks, milestones and config across several computers (and see them in the web dashboard), connect each device to a [devgrowth-cloud](https://github.com/EhDohWah/devgrowth-cloud) server:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
devgrowth register --api https://your-server # first device: creates the account
|
|
65
|
+
devgrowth login --api https://your-server # every other device
|
|
66
|
+
devgrowth sync # sync now (commands also sync on their own)
|
|
67
|
+
devgrowth sync status # pending changes, last sync
|
|
68
|
+
devgrowth dashboard # open the web dashboard
|
|
69
|
+
devgrowth logout # disconnect this device; local data stays
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also set `DEVGROWTH_API_URL` instead of passing `--api` each time. The default is `http://localhost:3000`.
|
|
73
|
+
|
|
74
|
+
**What happens on first login**
|
|
75
|
+
- **The account is empty** (usually your first device): this device's current stats, milestones and config become the account's starting point.
|
|
76
|
+
- **The account already has data**: this device's own data is copied to `~/.devgrowth/backups/<time>/`. The device then takes on the account's history, including its Markdown logs.
|
|
77
|
+
|
|
78
|
+
**Offline**
|
|
79
|
+
- Commands never wait on the network for more than a few seconds, and they never fail because you're offline.
|
|
80
|
+
- Changes queue up in `~/.devgrowth/events.jsonl` and upload the next time a command can reach the server.
|
|
81
|
+
- In scripts, pipe the password in: `printf '%s\n' "$PW" | devgrowth login --email me@example.com`.
|
|
82
|
+
|
|
83
|
+
## Banner
|
|
84
|
+
|
|
85
|
+
Running `devgrowth` with no arguments shows a splash screen: the `devGrowth`
|
|
86
|
+
wordmark in block letters with the turtle riding beside it, then tonight's
|
|
87
|
+
skill, weekly progress, and your streak. `devgrowth start` shows a one-line
|
|
88
|
+
version of it.
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
█ ▄▀▀▀▄ ▄ █ __ _
|
|
92
|
+
▄▄▄█ ▄▄▄ ▄ ▄ █ ▄ ▄▄ ▄▄▄ ▄ ▄ ▄█▄ █ ▄▄ / ♯ # \ /・_)
|
|
93
|
+
█ █ █▄▄▄█ █ █ █ ▀▀█ █▀ █ █ █ ▄ █ █ █▀ █ (ソ♯ # ♯ #Y /
|
|
94
|
+
▀▄▄▄█ ▀▄▄▄ ▀▄▀ ▀▄▄▄█ █ ▀▄▄▄▀ █▀ ▀█ █▄▄ █ █ (_)――-(_)′
|
|
95
|
+
|
|
96
|
+
Saw Eh Doh Wah@DESKTOP-BOD610M · v1.0.0
|
|
97
|
+
──────────────────────────────────────────────────────────────────────────
|
|
98
|
+
Tonight laravel — Deepen
|
|
99
|
+
Resource Laravel Daily
|
|
100
|
+
Session 21:00 · 20 min
|
|
101
|
+
Week 36 ░░░░░ 0/5 sessions
|
|
102
|
+
Streak 0 weeks (best: 0)
|
|
103
|
+
Totals 1 session · 0 min
|
|
104
|
+
Data ~/.devgrowth
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The banner is suppressed automatically when output is not a TTY, so piping and
|
|
108
|
+
scheduled runs stay clean:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
devgrowth config show | jq . # no banner
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Control | Effect |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `DEVGROWTH_NO_BANNER=1` | Never show the banner |
|
|
117
|
+
| `DEVGROWTH_BANNER=1` | Show it even when piped |
|
|
118
|
+
| `--no-banner` | Skip it for one `devgrowth start` |
|
|
119
|
+
| `NO_COLOR=1` | Banner without color (handled by chalk) |
|
|
120
|
+
|
|
121
|
+
Terminals narrower than the 74-column header automatically fall back to the
|
|
122
|
+
one-line banner, so the layout never wraps.
|
|
123
|
+
|
|
124
|
+
## Troubleshooting
|
|
125
|
+
|
|
126
|
+
- If `devgrowth` command is not found, you likely installed without `-g`
|
|
127
|
+
(`npm install devgrowth` puts it in a local `node_modules` folder, not on
|
|
128
|
+
your PATH). Run `npm install -g devgrowth` instead. If you're developing
|
|
129
|
+
from a clone, run `npm link` from the project directory.
|
|
130
|
+
- Still not found after a global install? The npm global bin folder may not
|
|
131
|
+
be on your PATH. Run `npm config get prefix` to find it (on Windows,
|
|
132
|
+
typically `%AppData%\npm`), confirm that folder is on PATH, then open a
|
|
133
|
+
new terminal.
|
|
134
|
+
- Windows install script requires the CLI to be in PATH. Ensure it has been
|
|
135
|
+
installed globally (or linked) first.
|
package/package.json
CHANGED
|
@@ -1,48 +1,52 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "devgrowth",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Zero-friction developer growth CLI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "devgrowth",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Zero-friction developer growth CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
9
|
+
"keywords": [
|
|
10
|
+
"cli",
|
|
11
|
+
"productivity",
|
|
12
|
+
"habit-tracker",
|
|
13
|
+
"developer-growth",
|
|
14
|
+
"learning"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/EhDohWah/devgrowth#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/EhDohWah/devgrowth/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/EhDohWah/devgrowth.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "EhDohWah <ehdohwah007@gmail.com>",
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"src",
|
|
29
|
+
"scripts"
|
|
30
|
+
],
|
|
31
|
+
"bin": {
|
|
32
|
+
"devgrowth": "bin/devgrowth.js"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
|
|
36
|
+
"test:win": "set NODE_OPTIONS=--experimental-vm-modules && jest --coverage"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"chalk": "^5.3.0",
|
|
40
|
+
"commander": "^11.1.0",
|
|
41
|
+
"devgrowth-core": "^1.0.0",
|
|
42
|
+
"fs-extra": "^11.2.0",
|
|
43
|
+
"node-notifier": "^10.0.1",
|
|
44
|
+
"open": "^9.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"jest": "^29.7.0"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/commands/config.js
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
import * as configManager from '../core/configManager.js';
|
|
2
|
-
import * as storage from '../core/storage.js';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (raw === '
|
|
10
|
-
if (
|
|
11
|
-
if (/^-?\d
|
|
12
|
-
return raw;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.log(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
import * as configManager from '../core/configManager.js';
|
|
2
|
+
import * as storage from '../core/storage.js';
|
|
3
|
+
import * as sync from '../core/sync.js';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import open from 'open';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
function parseValue(raw) {
|
|
9
|
+
if (raw === 'true') return true;
|
|
10
|
+
if (raw === 'false') return false;
|
|
11
|
+
if (/^-?\d+$/.test(raw)) return parseInt(raw, 10);
|
|
12
|
+
if (/^-?\d*\.\d+$/.test(raw)) return parseFloat(raw);
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function configCommand(subcommand, args = []) {
|
|
17
|
+
switch (subcommand) {
|
|
18
|
+
case 'show': {
|
|
19
|
+
const config = await configManager.getConfig();
|
|
20
|
+
console.log(JSON.stringify(config, null, 2));
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
case 'set': {
|
|
24
|
+
const [keyPath, value] = args;
|
|
25
|
+
if (!keyPath || value === undefined) {
|
|
26
|
+
console.error(chalk.red('Usage: devgrowth config set <key.path> <value>'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const parsed = parseValue(value);
|
|
30
|
+
await configManager.setConfigValue(keyPath, parsed);
|
|
31
|
+
console.log(chalk.green(`Updated ${keyPath} = ${JSON.stringify(parsed)}`));
|
|
32
|
+
// Sync notices config.json changed and records it for your other devices.
|
|
33
|
+
await sync.trySync();
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'edit': {
|
|
37
|
+
const configPath = path.join(storage.getDevGrowthDir(), 'config.json');
|
|
38
|
+
try {
|
|
39
|
+
await open(configPath);
|
|
40
|
+
console.log('Opening config in default editor...');
|
|
41
|
+
console.log(chalk.gray('Your edits sync to other devices the next time devgrowth syncs.'));
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(chalk.red(`Could not open editor: ${err.message}`));
|
|
44
|
+
console.log(chalk.yellow(`Fallback: Open this file manually:\n ${configPath}`));
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
default: {
|
|
49
|
+
console.error(chalk.red(`Unknown subcommand: ${subcommand}`));
|
|
50
|
+
console.log('Usage: devgrowth config <show|set|edit>');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import * as auth from '../core/auth.js';
|
|
3
|
+
import * as browser from '../core/browser.js';
|
|
4
|
+
|
|
5
|
+
export async function dashboardCommand(options = {}) {
|
|
6
|
+
const url = `${await auth.resolveApiUrl(options.api)}/`;
|
|
7
|
+
console.log(chalk.gray(`Opening ${url}`));
|
|
8
|
+
try {
|
|
9
|
+
await browser.openUrl(url);
|
|
10
|
+
} catch {
|
|
11
|
+
console.log(chalk.yellow(`Could not open the browser. Visit: ${url}`));
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/commands/history.js
CHANGED
|
@@ -1,73 +1,75 @@
|
|
|
1
|
-
import * as logger from '../core/logger.js';
|
|
2
|
-
import * as schedule from '../core/schedule.js';
|
|
3
|
-
import * as storage from '../core/storage.js';
|
|
4
|
-
import * as
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
year
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
1
|
+
import * as logger from '../core/logger.js';
|
|
2
|
+
import * as schedule from '../core/schedule.js';
|
|
3
|
+
import * as storage from '../core/storage.js';
|
|
4
|
+
import * as sync from '../core/sync.js';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
function getDateOfISOWeek(w, y) {
|
|
9
|
+
const simple = new Date(Date.UTC(y, 0, 1 + (w - 1) * 7));
|
|
10
|
+
const dow = simple.getUTCDay();
|
|
11
|
+
const ISOweekStart = new Date(simple);
|
|
12
|
+
if (dow <= 4) {
|
|
13
|
+
ISOweekStart.setUTCDate(simple.getUTCDate() - simple.getUTCDay() + 1);
|
|
14
|
+
} else {
|
|
15
|
+
ISOweekStart.setUTCDate(simple.getUTCDate() + 8 - simple.getUTCDay());
|
|
16
|
+
}
|
|
17
|
+
return ISOweekStart;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function historyCommand(options) {
|
|
21
|
+
await sync.trySync();
|
|
22
|
+
const now = new Date();
|
|
23
|
+
let year;
|
|
24
|
+
let month;
|
|
25
|
+
let weekFile;
|
|
26
|
+
|
|
27
|
+
if (options.week) {
|
|
28
|
+
const weekNum = parseInt(options.week, 10);
|
|
29
|
+
const requestedYear = options.year ? parseInt(options.year, 10) : now.getFullYear();
|
|
30
|
+
const mon = getDateOfISOWeek(weekNum, requestedYear);
|
|
31
|
+
// The week may start in the previous year (e.g. ISO week 1 of 2026 begins
|
|
32
|
+
// Dec 29, 2025) — key the file by the Monday's actual year/month.
|
|
33
|
+
year = mon.getUTCFullYear();
|
|
34
|
+
month = String(mon.getUTCMonth() + 1).padStart(2, '0');
|
|
35
|
+
weekFile = `week${weekNum}.md`;
|
|
36
|
+
} else {
|
|
37
|
+
// Current week: key by this week's Monday so month boundaries don't split logs
|
|
38
|
+
const weekStart = schedule.getWeekStart(now);
|
|
39
|
+
year = weekStart.getFullYear();
|
|
40
|
+
month = String(weekStart.getMonth() + 1).padStart(2, '0');
|
|
41
|
+
weekFile = schedule.getCurrentWeekFileName(now);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const logs = await logger.readWeekLogs(year, month, weekFile);
|
|
45
|
+
|
|
46
|
+
console.log(chalk.bold(`\nHistory \u2014 ${weekFile.replace('.md', '')}`));
|
|
47
|
+
console.log(chalk.gray('\u2500'.repeat(50)));
|
|
48
|
+
|
|
49
|
+
let entries = logger.parseLogEntries(logs);
|
|
50
|
+
|
|
51
|
+
if (options.skill) {
|
|
52
|
+
const skillLower = options.skill.toLowerCase();
|
|
53
|
+
entries = entries.filter(entry => {
|
|
54
|
+
const block = logs.split(/\n(?=## )/).find(b => b.includes(entry.date));
|
|
55
|
+
return block && block.toLowerCase().includes(`**skill:** ${skillLower}`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (options.search) {
|
|
60
|
+
const searchLower = options.search.toLowerCase();
|
|
61
|
+
entries = entries.filter(entry => {
|
|
62
|
+
const block = logs.split(/\n(?=## )/).find(b => b.includes(entry.date));
|
|
63
|
+
return block && block.toLowerCase().includes(searchLower);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (entries.length === 0) {
|
|
68
|
+
console.log(chalk.yellow('No logs found.'));
|
|
69
|
+
} else {
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
console.log(`${chalk.cyan(entry.date)} \u2014 ${entry.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
}
|