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 +129 -116
- package/bin/install.js +207 -108
- package/claude-ai/kharcha.zip +0 -0
- package/claude-ai/save.zip +0 -0
- package/claude-ai/yaad.zip +0 -0
- package/kit/scripts/ck_state.js +34 -0
- package/kit/skills/save/SKILL.md +6 -2
- package/package.json +33 -33
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
The
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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 --
|
|
4
|
-
// npx claude-yaad --zips
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
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
|
-
|
|
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
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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); });
|
package/claude-ai/kharcha.zip
CHANGED
|
Binary file
|
package/claude-ai/save.zip
CHANGED
|
Binary file
|
package/claude-ai/yaad.zip
CHANGED
|
Binary file
|
package/kit/scripts/ck_state.js
CHANGED
|
@@ -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
|
|
package/kit/skills/save/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
}
|