@intentic/claude-plugin 1.318.0 → 1.319.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.
@@ -1,252 +0,0 @@
1
- import { createRequire as __intenticRequire } from 'node:module'; const require = __intenticRequire(import.meta.url);
2
-
3
- // src/runtime.ts
4
- import { spawn } from "node:child_process";
5
- import { accessSync, closeSync, constants, mkdirSync, openSync, readFileSync, statSync, writeFileSync } from "node:fs";
6
- import { delimiter, dirname, join } from "node:path";
7
- var pluginRoot = (env = process.env) => env["CLAUDE_PLUGIN_ROOT"] ?? dirname(import.meta.dirname);
8
- var startDetached = (command, args, options) => {
9
- mkdirSync(dirname(options.log), { recursive: true });
10
- const out = openSync(options.log, "w");
11
- try {
12
- const child = spawn(command, args, { cwd: options.cwd, detached: true, stdio: ["ignore", out, out], env: { ...options.env ?? process.env } });
13
- child.on("error", () => void 0);
14
- child.unref();
15
- } finally {
16
- closeSync(out);
17
- }
18
- };
19
- var projectSlug = (project) => project.replace(/[^a-zA-Z0-9]/g, "-");
20
-
21
- // src/features.ts
22
- var OPTIONS = {
23
- output_cleaners: {
24
- type: "boolean",
25
- title: "Trim Bash output",
26
- description: "Clean what a successful command printed before Claude reads it (progress bars, install chatter, repeated lines, the middle of huge logs), keep the full text retrievable with `retrieve-output`, and ledger what each cleaner saved.",
27
- default: true
28
- },
29
- cleaners: {
30
- type: "string",
31
- title: "Cleaners",
32
- description: "Which cleaners run, as the sandbox spells it: empty for all, `-cap,-wide` to switch some off, `git,pnpm` to allow only those. `/intentic:stats` lists them by what they saved.",
33
- default: ""
34
- },
35
- output_holdout: {
36
- type: "number",
37
- title: "Untrimmed share",
38
- description: "Share of commands left untrimmed, so the saving can be measured against a real comparison and not only computed. 0 turns the comparison off; the exact per-command saving is reported either way.",
39
- default: 0,
40
- min: 0,
41
- max: 0.5
42
- },
43
- project_map: {
44
- type: "boolean",
45
- title: "Project map",
46
- description: "Open each session with a map of the project it starts in: its areas, what each is for, and where Claude is standing. Worked out from the tree every time, never stored, so it cannot go stale.",
47
- default: true
48
- },
49
- field_notes: {
50
- type: "boolean",
51
- title: "Field notes",
52
- description: "Open each session with this project's field notes: the traps past sessions hit and the commands that really verify. Written by `/intentic:field-notes` from your own sessions here; nothing is sent until you run it once.",
53
- default: true
54
- },
55
- iq: {
56
- type: "boolean",
57
- title: "iq search",
58
- description: "Teach Claude the `iq` code search CLI and recall past sessions on a matching prompt. Needs `npm i -g @intentic/iq`; without it this switch does nothing and says so once.",
59
- default: true
60
- },
61
- fileq: {
62
- type: "boolean",
63
- title: "fileq",
64
- description: "Let Claude read documents, PDFs, spreadsheets, slides, notebooks, images, audio and archives as markdown with the bundled `fileq` CLI. Off makes the command refuse; its one-line skill description stays listed, since a plugin cannot hide its own skills.",
65
- default: true
66
- },
67
- shadows: {
68
- type: "boolean",
69
- title: "Document shadows",
70
- description: "Keep a markdown rendering of every document in the project up to date in the background (a sweep at session start and every ten minutes, and each binary file Claude writes), so a read finds one ready. Costs background CPU on a document-heavy project.",
71
- default: false
72
- },
73
- holdout: {
74
- type: "number",
75
- title: "Measured share",
76
- description: "Share of sessions opened without the map, the field notes and the iq teaching, so `/intentic:stats` can show what each changed. 0 sends them to every session and measures nothing.",
77
- default: 0.1,
78
- min: 0,
79
- max: 0.5
80
- }
81
- };
82
- var asBoolean = (raw, fallback) => {
83
- const value = raw?.trim().toLowerCase();
84
- if (value === "true" || value === "1" || value === "yes" || value === "on") {
85
- return true;
86
- }
87
- if (value === "false" || value === "0" || value === "no" || value === "off") {
88
- return false;
89
- }
90
- return fallback;
91
- };
92
- var asShare = (raw, option) => {
93
- const value = raw === void 0 || raw.trim() === "" ? Number.NaN : Number(raw);
94
- return Number.isFinite(value) ? Math.min(option.max, Math.max(option.min, value)) : option.default;
95
- };
96
- var optionEnvName = (key) => `CLAUDE_PLUGIN_OPTION_${key.toUpperCase()}`;
97
- var readOptions = (env = process.env) => {
98
- const entries = Object.keys(OPTIONS).map((key) => {
99
- const option = OPTIONS[key];
100
- const raw = env[optionEnvName(key)];
101
- if (option.type === "boolean") {
102
- return [key, asBoolean(raw, option.default)];
103
- }
104
- if (option.type === "number") {
105
- return [key, asShare(raw, option)];
106
- }
107
- return [key, raw ?? option.default];
108
- });
109
- return Object.fromEntries(entries);
110
- };
111
-
112
- // src/hook-io.ts
113
- import { homedir } from "node:os";
114
- import { join as join2 } from "node:path";
115
- var readStdin = async () => {
116
- const chunks = [];
117
- for await (const chunk of process.stdin) {
118
- chunks.push(chunk);
119
- }
120
- return Buffer.concat(chunks).toString("utf8");
121
- };
122
- var parseHookInput = (text) => {
123
- try {
124
- const value = JSON.parse(text);
125
- return typeof value === "object" && value !== null ? value : void 0;
126
- } catch {
127
- return void 0;
128
- }
129
- };
130
- var runHook = async (handle) => {
131
- try {
132
- const raw = await readStdin();
133
- const input = parseHookInput(raw);
134
- if (input === void 0) {
135
- return;
136
- }
137
- const answer = await handle(input, raw);
138
- if (typeof answer === "string") {
139
- process.stdout.write(answer);
140
- } else if (answer !== void 0) {
141
- process.stdout.write(`${JSON.stringify(answer)}
142
- `);
143
- }
144
- } catch (error) {
145
- process.stderr.write(`intentic: ${error instanceof Error ? error.message : String(error)}
146
- `);
147
- }
148
- };
149
- var dataDir = (env = process.env) => env["CLAUDE_PLUGIN_DATA"] ?? join2(env["CLAUDE_CONFIG_DIR"] ?? join2(homedir(), ".claude"), "plugins", "data", "intentic-intentic");
150
- var projectDir = (input, env = process.env) => env["CLAUDE_PROJECT_DIR"] ?? input.cwd ?? process.cwd();
151
-
152
- // src/shadows.ts
153
- import { relative, isAbsolute, join as join3 } from "node:path";
154
-
155
- // ../fileq/src/lib/formats.ts
156
- import { extname } from "node:path";
157
- var EXTENSION_FORMAT = {
158
- ".docx": "docx",
159
- ".xlsx": "xlsx",
160
- ".pptx": "pptx",
161
- ".odt": "odt",
162
- ".ott": "odt",
163
- // a template is the same document with a different intent
164
- ".ods": "ods",
165
- ".ots": "ods",
166
- ".odp": "odp",
167
- ".otp": "odp",
168
- ".odg": "odp",
169
- // a drawing is a presentation of one page
170
- ".rtf": "rtf",
171
- ".epub": "epub",
172
- ".ipynb": "ipynb",
173
- // JSON: no magic bytes name it, so the extension is the whole recognition.
174
- ".cpuprofile": "profile",
175
- // V8 profiles are JSON too: named by extension alone, like a notebook.
176
- ".heapprofile": "profile",
177
- ".pdf": "pdf",
178
- ".png": "image",
179
- ".jpg": "image",
180
- ".jpeg": "image",
181
- ".gif": "image",
182
- ".webp": "image",
183
- ".tif": "image",
184
- ".tiff": "image",
185
- ".heic": "image",
186
- ".avif": "image",
187
- ".mp3": "media",
188
- ".wav": "media",
189
- ".flac": "media",
190
- ".ogg": "media",
191
- ".opus": "media",
192
- ".aac": "media",
193
- ".m4a": "media",
194
- ".mp4": "media",
195
- ".mov": "media",
196
- ".webm": "media",
197
- ".mkv": "media",
198
- ".html": "html",
199
- ".htm": "html",
200
- // Archives, including the three zip-underneath package formats (a jar, a war and a wheel are each a zip with a
201
- // manifest); a double extension like `.tar.gz` is recognized by its last one, which is the compressor.
202
- ".zip": "archive",
203
- ".jar": "archive",
204
- ".war": "archive",
205
- ".whl": "archive",
206
- ".tar": "archive",
207
- ".tgz": "archive",
208
- ".gz": "archive",
209
- ".bz2": "archive",
210
- ".xz": "archive",
211
- ".zst": "archive",
212
- ".7z": "archive",
213
- ".rar": "archive"
214
- };
215
- var isCandidatePath = (path) => extname(path).toLowerCase() in EXTENSION_FORMAT;
216
-
217
- // src/shadows.ts
218
- var SWEEP_EVERY_MS = 10 * 6e4;
219
- var fileq = (env) => join3(pluginRoot(env), "dist", "fileq.mjs");
220
- var logOf = (data, project, what) => join3(data, "shadows", `${projectSlug(project)}.${what}.log`);
221
- var fileqEnv = (env, project) => ({ ...env, WORKSPACE_ROOT: project });
222
- var derivablePath = (project, file) => {
223
- const rel = isAbsolute(file) ? relative(project, file) : file;
224
- return rel === "" || rel.startsWith("..") || isAbsolute(rel) || !isCandidatePath(rel) ? void 0 : rel.split("\\").join("/");
225
- };
226
- var deriveNow = (data, project, rel, env = process.env) => {
227
- startDetached(process.execPath, [fileq(env), "derive", rel], { cwd: project, log: logOf(data, project, "derive"), env: fileqEnv(env, project) });
228
- };
229
-
230
- // src/post-edit.ts
231
- var PATH_KEYS = ["file_path", "notebook_path"];
232
- var postEdit = (input, env = process.env) => {
233
- if (!readOptions(env).shadows) {
234
- return void 0;
235
- }
236
- const project = projectDir(input, env);
237
- for (const key of PATH_KEYS) {
238
- const file = input.tool_input?.[key];
239
- const rel = typeof file === "string" ? derivablePath(project, file) : void 0;
240
- if (rel !== void 0) {
241
- deriveNow(dataDir(env), project, rel, env);
242
- return void 0;
243
- }
244
- }
245
- return void 0;
246
- };
247
- if (process.argv[1]?.endsWith("post-edit.mjs")) {
248
- await runHook((input) => postEdit(input));
249
- }
250
- export {
251
- postEdit
252
- };