@vib795/agent-memory 0.3.1 → 0.5.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 +99 -17
- package/package.json +1 -9
- package/src/cli.js +41 -15
- package/src/pii.js +269 -0
- package/src/setup.js +4 -4
- package/install.ps1 +0 -71
- package/install.sh +0 -36
- package/scripts/postinstall.js +0 -45
- package/uninstall.ps1 +0 -54
- package/uninstall.sh +0 -39
- package/update.ps1 +0 -45
- package/update.sh +0 -26
package/README.md
CHANGED
|
@@ -124,6 +124,89 @@ Two things are deliberately dropped on the way through:
|
|
|
124
124
|
Importing the same file twice updates rather than duplicates, so re-running after a
|
|
125
125
|
change is safe.
|
|
126
126
|
|
|
127
|
+
### Sharing an export with another person
|
|
128
|
+
|
|
129
|
+
Scope decides *whose* knowledge travels. It does not decide whether the file can be
|
|
130
|
+
handed to a person, and those are separate questions. Every export is scanned for
|
|
131
|
+
personal identifiers on the way out, and the file carries a receipt naming the rule
|
|
132
|
+
set that ran and what it took.
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
$ agent-memory export --out carry.json
|
|
136
|
+
Exported 41 global-scope notes to carry.json.
|
|
137
|
+
rule set: pii/v1
|
|
138
|
+
redacted: 6 phone, 2 payment-card, 1 ssn
|
|
139
|
+
withheld: 1 note
|
|
140
|
+
oncall-roster — 8 identifiers (phone)
|
|
141
|
+
|
|
142
|
+
Structured identifiers only. Have an agent review this file for names and
|
|
143
|
+
identifying prose before sharing it outside your team.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Notes are **redacted in place**, so the knowledge survives and the identifier does
|
|
147
|
+
not. A note that is *mostly* identifiers — a roster, a contact sheet — is withheld
|
|
148
|
+
whole and named in the receipt, because a redacted skeleton is useless to the reader
|
|
149
|
+
and still re-identifiable from the structure that remains.
|
|
150
|
+
|
|
151
|
+
**Two layers, and the markers tell you which one acted.**
|
|
152
|
+
|
|
153
|
+
| | Runs at | Asks | Marker |
|
|
154
|
+
|---|---|---|---|
|
|
155
|
+
| `src/redact.js` | capture | could this authenticate as someone? | `<redacted:kind>` |
|
|
156
|
+
| `src/pii.js` | export | could this identify a person? | `[redacted:kind]` |
|
|
157
|
+
|
|
158
|
+
They disagree about your own email address on purpose. Capture keeps it, because it
|
|
159
|
+
is already public in every commit you have ever pushed. Export removes it, because it
|
|
160
|
+
is not yours to hand to someone else alongside a hundred notes about how a client
|
|
161
|
+
works.
|
|
162
|
+
|
|
163
|
+
**What the deterministic layer does not catch.** Names, job titles, postal addresses
|
|
164
|
+
and identifying prose have no shape to match on. They are language, and the honest
|
|
165
|
+
place to judge them is the model already in the conversation — so the receipt says so
|
|
166
|
+
instead of letting a pattern imply a completeness it does not have. Ask your agent to
|
|
167
|
+
review the export before it leaves your team.
|
|
168
|
+
|
|
169
|
+
## Evals and governance
|
|
170
|
+
|
|
171
|
+
Disclosure control is a claim until it is measured, so it is measured on every run of
|
|
172
|
+
`npm test`:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
[pii/v1] recall by kind
|
|
176
|
+
email 2/2
|
|
177
|
+
ip-address 2/2
|
|
178
|
+
payment-card 3/3
|
|
179
|
+
phone 4/4
|
|
180
|
+
ssn 2/2
|
|
181
|
+
[pii/v1] precision 15/15 on the negative corpus
|
|
182
|
+
[pii/v1] 3 known gaps pass through by design: person name, postal address, bare internal identifier
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The practices behind that number, which are the transferable part:
|
|
186
|
+
|
|
187
|
+
- **Both error directions are costed, not just the obvious one.** A miss discloses one
|
|
188
|
+
identifier. A false hit shreds a sentence, and a tool that mangles ordinary prose
|
|
189
|
+
gets switched off, which discloses everything. Recall is held at 1.0 because no
|
|
190
|
+
number of leaked identifiers is acceptable; precision is held at 1.0 against a
|
|
191
|
+
corpus built to tempt each detector with what it most resembles — a git SHA for a
|
|
192
|
+
card, a four-part version for an address, a date for a phone number.
|
|
193
|
+
- **Gaps are asserted as gaps.** `test/pii.eval.test.js` proves that names and
|
|
194
|
+
addresses pass through, so closing one has to be a deliberate act rather than a
|
|
195
|
+
silent change in what an old receipt meant.
|
|
196
|
+
- **The eval is checked for teeth.** Removing the Luhn validation drops precision to
|
|
197
|
+
14/15 and fails the build. A green suite that cannot go red is decoration.
|
|
198
|
+
- **Corpora are synthetic.** Reserved ranges only — RFC 5737 addresses, the card
|
|
199
|
+
networks' published test numbers — so the file that tests for leaks is not one.
|
|
200
|
+
- **Rule sets are versioned.** A receipt means something only against the rules that
|
|
201
|
+
produced it, so `pii/v1` is stamped into every export and changing a detector
|
|
202
|
+
changes the name.
|
|
203
|
+
|
|
204
|
+
For sharing outside your team: keep the receipt with the file, re-export rather than
|
|
205
|
+
hand-editing a file you already sent, and treat `--scope all` as a decision with your
|
|
206
|
+
name on it. What this is not: it is not encryption, access control, or a DLP product.
|
|
207
|
+
It is one command that refuses to hand over identifiers, and it says exactly what it
|
|
208
|
+
checked.
|
|
209
|
+
|
|
127
210
|
## Engagements
|
|
128
211
|
|
|
129
212
|
If you work for more than one client on one machine, knowledge from one of them is
|
|
@@ -279,10 +362,11 @@ npm install -g ./agent-memory
|
|
|
279
362
|
agent-memory setup
|
|
280
363
|
```
|
|
281
364
|
|
|
282
|
-
**Do not install from the git URL directly.** `npm install -g <git-url>`
|
|
283
|
-
this package: npm
|
|
284
|
-
|
|
285
|
-
|
|
365
|
+
**Do not install from the git URL directly.** `npm install -g <git-url>` does not
|
|
366
|
+
work for this package: npm resolves a git install through
|
|
367
|
+
`~/.npm/_cacache/tmp/git-clone*` and then removes that directory, leaving the global
|
|
368
|
+
install pointing at a path that no longer exists. Cloning first avoids npm's git
|
|
369
|
+
handling entirely. Verified on npm 11.18.
|
|
286
370
|
|
|
287
371
|
Every release is mirrored to **GitHub Packages**. Treat that as redundancy rather
|
|
288
372
|
than a second front door: GitHub Packages requires authentication even for public
|
|
@@ -324,17 +408,16 @@ drift, and `compact` regenerates the routing digest into both.
|
|
|
324
408
|
`agent-memory doctor` lists what it detected, so an install that appears to do
|
|
325
409
|
nothing tells you whether your editor was missed or simply ignored the files.
|
|
326
410
|
|
|
327
|
-
**Why it is not automatic.**
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
```
|
|
411
|
+
**Why it is not automatic.** Installing the package writes nothing to your machine.
|
|
412
|
+
There is no `postinstall` hook, and its absence is a security decision rather than an
|
|
413
|
+
omission: an install script that writes into *other* tools' agent directories —
|
|
414
|
+
`~/.claude`, `~/.codex`, your editor's prompt folder — is mechanically
|
|
415
|
+
indistinguishable from a supply-chain attack that hijacks an AI agent, and
|
|
416
|
+
supply-chain scanners classify it as exactly that. Installing this package and
|
|
417
|
+
granting it your agents are two separate decisions, so they are two commands.
|
|
418
|
+
`agent-memory setup` is the one that asks.
|
|
336
419
|
|
|
337
|
-
|
|
420
|
+
`agent-memory doctor` tells you where you stand. It checks the files
|
|
338
421
|
rather than the tools, and names any agent it found that has no skills in it:
|
|
339
422
|
|
|
340
423
|
```
|
|
@@ -387,10 +470,9 @@ Note that `npm install -g .` from a clone *symlinks* rather than copies, so
|
|
|
387
470
|
`skills/recall/SKILL.md` will show as modified. That is expected — the description
|
|
388
471
|
is generated state, and the committed value is only a placeholder.
|
|
389
472
|
|
|
390
|
-
Needs Node 22.5 or newer; `doctor` says so plainly if the version is too old
|
|
391
|
-
`postinstall` refuses rather than failing your install.
|
|
473
|
+
Needs Node 22.5 or newer; `doctor` says so plainly if the version is too old.
|
|
392
474
|
|
|
393
|
-
Run `npm test` for the suite (
|
|
475
|
+
Run `npm test` for the suite (87 tests, no dependencies). CI runs it on Linux,
|
|
394
476
|
macOS and Windows across Node 22 and 24, and separately installs the packed tarball
|
|
395
477
|
and exercises it end to end on all three.
|
|
396
478
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vib795/agent-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Durable cross-repo knowledge graph for GitHub Copilot and Claude Code. Markdown source of truth, disposable SQLite index, zero runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-copilot",
|
|
@@ -27,19 +27,11 @@
|
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"test": "node --test",
|
|
30
|
-
"postinstall": "node scripts/postinstall.js",
|
|
31
30
|
"setup": "node src/cli.js setup"
|
|
32
31
|
},
|
|
33
32
|
"files": [
|
|
34
33
|
"src/",
|
|
35
|
-
"scripts/",
|
|
36
34
|
"skills/",
|
|
37
|
-
"install.sh",
|
|
38
|
-
"install.ps1",
|
|
39
|
-
"update.sh",
|
|
40
|
-
"update.ps1",
|
|
41
|
-
"uninstall.sh",
|
|
42
|
-
"uninstall.ps1",
|
|
43
35
|
"README.md",
|
|
44
36
|
"HOWTO.md",
|
|
45
37
|
"LICENSE"
|
package/src/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { setup as runSetup, unlinkSkills, danglingSkillLinks, SKILLS } from './s
|
|
|
17
17
|
import { detectTargets, installableTargets } from './targets.js';
|
|
18
18
|
import { join, dirname } from 'node:path';
|
|
19
19
|
import { atomicWrite } from './atomic.js';
|
|
20
|
+
import { redactNodeForExport, buildReceipt, renderReceipt } from './pii.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* One process, one answer.
|
|
@@ -131,10 +132,10 @@ function cmdInit(opts) {
|
|
|
131
132
|
/**
|
|
132
133
|
* Link the skills into both agents and build the store.
|
|
133
134
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
135
|
+
* This is the only thing that writes into an agent's directory, and it only runs when
|
|
136
|
+
* a person types it. The package deliberately ships no install hook: writing into
|
|
137
|
+
* another tool's agent surface from an install script is the shape of a supply-chain
|
|
138
|
+
* agent hijack, whoever does it and for whatever reason.
|
|
138
139
|
*/
|
|
139
140
|
function cmdSetup() {
|
|
140
141
|
ensureStore();
|
|
@@ -496,10 +497,10 @@ function cmdDoctor() {
|
|
|
496
497
|
);
|
|
497
498
|
|
|
498
499
|
// Detection is not installation, and conflating them is how this tool reports
|
|
499
|
-
// healthy while doing nothing.
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
500
|
+
// healthy while doing nothing. Installing the package does not install the skills;
|
|
501
|
+
// only `agent-memory setup` does, so the CLI routinely lands on PATH with every
|
|
502
|
+
// agent directory empty while every other check here still passes. Verify the
|
|
503
|
+
// files, per agent, rather than trusting that setup ever ran.
|
|
503
504
|
const gaps = [];
|
|
504
505
|
for (const t of installableTargets()) {
|
|
505
506
|
const absent = SKILLS.filter((name) =>
|
|
@@ -581,6 +582,10 @@ const EXPORT_SCOPES = ['global', 'repo', 'all'];
|
|
|
581
582
|
* `captured_sha` is deliberately dropped. It names a commit that does not exist
|
|
582
583
|
* anywhere else, and a staleness signal that cannot be checked is worse than none:
|
|
583
584
|
* it would either read as current forever or claim the history was rewritten.
|
|
585
|
+
*
|
|
586
|
+
* Personal identifiers are removed on the way out, and the export carries a receipt
|
|
587
|
+
* saying which rule set ran and what it took. Scope decides whose knowledge travels;
|
|
588
|
+
* the receipt decides whether it can be handed to a person.
|
|
584
589
|
*/
|
|
585
590
|
function cmdExport(opts) {
|
|
586
591
|
const scope = opts.scope === true ? 'global' : (opts.scope ?? 'global');
|
|
@@ -612,21 +617,42 @@ function cmdExport(opts) {
|
|
|
612
617
|
source: n.source ?? 'manual',
|
|
613
618
|
}));
|
|
614
619
|
|
|
615
|
-
|
|
620
|
+
// Export is the only path by which the store leaves this machine, which makes it
|
|
621
|
+
// the only place disclosure control belongs. It is a different question from the
|
|
622
|
+
// one capture-time redaction answers, and src/pii.js says why at length.
|
|
623
|
+
const results = nodes.map((n) => ({ id: n.id, ...redactNodeForExport(n) }));
|
|
624
|
+
const clean = results.filter((r) => !r.withheld).map((r) => r.node);
|
|
625
|
+
const receipt = buildReceipt(results, { scanned: nodes.length });
|
|
626
|
+
const payload = `${JSON.stringify({ nodes: clean, redaction: receipt }, null, 2)}\n`;
|
|
627
|
+
|
|
616
628
|
if (typeof opts.out === 'string') {
|
|
617
629
|
atomicWrite(opts.out, payload);
|
|
618
630
|
return {
|
|
619
631
|
ok: true,
|
|
620
|
-
exported:
|
|
632
|
+
exported: clean.length,
|
|
633
|
+
withheld: receipt.withheld.length,
|
|
621
634
|
scope,
|
|
622
635
|
out: opts.out,
|
|
623
|
-
|
|
636
|
+
redaction: receipt,
|
|
637
|
+
text: [
|
|
638
|
+
`Exported ${clean.length} ${scope}-scope note${clean.length === 1 ? '' : 's'} to ${opts.out}.`,
|
|
639
|
+
...renderReceipt(receipt),
|
|
640
|
+
].join('\n'),
|
|
624
641
|
};
|
|
625
642
|
}
|
|
626
|
-
// Straight to stdout so it pipes, with the count on stderr where
|
|
627
|
-
// corrupt the document.
|
|
628
|
-
process.stderr.write(`${
|
|
629
|
-
|
|
643
|
+
// Straight to stdout so it pipes, with the count and the receipt on stderr where
|
|
644
|
+
// they will not corrupt the document.
|
|
645
|
+
process.stderr.write(`${clean.length} ${scope}-scope notes\n`);
|
|
646
|
+
for (const line of renderReceipt(receipt)) process.stderr.write(`${line}\n`);
|
|
647
|
+
return {
|
|
648
|
+
ok: true,
|
|
649
|
+
exported: clean.length,
|
|
650
|
+
withheld: receipt.withheld.length,
|
|
651
|
+
scope,
|
|
652
|
+
nodes: clean,
|
|
653
|
+
redaction: receipt,
|
|
654
|
+
text: payload.trimEnd(),
|
|
655
|
+
};
|
|
630
656
|
}
|
|
631
657
|
|
|
632
658
|
/**
|
package/src/pii.js
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disclosure control for export.
|
|
3
|
+
*
|
|
4
|
+
* This is not the same job as src/redact.js and must not be folded into it. That one
|
|
5
|
+
* runs at capture and asks "could this authenticate as someone?" — its threat is a
|
|
6
|
+
* credential reaching disk, and it deliberately preserves the operator's own email
|
|
7
|
+
* because that address is already public in every commit they have ever pushed.
|
|
8
|
+
*
|
|
9
|
+
* This one runs at export and asks "could this identify a person?" — its threat is a
|
|
10
|
+
* human reading the file. The operator's own email is the clearest case of the
|
|
11
|
+
* difference: safe to store, not yours to hand to someone else along with a hundred
|
|
12
|
+
* notes about how their client works.
|
|
13
|
+
*
|
|
14
|
+
* Two layers, and only one of them lives here. Structured identifiers have shape, so
|
|
15
|
+
* they are matched and validated deterministically, which makes them testable and the
|
|
16
|
+
* result reproducible from a named rule set. Names, addresses and identifying prose
|
|
17
|
+
* have no shape; they are language, and the honest place to judge them is the model
|
|
18
|
+
* already sitting in the conversation. The receipt says so out loud rather than
|
|
19
|
+
* letting a regex imply a completeness it does not have.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Bump when a detector changes. A receipt is only reproducible against its rule set. */
|
|
23
|
+
export const RULE_SET = 'pii/v1';
|
|
24
|
+
|
|
25
|
+
/** Detections in one note before the note is withheld whole rather than redacted. */
|
|
26
|
+
export const DEFAULT_WITHHOLD_COUNT = 8;
|
|
27
|
+
|
|
28
|
+
/** Share of the text that may be replaced before redaction stops being meaningful. */
|
|
29
|
+
export const DEFAULT_WITHHOLD_RATIO = 0.3;
|
|
30
|
+
|
|
31
|
+
/** Luhn, the checksum every payment card carries. Turns a digit run into a card. */
|
|
32
|
+
function luhnValid(digits) {
|
|
33
|
+
let sum = 0;
|
|
34
|
+
let double = false;
|
|
35
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
36
|
+
let d = digits.charCodeAt(i) - 48;
|
|
37
|
+
if (d < 0 || d > 9) return false;
|
|
38
|
+
if (double) {
|
|
39
|
+
d *= 2;
|
|
40
|
+
if (d > 9) d -= 9;
|
|
41
|
+
}
|
|
42
|
+
sum += d;
|
|
43
|
+
double = !double;
|
|
44
|
+
}
|
|
45
|
+
return sum % 10 === 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A US SSN has structure the Social Security Administration never issues.
|
|
50
|
+
* Checking it is what separates an SSN from any other nine digits with dashes.
|
|
51
|
+
*/
|
|
52
|
+
function ssnValid(area, group, serial) {
|
|
53
|
+
if (area === '000' || area === '666' || area[0] === '9') return false;
|
|
54
|
+
if (group === '00' || serial === '0000') return false;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function octetsValid(ip) {
|
|
59
|
+
const parts = ip.split('.');
|
|
60
|
+
return parts.length === 4 && parts.every((p) => p.length <= 3 && Number(p) <= 255);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Addresses that describe infrastructure rather than a person.
|
|
65
|
+
*
|
|
66
|
+
* The privacy interest in an IP is that a routable one, with a timestamp, can be put
|
|
67
|
+
* to a subscriber by whoever holds the logs. A pod CIDR cannot. Capture-time
|
|
68
|
+
* redaction already removes these ranges as infrastructure, so scrubbing them again
|
|
69
|
+
* here would only cost meaning in ordinary engineering notes.
|
|
70
|
+
*/
|
|
71
|
+
function reservedV4(ip) {
|
|
72
|
+
const [a, b] = ip.split('.').map(Number);
|
|
73
|
+
if (a === 0 || a === 10 || a === 127 || a >= 224) return true;
|
|
74
|
+
if (a === 169 && b === 254) return true;
|
|
75
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
76
|
+
if (a === 192 && b === 168) return true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Every detector is precision-first.
|
|
82
|
+
*
|
|
83
|
+
* A false negative leaks one identifier. A false positive shreds a sentence, and a
|
|
84
|
+
* tool that mangles ordinary prose gets switched off, which leaks everything. So each
|
|
85
|
+
* pattern here carries a checksum, a structural rule, or a separator requirement that
|
|
86
|
+
* ordinary technical writing does not satisfy. Bare digit runs are left to the
|
|
87
|
+
* semantic pass rather than guessed at, because a git SHA, an order number and a
|
|
88
|
+
* national ID are the same characters.
|
|
89
|
+
*
|
|
90
|
+
* Order is load-bearing. A grouped card number is also a valid phone shape, so cards
|
|
91
|
+
* are consumed before phones get a chance; reordering this array silently changes
|
|
92
|
+
* what the labels say happened.
|
|
93
|
+
*/
|
|
94
|
+
const DETECTORS = [
|
|
95
|
+
{
|
|
96
|
+
kind: 'email',
|
|
97
|
+
// No self-address exemption. That exemption is correct at capture and wrong here.
|
|
98
|
+
re: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
kind: 'ssn',
|
|
102
|
+
// Only the delimited form. Nine adjacent digits are too many other things.
|
|
103
|
+
re: /(?<![\w-])(\d{3})-(\d{2})-(\d{4})(?![\w-])/g,
|
|
104
|
+
validate: (m) => ssnValid(m[1], m[2], m[3]),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
kind: 'payment-card',
|
|
108
|
+
re: /(?<![\w-])(?:\d[ -]?){12,18}\d(?![\w-])/g,
|
|
109
|
+
validate: (m) => {
|
|
110
|
+
const digits = m[0].replace(/\D/g, '');
|
|
111
|
+
return digits.length >= 13 && digits.length <= 19 && luhnValid(digits);
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
kind: 'phone',
|
|
116
|
+
// Requires separators or a country code, so `10.2.4` and `v1.2.3` cannot match,
|
|
117
|
+
// and a bare run of ten digits is left alone on purpose.
|
|
118
|
+
re: /(?<![\w-])(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)|\d{2,4})[\s.-]\d{2,4}[\s.-]\d{2,5}(?:[\s.-]\d{1,5})?(?![\w-])/g,
|
|
119
|
+
validate: (m) => {
|
|
120
|
+
const digits = m[0].replace(/\D/g, '');
|
|
121
|
+
if (digits.length < 10 || digits.length > 15) return false;
|
|
122
|
+
// One repeated digit is a placeholder or a masked field, never a number.
|
|
123
|
+
if (/^(\d)\1+$/.test(digits)) return false;
|
|
124
|
+
// A date reads as three separated numbers too. Reject anything shaped like one
|
|
125
|
+
// rather than deciding by punctuation, which varies by locale.
|
|
126
|
+
return !/^\d{4}[.\-/]\d{1,2}[.\-/]\d{1,2}$/.test(m[0].trim());
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
kind: 'ip-address',
|
|
131
|
+
// Capture-time redaction removes RFC1918 ranges as infrastructure. A routable
|
|
132
|
+
// address is the one that can identify a subscriber, and it survives to here.
|
|
133
|
+
re: /(?<![\w.])\d{1,3}(?:\.\d{1,3}){3}(?![\w.])/g,
|
|
134
|
+
validate: (m) => {
|
|
135
|
+
if (!octetsValid(m[0])) return false;
|
|
136
|
+
// Four single-digit groups is a four-part version string (6.0.1.2) far more
|
|
137
|
+
// often than an address. A deliberate recall gap, measured in the eval and
|
|
138
|
+
// covered by the semantic pass rather than paid for in shredded prose.
|
|
139
|
+
if (/^\d(\.\d){3}$/.test(m[0])) return false;
|
|
140
|
+
return !reservedV4(m[0]);
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Scan text, returning the redacted form and what fired.
|
|
147
|
+
*
|
|
148
|
+
* Fail-closed in the same sense as capture: a non-string is a programming error, and
|
|
149
|
+
* returning it unscanned would be the one outcome worse than throwing.
|
|
150
|
+
*/
|
|
151
|
+
export function scanText(text) {
|
|
152
|
+
if (typeof text !== 'string') {
|
|
153
|
+
throw new TypeError('scanText() requires a string; refusing to export unscanned content');
|
|
154
|
+
}
|
|
155
|
+
const counts = new Map();
|
|
156
|
+
let redactedChars = 0;
|
|
157
|
+
let out = text;
|
|
158
|
+
|
|
159
|
+
for (const { kind, re, validate } of DETECTORS) {
|
|
160
|
+
out = out.replace(re, (...args) => {
|
|
161
|
+
const match = args.slice(0, -2);
|
|
162
|
+
const whole = match[0];
|
|
163
|
+
if (validate && !validate(match)) return whole;
|
|
164
|
+
counts.set(kind, (counts.get(kind) || 0) + 1);
|
|
165
|
+
redactedChars += whole.length;
|
|
166
|
+
return `[redacted:${kind}]`;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
text: out,
|
|
172
|
+
findings: [...counts.entries()].map(([kind, count]) => ({ kind, count })),
|
|
173
|
+
redactedChars,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Clean one node for export, or decide it cannot be cleaned.
|
|
179
|
+
*
|
|
180
|
+
* Withholding is not a fallback for a broken redactor; it answers a different
|
|
181
|
+
* question. A note that is mostly identifiers is a contact record rather than
|
|
182
|
+
* knowledge, and its redacted skeleton is both useless to the reader and still
|
|
183
|
+
* re-identifiable from the structure that remains. Better to name it in the receipt
|
|
184
|
+
* and leave it behind.
|
|
185
|
+
*/
|
|
186
|
+
export function redactNodeForExport(node, opts = {}) {
|
|
187
|
+
const withholdCount = opts.withholdCount ?? DEFAULT_WITHHOLD_COUNT;
|
|
188
|
+
const withholdRatio = opts.withholdRatio ?? DEFAULT_WITHHOLD_RATIO;
|
|
189
|
+
|
|
190
|
+
const clean = { ...node };
|
|
191
|
+
const counts = new Map();
|
|
192
|
+
let redactedChars = 0;
|
|
193
|
+
let textChars = 0;
|
|
194
|
+
|
|
195
|
+
for (const field of ['title', 'body']) {
|
|
196
|
+
if (typeof clean[field] !== 'string') continue;
|
|
197
|
+
textChars += clean[field].length;
|
|
198
|
+
const r = scanText(clean[field]);
|
|
199
|
+
clean[field] = r.text;
|
|
200
|
+
redactedChars += r.redactedChars;
|
|
201
|
+
for (const f of r.findings) counts.set(f.kind, (counts.get(f.kind) || 0) + f.count);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const findings = [...counts.entries()].map(([kind, count]) => ({ kind, count }));
|
|
205
|
+
const total = findings.reduce((n, f) => n + f.count, 0);
|
|
206
|
+
const ratio = textChars ? redactedChars / textChars : 0;
|
|
207
|
+
|
|
208
|
+
if (total >= withholdCount || ratio > withholdRatio) {
|
|
209
|
+
return {
|
|
210
|
+
node: null,
|
|
211
|
+
findings,
|
|
212
|
+
withheld: true,
|
|
213
|
+
reason:
|
|
214
|
+
total >= withholdCount ? `${total} identifiers` : `${Math.round(ratio * 100)}% of the text`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return { node: clean, findings, withheld: false, reason: null };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Fold per-note results into the receipt that ships with the export.
|
|
222
|
+
*
|
|
223
|
+
* The receipt is the governance artifact. An export without one is a file of unknown
|
|
224
|
+
* provenance, and the reason to write it down is that "we redact PII" is a claim
|
|
225
|
+
* while "pii/v1 removed four emails and withheld two notes" is evidence.
|
|
226
|
+
*/
|
|
227
|
+
export function buildReceipt(results, { scanned }) {
|
|
228
|
+
const counts = new Map();
|
|
229
|
+
const withheld = [];
|
|
230
|
+
for (const r of results) {
|
|
231
|
+
for (const f of r.findings) counts.set(f.kind, (counts.get(f.kind) || 0) + f.count);
|
|
232
|
+
if (r.withheld) {
|
|
233
|
+
withheld.push({ id: r.id, reason: r.reason, kinds: r.findings.map((f) => f.kind) });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const redactions = [...counts.entries()]
|
|
237
|
+
.map(([kind, count]) => ({ kind, count }))
|
|
238
|
+
.sort((a, b) => b.count - a.count || a.kind.localeCompare(b.kind));
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
ruleSet: RULE_SET,
|
|
242
|
+
scanned,
|
|
243
|
+
exported: scanned - withheld.length,
|
|
244
|
+
redactions,
|
|
245
|
+
withheld,
|
|
246
|
+
// Stated in the artifact rather than only in the docs, because the person who
|
|
247
|
+
// reads the receipt is the person deciding whether to send the file.
|
|
248
|
+
semanticReviewRequired:
|
|
249
|
+
'Structured identifiers only. Names, job titles, addresses and identifying prose are not detected here; have an agent review the export before sharing it outside your team.',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** The receipt as lines, for the humans who will read it in a terminal. */
|
|
254
|
+
export function renderReceipt(receipt) {
|
|
255
|
+
const out = [`rule set: ${receipt.ruleSet}`];
|
|
256
|
+
if (receipt.redactions.length) {
|
|
257
|
+
out.push(`redacted: ${receipt.redactions.map((r) => `${r.count} ${r.kind}`).join(', ')}`);
|
|
258
|
+
} else {
|
|
259
|
+
out.push('redacted: nothing matched');
|
|
260
|
+
}
|
|
261
|
+
if (receipt.withheld.length) {
|
|
262
|
+
out.push(`withheld: ${receipt.withheld.length} note${receipt.withheld.length === 1 ? '' : 's'}`);
|
|
263
|
+
for (const w of receipt.withheld) out.push(` ${w.id} — ${w.reason} (${w.kinds.join(', ')})`);
|
|
264
|
+
}
|
|
265
|
+
out.push('');
|
|
266
|
+
out.push('Structured identifiers only. Have an agent review this file for names and');
|
|
267
|
+
out.push('identifying prose before sharing it outside your team.');
|
|
268
|
+
return out;
|
|
269
|
+
}
|
package/src/setup.js
CHANGED
|
@@ -11,8 +11,8 @@ import { toPromptFile, isGenerated } from './promptfile.js';
|
|
|
11
11
|
/**
|
|
12
12
|
* Installation, in Node rather than in two shell scripts.
|
|
13
13
|
*
|
|
14
|
-
* This is the single implementation behind
|
|
15
|
-
*
|
|
14
|
+
* This is the single implementation behind both entry points: `agent-memory setup`,
|
|
15
|
+
* and install.sh / install.ps1 which are thin wrappers over it. Writing it once
|
|
16
16
|
* matters because the machine that has to run it is a Windows desktop this was never
|
|
17
17
|
* developed on, and a PowerShell copy of this logic would drift silently.
|
|
18
18
|
*
|
|
@@ -207,8 +207,8 @@ export function setup({ compactFn } = {}) {
|
|
|
207
207
|
const existing = (loadConfig().skillPaths || []).filter((p) => existsSync(p));
|
|
208
208
|
saveConfig({ skillPaths: [...new Set([...existing, ...skillPaths])] });
|
|
209
209
|
|
|
210
|
-
// compact is passed in so this module does not pull the database
|
|
211
|
-
//
|
|
210
|
+
// compact is passed in so this module does not pull the database into memory just
|
|
211
|
+
// to make some symlinks.
|
|
212
212
|
const result = compactFn ? compactFn() : null;
|
|
213
213
|
return {
|
|
214
214
|
targets,
|
package/install.ps1
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
<#
|
|
2
|
-
.SYNOPSIS
|
|
3
|
-
Installs agent-memory and all three skills from a checkout.
|
|
4
|
-
|
|
5
|
-
.DESCRIPTION
|
|
6
|
-
`npm install -g .` does this on its own via the postinstall hook. This script
|
|
7
|
-
exists for two cases: installing straight from a clone without npm, and finishing
|
|
8
|
-
the job when a managed npm config sets ignore-scripts=true and silently skips it.
|
|
9
|
-
|
|
10
|
-
The linking itself lives in src\setup.js, not here. That is deliberate: a PowerShell
|
|
11
|
-
reimplementation could only be tested on Windows, and the machine this was written
|
|
12
|
-
on is not Windows. One implementation, three entry points, no drift.
|
|
13
|
-
|
|
14
|
-
Skills are linked into both agent directories:
|
|
15
|
-
%USERPROFILE%\.agents\skills\<name> -> read by GitHub Copilot in every window
|
|
16
|
-
%USERPROFILE%\.claude\skills\<name> -> read by Claude Code
|
|
17
|
-
|
|
18
|
-
Directory junctions are used, which need neither admin rights nor Developer Mode.
|
|
19
|
-
They fail on a network-backed profile (FSLogix, roaming), and setup falls back to
|
|
20
|
-
copying and says so.
|
|
21
|
-
|
|
22
|
-
.EXAMPLE
|
|
23
|
-
powershell -ExecutionPolicy Bypass -File .\install.ps1
|
|
24
|
-
#>
|
|
25
|
-
|
|
26
|
-
$ErrorActionPreference = 'Stop'
|
|
27
|
-
|
|
28
|
-
$source = $PSScriptRoot
|
|
29
|
-
|
|
30
|
-
$node = Get-Command node -ErrorAction SilentlyContinue
|
|
31
|
-
if (-not $node) {
|
|
32
|
-
Write-Error "node not found on PATH. agent-memory needs Node >= 22.5."
|
|
33
|
-
exit 1
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
& node (Join-Path $source 'src\cli.js') setup
|
|
37
|
-
|
|
38
|
-
Write-Host ""
|
|
39
|
-
Write-Host "Linking the CLI" -ForegroundColor Cyan
|
|
40
|
-
# npm writes its warnings to stderr, and `2>&1` turns each one into an ErrorRecord,
|
|
41
|
-
# which $ErrorActionPreference = 'Stop' then treats as terminating. A managed npm
|
|
42
|
-
# config makes that certain rather than unlikely: an unknown key such as `always-auth`
|
|
43
|
-
# produces a warning on every single npm invocation, so this step could never succeed
|
|
44
|
-
# on the desktops this script exists for. It aborted the whole install — no fallback
|
|
45
|
-
# message, no doctor, no closing instructions — over a warning about an unrelated
|
|
46
|
-
# config key. Scope the preference to this one call.
|
|
47
|
-
$previousPreference = $ErrorActionPreference
|
|
48
|
-
$ErrorActionPreference = 'Continue'
|
|
49
|
-
try {
|
|
50
|
-
& npm install -g $source 2>&1 | Out-Null
|
|
51
|
-
$npmExit = $LASTEXITCODE
|
|
52
|
-
} catch {
|
|
53
|
-
$npmExit = 1
|
|
54
|
-
} finally {
|
|
55
|
-
$ErrorActionPreference = $previousPreference
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if ($npmExit -eq 0) {
|
|
59
|
-
Write-Host " [npm] agent-memory installed globally" -ForegroundColor Green
|
|
60
|
-
} else {
|
|
61
|
-
# A global install failing on a managed desktop is common and not worth aborting
|
|
62
|
-
# on. The skills are already linked; this one step can be finished by hand.
|
|
63
|
-
Write-Host " [npm] global install failed. Run this yourself:" -ForegroundColor Yellow
|
|
64
|
-
Write-Host " npm install -g `"$source`"" -ForegroundColor Yellow
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
Write-Host ""
|
|
68
|
-
& node (Join-Path $source 'src\cli.js') doctor
|
|
69
|
-
|
|
70
|
-
Write-Host ""
|
|
71
|
-
Write-Host "Installed. Restart VS Code, then try /recall, /remember, or /handoff." -ForegroundColor Cyan
|
package/install.sh
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Installs agent-memory and all three skills from a checkout, on macOS/Linux.
|
|
3
|
-
#
|
|
4
|
-
# `npm install -g .` does this on its own via the postinstall hook. This script
|
|
5
|
-
# exists for two cases: installing straight from a clone without npm, and finishing
|
|
6
|
-
# the job when a managed npm config sets ignore-scripts=true and silently skips it.
|
|
7
|
-
#
|
|
8
|
-
# The linking itself lives in src/setup.js, not here. One implementation, three entry
|
|
9
|
-
# points, so this script and its PowerShell twin cannot drift from each other.
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
|
|
14
|
-
if ! command -v node >/dev/null 2>&1; then
|
|
15
|
-
echo "node not found on PATH. agent-memory needs Node >= 22.5." >&2
|
|
16
|
-
exit 1
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
node "$source_dir/src/cli.js" setup
|
|
20
|
-
|
|
21
|
-
echo
|
|
22
|
-
echo "Linking the CLI"
|
|
23
|
-
if npm install -g "$source_dir" >/dev/null 2>&1; then
|
|
24
|
-
echo " [npm] agent-memory installed globally"
|
|
25
|
-
else
|
|
26
|
-
# A global install needing sudo is common and is not worth aborting on. The skills
|
|
27
|
-
# are already linked, and the user can finish this one step by hand.
|
|
28
|
-
echo " [npm] global install failed (permissions?). Run this yourself:"
|
|
29
|
-
echo " npm install -g \"$source_dir\""
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
echo
|
|
33
|
-
node "$source_dir/src/cli.js" doctor || true
|
|
34
|
-
|
|
35
|
-
echo
|
|
36
|
-
echo "Installed. Restart VS Code, then try /recall, /remember, or /handoff."
|
package/scripts/postinstall.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Link the skills when the package is installed.
|
|
4
|
-
*
|
|
5
|
-
* This must never fail an install. A missing symlink is a nuisance; an `npm install`
|
|
6
|
-
* that exits non-zero on a managed desktop is the kind of thing that gets a tool
|
|
7
|
-
* banned. Every failure here is reported and swallowed, and `agent-memory setup`
|
|
8
|
-
* remains available to finish the job by hand.
|
|
9
|
-
*
|
|
10
|
-
* Note that managed npm configurations often set `ignore-scripts=true`, in which case
|
|
11
|
-
* this file never runs and nothing warns you. That is precisely why the same work is
|
|
12
|
-
* exposed as a command, and why `doctor` names it.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const say = (msg) => process.stdout.write(`${msg}\n`);
|
|
16
|
-
|
|
17
|
-
if (process.env.AGENT_MEMORY_SKIP_POSTINSTALL) {
|
|
18
|
-
process.exit(0);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
const [maj, min] = process.versions.node.split('.').map((s) => Number.parseInt(s, 10));
|
|
23
|
-
if (maj < 22 || (maj === 22 && min < 5)) {
|
|
24
|
-
say(`agent-memory: Node ${process.versions.node} is too old; needs >= 22.5 for node:sqlite.`);
|
|
25
|
-
say('agent-memory: skills not linked. Upgrade Node, then run: agent-memory setup');
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const { setup } = await import('../src/setup.js');
|
|
30
|
-
const { compact } = await import('../src/compact.js');
|
|
31
|
-
const r = setup({ compactFn: () => compact() });
|
|
32
|
-
|
|
33
|
-
const links = r.installed.filter((s) => s.mode === 'link').length;
|
|
34
|
-
const copies = r.copies.length;
|
|
35
|
-
say(`agent-memory: linked ${links} skill${links === 1 ? '' : 's'}${copies ? `, copied ${copies}` : ''}.`);
|
|
36
|
-
say(`agent-memory: ${r.notes} notes indexed. Restart VS Code, then try /recall.`);
|
|
37
|
-
if (copies) {
|
|
38
|
-
say('agent-memory: copies happen on network-backed profiles; re-run `agent-memory setup` after upgrades.');
|
|
39
|
-
}
|
|
40
|
-
} catch (err) {
|
|
41
|
-
say(`agent-memory: automatic setup did not complete (${err.message}).`);
|
|
42
|
-
say('agent-memory: run `agent-memory setup` to finish. Nothing else is affected.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
process.exit(0);
|
package/uninstall.ps1
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
<#
|
|
2
|
-
.SYNOPSIS
|
|
3
|
-
Removes the skills and the CLI, in the order that is recoverable.
|
|
4
|
-
|
|
5
|
-
.DESCRIPTION
|
|
6
|
-
npm will not enforce that order and gets it wrong on its own: `npm uninstall -g`
|
|
7
|
-
deletes the package and leaves one link per skill per agent pointing at nothing,
|
|
8
|
-
which every one of those agents still tries to load. By then the binary that would
|
|
9
|
-
have cleaned them up is gone too, so tooling cannot fix it. This unlinks first and
|
|
10
|
-
removes the package second.
|
|
11
|
-
|
|
12
|
-
Your notes are never touched. They are plain markdown under
|
|
13
|
-
%USERPROFILE%\.agents\memory, they outlive the tool that indexed them, and removing
|
|
14
|
-
them is your call, not this script's.
|
|
15
|
-
|
|
16
|
-
.EXAMPLE
|
|
17
|
-
powershell -ExecutionPolicy Bypass -File .\uninstall.ps1
|
|
18
|
-
#>
|
|
19
|
-
|
|
20
|
-
# Not 'Stop': a half-finished uninstall is worse than a reported failure, so each step
|
|
21
|
-
# is allowed to fail and say so.
|
|
22
|
-
$ErrorActionPreference = 'Continue'
|
|
23
|
-
|
|
24
|
-
$source = $PSScriptRoot
|
|
25
|
-
|
|
26
|
-
$node = Get-Command node -ErrorAction SilentlyContinue
|
|
27
|
-
if (-not $node) {
|
|
28
|
-
Write-Error "node not found on PATH. agent-memory needs Node >= 22.5."
|
|
29
|
-
exit 1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
Write-Host "Removing skills" -ForegroundColor Cyan
|
|
33
|
-
# Run from the checkout rather than the installed binary, so this still works when the
|
|
34
|
-
# global package is already gone.
|
|
35
|
-
& node (Join-Path $source 'src\cli.js') uninstall
|
|
36
|
-
|
|
37
|
-
Write-Host ""
|
|
38
|
-
Write-Host "Removing the CLI" -ForegroundColor Cyan
|
|
39
|
-
try {
|
|
40
|
-
& npm uninstall -g '@vib795/agent-memory' 2>&1 | Out-Null
|
|
41
|
-
$npmExit = $LASTEXITCODE
|
|
42
|
-
} catch {
|
|
43
|
-
$npmExit = 1
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if ($npmExit -eq 0) {
|
|
47
|
-
Write-Host " [npm] package removed" -ForegroundColor Green
|
|
48
|
-
} else {
|
|
49
|
-
Write-Host " [npm] not removed. It may not be installed globally:" -ForegroundColor Yellow
|
|
50
|
-
Write-Host " npm uninstall -g `"@vib795/agent-memory`"" -ForegroundColor Yellow
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
Write-Host ""
|
|
54
|
-
Write-Host "Done. Your notes are untouched." -ForegroundColor Cyan
|
package/uninstall.sh
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Removes the skills and the CLI, in the order that is recoverable.
|
|
3
|
-
#
|
|
4
|
-
# npm will not enforce that order and gets it wrong on its own: `npm uninstall -g`
|
|
5
|
-
# deletes the package and leaves one link per skill per agent pointing at nothing,
|
|
6
|
-
# which every one of those agents still tries to load. By then the binary that would
|
|
7
|
-
# have cleaned them up is gone too, so tooling cannot fix it. Unlink first, remove
|
|
8
|
-
# the package second.
|
|
9
|
-
#
|
|
10
|
-
# Your notes are never touched. They are plain markdown under ~/.agents/memory, they
|
|
11
|
-
# outlive the tool that indexed them, and removing them is your call, not this script's.
|
|
12
|
-
#
|
|
13
|
-
# No `set -e`: a half-finished uninstall is worse than a reported failure, so each
|
|
14
|
-
# step is allowed to fail and say so.
|
|
15
|
-
set -uo pipefail
|
|
16
|
-
|
|
17
|
-
source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
-
|
|
19
|
-
if ! command -v node >/dev/null 2>&1; then
|
|
20
|
-
echo "node not found on PATH. agent-memory needs Node >= 22.5." >&2
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
echo "Removing skills"
|
|
25
|
-
# Run from the checkout rather than the installed binary, so this still works when
|
|
26
|
-
# the global package is already gone.
|
|
27
|
-
node "$source_dir/src/cli.js" uninstall
|
|
28
|
-
|
|
29
|
-
echo
|
|
30
|
-
echo "Removing the CLI"
|
|
31
|
-
if npm uninstall -g @vib795/agent-memory >/dev/null 2>&1; then
|
|
32
|
-
echo " [npm] package removed"
|
|
33
|
-
else
|
|
34
|
-
echo " [npm] not removed. It may not be installed globally, or may need sudo:"
|
|
35
|
-
echo " npm uninstall -g @vib795/agent-memory"
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
echo
|
|
39
|
-
echo "Done. Your notes are untouched at ${AGENT_MEMORY_HOME:-$HOME/.agents/memory}."
|
package/update.ps1
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
<#
|
|
2
|
-
.SYNOPSIS
|
|
3
|
-
Updates a clone install: pull, then re-run the installer.
|
|
4
|
-
|
|
5
|
-
.DESCRIPTION
|
|
6
|
-
The install logic is deliberately not repeated here. install.ps1 already relinks the
|
|
7
|
-
skills, relinks the CLI and runs doctor; an update is exactly that plus a pull, and
|
|
8
|
-
writing it a second time is how the two drift.
|
|
9
|
-
|
|
10
|
-
Re-running setup is not optional on an upgrade. Prompt files are copies rather than
|
|
11
|
-
links, so VS Code keeps reading the old text until something rewrites it.
|
|
12
|
-
|
|
13
|
-
.EXAMPLE
|
|
14
|
-
powershell -ExecutionPolicy Bypass -File .\update.ps1
|
|
15
|
-
#>
|
|
16
|
-
|
|
17
|
-
$ErrorActionPreference = 'Stop'
|
|
18
|
-
|
|
19
|
-
$source = $PSScriptRoot
|
|
20
|
-
|
|
21
|
-
if (-not (Test-Path (Join-Path $source '.git'))) {
|
|
22
|
-
Write-Host "This is not a git checkout, so there is nothing to pull." -ForegroundColor Yellow
|
|
23
|
-
Write-Host "If you installed from npm, update with:" -ForegroundColor Yellow
|
|
24
|
-
Write-Host " npm install -g @vib795/agent-memory@latest" -ForegroundColor Yellow
|
|
25
|
-
Write-Host " agent-memory setup" -ForegroundColor Yellow
|
|
26
|
-
exit 1
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
Write-Host "Pulling" -ForegroundColor Cyan
|
|
30
|
-
# git writes ordinary progress to stderr, and `Stop` would treat that as terminating.
|
|
31
|
-
$previousPreference = $ErrorActionPreference
|
|
32
|
-
$ErrorActionPreference = 'Continue'
|
|
33
|
-
try {
|
|
34
|
-
& git -C $source pull --ff-only 2>&1 | Write-Host
|
|
35
|
-
$gitExit = $LASTEXITCODE
|
|
36
|
-
} finally {
|
|
37
|
-
$ErrorActionPreference = $previousPreference
|
|
38
|
-
}
|
|
39
|
-
if ($gitExit -ne 0) {
|
|
40
|
-
Write-Error "git pull failed. Resolve that first, then run this again."
|
|
41
|
-
exit 1
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
Write-Host ""
|
|
45
|
-
& powershell -ExecutionPolicy Bypass -File (Join-Path $source 'install.ps1')
|
package/update.sh
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Updates a clone install: pull, then re-run the installer.
|
|
3
|
-
#
|
|
4
|
-
# The install logic is deliberately not repeated here. install.sh already relinks the
|
|
5
|
-
# skills, relinks the CLI and runs doctor; an update is exactly that plus a pull, and
|
|
6
|
-
# writing it a second time is how the two drift.
|
|
7
|
-
#
|
|
8
|
-
# Re-running setup is not optional on an upgrade. Prompt files are copies rather than
|
|
9
|
-
# links, so an editor keeps reading the old text until something rewrites it.
|
|
10
|
-
set -euo pipefail
|
|
11
|
-
|
|
12
|
-
source_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
-
|
|
14
|
-
if [ ! -d "$source_dir/.git" ]; then
|
|
15
|
-
echo "This is not a git checkout, so there is nothing to pull." >&2
|
|
16
|
-
echo "If you installed from npm, update with:" >&2
|
|
17
|
-
echo " npm install -g @vib795/agent-memory@latest" >&2
|
|
18
|
-
echo " agent-memory setup" >&2
|
|
19
|
-
exit 1
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
echo "Pulling"
|
|
23
|
-
git -C "$source_dir" pull --ff-only
|
|
24
|
-
|
|
25
|
-
echo
|
|
26
|
-
exec "$source_dir/install.sh"
|