@kaydash9999/banker-plugins 0.20.2 → 0.20.5
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +179 -1
- package/PRIVACY.md +13 -3
- package/README.md +25 -2
- package/bin/banker.js +84 -40
- package/bin/lib/telemetry-config.mjs +31 -16
- package/codex/manifest.json +1 -1
- package/codex/transform-matrix.md +1 -1
- package/commands/setup.md +23 -0
- package/hooks/progress.mjs +143 -38
- package/hooks/register.mjs +13 -3
- package/hooks/update-checkin.mjs +15 -7
- package/hooks/update-fetch.mjs +15 -9
- package/hooks/update-notify.mjs +2 -9
- package/package.json +1 -1
- package/skills/3d-intro-build/SKILL.md +11 -5
- package/skills/3d-intro-build/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-build/references/curate.mjs +48 -14
- package/skills/3d-intro-build/references/preview-lib.mjs +51 -15
- package/skills/3d-intro-build/references/prompts.md +4 -4
- package/skills/3d-intro-build/references/serve.mjs +6 -1
- package/skills/3d-intro-build/references/verify-intro.mjs +16 -9
- package/skills/3d-intro-build/references/video-pool.mjs +250 -135
- package/skills/3d-intro-setup/SKILL.md +41 -10
- package/skills/3d-intro-setup/references/azure-adapter.mjs +374 -81
- package/skills/3d-intro-setup/references/video-pool.mjs +250 -135
- package/skills/anti-drm/SKILL.md +6 -3
- package/skills/anti-drm/scripts/anti-drm.mjs +49 -12
- package/skills/anti-drm/scripts/instruction-toggle.mjs +105 -20
- package/skills/audit-mock/SKILL.md +1 -1
- package/skills/audit-security/SKILL.md +64 -2
- package/skills/deep-research/SKILL.md +5 -0
- package/skills/docs-setup/SKILL.md +37 -7
- package/skills/lineage/SKILL.md +12 -12
- package/skills/lineage/lineage.py +399 -300
- package/skills/motion-graphic-make/SKILL.md +1 -1
- package/skills/motion-graphic-make/references/curate.mjs +48 -14
- package/skills/motion-graphic-make/references/preview-lib.mjs +51 -15
- package/skills/motion-graphic-make/references/serve.mjs +6 -1
- package/skills/motion-graphic-setup/SKILL.md +58 -13
- package/skills/obsidizer/SKILL.md +1 -1
- package/skills/obsidizer/obsidize.mjs +8 -1
- package/skills/omc-patch/SKILL.md +80 -23
- package/skills/omc-patch/scripts/omc-patch.mjs +460 -116
- package/skills/payload-mon/SKILL.md +4 -2
- package/skills/payload-mon/scripts/payload-mon.mjs +88 -12
- package/skills/payload-mon/scripts/payload-size.mjs +4 -1
- package/skills/pdf-vision-extract/SKILL.md +11 -6
- package/skills/ralph-qa/SKILL.md +125 -151
- package/skills/ralph-qa/references/gemini-seat.mjs +67 -19
- package/skills/ralph-qa/references/payload-scan.mjs +22 -7
- package/skills/ralph-qa/references/seat-payload.mjs +646 -0
- package/skills/ralph-qa/references/seat-run.mjs +956 -0
- package/skills/ralph-qa/references/seat-spawn.mjs +205 -0
- package/skills/ralph-qa/references/verifier-probe.mjs +57 -16
- package/skills/setup-harness-factory/SKILL.md +17 -1
- package/skills/setup-insane-search/SKILL.md +16 -0
- package/skills/setup-java/SKILL.md +20 -1
- package/skills/setup-lsp/SKILL.md +56 -13
- package/skills/setup-mcp/SKILL.md +73 -4
- package/skills/setup-node/SKILL.md +31 -6
- package/skills/setup-omc/SKILL.md +24 -0
- package/skills/setup-omc-hud/SKILL.md +9 -6
- package/skills/setup-omc-hud/assets/apply-hud.mjs +242 -27
- package/skills/setup-omc-hud/assets/omc-hud-custom.mjs +81 -14
- package/skills/setup-omc-hud/scripts/claude-update-last.mjs +5 -3
- package/skills/setup-playwright/SKILL.md +45 -2
- package/skills/setup-pwsh/SKILL.md +19 -0
- package/skills/setup-python/SKILL.md +45 -7
- package/skills/setup-sandbox/SKILL.md +30 -5
- package/skills/setup-stitch/SKILL.md +22 -0
- package/skills/setup-tmux/SKILL.md +24 -4
- package/skills/tone-compact/SKILL.md +2 -1
- package/skills/tone-compact/scripts/instruction-toggle.mjs +105 -20
- package/skills/ultra-chain/SKILL.md +4 -3
- package/skills/ultra-chain/references/preview-lib.mjs +51 -15
- package/skills/ultra-chain/references/serve.mjs +6 -1
- package/skills/ultra-interview/SKILL.md +1 -0
- package/skills/update-banker/SKILL.md +19 -3
- package/skills/vertical-pptx/SKILL.md +6 -3
- package/skills/vertical-pptx-setup/SKILL.md +76 -20
|
@@ -109,12 +109,18 @@ function projectRoot(dir) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// <g>/projects.json, gemini's map from a project folder to its folder name under tmp/ and
|
|
112
|
-
// history/, read the other way round: folder name to project
|
|
112
|
+
// history/, read the other way round: folder name to the project folders that claim it.
|
|
113
113
|
function registered(g) {
|
|
114
114
|
try {
|
|
115
115
|
const { projects } = JSON.parse(readFileSync(join(g, "projects.json"), "utf8"));
|
|
116
116
|
if (!projects || typeof projects !== "object" || Array.isArray(projects)) return new Map();
|
|
117
|
-
|
|
117
|
+
const owners = new Map();
|
|
118
|
+
for (const [owner, name] of Object.entries(projects)) {
|
|
119
|
+
if (typeof name !== "string") continue;
|
|
120
|
+
if (!owners.has(name)) owners.set(name, new Set());
|
|
121
|
+
owners.get(name).add(resolve(owner));
|
|
122
|
+
}
|
|
123
|
+
return owners;
|
|
118
124
|
} catch {
|
|
119
125
|
return new Map();
|
|
120
126
|
}
|
|
@@ -122,8 +128,10 @@ function registered(g) {
|
|
|
122
128
|
|
|
123
129
|
// Each folder gemini keeps under tmp/ or history/, here and under .cache/.gemini, with the
|
|
124
130
|
// folder its .project_root names. A folder without the marker (a seat that wrote its session
|
|
125
|
-
// again after its run cleaned up) gets the project projects.json gives its name.
|
|
126
|
-
//
|
|
131
|
+
// again after its run cleaned up) gets the project projects.json gives its name. A name two
|
|
132
|
+
// project folders claim (a hand edit, another gemini) is no seat's for sure: its unmarked folder
|
|
133
|
+
// gets no owner, only the `claimants`, and stays. One folder written twice ("x", "x/") is one owner.
|
|
134
|
+
// Only folders read from tmp/ and history/ are looked up, so no name there reaches another folder.
|
|
127
135
|
function projectFolders(env) {
|
|
128
136
|
const home = geminiHome(env);
|
|
129
137
|
return [join(home, ".gemini"), join(home, ".cache", ".gemini")].flatMap((g) => {
|
|
@@ -131,29 +139,50 @@ function projectFolders(env) {
|
|
|
131
139
|
return ["tmp", "history"].flatMap((kind) => listDir(join(g, kind)).map((name) => {
|
|
132
140
|
const dir = join(g, kind, name);
|
|
133
141
|
const root = projectRoot(dir);
|
|
134
|
-
return { dir, owner: root
|
|
142
|
+
if (root !== undefined) return { dir, owner: root, claimants: [] };
|
|
143
|
+
const claimants = [...(names.get(name) ?? [])];
|
|
144
|
+
return { dir, owner: claimants.length === 1 ? claimants[0] : null, claimants };
|
|
135
145
|
}));
|
|
136
|
-
}).filter((p) => p.owner);
|
|
146
|
+
}).filter((p) => p.owner || p.claimants.length > 1);
|
|
137
147
|
}
|
|
138
148
|
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
export function sessionFolders(seat, { env = process.env, platform = process.platform } = {}) {
|
|
149
|
+
// Whether a project folder is `seat`, by the path as written or as resolved (it may be gone).
|
|
150
|
+
function seatOwner(seat, platform) {
|
|
142
151
|
const want = new Set([norm(seat, platform), norm(real(seat), platform)]);
|
|
143
|
-
return
|
|
152
|
+
return (owner) => want.has(norm(owner, platform));
|
|
144
153
|
}
|
|
145
154
|
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// A run still going has its seat folder, so its records stay.
|
|
155
|
+
// Whether a project folder is <base>/ralph-qa.<id>/gemini and gone (a run cut short before its
|
|
156
|
+
// clean). A run still going has its seat folder, so its records stay.
|
|
149
157
|
const SEAT_PARENT = /^ralph-qa\.[A-Za-z0-9]+$/;
|
|
150
|
-
|
|
158
|
+
function staleOwner(base, platform, exists) {
|
|
151
159
|
const roots = new Set([norm(base, platform), norm(real(base), platform)]);
|
|
152
|
-
|
|
160
|
+
return (owner) => {
|
|
153
161
|
const o = norm(owner, platform);
|
|
154
162
|
return basename(o) === "gemini" && SEAT_PARENT.test(basename(dirname(o))) && roots.has(dirname(dirname(o))) && !exists(owner);
|
|
155
163
|
};
|
|
156
|
-
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The folders to remove, of the project folders `mine` takes, and those left because their name
|
|
167
|
+
// is also another project folder's.
|
|
168
|
+
function folderPlan(env, mine) {
|
|
169
|
+
const all = projectFolders(env);
|
|
170
|
+
return {
|
|
171
|
+
dirs: all.filter((p) => p.owner && mine(p.owner)).map((p) => p.dir),
|
|
172
|
+
skipped: all.filter((p) => !p.owner && p.claimants.some(mine)).map((p) => p.dir),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The folders gemini keeps for sessions run in `seat`, matched by the path as written and as
|
|
177
|
+
// resolved; the seat folder may be gone already.
|
|
178
|
+
export function sessionFolders(seat, { env = process.env, platform = process.platform } = {}) {
|
|
179
|
+
return folderPlan(env, seatOwner(seat, platform)).dirs;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// The session folders of seat folders under `base` that are gone: .project_root names
|
|
183
|
+
// <base>/ralph-qa.<id>/gemini and that folder no longer exists.
|
|
184
|
+
export function staleSessionFolders(base, { env = process.env, platform = process.platform, exists = existsSync } = {}) {
|
|
185
|
+
return folderPlan(env, staleOwner(base, platform, exists)).dirs;
|
|
157
186
|
}
|
|
158
187
|
|
|
159
188
|
function check(seat) {
|
|
@@ -176,9 +205,27 @@ function check(seat) {
|
|
|
176
205
|
return 0;
|
|
177
206
|
}
|
|
178
207
|
|
|
179
|
-
|
|
208
|
+
// Removes `dirs`; gives back what it removed and the folders it left because their name is another project's too.
|
|
209
|
+
function removeFolders({ dirs, skipped }) {
|
|
180
210
|
for (const dir of dirs) rmSync(dir, { recursive: true, force: true });
|
|
181
|
-
|
|
211
|
+
return { removed: dirs, skipped };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// clean and sweep for a caller in the same process (the ralph-qa runner, seat-run.mjs).
|
|
215
|
+
export function cleanSeat(seat, { env = process.env, platform = process.platform } = {}) {
|
|
216
|
+
return removeFolders(folderPlan(env, seatOwner(seat, platform)));
|
|
217
|
+
}
|
|
218
|
+
export function sweepBase(base, { env = process.env, platform = process.platform, exists = existsSync } = {}) {
|
|
219
|
+
return removeFolders(folderPlan(env, staleOwner(base, platform, exists)));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Removes `dirs` and says which folders it left (`skipped`), on stdout as JSON and one line each on stderr.
|
|
223
|
+
function remove(plan) {
|
|
224
|
+
const { removed, skipped } = removeFolders(plan);
|
|
225
|
+
for (const dir of skipped) {
|
|
226
|
+
process.stderr.write(`gemini-seat: left ${dir}: two project folders in projects.json claim its name, so it may hold another project's sessions; remove it by hand if they are the seat's\n`);
|
|
227
|
+
}
|
|
228
|
+
process.stdout.write(JSON.stringify({ removed, skipped }) + "\n");
|
|
182
229
|
return 0;
|
|
183
230
|
}
|
|
184
231
|
|
|
@@ -193,7 +240,8 @@ function main(argv) {
|
|
|
193
240
|
return 2;
|
|
194
241
|
}
|
|
195
242
|
if (cmd === "check") return check(folder);
|
|
196
|
-
|
|
243
|
+
const mine = cmd === "clean" ? seatOwner(folder, process.platform) : staleOwner(folder, process.platform, existsSync);
|
|
244
|
+
return remove(folderPlan(process.env, mine));
|
|
197
245
|
}
|
|
198
246
|
|
|
199
247
|
const invokedDirectly = () => {
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
* none): the whole block is the secret, not just the BEGIN line. The matched text itself is never
|
|
11
11
|
* printed, so the report cannot become the leak. The patterns cover common key and token
|
|
12
12
|
* formats, private key blocks (PGP too), secret assignments (quoted, or unquoted with letters
|
|
13
|
-
* and digits), passwords (quoted, with a symbol, alone on a config or env-file line,
|
|
14
|
-
*
|
|
13
|
+
* and digits), passwords (quoted, with a symbol, alone on a config or env-file line, a KEY=value
|
|
14
|
+
* word after a space, a quote, `(` or `=` as in `env`, `docker -e`, `--from-literal=`, or after a Dockerfile
|
|
15
|
+
* `ENV KEY value`; the shell's own PWD or OLDPWD holding a path is no password),
|
|
16
|
+
* secrets given as command-line flags, curl's user and password flag,
|
|
15
17
|
* hex keys, Basic and Bearer credentials, credentials in URLs and
|
|
16
18
|
* long high-entropy strings (a lockfile's sha512- integrity hash and a Go or Terraform h1: hash
|
|
17
19
|
* aside); they do not prove a payload clean. A Docker secret file's path after a *_FILE key is
|
|
18
|
-
* not read as the password it points to
|
|
20
|
+
* not read as the password it points to, and a value masked as a whole `[REDACTED]` word (the mask
|
|
21
|
+
* SKILL.md asks for) is no value, so a masked payload scans clean; a mask glued to the rest of a value is not. Every repeat and lookahead is bounded (a run longer than
|
|
19
22
|
* 65536 characters is read in pieces) or anchored, so a long line takes linear time and cannot
|
|
20
23
|
* overflow the regex stack.
|
|
21
24
|
*/
|
|
@@ -25,6 +28,9 @@ import { fileURLToPath } from "node:url";
|
|
|
25
28
|
const SECRET_KEY = String.raw`(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|access[_-]?key|credential|비밀번호|패스워드)[\w-]{0,64}["']?\s*[:=]\s*`;
|
|
26
29
|
// `pass` and `passphrase` count only as a word of their own (DB_PASS, "pass"), not inside bypass.
|
|
27
30
|
const PASS_WORD = String.raw`(?:passw(?:or)?d|pwd|passcode|(?<![a-z])pass(?:phrase)?(?![a-z]))`;
|
|
31
|
+
// An env-style key that holds a password word, up to its `=`, and a value that is no reference or whole placeholder
|
|
32
|
+
// (`$X`, `<your-password>`, `{{ x }}`, `%(x)s`, `%X%`). A value that only starts with `<` or `{` is still read.
|
|
33
|
+
const ENV_KEY = String.raw`[A-Za-z0-9_]{0,64}(?:passw(?:or)?d|pwd|passcode)[A-Za-z0-9_]{0,64}=(?!-|\$|<[^>\s]{1,64}>|\{\{|%\(|%[A-Za-z_][A-Za-z0-9_]{0,64}%)`;
|
|
28
34
|
const PASSWORD_KEY = String.raw`(?:${PASS_WORD}|비밀번호|패스워드)[\w-]{0,64}["']?\s*[:=]\s*`;
|
|
29
35
|
// An unquoted value that is not code: none of the characters an expression or a reference
|
|
30
36
|
// holds, and at least one symbol (an identifier has none).
|
|
@@ -54,7 +60,9 @@ export const PATTERNS = [
|
|
|
54
60
|
["assigned-secret", new RegExp(SECRET_KEY + String.raw`["'](?:[^"'\s]{12,256}|(?=[^"'\s]{0,10}\d)[^"'\s]{8,11})["']`, "i")],
|
|
55
61
|
["assigned-secret-bare", new RegExp(SECRET_KEY + String.raw`(?=[^\s"']{0,256}\d)(?=[^\s"']{0,256}[A-Za-z])[^\s"'(\[{]{12,256}`, "i")],
|
|
56
62
|
["assigned-password", new RegExp(PASSWORD_KEY + String.raw`(?:["'][^"'\s]{6,256}["']|(?=${BARE}{0,256}[^\w\s;,"'().\[\]{}$<>])${BARE}{6,256})`, "i")],
|
|
57
|
-
|
|
63
|
+
// at the head of a line, or as a word after a space, a quote, `(` or `=` (env, docker -e, --from-literal=, a subshell),
|
|
64
|
+
// its value ending the word
|
|
65
|
+
["env-password", new RegExp(String.raw`^[+\- ]?\s*(?:export\s+)?${ENV_KEY}[^\s"']{6,256}|(?<=[\s"'(=])${ENV_KEY}[^\s"',;()\[\]{}]{6,256}(?=[\s"']|$)`, "i")],
|
|
58
66
|
// a password alone on a config line: YAML, compose lists, .properties, Dockerfile ENV, diff marks
|
|
59
67
|
["config-password", new RegExp(String.raw`^[+\- ]?\s*(?:-\s+|export\s+|ENV\s+)?[\w.-]{0,64}${PASS_WORD}[\w.-]{0,64}["']?\s*[:=]\s*["']?(?![-$<{%])[^\s"',;()\[\]{}]{6,256}["']?\s*(?:#.*)?$`, "i")],
|
|
60
68
|
// a Dockerfile's legacy `ENV KEY value` form, the value alone after the space
|
|
@@ -94,10 +102,16 @@ const highEntropy = (line) => [...line.matchAll(LONG_RUN)].some((m) => !publicHa
|
|
|
94
102
|
// not that password: the kinds that read a value after a key name see a `-` there instead. The
|
|
95
103
|
// other kinds still read the line as it is, so a key's own format shows wherever it sits.
|
|
96
104
|
const SECRET_FILE_PATH = /(?<![\w.-])([\w.-]{0,128}_FILE["']?(?:[ \t]*[:=][ \t]*|[ \t]+)["']?)\/[\w.@-]{1,128}(?:\/[\w.@-]{1,128}){0,32}(?=["']?(?:[\s,;#]|$))/gi;
|
|
105
|
+
// The shell's own working folder (PWD, OLDPWD, as `env`, `declare -x` or a JSON dump shows it) holding a plain path:
|
|
106
|
+
// no password, though PWD reads like one. A key that only ends in PWD (MYSQL_PWD) and a value that is no plain path
|
|
107
|
+
// stay. Taken out before the keyed patterns read the line, as lineage does.
|
|
108
|
+
const SHELL_DIR = /(?<=^[+\- ]?|[\s"'$])((?:OLD)?PWD["']?[ \t]*[:=][ \t]*["']?)(?:~|\/|[A-Za-z]:[\\/])[^\s"'!#$%&*;|<>()[\]{}`^?=]{0,4096}(?=["']?(?:[\s,]|$))/g;
|
|
97
109
|
const KEYED = new Set(["assigned-secret", "assigned-secret-bare", "assigned-password", "env-password", "config-password",
|
|
98
110
|
"dockerfile-env-password"]);
|
|
99
111
|
|
|
100
112
|
const KEY_END = new RegExp(`-----END ${PRIVATE_KEY}`);
|
|
113
|
+
// The mask SKILL.md asks for, as a word of its own. One glued to the rest of a value is no mask: what is left is read.
|
|
114
|
+
const WHOLE_MASK = /(?<!\w)\[REDACTED\](?!\w)/g;
|
|
101
115
|
|
|
102
116
|
// Each line's secret-like kinds, once per kind and line. A private key closes at the first END
|
|
103
117
|
// marker at or after its line: the END lines are found once and walked with the lines, so many
|
|
@@ -109,12 +123,13 @@ export function scan(text) {
|
|
|
109
123
|
let next = 0;
|
|
110
124
|
lines.forEach((line, i) => {
|
|
111
125
|
while (next < ends.length && ends[next] < i + 1) next += 1;
|
|
112
|
-
const
|
|
126
|
+
const plain = line.replace(WHOLE_MASK, "-");
|
|
127
|
+
const keyed = plain.replace(SECRET_FILE_PATH, "$1-").replace(SHELL_DIR, "$1-");
|
|
113
128
|
for (const [kind, re] of PATTERNS) {
|
|
114
|
-
if (!re.test(KEYED.has(kind) ? keyed :
|
|
129
|
+
if (!re.test(KEYED.has(kind) ? keyed : plain)) continue;
|
|
115
130
|
hits.push(kind === "private-key" ? { line: i + 1, kind, end: ends[next] ?? null } : { line: i + 1, kind });
|
|
116
131
|
}
|
|
117
|
-
if (highEntropy(
|
|
132
|
+
if (highEntropy(plain)) hits.push({ line: i + 1, kind: "high-entropy" });
|
|
118
133
|
});
|
|
119
134
|
return { count: hits.length, hits };
|
|
120
135
|
}
|