@kienntpixon/dieu-phoi 0.1.2 → 0.2.1
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/CHANGELOG.md +28 -0
- package/README.md +15 -9
- package/bin/dieu-phoi.cjs +14 -2
- package/hook.cjs +651 -5
- package/lib/aggregate.cjs +68 -2
- package/lib/args.cjs +1 -1
- package/lib/choose.cjs +13 -1
- package/lib/commands.cjs +217 -22
- package/lib/setup.cjs +111 -7
- package/package.json +1 -1
package/lib/setup.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const core = require('../hook.cjs');
|
|
6
7
|
|
|
@@ -70,10 +71,11 @@ function alreadyRegistered(settings, cmd) {
|
|
|
70
71
|
if (h.command !== cmd) continue;
|
|
71
72
|
if (event === 'PostToolUse' && g.matcher === 'Agent') good++;
|
|
72
73
|
else if (event === 'SubagentStop') good++;
|
|
74
|
+
else if (event === 'SessionStart' && g.matcher === 'compact') good++;
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
|
-
return ours ===
|
|
78
|
+
return ours === 3 && good === 3;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
function addOurHooks(settings, hookPath) {
|
|
@@ -84,6 +86,84 @@ function addOurHooks(settings, hookPath) {
|
|
|
84
86
|
h.PostToolUse.push({ matcher: 'Agent', hooks: [{ type: 'command', command: cmd, timeout: 10 }] });
|
|
85
87
|
if (!Array.isArray(h.SubagentStop)) h.SubagentStop = [];
|
|
86
88
|
h.SubagentStop.push({ hooks: [{ type: 'command', command: cmd, timeout: 10 }] });
|
|
89
|
+
// after /compact or auto-compact: re-inject the orchestration state rebuilt from the log
|
|
90
|
+
if (!Array.isArray(h.SessionStart)) h.SessionStart = [];
|
|
91
|
+
h.SessionStart.push({ matcher: 'compact', hooks: [{ type: 'command', command: cmd, timeout: 10 }] });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ------------------------------------------------------------------ run-dir permissions
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run directory for subagent reports: ~/.dieu-phoi/runs (override: DIEU_PHOI_RUNS_DIR, absolute).
|
|
98
|
+
* Deliberately OUTSIDE ~/.claude: `.claude` is a Claude Code protected path, where allow rules never
|
|
99
|
+
* pre-approve writes, so background agents could not write their reports there.
|
|
100
|
+
*/
|
|
101
|
+
function runsDir() {
|
|
102
|
+
return core.runsDir();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Run dir used by 0.2.0 dev builds (<claudeDir>/dieu-phoi/runs); its rules are removed by setup/uninstall. */
|
|
106
|
+
function legacyRunsDirOf(claudeDir) {
|
|
107
|
+
return path.join(path.resolve(claudeDir), 'dieu-phoi', 'runs');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Permission entries that let background subagents (default permission mode) read and write reports in
|
|
112
|
+
* `runs`: Read(<p>/**) + Edit(<p>/**) allow rules (Edit rules cover every file-writing tool; Claude
|
|
113
|
+
* Code ignores path rules on Write and warns about them, so no Write(...) rule) and the dir itself in
|
|
114
|
+
* permissions.additionalDirectories. <p> = ~/<rel> under the home dir, else //<abs> (Windows: //c/...).
|
|
115
|
+
*/
|
|
116
|
+
function permissionEntries(runs, homeDir) {
|
|
117
|
+
const home = path.resolve(homeDir || os.homedir());
|
|
118
|
+
const rel = path.relative(home, runs);
|
|
119
|
+
let pat;
|
|
120
|
+
if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
|
|
121
|
+
pat = `~/${toSlash(rel)}/**`;
|
|
122
|
+
} else {
|
|
123
|
+
const abs = toSlash(runs);
|
|
124
|
+
const m = /^([A-Za-z]):\/(.*)$/.exec(abs);
|
|
125
|
+
pat = m ? `//${m[1].toLowerCase()}/${m[2]}/**` : `/${abs}/**`;
|
|
126
|
+
}
|
|
127
|
+
return { allow: [`Read(${pat})`, `Edit(${pat})`], dir: toSlash(runs) };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Add our permission entries (idempotent). Returns true when settings changed. */
|
|
131
|
+
function addOurPermissions(settings, entries) {
|
|
132
|
+
let changed = false;
|
|
133
|
+
if (!settings.permissions || typeof settings.permissions !== 'object' || Array.isArray(settings.permissions)) {
|
|
134
|
+
settings.permissions = {};
|
|
135
|
+
changed = true;
|
|
136
|
+
}
|
|
137
|
+
const p = settings.permissions;
|
|
138
|
+
if (!Array.isArray(p.allow)) { p.allow = []; changed = true; }
|
|
139
|
+
for (const r of entries.allow) if (!p.allow.includes(r)) { p.allow.push(r); changed = true; }
|
|
140
|
+
if (!Array.isArray(p.additionalDirectories)) { p.additionalDirectories = []; changed = true; }
|
|
141
|
+
if (!p.additionalDirectories.includes(entries.dir)) { p.additionalDirectories.push(entries.dir); changed = true; }
|
|
142
|
+
return changed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Remove exactly our permission entries; a list we emptied is deleted. Returns number removed. */
|
|
146
|
+
function stripOurPermissions(settings, entries) {
|
|
147
|
+
const p = settings.permissions;
|
|
148
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) return 0;
|
|
149
|
+
let removed = 0;
|
|
150
|
+
const strip = (key, items) => {
|
|
151
|
+
if (!Array.isArray(p[key])) return;
|
|
152
|
+
const kept = p[key].filter((x) => !items.includes(x));
|
|
153
|
+
if (kept.length === p[key].length) return;
|
|
154
|
+
removed += p[key].length - kept.length;
|
|
155
|
+
if (kept.length) p[key] = kept; else delete p[key];
|
|
156
|
+
};
|
|
157
|
+
strip('allow', entries.allow);
|
|
158
|
+
strip('additionalDirectories', [entries.dir]);
|
|
159
|
+
return removed;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hasOurPermissions(settings, entries) {
|
|
163
|
+
const p = settings && settings.permissions;
|
|
164
|
+
if (!p || typeof p !== 'object') return false;
|
|
165
|
+
return entries.allow.every((r) => Array.isArray(p.allow) && p.allow.includes(r))
|
|
166
|
+
&& Array.isArray(p.additionalDirectories) && p.additionalDirectories.includes(entries.dir);
|
|
87
167
|
}
|
|
88
168
|
|
|
89
169
|
function readSettings(file) {
|
|
@@ -204,11 +284,20 @@ function setup(opts) {
|
|
|
204
284
|
const cmd = hookCommand(paths.hook);
|
|
205
285
|
let removed = 0;
|
|
206
286
|
let res = { changed: false, backup: null };
|
|
287
|
+
let dirty = false;
|
|
207
288
|
if (!(before.exists && alreadyRegistered(obj, cmd))) {
|
|
208
289
|
removed = stripOurHooks(obj);
|
|
209
290
|
addOurHooks(obj, paths.hook);
|
|
210
|
-
|
|
291
|
+
dirty = true;
|
|
211
292
|
}
|
|
293
|
+
// 4. run dir + permissions so background subagents can write their reports there
|
|
294
|
+
const perms = permissionEntries(runsDir());
|
|
295
|
+
fs.mkdirSync(perms.dir, { recursive: true });
|
|
296
|
+
// rules a 0.2.0 dev build added for <claudeDir>/dieu-phoi/runs (protected path, never worked) go away
|
|
297
|
+
const legacyRemoved = stripOurPermissions(obj, permissionEntries(legacyRunsDirOf(claudeDir)));
|
|
298
|
+
if (legacyRemoved) dirty = true;
|
|
299
|
+
if (addOurPermissions(obj, perms)) dirty = true;
|
|
300
|
+
if (dirty) res = writeSettings(settingsFile, before, obj);
|
|
212
301
|
|
|
213
302
|
return {
|
|
214
303
|
home,
|
|
@@ -218,6 +307,9 @@ function setup(opts) {
|
|
|
218
307
|
settings_changed: res.changed,
|
|
219
308
|
backup: res.backup,
|
|
220
309
|
replaced_entries: removed,
|
|
310
|
+
runs_dir: perms.dir,
|
|
311
|
+
permissions: perms,
|
|
312
|
+
legacy_permissions_removed: legacyRemoved,
|
|
221
313
|
config: cfg,
|
|
222
314
|
};
|
|
223
315
|
}
|
|
@@ -229,29 +321,41 @@ function uninstall(opts) {
|
|
|
229
321
|
const before = readSettings(settingsFile);
|
|
230
322
|
const obj = JSON.parse(JSON.stringify(before.obj));
|
|
231
323
|
const removed = stripOurHooks(obj);
|
|
324
|
+
const permsRemoved = stripOurPermissions(obj, permissionEntries(runsDir()))
|
|
325
|
+
+ stripOurPermissions(obj, permissionEntries(legacyRunsDirOf(claudeDir)));
|
|
232
326
|
let res = { changed: false, backup: null };
|
|
233
|
-
if (removed) res = writeSettings(settingsFile, before, obj);
|
|
327
|
+
if (removed || permsRemoved) res = writeSettings(settingsFile, before, obj);
|
|
234
328
|
const hookFile = core.pathsFor(home).hook;
|
|
235
329
|
let hookRemoved = false;
|
|
236
330
|
if (opts.purge && fs.existsSync(hookFile)) {
|
|
237
331
|
fs.unlinkSync(hookFile);
|
|
238
332
|
hookRemoved = true;
|
|
239
333
|
}
|
|
240
|
-
return { removed, settings: settingsFile, backup: res.backup, hook_removed: hookRemoved, data_kept: core.pathsFor(home).data };
|
|
334
|
+
return { removed, permissions_removed: permsRemoved, settings: settingsFile, backup: res.backup, hook_removed: hookRemoved, data_kept: core.pathsFor(home).data };
|
|
241
335
|
}
|
|
242
336
|
|
|
243
337
|
/** Inspect settings for our registration. */
|
|
244
338
|
function registration(claudeDir) {
|
|
245
339
|
const file = path.join(path.resolve(claudeDir), 'settings.json');
|
|
246
|
-
const
|
|
340
|
+
const perms = permissionEntries(runsDir());
|
|
341
|
+
const legacy = permissionEntries(legacyRunsDirOf(claudeDir));
|
|
342
|
+
const out = {
|
|
343
|
+
file, PostToolUse: false, SubagentStop: false, SessionStart: false, commands: [], permissions: false, permission_entries: perms,
|
|
344
|
+
runs_dir: perms.dir, runs_dir_exists: fs.existsSync(perms.dir), legacy_permissions: false,
|
|
345
|
+
};
|
|
247
346
|
let obj;
|
|
248
347
|
try { obj = readSettings(file).obj; } catch (_) { return out; }
|
|
348
|
+
out.permissions = hasOurPermissions(obj, perms);
|
|
349
|
+
const lp = obj.permissions || {};
|
|
350
|
+
out.legacy_permissions = legacy.allow.some((r) => Array.isArray(lp.allow) && lp.allow.includes(r))
|
|
351
|
+
|| (Array.isArray(lp.additionalDirectories) && lp.additionalDirectories.includes(legacy.dir));
|
|
249
352
|
const hooks = obj.hooks || {};
|
|
250
|
-
for (const ev of ['PostToolUse', 'SubagentStop']) {
|
|
353
|
+
for (const ev of ['PostToolUse', 'SubagentStop', 'SessionStart']) {
|
|
251
354
|
for (const g of Array.isArray(hooks[ev]) ? hooks[ev] : []) {
|
|
252
355
|
for (const h of (g && Array.isArray(g.hooks) ? g.hooks : [])) {
|
|
253
356
|
if (!isOurs(h)) continue;
|
|
254
357
|
if (ev === 'PostToolUse' && g.matcher && !/\bAgent\b/.test(g.matcher)) continue;
|
|
358
|
+
if (ev === 'SessionStart' && g.matcher !== 'compact') continue;
|
|
255
359
|
out[ev] = true;
|
|
256
360
|
out.commands.push(h.command);
|
|
257
361
|
}
|
|
@@ -262,5 +366,5 @@ function registration(claudeDir) {
|
|
|
262
366
|
|
|
263
367
|
module.exports = {
|
|
264
368
|
MARKER, setup, uninstall, registration, stripOurHooks, addOurHooks, alreadyRegistered, hookCommand,
|
|
265
|
-
skillVersionOf, findSkillDir, tsStamp,
|
|
369
|
+
skillVersionOf, findSkillDir, tsStamp, runsDir, legacyRunsDirOf, permissionEntries, addOurPermissions, stripOurPermissions, hasOurPermissions,
|
|
266
370
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kienntpixon/dieu-phoi",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Hook + CLI ghi log subagent của Claude Code và chọn model/effort theo chi phí + thời gian tới lúc nghiệm thu.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kiennt <kiennt@pixon.games>",
|