claude-yaad 1.0.1 → 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 CHANGED
@@ -1,116 +1,129 @@
1
- # claude-yaad
2
-
3
- **Session memory and usage savers for Claude Code. One command, zero tokens.**
4
-
5
- *Yaad* means "memory" in Urdu. Claude Code forgets everything on `/clear`, and long sessions burn your usage limit because every message re-sends the whole conversation. `claude-yaad` fixes both:
6
-
7
- - **Each project remembers where it was.** A fresh session (after `/clear`, a compact, a crash or a new day) starts by reading that project's own small state file.
8
- - **Long sessions get flagged.** You get a one-line warning when the context passes ~200k tokens, so you can `/save` and `/clear` before every message gets expensive.
9
- - **Sessions never grow huge.** Auto-compact runs at a 150k-token window, so a long chat can't creep to 200k+ tokens re-read on every message (this was the single biggest usage drain we measured: one chat re-read 216 million tokens in a night). Your state file keeps the continuity after each compact.
10
- - **Subagents default to Sonnet** instead of the main model. Pass a model explicitly when one piece really needs the big one.
11
- - **Three skills:** `/save`, `/yaad` and `/kharcha` (find what is eating your usage).
12
-
13
- The hooks are plain Node scripts. They read only the tail of the transcript, finish in under a second, and print nothing unless there is something to say. They cost **zero model tokens** except the short state they print at session start.
14
-
15
- ---
16
-
17
- ## Install
18
-
19
- You need **Node.js 18+** and **Claude Code**.
20
-
21
- ```bash
22
- npx claude-yaad
23
- ```
24
-
25
- Not on npm yet? Install straight from GitHub; it works the same:
26
-
27
- ```bash
28
- npx github:rahmangamer12/claude-yaad
29
- ```
30
-
31
- Then **restart Claude Code**. That's it.
32
-
33
- What the installer does:
34
-
35
- | | Where |
36
- |---|---|
37
- | Hook script `ck_state.js` | `~/.claude/scripts/` |
38
- | Skills `save`, `yaad`, `kharcha` | `~/.claude/skills/` (an older skill with the same name is moved to `~/.claude/skill-backups/`) |
39
- | Hooks: SessionStart, PreCompact, SessionEnd, UserPromptSubmit | `~/.claude/settings.json` (backed up first as `settings.json.bak-claude-kit-<time>`) |
40
- | `CLAUDE_CODE_AUTO_COMPACT_WINDOW=150k` | `env` in `~/.claude/settings.json`; skipped if you already set a window, or with `--keep-compact-window` |
41
- | `CLAUDE_CODE_SUBAGENT_MODEL=sonnet` | `env` in `~/.claude/settings.json`; skipped if you already set one, or with `--keep-subagent-model` |
42
-
43
- Your other hooks, settings and skills are left alone. Running it again updates the kit in place.
44
-
45
- ### Options
46
-
47
- ```bash
48
- npx claude-yaad --keep-subagent-model # do not touch the subagent model
49
- npx claude-yaad --keep-compact-window # do not touch the auto-compact window
50
- npx claude-yaad --zips # put the skill zips for claude.ai in the current folder
51
- npx claude-yaad --uninstall # remove the hooks and the script (keeps skills and state)
52
- npx claude-yaad --uninstall --purge # also remove the three skills
53
- ```
54
-
55
- ---
56
-
57
- ## Daily use
58
-
59
- | You want to… | Do this |
60
- |---|---|
61
- | Stop for now, or `/clear` | `/save`, then `/clear`. Nothing is lost. |
62
- | Pick up where you left off | Just start. The state shows at the top of the session. Ask `/yaad` for a short summary. |
63
- | The hook says the context is heavy | Finish the current step, `/save`, `/clear`. |
64
- | Usage limit going too fast | `/kharcha`: it checks the usual culprits and fixes the safe ones. |
65
-
66
- ### How the memory works
67
-
68
- One file per project: `~/.claude/state/projects/<project-slug>.md`. The slug is the working directory with every character that is not a letter or digit turned into `-`, e.g. `F:\Work\Shop Site` → `F--Work-Shop-Site.md`.
69
-
70
- The file has two parts:
71
-
72
- 1. **Your part, written by `/save`:** what we are doing, key paths, done, next, the commands to run, and gotchas. 60 lines max.
73
- 2. **The auto part, written by the hook** on every compact and session end: your last few requests, the files edited, and the last reply. It needs no model call, so it is free.
74
-
75
- Because every project has its own file, two Claude windows on two projects never overwrite each other's memory.
76
-
77
- ### Settings you can change
78
-
79
- Set these as environment variables, or in `env` in `settings.json`:
80
-
81
- | Variable | Default | Meaning |
82
- |---|---|---|
83
- | `CK_WARN_TOKENS` | `200000` | Warn when the context passes this many tokens |
84
- | `CK_SHOW_CHARS` | `3500` | How much of the state file to show at session start |
85
-
86
- ---
87
-
88
- ## claude.ai (web) and the Claude desktop app
89
-
90
- Chat on claude.ai and in the desktop app has no hooks and no lasting disk, so only the skills apply there:
91
-
92
- 1. `npx claude-yaad --zips` puts `save.zip`, `yaad.zip` and `kharcha.zip` in your folder. They are also in the [`claude-ai/`](claude-ai) folder of this repo.
93
- 2. claude.ai → **Settings → Capabilities → Skills → Upload skill**, one zip at a time.
94
- 3. In chat, `/save` prints a state block. Paste it into your **Project instructions** (or the first message of the next chat), and `/yaad` reads it from there.
95
-
96
- The **Claude Code tab** of the desktop app and the IDE extensions use the same `~/.claude` folder as the terminal, so the full kit works there after the normal install.
97
-
98
- ---
99
-
100
- ## Why this saves usage
101
-
102
- - The biggest cost is **context length × number of messages**. A 400k-token session costs about 10× a 40k one *per message*. Cheap memory makes `/clear` painless, so you clear more.
103
- - **Subagents are full extra sessions.** Running them on Sonnet by default cuts their cost a lot. Your main session keeps the model you chose.
104
- - Older autosave hooks parsed the whole transcript, which can be hundreds of MB, and wrote one global file that every window overwrote. This reads the last 3 MB and keeps one file per project.
105
-
106
- ## Uninstall
107
-
108
- ```bash
109
- npx claude-yaad --uninstall --purge
110
- ```
111
-
112
- Your state files in `~/.claude/state/projects/` are kept. Delete them by hand if you want.
113
-
114
- ## License
115
-
116
- MIT
1
+ # claude-yaad
2
+
3
+ **Session memory and usage savers for Claude Code. One command, zero tokens.**
4
+
5
+ *Yaad* means "memory" in Urdu. Claude Code forgets everything on `/clear`, and long sessions burn your usage limit because every message re-sends the whole conversation. `claude-yaad` fixes both:
6
+
7
+ - **Each project remembers where it was.** A fresh session (after `/clear`, a compact, a crash or a new day) starts by reading that project's own small state file.
8
+ - **Long sessions get flagged.** You get a one-line warning when the context passes ~200k tokens, so you can `/save` and `/clear` before every message gets expensive.
9
+ - **Sessions never grow huge.** Auto-compact runs at a 150k-token window, so a long chat can't creep to 200k+ tokens re-read on every message (this was the single biggest usage drain we measured: one chat re-read 216 million tokens in a night). Your state file keeps the continuity after each compact.
10
+ - **Subagents default to Sonnet** instead of the main model. Pass a model explicitly when one piece really needs the big one.
11
+ - **Three skills:** `/save`, `/yaad` and `/kharcha` (find what is eating your usage).
12
+ - **One command sets up everything:** Claude Code, plus ready-to-upload skill zips for claude.ai web and the Claude desktop chat, and it opens the upload page for you.
13
+ - **Keeps itself up to date:** once a day it checks npm; when a new version is out, Claude tells you and runs the update when you say yes.
14
+
15
+ The hooks are plain Node scripts. They read only the tail of the transcript, finish in under a second, and print nothing unless there is something to say. They cost **zero model tokens** except the short state they print at session start.
16
+
17
+ ---
18
+
19
+ ## Install
20
+
21
+ You need **Node.js 18+** and **Claude Code**.
22
+
23
+ ```bash
24
+ npx claude-yaad
25
+ ```
26
+
27
+ Or straight from GitHub; it works the same:
28
+
29
+ ```bash
30
+ npx github:rahmangamer12/claude-yaad
31
+ ```
32
+
33
+ Then **restart Claude Code**. That's it.
34
+
35
+ At the end it asks one question: open the claude.ai Skills page and the zips folder? Press Enter, then drag the three zips onto **Upload skill**. That covers web and desktop chat too. (`--yes` opens them without asking, `--no-open` skips it.)
36
+
37
+ What the installer does:
38
+
39
+ | | Where |
40
+ |---|---|
41
+ | Hook script `ck_state.js` | `~/.claude/scripts/` |
42
+ | Skills `save`, `yaad`, `kharcha` | `~/.claude/skills/` (an older skill with the same name is moved to `~/.claude/skill-backups/`) |
43
+ | Hooks: SessionStart, PreCompact, SessionEnd, UserPromptSubmit | `~/.claude/settings.json` (backed up first as `settings.json.bak-claude-kit-<time>`) |
44
+ | `CLAUDE_CODE_AUTO_COMPACT_WINDOW=150k` | `env` in `~/.claude/settings.json`; skipped if you already set a window, or with `--keep-compact-window` |
45
+ | `CLAUDE_CODE_SUBAGENT_MODEL=sonnet` | `env` in `~/.claude/settings.json`; skipped if you already set one, or with `--keep-subagent-model` |
46
+ | Skill zips for claude.ai | `~/.claude/claude-yaad-zips/` (built from the same skills, so they never drift) |
47
+ | Installed version, for the update check | `~/.claude/state/claude-yaad.json` |
48
+
49
+ Your other hooks, settings and skills are left alone. Running it again updates the kit in place.
50
+
51
+ ### Options
52
+
53
+ ```bash
54
+ npx claude-yaad --keep-subagent-model # do not touch the subagent model
55
+ npx claude-yaad --keep-compact-window # do not touch the auto-compact window
56
+ npx claude-yaad --yes # open the claude.ai Skills page without asking
57
+ npx claude-yaad --no-open # never open anything
58
+ npx claude-yaad --zips # only write the skill zips for claude.ai into the current folder
59
+ npx claude-yaad --uninstall # remove the hooks and the script (keeps skills and state)
60
+ npx claude-yaad --uninstall --purge # also remove the three skills
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Daily use
66
+
67
+ | You want to… | Do this |
68
+ |---|---|
69
+ | Stop for now, or `/clear` | `/save`, then `/clear`. Nothing is lost. |
70
+ | Pick up where you left off | Just start. The state shows at the top of the session. Ask `/yaad` for a short summary. |
71
+ | The hook says the context is heavy | Finish the current step, `/save`, `/clear`. |
72
+ | Usage limit going too fast | `/kharcha`: it checks the usual culprits and fixes the safe ones. |
73
+
74
+ ### How the memory works
75
+
76
+ One file per project: `~/.claude/state/projects/<project-slug>.md`. The slug is the working directory with every character that is not a letter or digit turned into `-`, e.g. `F:\Work\Shop Site` → `F--Work-Shop-Site.md`.
77
+
78
+ The file has two parts:
79
+
80
+ 1. **Your part, written by `/save`:** what we are doing, key paths, done, next, the commands to run, and gotchas. 60 lines max.
81
+ 2. **The auto part, written by the hook** on every compact and session end: your last few requests, the files edited, and the last reply. It needs no model call, so it is free.
82
+
83
+ Because every project has its own file, two Claude windows on two projects never overwrite each other's memory.
84
+
85
+ ### Settings you can change
86
+
87
+ Set these as environment variables, or in `env` in `settings.json`:
88
+
89
+ | Variable | Default | Meaning |
90
+ |---|---|---|
91
+ | `CK_WARN_TOKENS` | `200000` | Warn when the context passes this many tokens |
92
+ | `CK_SHOW_CHARS` | `3500` | How much of the state file to show at session start |
93
+ | `CK_NO_UPDATE_CHECK` | unset | `1` turns off the daily update check |
94
+
95
+ ### Updates
96
+
97
+ Once a day, at session start, the hook starts a background request to `registry.npmjs.org/claude-yaad/latest` (no data about you is sent; it does not slow the start). If a newer version exists, the next session opens with a one-line note and Claude asks whether to update; say yes and it runs `npx -y claude-yaad@latest`. Nothing updates without your yes.
98
+
99
+ ---
100
+
101
+ ## claude.ai (web) and the Claude desktop app
102
+
103
+ Chat on claude.ai and in the desktop app has no hooks and no lasting disk, so only the skills apply there:
104
+
105
+ 1. The normal `npx claude-yaad` already built `save.zip`, `yaad.zip` and `kharcha.zip` in `~/.claude/claude-yaad-zips/` and offered to open the upload page. (They are also in the [`claude-ai/`](claude-ai) folder of this repo, or run `npx claude-yaad --zips`.)
106
+ 2. claude.ai → **Settings → Capabilities → Skills → Upload skill**, one zip at a time. Do this once; it applies to web and desktop chat.
107
+ 3. In chat, Claude prints a short state block on its own when a task finishes or the chat gets long (or when you say `/save`). Paste it into your **Project instructions** (or the first message of the next chat), and `/yaad` reads it from there.
108
+
109
+ The **Claude Code tab** of the desktop app and the IDE extensions use the same `~/.claude` folder as the terminal, so the full kit works there after the normal install.
110
+
111
+ ---
112
+
113
+ ## Why this saves usage
114
+
115
+ - The biggest cost is **context length × number of messages**. A 400k-token session costs about 10× a 40k one *per message*. Cheap memory makes `/clear` painless, so you clear more.
116
+ - **Subagents are full extra sessions.** Running them on Sonnet by default cuts their cost a lot. Your main session keeps the model you chose.
117
+ - Older autosave hooks parsed the whole transcript, which can be hundreds of MB, and wrote one global file that every window overwrote. This reads the last 3 MB and keeps one file per project.
118
+
119
+ ## Uninstall
120
+
121
+ ```bash
122
+ npx claude-yaad --uninstall --purge
123
+ ```
124
+
125
+ Your state files in `~/.claude/state/projects/` are kept. Delete them by hand if you want.
126
+
127
+ ## License
128
+
129
+ MIT
package/bin/install.js CHANGED
@@ -1,108 +1,207 @@
1
- #!/usr/bin/env node
2
- // claude-yaad installer: npx claude-yaad -> install / update
3
- // npx claude-yaad --uninstall -> remove hooks + kit files (skills kept unless --purge)
4
- // npx claude-yaad --zips -> copy claude.ai skill zips to the current folder
5
- 'use strict';
6
- const fs = require('fs');
7
- const os = require('os');
8
- const path = require('path');
9
-
10
- const ROOT = path.join(__dirname, '..');
11
- const CLAUDE = path.join(os.homedir(), '.claude');
12
- const SETTINGS = path.join(CLAUDE, 'settings.json');
13
- const SCRIPT = path.join(CLAUDE, 'scripts', 'ck_state.js');
14
- const SKILLS = ['save', 'yaad', 'kharcha'];
15
- const args = process.argv.slice(2);
16
- const has = (f) => args.includes(f);
17
- const log = (s) => console.log(' ' + s);
18
-
19
- function copyDir(src, dst) {
20
- fs.mkdirSync(dst, { recursive: true });
21
- for (const e of fs.readdirSync(src, { withFileTypes: true })) {
22
- const s = path.join(src, e.name), d = path.join(dst, e.name);
23
- e.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
24
- }
25
- }
26
- function readSettings() {
27
- try { return JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); } catch (e) {
28
- if (fs.existsSync(SETTINGS)) { console.error('settings.json is not valid JSON; fix it first: ' + SETTINGS); process.exit(1); }
29
- return {};
30
- }
31
- }
32
- function backup() {
33
- if (!fs.existsSync(SETTINGS)) return;
34
- const b = SETTINGS + '.bak-claude-kit-' + Date.now();
35
- fs.copyFileSync(SETTINGS, b);
36
- log('backup: ' + b);
37
- }
38
- const cmd = () => `node "${SCRIPT.replace(/\\/g, '/')}"`;
39
- // Old hooks this kit replaces (the earlier python autosave and the global CURRENT.md loader).
40
- const isOurs = (h) => /ck_state\.js|autosave\.py|state\/CURRENT\.md|state\\CURRENT\.md/.test((h && h.command) || '');
41
-
42
- function stripOurs(s) {
43
- for (const ev of Object.keys(s.hooks || {})) {
44
- s.hooks[ev] = s.hooks[ev]
45
- .map((g) => ({ ...g, hooks: (g.hooks || []).filter((h) => !isOurs(h)) }))
46
- .filter((g) => g.hooks.length);
47
- if (!s.hooks[ev].length) delete s.hooks[ev];
48
- }
49
- }
50
- function addHook(s, ev, matcher, timeout) {
51
- s.hooks = s.hooks || {};
52
- s.hooks[ev] = s.hooks[ev] || [];
53
- const g = { hooks: [{ type: 'command', command: cmd(), timeout }] };
54
- if (matcher) g.matcher = matcher;
55
- s.hooks[ev].unshift(g);
56
- }
57
-
58
- if (has('--zips')) {
59
- const src = path.join(ROOT, 'claude-ai');
60
- for (const f of fs.readdirSync(src)) fs.copyFileSync(path.join(src, f), path.join(process.cwd(), f));
61
- console.log('Skill zips copied here. claude.ai → Settings → Capabilities → Skills → Upload skill (one zip at a time).');
62
- process.exit(0);
63
- }
64
-
65
- if (has('--uninstall')) {
66
- const s = readSettings(); backup(); stripOurs(s);
67
- if (s.env && s.env.CLAUDE_CODE_SUBAGENT_MODEL === 'sonnet') delete s.env.CLAUDE_CODE_SUBAGENT_MODEL;
68
- if (s.env && s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW === '150k') delete s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
69
- fs.writeFileSync(SETTINGS, JSON.stringify(s, null, 2));
70
- try { fs.unlinkSync(SCRIPT); } catch {}
71
- if (has('--purge')) for (const k of SKILLS) fs.rmSync(path.join(CLAUDE, 'skills', k), { recursive: true, force: true });
72
- console.log('claude-kit removed. Your state files in ~/.claude/state/projects are kept.');
73
- process.exit(0);
74
- }
75
-
76
- console.log('claude-kit: installing into ' + CLAUDE);
77
- copyDir(path.join(ROOT, 'kit', 'scripts'), path.join(CLAUDE, 'scripts'));
78
- log('hook script: ' + SCRIPT);
79
- for (const k of SKILLS) {
80
- const dst = path.join(CLAUDE, 'skills', k);
81
- if (fs.existsSync(dst) && !has('--force')) {
82
- const old = path.join(CLAUDE, 'skill-backups', k + '-' + Date.now());
83
- fs.mkdirSync(path.dirname(old), { recursive: true });
84
- fs.renameSync(dst, old);
85
- log(`skill ${k}: previous version kept at ${old}`);
86
- }
87
- copyDir(path.join(ROOT, 'kit', 'skills', k), dst);
88
- log('skill: /' + k);
89
- }
90
- const s = readSettings(); backup(); stripOurs(s);
91
- addHook(s, 'SessionStart', 'startup|clear|compact|resume', 10);
92
- addHook(s, 'PreCompact', 'auto|manual', 20);
93
- addHook(s, 'SessionEnd', null, 20);
94
- addHook(s, 'UserPromptSubmit', null, 10);
95
- s.env = s.env || {};
96
- if (!has('--keep-subagent-model') && !s.env.CLAUDE_CODE_SUBAGENT_MODEL) {
97
- s.env.CLAUDE_CODE_SUBAGENT_MODEL = 'sonnet';
98
- log('subagents default to Sonnet (pass a model explicitly for hard work)');
99
- }
100
- if (!has('--keep-compact-window') && !s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW && !s.autoCompactWindow) {
101
- s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = '150k';
102
- log('auto-compact window 150k: sessions never grow huge, so every message stays cheap');
103
- }
104
- fs.mkdirSync(CLAUDE, { recursive: true });
105
- fs.writeFileSync(SETTINGS, JSON.stringify(s, null, 2));
106
- log('hooks: SessionStart, PreCompact, SessionEnd, UserPromptSubmit');
107
- console.log('\nDone. Restart Claude Code. Use /save before /clear, /yaad to pick up, /kharcha to find waste.');
108
- console.log('claude.ai web / Claude desktop chat: run npx claude-yaad --zips and upload the zips there.');
1
+ #!/usr/bin/env node
2
+ // claude-yaad installer: npx claude-yaad -> install / update everything (Claude Code + claude.ai zips)
3
+ // npx claude-yaad --yes -> same, and open the claude.ai Skills page without asking
4
+ // npx claude-yaad --zips -> only write the claude.ai skill zips into the current folder
5
+ // npx claude-yaad --uninstall -> remove hooks + kit files (skills kept unless --purge)
6
+ 'use strict';
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const zlib = require('zlib');
11
+ const cp = require('child_process');
12
+ const readline = require('readline');
13
+
14
+ const ROOT = path.join(__dirname, '..');
15
+ const VERSION = require(path.join(ROOT, 'package.json')).version;
16
+ const CLAUDE = path.join(os.homedir(), '.claude');
17
+ const SETTINGS = path.join(CLAUDE, 'settings.json');
18
+ const SCRIPT = path.join(CLAUDE, 'scripts', 'ck_state.js');
19
+ const META = path.join(CLAUDE, 'state', 'claude-yaad.json');
20
+ const ZIPDIR = path.join(CLAUDE, 'claude-yaad-zips');
21
+ const SKILLS_URL = 'https://claude.ai/settings/capabilities';
22
+ const SKILLS = ['save', 'yaad', 'kharcha'];
23
+ const args = process.argv.slice(2);
24
+ const has = (f) => args.includes(f);
25
+ const log = (s) => console.log(' ' + s);
26
+
27
+ function copyDir(src, dst) {
28
+ fs.mkdirSync(dst, { recursive: true });
29
+ for (const e of fs.readdirSync(src, { withFileTypes: true })) {
30
+ const s = path.join(src, e.name), d = path.join(dst, e.name);
31
+ e.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
32
+ }
33
+ }
34
+ function readJSON(f, dflt) { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch { return dflt; } }
35
+ function readSettings() {
36
+ try { return JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); } catch (e) {
37
+ if (fs.existsSync(SETTINGS)) { console.error('settings.json is not valid JSON; fix it first: ' + SETTINGS); process.exit(1); }
38
+ return {};
39
+ }
40
+ }
41
+ function backup() {
42
+ if (!fs.existsSync(SETTINGS)) return;
43
+ const b = SETTINGS + '.bak-claude-kit-' + Date.now();
44
+ fs.copyFileSync(SETTINGS, b);
45
+ log('backup: ' + b);
46
+ }
47
+ const cmd = () => `node "${SCRIPT.replace(/\\/g, '/')}"`;
48
+ // Old hooks this kit replaces (the earlier python autosave and the global CURRENT.md loader).
49
+ const isOurs = (h) => /ck_state\.js|autosave\.py|state\/CURRENT\.md|state\\CURRENT\.md/.test((h && h.command) || '');
50
+
51
+ function stripOurs(s) {
52
+ for (const ev of Object.keys(s.hooks || {})) {
53
+ s.hooks[ev] = s.hooks[ev]
54
+ .map((g) => ({ ...g, hooks: (g.hooks || []).filter((h) => !isOurs(h)) }))
55
+ .filter((g) => g.hooks.length);
56
+ if (!s.hooks[ev].length) delete s.hooks[ev];
57
+ }
58
+ }
59
+ function addHook(s, ev, matcher, timeout) {
60
+ s.hooks = s.hooks || {};
61
+ s.hooks[ev] = s.hooks[ev] || [];
62
+ const g = { hooks: [{ type: 'command', command: cmd(), timeout }] };
63
+ if (matcher) g.matcher = matcher;
64
+ s.hooks[ev].unshift(g);
65
+ }
66
+
67
+ // ---- claude.ai skill zips, built from kit/skills so they never drift from the Claude Code skills ----
68
+ const CRC = (() => {
69
+ const t = new Int32Array(256);
70
+ for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c; }
71
+ return t;
72
+ })();
73
+ function crc32(buf) { let c = -1; for (const x of buf) c = CRC[(c ^ x) & 255] ^ (c >>> 8); return (c ^ -1) >>> 0; }
74
+ function makeZip(entries) {
75
+ const d = new Date();
76
+ const time = (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1);
77
+ const date = ((d.getFullYear() - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
78
+ const local = [], central = [];
79
+ let off = 0;
80
+ for (const e of entries) {
81
+ const name = Buffer.from(e.name, 'utf8'), comp = zlib.deflateRawSync(e.data), crc = crc32(e.data);
82
+ const h = Buffer.alloc(30);
83
+ h.writeUInt32LE(0x04034b50, 0); h.writeUInt16LE(20, 4); h.writeUInt16LE(0x0800, 6); h.writeUInt16LE(8, 8);
84
+ h.writeUInt16LE(time, 10); h.writeUInt16LE(date, 12); h.writeUInt32LE(crc, 14);
85
+ h.writeUInt32LE(comp.length, 18); h.writeUInt32LE(e.data.length, 22); h.writeUInt16LE(name.length, 26);
86
+ local.push(h, name, comp);
87
+ const c = Buffer.alloc(46);
88
+ c.writeUInt32LE(0x02014b50, 0); c.writeUInt16LE(20, 4); c.writeUInt16LE(20, 6); c.writeUInt16LE(0x0800, 8);
89
+ c.writeUInt16LE(8, 10); c.writeUInt16LE(time, 12); c.writeUInt16LE(date, 14); c.writeUInt32LE(crc, 16);
90
+ c.writeUInt32LE(comp.length, 20); c.writeUInt32LE(e.data.length, 24); c.writeUInt16LE(name.length, 28);
91
+ c.writeUInt32LE(off, 42);
92
+ central.push(c, name);
93
+ off += 30 + name.length + comp.length;
94
+ }
95
+ const cd = Buffer.concat(central), end = Buffer.alloc(22);
96
+ end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10);
97
+ end.writeUInt32LE(cd.length, 12); end.writeUInt32LE(off, 16);
98
+ return Buffer.concat([...local, cd, end]);
99
+ }
100
+ function skillEntries(dir, prefix) {
101
+ const out = [];
102
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
103
+ const p = path.join(dir, e.name), n = prefix + '/' + e.name;
104
+ if (e.isDirectory()) out.push(...skillEntries(p, n)); else out.push({ name: n, data: fs.readFileSync(p) });
105
+ }
106
+ return out;
107
+ }
108
+ function writeZips(dst) {
109
+ fs.mkdirSync(dst, { recursive: true });
110
+ for (const k of SKILLS) fs.writeFileSync(path.join(dst, k + '.zip'), makeZip(skillEntries(path.join(ROOT, 'kit', 'skills', k), k)));
111
+ }
112
+
113
+ function openThing(target) {
114
+ try {
115
+ const o = { detached: true, stdio: 'ignore' };
116
+ if (process.platform === 'win32') cp.spawn('cmd', ['/c', 'start', '""', `"${target}"`], { ...o, windowsVerbatimArguments: true, windowsHide: true }).unref();
117
+ else cp.spawn(process.platform === 'darwin' ? 'open' : 'xdg-open', [target], o).unref();
118
+ return true;
119
+ } catch { return false; }
120
+ }
121
+ function ask(q) {
122
+ if (has('--yes') || has('-y')) return Promise.resolve(true);
123
+ if (has('--no-open') || !process.stdin.isTTY) return Promise.resolve(false);
124
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
125
+ return new Promise((res) => rl.question(q, (a) => { rl.close(); res(!/^\s*n/i.test(a)); }));
126
+ }
127
+
128
+ async function main() {
129
+ if (has('--zips')) {
130
+ writeZips(process.cwd());
131
+ console.log('Skill zips written here: ' + SKILLS.map((k) => k + '.zip').join(', '));
132
+ console.log('claude.ai → Settings → Capabilities → Skills → Upload skill (one zip at a time).');
133
+ return;
134
+ }
135
+
136
+ if (has('--uninstall')) {
137
+ const s = readSettings(); backup(); stripOurs(s);
138
+ if (s.env && s.env.CLAUDE_CODE_SUBAGENT_MODEL === 'sonnet') delete s.env.CLAUDE_CODE_SUBAGENT_MODEL;
139
+ if (s.env && s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW === '150k') delete s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
140
+ fs.writeFileSync(SETTINGS, JSON.stringify(s, null, 2));
141
+ for (const f of [SCRIPT, META]) try { fs.unlinkSync(f); } catch {}
142
+ fs.rmSync(ZIPDIR, { recursive: true, force: true });
143
+ if (has('--purge')) for (const k of SKILLS) fs.rmSync(path.join(CLAUDE, 'skills', k), { recursive: true, force: true });
144
+ console.log('claude-yaad removed. Your state files in ~/.claude/state/projects are kept.');
145
+ return;
146
+ }
147
+
148
+ const prev = readJSON(META, {});
149
+ console.log(prev.version && prev.version !== VERSION
150
+ ? `claude-yaad: updating ${prev.version} → ${VERSION} in ${CLAUDE}`
151
+ : `claude-yaad ${VERSION}: installing into ${CLAUDE}`);
152
+
153
+ // 1. Claude Code: hook script, skills, hooks, env
154
+ copyDir(path.join(ROOT, 'kit', 'scripts'), path.join(CLAUDE, 'scripts'));
155
+ log('hook script: ' + SCRIPT);
156
+ for (const k of SKILLS) {
157
+ const dst = path.join(CLAUDE, 'skills', k);
158
+ // A skill we installed before is simply replaced; anyone else's skill with this name is kept aside.
159
+ if (fs.existsSync(dst) && !prev.version && !has('--force')) {
160
+ const old = path.join(CLAUDE, 'skill-backups', k + '-' + Date.now());
161
+ fs.mkdirSync(path.dirname(old), { recursive: true });
162
+ fs.renameSync(dst, old);
163
+ log(`skill ${k}: previous version kept at ${old}`);
164
+ } else fs.rmSync(dst, { recursive: true, force: true });
165
+ copyDir(path.join(ROOT, 'kit', 'skills', k), dst);
166
+ log('skill: /' + k);
167
+ }
168
+ const s = readSettings(); backup(); stripOurs(s);
169
+ addHook(s, 'SessionStart', 'startup|clear|compact|resume', 10);
170
+ addHook(s, 'PreCompact', 'auto|manual', 20);
171
+ addHook(s, 'SessionEnd', null, 20);
172
+ addHook(s, 'UserPromptSubmit', null, 10);
173
+ s.env = s.env || {};
174
+ if (!has('--keep-subagent-model') && !s.env.CLAUDE_CODE_SUBAGENT_MODEL) {
175
+ s.env.CLAUDE_CODE_SUBAGENT_MODEL = 'sonnet';
176
+ log('subagents default to Sonnet (pass a model explicitly for hard work)');
177
+ }
178
+ if (!has('--keep-compact-window') && !s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW && !s.autoCompactWindow) {
179
+ s.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = '150k';
180
+ log('auto-compact window 150k: sessions never grow huge, so every message stays cheap');
181
+ }
182
+ fs.mkdirSync(CLAUDE, { recursive: true });
183
+ fs.writeFileSync(SETTINGS, JSON.stringify(s, null, 2));
184
+ log('hooks: SessionStart, PreCompact, SessionEnd, UserPromptSubmit');
185
+ log('auto-update: once a day the hook checks npm and Claude offers the update (CK_NO_UPDATE_CHECK=1 turns it off)');
186
+ fs.mkdirSync(path.dirname(META), { recursive: true });
187
+ fs.writeFileSync(META, JSON.stringify({ version: VERSION, installed: new Date().toISOString(), latest: VERSION, lastCheck: Date.now() }, null, 2));
188
+
189
+ // 2. claude.ai web / Claude desktop chat: zips ready to upload
190
+ writeZips(ZIPDIR);
191
+ log('claude.ai skill zips: ' + ZIPDIR);
192
+
193
+ console.log('\nDone. Restart Claude Code (and any long-running sessions) so the new settings apply.');
194
+ console.log('Use /save before /clear, /yaad to pick up, /kharcha to find waste.\n');
195
+
196
+ if (await ask('Open the claude.ai Skills page and the zips folder now, for web/desktop chat? [Y/n] ')) {
197
+ openThing(SKILLS_URL);
198
+ openThing(ZIPDIR);
199
+ console.log('\nOn claude.ai: Skills → Upload skill → drag save.zip, then yaad.zip, then kharcha.zip.');
200
+ console.log('(If the page does not show Skills, go to Settings → Capabilities.)');
201
+ } else {
202
+ console.log('For claude.ai web/desktop chat later: upload the three zips in ' + ZIPDIR);
203
+ console.log('at ' + SKILLS_URL + ' → Skills → Upload skill.');
204
+ }
205
+ }
206
+
207
+ main().catch((e) => { console.error('claude-yaad: ' + (e && e.message || e)); process.exit(1); });
Binary file
Binary file
Binary file
@@ -17,6 +17,39 @@ const WARN = parseInt(process.env.CK_WARN_TOKENS || '200000', 10);
17
17
  const SHOW = parseInt(process.env.CK_SHOW_CHARS || '3500', 10);
18
18
  const TAIL = 3 * 1024 * 1024;
19
19
 
20
+ // ---- auto-update: a detached child asks npm for the latest version at most once a day;
21
+ // the next SessionStart tells Claude, which offers the update to the user (one "yes" and it runs).
22
+ const META = path.join(HOME, '.claude', 'state', 'claude-yaad.json');
23
+ const readMeta = () => { try { return JSON.parse(fs.readFileSync(META, 'utf8')); } catch { return null; } };
24
+ const writeMeta = (m) => { try { fs.writeFileSync(META, JSON.stringify(m, null, 2)); } catch {} };
25
+ const newer = (a, b) => {
26
+ const x = String(a).split('.').map(Number), y = String(b).split('.').map(Number);
27
+ for (let i = 0; i < 3; i++) if ((x[i] || 0) !== (y[i] || 0)) return (x[i] || 0) > (y[i] || 0);
28
+ return false;
29
+ };
30
+ if (process.argv[2] === 'check-update') {
31
+ const req = require('https').get('https://registry.npmjs.org/claude-yaad/latest',
32
+ { headers: { accept: 'application/json' }, timeout: 8000 }, (res) => {
33
+ let b = ''; res.on('data', (c) => (b += c));
34
+ res.on('end', () => { try { const v = JSON.parse(b).version; const m = readMeta(); if (m && v) { m.latest = v; writeMeta(m); } } catch {} });
35
+ });
36
+ req.on('timeout', () => req.destroy()); req.on('error', () => {});
37
+ return;
38
+ }
39
+ function updateNote() {
40
+ const m = readMeta();
41
+ if (!m || process.env.CK_NO_UPDATE_CHECK === '1') return;
42
+ if (m.latest && m.version && newer(m.latest, m.version))
43
+ console.log(`[claude-yaad] Naya version ${m.latest} aa gaya hai (laga hua ${m.version}). User ko ek line mein batao aur poocho "update karun?"; haan kahe to chalao: npx -y claude-yaad@latest`);
44
+ if (!m.lastCheck || Date.now() - m.lastCheck > 864e5) {
45
+ m.lastCheck = Date.now(); writeMeta(m);
46
+ try {
47
+ require('child_process').spawn(process.execPath, [__filename, 'check-update'],
48
+ { detached: true, stdio: 'ignore', windowsHide: true }).unref();
49
+ } catch {}
50
+ }
51
+ }
52
+
20
53
  function readStdin() {
21
54
  try { return JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch { return {}; }
22
55
  }
@@ -90,6 +123,7 @@ if (ev === 'SessionStart') {
90
123
  console.log('=== Pichli dafa kahan the ===');
91
124
  console.log(txt.length > SHOW ? txt.slice(0, SHOW) + '\n…(baqi file mein)' : txt);
92
125
  }
126
+ updateNote();
93
127
  process.exit(0);
94
128
  }
95
129
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: save
3
- description: Kaam ka state save karo taake /clear, compact ya band karne par kuch na khoye. Use when the user says save karo, clear karne laga hoon, band kar raha hoon, yaad rakho kahan the, handoff, bas aaj ke liye — and proactively when a piece of work reaches a natural stopping point, before a long or risky operation, or when a hook says the context is getting heavy.
3
+ description: Kaam ka state save karo taake /clear, compact ya band karne par kuch na khoye. Use when the user says save karo, clear karne laga hoon, band kar raha hoon, yaad rakho kahan the, handoff, bas aaj ke liye — and proactively when a piece of work reaches a natural stopping point, before a long or risky operation, or when a hook says the context is getting heavy. In claude.ai web / Claude desktop chat, also run it on your own when a task finishes or the chat gets long.
4
4
  ---
5
5
 
6
6
  # /save — freeze this project's state in one small file
@@ -16,7 +16,11 @@ The file has two parts:
16
16
  - **Auto part (the hook's):** that marker line and everything below it. Never edit it, and keep it in place: the hook rewrites it on its own.
17
17
  If the file does not exist, just write the top part.
18
18
 
19
- **claude.ai web / Claude desktop chat (no file system that survives):** do not write a file. Print the same block in a code box and tell the user: "Ye block Project ke instructions/knowledge mein paste kar do, ya naye chat ke pehle message mein." That is the only memory that survives there.
19
+ **claude.ai web / Claude desktop chat (no file system that survives):** do not write a file. Print the same block in a code box. That block is the only memory that survives there, so **do this on your own, without being asked**, at each of these points (once per point, never after every reply):
20
+ - a task in this chat is finished, or the user says they are stopping;
21
+ - the chat has run long (about 25 messages), so the next one should start fresh.
22
+
23
+ After the box, one line: "Ye block Project instructions mein paste kar do (ya naye chat ke pehle message mein), agli chat `/yaad` se yahin se shuru hogi."
20
24
 
21
25
  ## What goes in (60 lines maximum, hard cap)
22
26
 
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
- {
2
- "name": "claude-yaad",
3
- "version": "1.0.1",
4
- "description": "Zero-token session memory and usage savers for Claude Code: per-project state that survives /clear and compaction, /save /yaad /kharcha skills, context-size warning, Sonnet subagents.",
5
- "bin": {
6
- "claude-yaad": "bin/install.js"
7
- },
8
- "files": [
9
- "bin",
10
- "kit",
11
- "claude-ai",
12
- "README.md",
13
- "LICENSE"
14
- ],
15
- "engines": {
16
- "node": ">=18"
17
- },
18
- "keywords": [
19
- "claude",
20
- "claude-code",
21
- "skills",
22
- "hooks",
23
- "tokens"
24
- ],
25
- "license": "MIT",
26
- "repository": {
27
- "type": "git",
28
- "url": "git+https://github.com/rahmangamer12/claude-yaad.git"
29
- },
30
- "homepage": "https://github.com/rahmangamer12/claude-yaad#readme",
31
- "bugs": {
32
- "url": "https://github.com/rahmangamer12/claude-yaad/issues"
33
- }
1
+ {
2
+ "name": "claude-yaad",
3
+ "version": "1.1.0",
4
+ "description": "Zero-token session memory and usage savers for Claude Code: per-project state that survives /clear and compaction, /save /yaad /kharcha skills, context-size warning, Sonnet subagents.",
5
+ "bin": {
6
+ "claude-yaad": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "kit",
11
+ "claude-ai",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "keywords": [
19
+ "claude",
20
+ "claude-code",
21
+ "skills",
22
+ "hooks",
23
+ "tokens"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/rahmangamer12/claude-yaad.git"
29
+ },
30
+ "homepage": "https://github.com/rahmangamer12/claude-yaad#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/rahmangamer12/claude-yaad/issues"
33
+ }
34
34
  }