@geohar/pi-cribsheet 0.0.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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +275 -0
- package/dist/index.js.map +1 -0
- package/dist/pi.d.ts +70 -0
- package/dist/pi.d.ts.map +1 -0
- package/dist/pi.js +10 -0
- package/dist/pi.js.map +1 -0
- package/dist/sharedserver-resolve.d.ts +19 -0
- package/dist/sharedserver-resolve.d.ts.map +1 -0
- package/dist/sharedserver-resolve.js +188 -0
- package/dist/sharedserver-resolve.js.map +1 -0
- package/instructions.txt +140 -0
- package/mcp.json.example +7 -0
- package/package.json +56 -0
- package/src/index.ts +319 -0
- package/src/pi.ts +70 -0
- package/src/sharedserver-resolve.ts +221 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Resolve the `sharedserver` binary, fetching it when none is usable.
|
|
2
|
+
//
|
|
3
|
+
// VENDORED FILE. This is copied byte-identical into the consuming OpenCode plugins
|
|
4
|
+
// (cribsheet, svg-mcp) by their scripts/sync-vendored.sh, so the drift check is a plain
|
|
5
|
+
// diff. There is no way to share it as a dependency without coupling their versions to
|
|
6
|
+
// sharedserver's — see plugins/claude/bin/sharedserver for the same reasoning on the
|
|
7
|
+
// shell side. Edit it HERE; consumers re-sync.
|
|
8
|
+
//
|
|
9
|
+
// Everything repo-specific arrives through ResolveConfig rather than being hardcoded,
|
|
10
|
+
// mirroring bin/sharedserver.conf.
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** Oldest release these plugins are correct against: 0.5.0 added the PID-reuse guard,
|
|
16
|
+
* without which a recycled client PID can hold a server open indefinitely. */
|
|
17
|
+
const HARDCODED_FLOOR = "0.5.0";
|
|
18
|
+
const CANDIDATE_BINARIES = [
|
|
19
|
+
"sharedserver",
|
|
20
|
+
join(homedir(), ".cargo", "bin", "sharedserver"),
|
|
21
|
+
join(homedir(), ".local", "bin", "sharedserver"),
|
|
22
|
+
"/usr/local/bin/sharedserver",
|
|
23
|
+
"/opt/homebrew/bin/sharedserver",
|
|
24
|
+
];
|
|
25
|
+
function parseVersion(text) {
|
|
26
|
+
const m = text.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
27
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : undefined;
|
|
28
|
+
}
|
|
29
|
+
function gte(a, b) {
|
|
30
|
+
for (let i = 0; i < 3; i++)
|
|
31
|
+
if (a[i] !== b[i])
|
|
32
|
+
return a[i] > b[i];
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function versionOf(candidate, env) {
|
|
36
|
+
const r = spawnSync(candidate, ["--version"], { env });
|
|
37
|
+
if (r.error)
|
|
38
|
+
return undefined;
|
|
39
|
+
return parseVersion(`${r.stdout?.toString() ?? ""}${r.stderr?.toString() ?? ""}`);
|
|
40
|
+
}
|
|
41
|
+
/** Synchronous sleep — this path runs before anything can be awaited, so the lock wait
|
|
42
|
+
* cannot use timers. Atomics.wait blocks without spinning. */
|
|
43
|
+
function sleepSync(ms) {
|
|
44
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
45
|
+
}
|
|
46
|
+
/** First sharedserver found at ANY version, or undefined. */
|
|
47
|
+
function probeAny(override, env) {
|
|
48
|
+
const candidates = [override, env.SHAREDSERVER_BIN, ...CANDIDATE_BINARIES].filter((v) => typeof v === "string" && v.length > 0);
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
if (candidate.includes("/")) {
|
|
51
|
+
if (existsSync(candidate))
|
|
52
|
+
return candidate;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Presence is "spawn did not fail", NOT "exited 0" — a build that rejects some
|
|
56
|
+
// flag is still present.
|
|
57
|
+
if (!spawnSync(candidate, ["--version"], { stdio: "ignore", env }).error)
|
|
58
|
+
return candidate;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Download and run the cargo-dist installer, then re-probe. */
|
|
63
|
+
function installPinned(cfg, url, what, env, log, toast) {
|
|
64
|
+
// The SAME lock directory as the shell shim — cross-client coordination, so a
|
|
65
|
+
// Claude session and an OpenCode session starting together do not race.
|
|
66
|
+
const lockdir = join(env.TMPDIR || "/tmp", ".sharedserver-install.lock");
|
|
67
|
+
let haveLock = false;
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(lockdir);
|
|
70
|
+
haveLock = true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
const deadline = Date.now() + 20_000;
|
|
74
|
+
while (existsSync(lockdir) && Date.now() < deadline)
|
|
75
|
+
sleepSync(250);
|
|
76
|
+
const after = probeAny(undefined, env);
|
|
77
|
+
if (after)
|
|
78
|
+
return after;
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(lockdir);
|
|
81
|
+
haveLock = true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
log?.("warn", `${cfg.label}: another process is installing sharedserver and did not finish; remove the stale lock if this persists: ${lockdir}`);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
// Re-probe under the lock: the winner may have finished while we waited.
|
|
90
|
+
const already = probeAny(undefined, env);
|
|
91
|
+
const floor = parseVersion(cfg.minVersion ?? HARDCODED_FLOOR);
|
|
92
|
+
if (already) {
|
|
93
|
+
const v = versionOf(already, env);
|
|
94
|
+
if (v && floor && gte(v, floor))
|
|
95
|
+
return already;
|
|
96
|
+
}
|
|
97
|
+
// Download to a file, then run it. `curl … | sh` would report success on a 404,
|
|
98
|
+
// since a pipeline's status is sh's; spawnSync uses no shell, so this is exact.
|
|
99
|
+
const script = join(lockdir, "installer.sh");
|
|
100
|
+
const dl = spawnSync("curl", ["--proto", "=https", "--tlsv1.2", "-LsSf", url, "-o", script], { env });
|
|
101
|
+
if (dl.error || dl.status !== 0) {
|
|
102
|
+
const msg = `${cfg.label}: could not download the sharedserver installer (${what})`;
|
|
103
|
+
log?.("warn", msg);
|
|
104
|
+
toast?.("warning", msg);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (!existsSync(script) || statSync(script).size === 0) {
|
|
108
|
+
log?.("warn", `${cfg.label}: the downloaded sharedserver installer was empty`);
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
// cargo-dist embeds the expected sha256 but looks up only `sha256sum` and SKIPS
|
|
112
|
+
// verification when missing — i.e. always, on macOS. Give it one (identical
|
|
113
|
+
// output) rather than execute an unverified binary.
|
|
114
|
+
let runEnv = env;
|
|
115
|
+
if (spawnSync("sh", ["-c", "command -v sha256sum"], { stdio: "ignore" }).status !== 0) {
|
|
116
|
+
if (spawnSync("sh", ["-c", "command -v shasum"], { stdio: "ignore" }).status !== 0) {
|
|
117
|
+
log?.("warn", `${cfg.label}: refusing to install sharedserver — no sha256sum or shasum to verify the download`);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
writeFileSync(join(lockdir, "sha256sum"), '#!/bin/sh\nexec shasum -a 256 "$@"\n', { mode: 0o755 });
|
|
121
|
+
runEnv = { ...env, PATH: `${lockdir}:${env.PATH ?? ""}` };
|
|
122
|
+
}
|
|
123
|
+
const run = spawnSync("sh", [script], { env: runEnv, stdio: "ignore" });
|
|
124
|
+
if (run.error || run.status !== 0) {
|
|
125
|
+
log?.("warn", `${cfg.label}: the sharedserver installer ran but failed`);
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return probeAny(undefined, env);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
if (haveLock) {
|
|
132
|
+
try {
|
|
133
|
+
rmSync(lockdir, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
/* best effort — a leaked lock costs the next run its bounded wait */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Resolve the sharedserver binary, fetching a release when nothing usable is present.
|
|
142
|
+
*
|
|
143
|
+
* Behaviourally identical to plugins/claude/bin/sharedserver — same ladder, floor,
|
|
144
|
+
* lock and degrade-rather-than-die fallback, so a user running both clients gets the
|
|
145
|
+
* same answer to "which sharedserver am I on, and why". */
|
|
146
|
+
export function resolveSharedserver(cfg, override, env, log, toast) {
|
|
147
|
+
const label = cfg.label ?? "sharedserver";
|
|
148
|
+
const minVersion = cfg.minVersion ?? cfg.pkgVersion ?? HARDCODED_FLOOR;
|
|
149
|
+
const url = cfg.installerUrl ??
|
|
150
|
+
(cfg.pkgVersion
|
|
151
|
+
? `https://github.com/georgeharker/sharedserver/releases/download/v${cfg.pkgVersion}/sharedserver-installer.sh`
|
|
152
|
+
: "https://github.com/georgeharker/sharedserver/releases/latest/download/sharedserver-installer.sh");
|
|
153
|
+
const what = cfg.installerUrl || !cfg.pkgVersion ? "the latest release" : `v${cfg.pkgVersion}`;
|
|
154
|
+
const resolved = { ...cfg, label, minVersion };
|
|
155
|
+
const floor = parseVersion(minVersion);
|
|
156
|
+
// An explicit binary / SHAREDSERVER_BIN is never second-guessed: the user named a
|
|
157
|
+
// specific one, so quietly downloading a different one is the wrong answer.
|
|
158
|
+
const explicit = override ?? env.SHAREDSERVER_BIN;
|
|
159
|
+
if (explicit) {
|
|
160
|
+
const present = explicit.includes("/")
|
|
161
|
+
? existsSync(explicit)
|
|
162
|
+
: !spawnSync(explicit, ["--version"], { stdio: "ignore", env }).error;
|
|
163
|
+
if (present) {
|
|
164
|
+
const v = versionOf(explicit, env);
|
|
165
|
+
if (v && floor && !gte(v, floor)) {
|
|
166
|
+
const msg = `${label}: sharedserver at ${explicit} is ${v.join(".")}; this plugin expects >= ${minVersion}. Using it anyway because you set it explicitly.`;
|
|
167
|
+
log?.("warn", msg);
|
|
168
|
+
toast?.("warning", msg);
|
|
169
|
+
}
|
|
170
|
+
return explicit;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const found = probeAny(override, env);
|
|
174
|
+
if (found) {
|
|
175
|
+
const v = versionOf(found, env);
|
|
176
|
+
if (v && floor && gte(v, floor))
|
|
177
|
+
return found;
|
|
178
|
+
// Too old: fetch, but remember this one — if the download fails we would rather
|
|
179
|
+
// run the old binary than nothing.
|
|
180
|
+
const msg = `${label}: sharedserver at ${found} is ${v?.join(".") ?? "an unknown version"}; this plugin expects >= ${minVersion}. Fetching ${what}. To keep your own build, set SHAREDSERVER_BIN=${found}`;
|
|
181
|
+
log?.("warn", msg);
|
|
182
|
+
toast?.("warning", msg);
|
|
183
|
+
return installPinned(resolved, url, what, env, log, toast) ?? found;
|
|
184
|
+
}
|
|
185
|
+
log?.("info", `${label}: no usable sharedserver found; fetching ${what} (one time). This needs no Rust toolchain.`);
|
|
186
|
+
return installPinned(resolved, url, what, env, log, toast);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=sharedserver-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sharedserver-resolve.js","sourceRoot":"","sources":["../src/sharedserver-resolve.ts"],"names":[],"mappings":"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"}
|
package/instructions.txt
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Memory (cribsheet)
|
|
2
|
+
|
|
3
|
+
<!-- Canonical reach-for-crib directive. Copy into your global
|
|
4
|
+
$CLAUDE_CONFIG_DIR/CLAUDE.md, or let a cribsheet plugin inject it each session
|
|
5
|
+
(the plugins symlink this file). It directs *approach*; the MCP tool
|
|
6
|
+
descriptions carry the mechanics. -->
|
|
7
|
+
|
|
8
|
+
Five rules. Each fires on a **cue you can catch yourself in the middle of**, not on
|
|
9
|
+
a topic you have to remember to think about. That's deliberate: you will not remember
|
|
10
|
+
to "consult memory" — but you can notice your own hand reaching for `grep`.
|
|
11
|
+
|
|
12
|
+
Rules 4 and 5 are the ones you will walk past most, because their cues don't feel like
|
|
13
|
+
actions: settling a design question and writing a todo list both feel like *thinking*,
|
|
14
|
+
not like *reaching for a tool*. Watch for them anyway.
|
|
15
|
+
|
|
16
|
+
### 1. About to grep? That's the cue.
|
|
17
|
+
|
|
18
|
+
`grep` / `rg` / `Glob`, or opening a file to see what it does, is a **stop sign, not
|
|
19
|
+
an action**. At that exact moment:
|
|
20
|
+
|
|
21
|
+
| your next move was | do this instead |
|
|
22
|
+
|---|---|
|
|
23
|
+
| grep for a symbol — by name, or by hunch | `code_lookup "what it does, in your own words"` — answers by intent, across files |
|
|
24
|
+
| open the file to understand a symbol | `code_dossier <symbol>` — signature, description, callers, callees, references, in one call |
|
|
25
|
+
| chain greps to trace a caller | `code_xref` / `code_graph` |
|
|
26
|
+
| *(repo isn't indexed)* | `project_index` — one call, and the **expected first step**, not a dead end |
|
|
27
|
+
|
|
28
|
+
grep is the **fallback for when the index misses**. It is never the opening move.
|
|
29
|
+
There is no shortcut around indexing; the utility *is* the index.
|
|
30
|
+
|
|
31
|
+
**Catch yourself:** if you've read three files to answer one question, this rule
|
|
32
|
+
already fired and you walked past it. Stop where you are, `code_lookup`, redo it.
|
|
33
|
+
|
|
34
|
+
### 2. About to answer from your own knowledge? Look it up first.
|
|
35
|
+
|
|
36
|
+
Cue: you're about to explain a project, a past decision, or a convention — or open a
|
|
37
|
+
codebase cold. `lookup` / `apropos` **before** you answer, not after. What's stored may
|
|
38
|
+
be more current than what you'd reconstruct, and *you can't tell which from the inside*:
|
|
39
|
+
a confident reconstruction feels exactly like a correct one.
|
|
40
|
+
|
|
41
|
+
### 3. Just understood something? Store it before you move on.
|
|
42
|
+
|
|
43
|
+
Cue: the "ah, *that's* why" moment — a convention, a gotcha, a contract. The
|
|
44
|
+
moment passes; the note has to be written inside it. `lookup` first, then `append`/`edit`
|
|
45
|
+
over creating a near-duplicate.
|
|
46
|
+
|
|
47
|
+
**Not a decision, and not a task.** Those have their own verbs — rules 4 and 5. If what
|
|
48
|
+
you just understood is *why we chose this*, `note_store` is the wrong verb: it takes the
|
|
49
|
+
prose and silently drops the edges, which were the part worth keeping.
|
|
50
|
+
|
|
51
|
+
Writes need an explicit `project=` — they do **not** inherit your current project. Name
|
|
52
|
+
the project the fact is *about*: the repo you're in, or `default` (or that tool's own
|
|
53
|
+
project) for cross-cutting knowledge like a CLI or editor convention. Say where it landed.
|
|
54
|
+
To pin a hard-won insight about a symbol: `code_append <symbol> "…"`.
|
|
55
|
+
|
|
56
|
+
### 4. Just settled a question — or about to explain why? Use the decision graph.
|
|
57
|
+
|
|
58
|
+
Cue: a design question stopped being open. "Chunks are keyed by X." "Writes must name
|
|
59
|
+
their project." It's the moment the arguing stops — and it feels like you merely
|
|
60
|
+
*finished thinking*, which is exactly why this rule gets walked past.
|
|
61
|
+
|
|
62
|
+
Decisions have their **own verbs**. Use those, not `note_*`: a note holds the prose,
|
|
63
|
+
but only the facet knows what a decision rests on, what rests on it, and what your
|
|
64
|
+
change just invalidated. That last part is the whole point.
|
|
65
|
+
|
|
66
|
+
| your next move was | do this instead |
|
|
67
|
+
|---|---|
|
|
68
|
+
| a question just got settled, and it has consequences | `design_add <title> <rationale>` with `--dep` on what it builds on |
|
|
69
|
+
| the new decision *replaces* an existing one | `design_supersede` — a second `design_add` leaves both standing and both findable |
|
|
70
|
+
| explain *why* it's built this way | `design_lookup "<query>"` **before** reconstructing it from the code — hits flag stale decisions |
|
|
71
|
+
| read / cite a decision | `design_read <ref>` — body **plus** deps, dependents, and whether either moved |
|
|
72
|
+
| change a decision, or code that implements one | `design_check` **first** — then `design_edit`/`design_append`, whose result lists what you just tainted |
|
|
73
|
+
| propose an architecture change | `design_tree` (or `--dependents`) — see what rests on it before you move it |
|
|
74
|
+
|
|
75
|
+
- **Adding is SILENT — so check what it lands next to.** `design_add` seeds its
|
|
76
|
+
`checked` state from its deps as they read right now, so a new decision is
|
|
77
|
+
born verified and nothing gains taint just because it appeared. The graph will
|
|
78
|
+
hold two decisions that flatly contradict each other and never say a word: it
|
|
79
|
+
tracks the edges you declared, not agreement. So `design_lookup` the area
|
|
80
|
+
first, and read the `similar` list the result hands back. Restates an existing
|
|
81
|
+
decision → `design_append`/`design_edit` **that one**. Replaces it →
|
|
82
|
+
`design_supersede`. Changes the ground under other work → `design_check`,
|
|
83
|
+
which is the only verb that answers "what did I just break".
|
|
84
|
+
- **A dep is a promise**: *if that changes, reconsider this.* A plain note can't
|
|
85
|
+
make that promise; the graph can. Every edge checks — there is no "just for
|
|
86
|
+
reference" edge that lets an origin change silently.
|
|
87
|
+
- **Each stale decision** comes back with the chain explaining *why*, what kind of
|
|
88
|
+
change did it, when, and the verb to run. After reconsidering, `design_reaffirm`
|
|
89
|
+
marks it current. Taint means *a dep moved*, not *you were wrong* — reaffirming
|
|
90
|
+
is the cheap, normal ending, so don't treat it as a crisis. When it genuinely no
|
|
91
|
+
longer holds, `design_supersede`.
|
|
92
|
+
- **A hit marked `tainted`** is a decision whose ground shifted and nobody
|
|
93
|
+
re-read. Don't quietly reason from it — `design_read` it first.
|
|
94
|
+
|
|
95
|
+
**Catch yourself:** if you've just written a paragraph explaining why something is
|
|
96
|
+
built the way it is, you either read that out of the graph or you owe it to the
|
|
97
|
+
graph. Same for "let's go with X" — that's a `design_add`, not a sentence.
|
|
98
|
+
|
|
99
|
+
### 5. About to write a todo list — in a file, in chat, or in your head? That's the cue.
|
|
100
|
+
|
|
101
|
+
`plan_add` instead. An in-chat list dies with the chat and a list in your head dies
|
|
102
|
+
with the context window; a plan item outlives both. One call takes the whole batch,
|
|
103
|
+
and deps can point at earlier items in the same batch.
|
|
104
|
+
|
|
105
|
+
- **`plan_lookup` before you add — it may already be planned.** Another session,
|
|
106
|
+
or another agent, may have written the item you're about to write. An item
|
|
107
|
+
that duplicates a `done` one is louder than a duplicate: either the work
|
|
108
|
+
regressed or you've mis-scoped this, and both are worth knowing *before* a
|
|
109
|
+
second entry exists.
|
|
110
|
+
- **`--dep` the DECISION an item rests on, not just the items before it.** Deps
|
|
111
|
+
mix, and they block differently: a **plan** dep blocks until it's done; a
|
|
112
|
+
**design** dep blocks while it's *tainted*; a **note** dep never blocks. So an
|
|
113
|
+
item that declares the decision it implements drops out of `plan_next` the
|
|
114
|
+
moment that decision's ground moves — the work stops looking actionable
|
|
115
|
+
exactly when it stops being safe to do. That is the plan facet's version of
|
|
116
|
+
"check it didn't break", and it is the only one that fires without you asking.
|
|
117
|
+
- **`plan_status` as you go, not at the end** — `in-progress` is a *claim* other
|
|
118
|
+
agents can see, and marking one `done` tells you what it just unblocked.
|
|
119
|
+
- **`plan_next` is the "where was I" call** — run it when you pick work back up, and
|
|
120
|
+
any future session (yours or another agent's) gets exactly what's actionable now.
|
|
121
|
+
|
|
122
|
+
**Catch yourself:** if you've just typed "first I'll… then I'll…", or reached for a
|
|
123
|
+
scratch todo list, this rule already fired.
|
|
124
|
+
|
|
125
|
+
### Importing a doc (either facet)
|
|
126
|
+
|
|
127
|
+
Asked to capture a DESIGN.md, a spec, or a plan file? `design_import` / `plan_import`
|
|
128
|
+
that doc, then follow the procedure it returns. They run no model and write nothing —
|
|
129
|
+
they hand you the doc's exact citable sections, what already draws on it, and the
|
|
130
|
+
steps. *You* read and judge. Each entry cites the **section** it came from
|
|
131
|
+
(`--source "DESIGN.md#10.3 Fusion"`, never the whole file), so a later edit to that
|
|
132
|
+
passage re-checks exactly what was drawn from it. Extracted decisions land
|
|
133
|
+
`proposed`: they gate work and taint nothing until a human confirms them with
|
|
134
|
+
`design_promote`.
|
|
135
|
+
|
|
136
|
+
<!-- Project selection for *reads*: code tools act on ONE current project — set it once
|
|
137
|
+
with `use_project <name>`, or it's inferred from `project_path` on your first code
|
|
138
|
+
call; reads then need no project args. To read a *different* project you must name it
|
|
139
|
+
(`project=` / `project_path=`). `project_path` means "which repo I mean" — it is not
|
|
140
|
+
your shell cwd. -->
|
package/mcp.json.example
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geohar/pi-cribsheet",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Pi extension: run the crib (cribsheet) memory + code-index MCP server via sharedserver, inject the reach-for-crib directive, and ship the /crib recall command. Pairs with pi-mcp-adapter.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"pi": {
|
|
9
|
+
"extensions": [
|
|
10
|
+
"./src/index.ts"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src",
|
|
15
|
+
"dist",
|
|
16
|
+
"instructions.txt",
|
|
17
|
+
"mcp.json.example",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepack": "node -e \"require('fs').copyFileSync('../../CLAUDE.md.example', './instructions.txt')\"",
|
|
26
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"pi",
|
|
30
|
+
"pi-extension",
|
|
31
|
+
"pi-mono",
|
|
32
|
+
"mcp",
|
|
33
|
+
"cribsheet",
|
|
34
|
+
"crib",
|
|
35
|
+
"memory",
|
|
36
|
+
"sharedserver",
|
|
37
|
+
"extension"
|
|
38
|
+
],
|
|
39
|
+
"author": "George Harker <george@georgeharker.com>",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/georgeharker/cribsheet.git",
|
|
44
|
+
"directory": "plugins/pi"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"typescript": "^5.6.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|