@hasna/hooks 0.3.11 → 0.4.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/README.md +22 -0
- package/bin/index.js +324 -102
- package/hooks/codewith-native-common.test.ts +364 -12
- package/hooks/codewith-native-common.ts +558 -18
- package/hooks/worktree-guard/README.md +44 -0
- package/hooks/worktree-guard/src/hook.ts +50 -8
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -5346,41 +5346,83 @@ function formatInstallResults(results, extra) {
|
|
|
5346
5346
|
}]
|
|
5347
5347
|
};
|
|
5348
5348
|
}
|
|
5349
|
+
function truncateText(value, max = 160) {
|
|
5350
|
+
const text = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
5351
|
+
if (text.length <= max)
|
|
5352
|
+
return text;
|
|
5353
|
+
return `${text.slice(0, Math.max(0, max - 3))}...`;
|
|
5354
|
+
}
|
|
5355
|
+
function boundedLimit(value, fallback, max) {
|
|
5356
|
+
if (!Number.isFinite(value) || !value || value <= 0)
|
|
5357
|
+
return fallback;
|
|
5358
|
+
return Math.min(Math.floor(value), max);
|
|
5359
|
+
}
|
|
5360
|
+
function compactHook(hook) {
|
|
5361
|
+
return {
|
|
5362
|
+
name: hook.name,
|
|
5363
|
+
event: hook.event,
|
|
5364
|
+
matcher: hook.matcher,
|
|
5365
|
+
category: hook.category
|
|
5366
|
+
};
|
|
5367
|
+
}
|
|
5368
|
+
function compactHookResult(hooks, limit, detail) {
|
|
5369
|
+
const visible = hooks.slice(0, limit).map(compactHook);
|
|
5370
|
+
const omitted = Math.max(0, hooks.length - visible.length);
|
|
5371
|
+
return {
|
|
5372
|
+
hooks: visible,
|
|
5373
|
+
count: visible.length,
|
|
5374
|
+
total: hooks.length,
|
|
5375
|
+
omitted,
|
|
5376
|
+
hint: omitted > 0 ? `Use limit, compact:false, or ${detail} for more detail.` : `Use compact:false or ${detail} for full details.`
|
|
5377
|
+
};
|
|
5378
|
+
}
|
|
5379
|
+
function compactEvent(row) {
|
|
5380
|
+
return {
|
|
5381
|
+
id: row.id,
|
|
5382
|
+
timestamp: row.timestamp,
|
|
5383
|
+
hook_name: row.hook_name,
|
|
5384
|
+
tool_name: row.tool_name ?? undefined,
|
|
5385
|
+
session_id: row.session_id ? String(row.session_id).slice(0, 12) : undefined,
|
|
5386
|
+
error: row.error ? truncateText(row.error, 180) : undefined,
|
|
5387
|
+
tool_input_preview: row.tool_input ? truncateText(row.tool_input, 180) : undefined
|
|
5388
|
+
};
|
|
5389
|
+
}
|
|
5349
5390
|
function createHooksServer() {
|
|
5350
5391
|
const server = new McpServer({
|
|
5351
5392
|
name: "@hasna/hooks",
|
|
5352
5393
|
version: pkg.version
|
|
5353
5394
|
});
|
|
5354
|
-
|
|
5395
|
+
const defineTool = (name, description, schema, handler) => server.tool(name, description, schema, handler);
|
|
5396
|
+
defineTool("hooks_list", "List available hooks. Compact by default; pass compact:false for full HookMeta objects.", {
|
|
5355
5397
|
category: z.string().optional().describe("Filter by category name (e.g. 'Git Safety', 'Code Quality', 'Security')"),
|
|
5356
|
-
compact: z.boolean().default(
|
|
5357
|
-
|
|
5358
|
-
|
|
5398
|
+
compact: z.boolean().default(true).describe("Return compact summaries by default. Set false for full fields."),
|
|
5399
|
+
limit: z.number().default(25).describe("Max compact rows to return")
|
|
5400
|
+
}, async ({ category, compact, limit }) => {
|
|
5401
|
+
const maxRows = boundedLimit(limit, 25, 100);
|
|
5359
5402
|
if (category) {
|
|
5360
5403
|
const cat = CATEGORIES.find((c) => c.toLowerCase() === category.toLowerCase());
|
|
5361
5404
|
if (!cat) {
|
|
5362
5405
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Unknown category: ${category}`, available: [...CATEGORIES] }) }] };
|
|
5363
5406
|
}
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
if (compact) {
|
|
5367
|
-
return { content: [{ type: "text", text: JSON.stringify(slim(HOOKS)) }] };
|
|
5407
|
+
const hooks = getHooksByCategory(cat);
|
|
5408
|
+
return { content: [{ type: "text", text: JSON.stringify(compact ? compactHookResult(hooks, maxRows, "hooks_info") : hooks) }] };
|
|
5368
5409
|
}
|
|
5369
5410
|
const result = {};
|
|
5370
5411
|
for (const cat of CATEGORIES) {
|
|
5371
5412
|
result[cat] = getHooksByCategory(cat);
|
|
5372
5413
|
}
|
|
5373
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
5414
|
+
return { content: [{ type: "text", text: JSON.stringify(compact ? compactHookResult(HOOKS, maxRows, "hooks_info") : result) }] };
|
|
5374
5415
|
});
|
|
5375
|
-
|
|
5416
|
+
defineTool("hooks_search", "Search hooks. Compact by default; pass compact:false for full HookMeta objects.", {
|
|
5376
5417
|
query: z.string().describe("Search query"),
|
|
5377
|
-
compact: z.boolean().default(
|
|
5378
|
-
|
|
5418
|
+
compact: z.boolean().default(true).describe("Return compact summaries by default. Set false for full fields."),
|
|
5419
|
+
limit: z.number().default(10).describe("Max compact rows to return")
|
|
5420
|
+
}, async ({ query, compact, limit }) => {
|
|
5379
5421
|
const results = searchHooks(query);
|
|
5380
|
-
const out = compact ? results
|
|
5422
|
+
const out = compact ? compactHookResult(results, boundedLimit(limit, 10, 100), "hooks_info") : results;
|
|
5381
5423
|
return { content: [{ type: "text", text: JSON.stringify(out) }] };
|
|
5382
5424
|
});
|
|
5383
|
-
|
|
5425
|
+
defineTool("hooks_info", "Get detailed information about a specific hook including install status", { name: z.string().describe("Hook name (e.g. 'gitguard', 'checkpoint')") }, async ({ name }) => {
|
|
5384
5426
|
const meta = getHook(name);
|
|
5385
5427
|
if (!meta) {
|
|
5386
5428
|
return { content: [{ type: "text", text: JSON.stringify({ error: `Hook '${name}' not found` }) }] };
|
|
@@ -5389,7 +5431,7 @@ function createHooksServer() {
|
|
|
5389
5431
|
const projectInstalled = getRegisteredHooks("project").includes(meta.name);
|
|
5390
5432
|
return { content: [{ type: "text", text: JSON.stringify({ ...meta, global: globalInstalled, project: projectInstalled }) }] };
|
|
5391
5433
|
});
|
|
5392
|
-
|
|
5434
|
+
defineTool("hooks_install", "Install one or more hooks by registering them in agent settings", {
|
|
5393
5435
|
hooks: z.array(z.string()).describe("Hook names to install"),
|
|
5394
5436
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
5395
5437
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed"),
|
|
@@ -5398,7 +5440,7 @@ function createHooksServer() {
|
|
|
5398
5440
|
const results = hooks.map((name) => installHook(name, { scope, overwrite, profile }));
|
|
5399
5441
|
return formatInstallResults(results, { scope, profile });
|
|
5400
5442
|
});
|
|
5401
|
-
|
|
5443
|
+
defineTool("hooks_install_category", "Install all hooks in a category", {
|
|
5402
5444
|
category: z.string().describe("Category name"),
|
|
5403
5445
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
5404
5446
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
@@ -5411,21 +5453,21 @@ function createHooksServer() {
|
|
|
5411
5453
|
const results = hooks.map((name) => installHook(name, { scope, overwrite }));
|
|
5412
5454
|
return formatInstallResults(results, { category: cat, scope });
|
|
5413
5455
|
});
|
|
5414
|
-
|
|
5456
|
+
defineTool("hooks_install_all", "Install all available hooks", {
|
|
5415
5457
|
scope: z.enum(["global", "project"]).default("global").describe("Install scope"),
|
|
5416
5458
|
overwrite: z.boolean().default(false).describe("Overwrite if already installed")
|
|
5417
5459
|
}, async ({ scope, overwrite }) => {
|
|
5418
5460
|
const results = HOOKS.map((h) => installHook(h.name, { scope, overwrite }));
|
|
5419
5461
|
return formatInstallResults(results, { scope });
|
|
5420
5462
|
});
|
|
5421
|
-
|
|
5463
|
+
defineTool("hooks_remove", "Remove (unregister) a hook from agent settings", {
|
|
5422
5464
|
name: z.string().describe("Hook name to remove"),
|
|
5423
5465
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to remove from")
|
|
5424
5466
|
}, async ({ name, scope }) => {
|
|
5425
5467
|
const removed = removeHook(name, scope);
|
|
5426
5468
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, removed, scope }) }] };
|
|
5427
5469
|
});
|
|
5428
|
-
|
|
5470
|
+
defineTool("hooks_doctor", "Check health of installed hooks \u2014 verifies hook source exists, settings are correct", {
|
|
5429
5471
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to check")
|
|
5430
5472
|
}, async ({ scope }) => {
|
|
5431
5473
|
const settingsPath = getSettingsPath(scope);
|
|
@@ -5467,14 +5509,17 @@ function createHooksServer() {
|
|
|
5467
5509
|
}
|
|
5468
5510
|
return { content: [{ type: "text", text: JSON.stringify({ healthy: issues.length === 0, healthy_hooks: healthy, issues, registered, scope }) }] };
|
|
5469
5511
|
});
|
|
5470
|
-
|
|
5512
|
+
defineTool("hooks_categories", "List all hook categories with counts", {}, async () => {
|
|
5471
5513
|
const result = CATEGORIES.map((cat) => ({
|
|
5472
5514
|
name: cat,
|
|
5473
5515
|
count: getHooksByCategory(cat).length
|
|
5474
5516
|
}));
|
|
5475
5517
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
5476
5518
|
});
|
|
5477
|
-
|
|
5519
|
+
defineTool("hooks_docs", "Get documentation. Hook README content is summarized by default; pass verbose:true for the full README.", {
|
|
5520
|
+
name: z.string().optional().describe("Hook name for specific docs, omit for general docs"),
|
|
5521
|
+
verbose: z.boolean().default(false).describe("Return full hook README when true")
|
|
5522
|
+
}, async ({ name, verbose }) => {
|
|
5478
5523
|
if (name) {
|
|
5479
5524
|
const meta = getHook(name);
|
|
5480
5525
|
if (!meta) {
|
|
@@ -5486,7 +5531,24 @@ function createHooksServer() {
|
|
|
5486
5531
|
if (existsSync6(readmePath)) {
|
|
5487
5532
|
readme = readFileSync4(readmePath, "utf-8");
|
|
5488
5533
|
}
|
|
5489
|
-
|
|
5534
|
+
if (verbose) {
|
|
5535
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...meta, readme }) }] };
|
|
5536
|
+
}
|
|
5537
|
+
return {
|
|
5538
|
+
content: [{
|
|
5539
|
+
type: "text",
|
|
5540
|
+
text: JSON.stringify({
|
|
5541
|
+
...compactHook(meta),
|
|
5542
|
+
displayName: meta.displayName,
|
|
5543
|
+
version: meta.version,
|
|
5544
|
+
description: truncateText(meta.description, 220),
|
|
5545
|
+
readme_preview: truncateText(readme, 500),
|
|
5546
|
+
readme_lines: readme ? readme.split(`
|
|
5547
|
+
`).length : 0,
|
|
5548
|
+
hint: "Call hooks_docs with verbose:true for the full README or hooks_info for metadata."
|
|
5549
|
+
})
|
|
5550
|
+
}]
|
|
5551
|
+
};
|
|
5490
5552
|
}
|
|
5491
5553
|
return {
|
|
5492
5554
|
content: [{
|
|
@@ -5514,17 +5576,39 @@ function createHooksServer() {
|
|
|
5514
5576
|
}]
|
|
5515
5577
|
};
|
|
5516
5578
|
});
|
|
5517
|
-
|
|
5518
|
-
scope: z.enum(["global", "project"]).default("global").describe("Scope to check")
|
|
5519
|
-
|
|
5579
|
+
defineTool("hooks_registered", "Get currently registered hooks for a scope. Compact by default.", {
|
|
5580
|
+
scope: z.enum(["global", "project"]).default("global").describe("Scope to check"),
|
|
5581
|
+
compact: z.boolean().default(true).describe("Return compact summaries by default. Set false for descriptions."),
|
|
5582
|
+
limit: z.number().default(25).describe("Max compact rows to return")
|
|
5583
|
+
}, async ({ scope, compact, limit }) => {
|
|
5520
5584
|
const registered = getRegisteredHooks(scope);
|
|
5521
5585
|
const result = registered.map((name) => {
|
|
5522
5586
|
const meta = getHook(name);
|
|
5523
5587
|
return { name, event: meta?.event, matcher: meta?.matcher ?? "", version: meta?.version, description: meta?.description };
|
|
5524
5588
|
});
|
|
5525
|
-
|
|
5589
|
+
if (!compact)
|
|
5590
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
5591
|
+
const maxRows = boundedLimit(limit, 25, 100);
|
|
5592
|
+
const visible = result.slice(0, maxRows).map((hook) => ({
|
|
5593
|
+
name: hook.name,
|
|
5594
|
+
event: hook.event,
|
|
5595
|
+
matcher: hook.matcher,
|
|
5596
|
+
version: hook.version
|
|
5597
|
+
}));
|
|
5598
|
+
return {
|
|
5599
|
+
content: [{
|
|
5600
|
+
type: "text",
|
|
5601
|
+
text: JSON.stringify({
|
|
5602
|
+
hooks: visible,
|
|
5603
|
+
count: visible.length,
|
|
5604
|
+
total: result.length,
|
|
5605
|
+
omitted: Math.max(0, result.length - visible.length),
|
|
5606
|
+
hint: "Use compact:false for descriptions or hooks_info for one hook."
|
|
5607
|
+
})
|
|
5608
|
+
}]
|
|
5609
|
+
};
|
|
5526
5610
|
});
|
|
5527
|
-
|
|
5611
|
+
defineTool("hooks_run", "Execute a hook programmatically with the given input and return its output", {
|
|
5528
5612
|
name: z.string().describe("Hook name (e.g. 'gitguard', 'checkpoint')"),
|
|
5529
5613
|
input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Hook input as JSON object (HookInput)"),
|
|
5530
5614
|
profile: z.string().optional().describe("Agent profile ID to inject into hook input"),
|
|
@@ -5588,7 +5672,7 @@ function createHooksServer() {
|
|
|
5588
5672
|
}]
|
|
5589
5673
|
};
|
|
5590
5674
|
});
|
|
5591
|
-
|
|
5675
|
+
defineTool("hooks_update", "Re-register installed hooks to pick up new package version (reinstalls with overwrite)", {
|
|
5592
5676
|
hooks: z.array(z.string()).optional().describe("Hook names to update (omit to update all installed hooks)"),
|
|
5593
5677
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to update")
|
|
5594
5678
|
}, async ({ hooks, scope }) => {
|
|
@@ -5607,10 +5691,11 @@ function createHooksServer() {
|
|
|
5607
5691
|
const failed = results.filter((r) => !r.success).map((r) => ({ hook: r.hook, error: r.error }));
|
|
5608
5692
|
return { content: [{ type: "text", text: JSON.stringify({ updated, failed, total: results.length }) }] };
|
|
5609
5693
|
});
|
|
5610
|
-
|
|
5694
|
+
defineTool("hooks_context", "Get compact agent context in one call: installed hooks, active profile summary, settings path, and doctor status.", {
|
|
5611
5695
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to inspect"),
|
|
5612
|
-
profile: z.string().optional().describe("Agent profile ID to include in context")
|
|
5613
|
-
|
|
5696
|
+
profile: z.string().optional().describe("Agent profile ID to include in context"),
|
|
5697
|
+
verbose: z.boolean().default(false).describe("Include full profile preferences/details when true")
|
|
5698
|
+
}, async ({ scope, profile, verbose }) => {
|
|
5614
5699
|
const settingsPath = getSettingsPath(scope);
|
|
5615
5700
|
const registered = getRegisteredHooks(scope);
|
|
5616
5701
|
const hooks = registered.map((name) => {
|
|
@@ -5636,11 +5721,13 @@ function createHooksServer() {
|
|
|
5636
5721
|
};
|
|
5637
5722
|
if (profile) {
|
|
5638
5723
|
const p = getProfile(profile);
|
|
5639
|
-
ctx.profile = p ?? null;
|
|
5724
|
+
ctx.profile = p && !verbose ? { agent_id: p.agent_id, agent_type: p.agent_type, name: p.name } : p ?? null;
|
|
5725
|
+
if (p && !verbose)
|
|
5726
|
+
ctx.profile_hint = "Use verbose:true for profile preferences.";
|
|
5640
5727
|
}
|
|
5641
5728
|
return { content: [{ type: "text", text: JSON.stringify(ctx) }] };
|
|
5642
5729
|
});
|
|
5643
|
-
|
|
5730
|
+
defineTool("hooks_preview", "Simulate which installed PreToolUse hooks would fire for a given tool call and what decision each returns. Use this to understand your hook environment before taking an action.", {
|
|
5644
5731
|
tool_name: z.string().describe("Tool name to simulate (e.g. 'Bash', 'Write', 'Edit')"),
|
|
5645
5732
|
tool_input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Tool input to pass to matching hooks"),
|
|
5646
5733
|
scope: z.enum(["global", "project"]).default("global").describe("Scope to check"),
|
|
@@ -5705,7 +5792,7 @@ function createHooksServer() {
|
|
|
5705
5792
|
}]
|
|
5706
5793
|
};
|
|
5707
5794
|
});
|
|
5708
|
-
|
|
5795
|
+
defineTool("hooks_setup", "Single-shot agent onboarding: create an agent profile + install recommended hooks in one call. Ideal for agents setting up hooks at session start.", {
|
|
5709
5796
|
agent_type: z.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|
|
5710
5797
|
name: z.string().optional().describe("Optional display name for the agent"),
|
|
5711
5798
|
hooks: z.array(z.string()).optional().describe("Hook names to install (omit for sensible defaults: gitguard, checkpoint, checktests, protectfiles)"),
|
|
@@ -5723,7 +5810,7 @@ function createHooksServer() {
|
|
|
5723
5810
|
}]
|
|
5724
5811
|
};
|
|
5725
5812
|
});
|
|
5726
|
-
|
|
5813
|
+
defineTool("hooks_batch_run", "Run multiple hooks in parallel in a single call. Returns all results at once \u2014 more efficient than N separate hooks_run calls.", {
|
|
5727
5814
|
hooks: z.array(z.object({
|
|
5728
5815
|
name: z.string().describe("Hook name"),
|
|
5729
5816
|
input: z.record(z.string(), z.unknown()).default(() => ({})).describe("Hook input JSON")
|
|
@@ -5761,7 +5848,7 @@ function createHooksServer() {
|
|
|
5761
5848
|
}));
|
|
5762
5849
|
return { content: [{ type: "text", text: JSON.stringify({ results, count: results.length }) }] };
|
|
5763
5850
|
});
|
|
5764
|
-
|
|
5851
|
+
defineTool("hooks_disable", "Temporarily disable a registered hook without removing it. Stores disabled list in settings under hooks.__disabled.", {
|
|
5765
5852
|
name: z.string().describe("Hook name to disable"),
|
|
5766
5853
|
scope: z.enum(["global", "project"]).default("global").describe("Scope")
|
|
5767
5854
|
}, async ({ name, scope }) => {
|
|
@@ -5784,7 +5871,7 @@ function createHooksServer() {
|
|
|
5784
5871
|
`);
|
|
5785
5872
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: true, scope }) }] };
|
|
5786
5873
|
});
|
|
5787
|
-
|
|
5874
|
+
defineTool("hooks_enable", "Re-enable a previously disabled hook.", {
|
|
5788
5875
|
name: z.string().describe("Hook name to enable"),
|
|
5789
5876
|
scope: z.enum(["global", "project"]).default("global").describe("Scope")
|
|
5790
5877
|
}, async ({ name, scope }) => {
|
|
@@ -5804,25 +5891,50 @@ function createHooksServer() {
|
|
|
5804
5891
|
}
|
|
5805
5892
|
return { content: [{ type: "text", text: JSON.stringify({ hook: name, disabled: false, scope }) }] };
|
|
5806
5893
|
});
|
|
5807
|
-
|
|
5894
|
+
defineTool("hooks_init", "Register a new agent profile \u2014 returns a unique agent_id for use with hook installation and execution", {
|
|
5808
5895
|
agent_type: z.enum(["claude", "gemini", "custom"]).default("claude").describe("Type of AI agent"),
|
|
5809
5896
|
name: z.string().optional().describe("Optional display name for the agent")
|
|
5810
5897
|
}, async ({ agent_type, name }) => {
|
|
5811
5898
|
const profile = createProfile({ agent_type, name });
|
|
5812
5899
|
return { content: [{ type: "text", text: JSON.stringify(profile) }] };
|
|
5813
5900
|
});
|
|
5814
|
-
|
|
5901
|
+
defineTool("hooks_profiles", "List registered agent profiles. Compact by default.", {
|
|
5902
|
+
compact: z.boolean().default(true).describe("Return compact summaries by default. Set false for full profiles."),
|
|
5903
|
+
limit: z.number().default(25).describe("Max compact rows to return")
|
|
5904
|
+
}, async ({ compact, limit }) => {
|
|
5815
5905
|
const profiles = listProfiles();
|
|
5816
|
-
|
|
5906
|
+
if (!compact)
|
|
5907
|
+
return { content: [{ type: "text", text: JSON.stringify(profiles) }] };
|
|
5908
|
+
const maxRows = boundedLimit(limit, 25, 100);
|
|
5909
|
+
const visible = profiles.slice(0, maxRows).map((profile) => ({
|
|
5910
|
+
agent_id: profile.agent_id,
|
|
5911
|
+
agent_type: profile.agent_type,
|
|
5912
|
+
name: profile.name,
|
|
5913
|
+
last_seen_at: profile.last_seen_at
|
|
5914
|
+
}));
|
|
5915
|
+
return {
|
|
5916
|
+
content: [{
|
|
5917
|
+
type: "text",
|
|
5918
|
+
text: JSON.stringify({
|
|
5919
|
+
profiles: visible,
|
|
5920
|
+
count: visible.length,
|
|
5921
|
+
total: profiles.length,
|
|
5922
|
+
omitted: Math.max(0, profiles.length - visible.length),
|
|
5923
|
+
hint: "Use compact:false for full profile preferences."
|
|
5924
|
+
})
|
|
5925
|
+
}]
|
|
5926
|
+
};
|
|
5817
5927
|
});
|
|
5818
|
-
|
|
5928
|
+
defineTool("hooks_log_list", "List hook events from SQLite. Compact summaries by default; set compact:false for full event rows.", {
|
|
5819
5929
|
hook_name: z.string().optional().describe("Filter by hook name (e.g. 'sessionlog', 'costwatch')"),
|
|
5820
5930
|
session_id: z.string().optional().describe("Filter by session ID prefix"),
|
|
5821
|
-
limit: z.number().
|
|
5822
|
-
since: z.string().optional().describe("ISO timestamp or duration string (e.g. '1h', '30m', '7d') to filter from")
|
|
5823
|
-
|
|
5931
|
+
limit: z.number().optional().describe("Max number of events to return. Defaults to 20 compact rows or 50 full rows."),
|
|
5932
|
+
since: z.string().optional().describe("ISO timestamp or duration string (e.g. '1h', '30m', '7d') to filter from"),
|
|
5933
|
+
compact: z.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
|
|
5934
|
+
}, async ({ hook_name, session_id, limit, since, compact }) => {
|
|
5824
5935
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
5825
5936
|
const db = getDb2();
|
|
5937
|
+
const maxRows = boundedLimit(limit, compact ? 20 : 50, compact ? 100 : 500);
|
|
5826
5938
|
function parseDuration(s) {
|
|
5827
5939
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
5828
5940
|
if (!m)
|
|
@@ -5849,24 +5961,48 @@ function createHooksServer() {
|
|
|
5849
5961
|
}
|
|
5850
5962
|
}
|
|
5851
5963
|
sql += " ORDER BY timestamp DESC LIMIT ?";
|
|
5852
|
-
params.push(
|
|
5964
|
+
params.push(maxRows);
|
|
5853
5965
|
const rows = db.query(sql).all(...params);
|
|
5854
|
-
return {
|
|
5966
|
+
return {
|
|
5967
|
+
content: [{
|
|
5968
|
+
type: "text",
|
|
5969
|
+
text: JSON.stringify({
|
|
5970
|
+
events: compact ? rows.map(compactEvent) : rows,
|
|
5971
|
+
count: rows.length,
|
|
5972
|
+
compact,
|
|
5973
|
+
hint: compact ? "Use compact:false for full tool_input/output fields." : undefined
|
|
5974
|
+
})
|
|
5975
|
+
}]
|
|
5976
|
+
};
|
|
5855
5977
|
});
|
|
5856
|
-
|
|
5857
|
-
n: z.number().default(20).describe("Number of most recent events to return")
|
|
5858
|
-
|
|
5978
|
+
defineTool("hooks_log_tail", "Show recent hook events from SQLite. Compact summaries by default.", {
|
|
5979
|
+
n: z.number().default(20).describe("Number of most recent events to return"),
|
|
5980
|
+
compact: z.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
|
|
5981
|
+
}, async ({ n, compact }) => {
|
|
5859
5982
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
5860
5983
|
const db = getDb2();
|
|
5861
|
-
const
|
|
5862
|
-
|
|
5984
|
+
const maxRows = boundedLimit(n, 20, compact ? 100 : 500);
|
|
5985
|
+
const rows = db.query("SELECT * FROM hook_events ORDER BY timestamp DESC LIMIT ?").all(maxRows);
|
|
5986
|
+
return {
|
|
5987
|
+
content: [{
|
|
5988
|
+
type: "text",
|
|
5989
|
+
text: JSON.stringify({
|
|
5990
|
+
events: compact ? rows.map(compactEvent) : rows,
|
|
5991
|
+
count: rows.length,
|
|
5992
|
+
compact,
|
|
5993
|
+
hint: compact ? "Use compact:false for full tool_input/output fields." : undefined
|
|
5994
|
+
})
|
|
5995
|
+
}]
|
|
5996
|
+
};
|
|
5863
5997
|
});
|
|
5864
|
-
|
|
5998
|
+
defineTool("hooks_log_errors", "Show hook events that contain errors. Compact summaries by default.", {
|
|
5865
5999
|
since: z.string().default("24h").describe("Duration string (e.g. '1h', '30m', '7d') or ISO timestamp"),
|
|
5866
|
-
limit: z.number().
|
|
5867
|
-
|
|
6000
|
+
limit: z.number().optional().describe("Max number of error events to return. Defaults to 20 compact rows or 50 full rows."),
|
|
6001
|
+
compact: z.boolean().default(true).describe("Return compact event summaries by default. Set false for full rows.")
|
|
6002
|
+
}, async ({ since, limit, compact }) => {
|
|
5868
6003
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
5869
6004
|
const db = getDb2();
|
|
6005
|
+
const maxRows = boundedLimit(limit, compact ? 20 : 50, compact ? 100 : 500);
|
|
5870
6006
|
function parseDuration(s) {
|
|
5871
6007
|
const m = s.match(/^(\d+)(s|m|h|d)$/);
|
|
5872
6008
|
if (!m)
|
|
@@ -5876,10 +6012,20 @@ function createHooksServer() {
|
|
|
5876
6012
|
return new Date(Date.now() - n * ms).toISOString();
|
|
5877
6013
|
}
|
|
5878
6014
|
const ts = since.match(/^\d{4}/) ? since : parseDuration(since);
|
|
5879
|
-
const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts,
|
|
5880
|
-
return {
|
|
6015
|
+
const rows = db.query("SELECT * FROM hook_events WHERE error IS NOT NULL AND timestamp >= ? ORDER BY timestamp DESC LIMIT ?").all(ts, maxRows);
|
|
6016
|
+
return {
|
|
6017
|
+
content: [{
|
|
6018
|
+
type: "text",
|
|
6019
|
+
text: JSON.stringify({
|
|
6020
|
+
events: compact ? rows.map(compactEvent) : rows,
|
|
6021
|
+
count: rows.length,
|
|
6022
|
+
compact,
|
|
6023
|
+
hint: compact ? "Use compact:false for full tool_input/output fields." : undefined
|
|
6024
|
+
})
|
|
6025
|
+
}]
|
|
6026
|
+
};
|
|
5881
6027
|
});
|
|
5882
|
-
|
|
6028
|
+
defineTool("hooks_log_summary", "Summarize hook execution: counts per hook, error rates, and recent activity.", {
|
|
5883
6029
|
since: z.string().default("24h").describe("Duration string (e.g. '1h', '24h', '7d') or ISO timestamp")
|
|
5884
6030
|
}, async ({ since }) => {
|
|
5885
6031
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
@@ -5913,11 +6059,11 @@ function createHooksServer() {
|
|
|
5913
6059
|
}]
|
|
5914
6060
|
};
|
|
5915
6061
|
});
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6062
|
+
defineTool("storage_status", "Show hooks storage sync configuration and local sync history.", {}, async () => ({ content: [{ type: "text", text: JSON.stringify(getStorageStatus()) }] }));
|
|
6063
|
+
defineTool("storage_push", "Push local hook data to storage PostgreSQL.", { tables: z.array(z.string()).optional() }, async (params) => ({ content: [{ type: "text", text: JSON.stringify(await storagePush(params.tables ? { tables: params.tables } : undefined)) }] }));
|
|
6064
|
+
defineTool("storage_pull", "Pull hook data from storage PostgreSQL to local SQLite.", { tables: z.array(z.string()).optional() }, async (params) => ({ content: [{ type: "text", text: JSON.stringify(await storagePull(params.tables ? { tables: params.tables } : undefined)) }] }));
|
|
6065
|
+
defineTool("storage_sync", "Bidirectional hooks storage sync: pull then push.", { tables: z.array(z.string()).optional() }, async (params) => ({ content: [{ type: "text", text: JSON.stringify(await storageSync(params.tables ? { tables: params.tables } : undefined)) }] }));
|
|
6066
|
+
defineTool("send_feedback", "Send feedback about this service", {
|
|
5921
6067
|
message: z.string(),
|
|
5922
6068
|
email: z.string().optional(),
|
|
5923
6069
|
category: z.enum(["bug", "feature", "general"]).optional()
|
|
@@ -5936,7 +6082,7 @@ function createHooksServer() {
|
|
|
5936
6082
|
return { content: [{ type: "text", text: String(e) }], isError: true };
|
|
5937
6083
|
}
|
|
5938
6084
|
});
|
|
5939
|
-
|
|
6085
|
+
defineTool("register_agent", "Register an agent session. Returns agent_id. Auto-triggers a heartbeat.", {
|
|
5940
6086
|
name: z.string(),
|
|
5941
6087
|
session_id: z.string().optional()
|
|
5942
6088
|
}, async (params) => {
|
|
@@ -5952,7 +6098,7 @@ function createHooksServer() {
|
|
|
5952
6098
|
_hooksAgents.set(id, ag);
|
|
5953
6099
|
return { content: [{ type: "text", text: JSON.stringify(ag) }] };
|
|
5954
6100
|
});
|
|
5955
|
-
|
|
6101
|
+
defineTool("heartbeat", "Update last_seen_at to signal agent is active.", {
|
|
5956
6102
|
agent_id: z.string()
|
|
5957
6103
|
}, async (params) => {
|
|
5958
6104
|
const ag = _hooksAgents.get(params.agent_id);
|
|
@@ -5961,7 +6107,7 @@ function createHooksServer() {
|
|
|
5961
6107
|
ag.last_seen_at = new Date().toISOString();
|
|
5962
6108
|
return { content: [{ type: "text", text: JSON.stringify({ agent_id: ag.id, last_seen_at: ag.last_seen_at }) }] };
|
|
5963
6109
|
});
|
|
5964
|
-
|
|
6110
|
+
defineTool("set_focus", "Set active project context for this agent session.", {
|
|
5965
6111
|
agent_id: z.string(),
|
|
5966
6112
|
project_id: z.string().optional()
|
|
5967
6113
|
}, async (params) => {
|
|
@@ -5971,8 +6117,32 @@ function createHooksServer() {
|
|
|
5971
6117
|
ag.project_id = params.project_id;
|
|
5972
6118
|
return { content: [{ type: "text", text: JSON.stringify({ agent_id: ag.id, project_id: ag.project_id ?? null }) }] };
|
|
5973
6119
|
});
|
|
5974
|
-
|
|
5975
|
-
|
|
6120
|
+
defineTool("list_agents", "List registered agents. Compact by default.", {
|
|
6121
|
+
compact: z.boolean().default(true).describe("Return compact agent summaries by default. Set false for full records."),
|
|
6122
|
+
limit: z.number().default(25).describe("Max compact rows to return")
|
|
6123
|
+
}, async ({ compact, limit }) => {
|
|
6124
|
+
const agents = [..._hooksAgents.values()];
|
|
6125
|
+
if (!compact)
|
|
6126
|
+
return { content: [{ type: "text", text: JSON.stringify(agents) }] };
|
|
6127
|
+
const maxRows = boundedLimit(limit, 25, 100);
|
|
6128
|
+
const visible = agents.slice(0, maxRows).map((agent) => ({
|
|
6129
|
+
id: agent.id,
|
|
6130
|
+
name: agent.name,
|
|
6131
|
+
project_id: agent.project_id,
|
|
6132
|
+
last_seen_at: agent.last_seen_at
|
|
6133
|
+
}));
|
|
6134
|
+
return {
|
|
6135
|
+
content: [{
|
|
6136
|
+
type: "text",
|
|
6137
|
+
text: JSON.stringify({
|
|
6138
|
+
agents: visible,
|
|
6139
|
+
count: visible.length,
|
|
6140
|
+
total: agents.length,
|
|
6141
|
+
omitted: Math.max(0, agents.length - visible.length),
|
|
6142
|
+
hint: "Use compact:false for session_id and full records."
|
|
6143
|
+
})
|
|
6144
|
+
}]
|
|
6145
|
+
};
|
|
5976
6146
|
});
|
|
5977
6147
|
return server;
|
|
5978
6148
|
}
|
|
@@ -8208,6 +8378,39 @@ function resolveTarget(options) {
|
|
|
8208
8378
|
return "all";
|
|
8209
8379
|
return "claude";
|
|
8210
8380
|
}
|
|
8381
|
+
function parseLimit(value, fallback, max) {
|
|
8382
|
+
const parsed = value ? parseInt(value, 10) : fallback;
|
|
8383
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
8384
|
+
return fallback;
|
|
8385
|
+
return Math.min(parsed, max);
|
|
8386
|
+
}
|
|
8387
|
+
function truncateText2(value, max = 96) {
|
|
8388
|
+
const text = (value ?? "").replace(/\s+/g, " ").trim();
|
|
8389
|
+
if (text.length <= max)
|
|
8390
|
+
return text;
|
|
8391
|
+
return `${text.slice(0, Math.max(0, max - 3))}...`;
|
|
8392
|
+
}
|
|
8393
|
+
function readmePreview(readme, max = 280) {
|
|
8394
|
+
const blocks = readme.split(/\n\s*\n/).map((part) => part.trim()).filter(Boolean).filter((part) => !part.startsWith("#")).filter((part) => !part.startsWith("```")).filter((part) => !/^\[!\[/.test(part));
|
|
8395
|
+
const preview = blocks[0] ?? readme.split(`
|
|
8396
|
+
`).map((line) => line.trim()).find((line) => line && !line.startsWith("#"));
|
|
8397
|
+
return preview ? truncateText2(preview, max) : undefined;
|
|
8398
|
+
}
|
|
8399
|
+
function hookSummaryLine(hook, options = {}) {
|
|
8400
|
+
const matcher = hook.matcher ? ` ${hook.matcher}` : "";
|
|
8401
|
+
const description = options.verbose ? ` - ${truncateText2(hook.description, 110)}` : "";
|
|
8402
|
+
return ` ${chalk2.cyan(hook.name.padEnd(17))} ${chalk2.dim(`[${hook.event}${matcher}]`)} ${chalk2.dim(hook.category)}${description}`;
|
|
8403
|
+
}
|
|
8404
|
+
function printDisclosureHint(hidden, detailCommand, options = {}) {
|
|
8405
|
+
const rowControls = options.includeAll ? "--limit, --all, --verbose" : "--limit, --verbose";
|
|
8406
|
+
if (hidden > 0) {
|
|
8407
|
+
console.log(chalk2.dim(`
|
|
8408
|
+
Showing a compact subset. ${hidden} more hidden; use ${rowControls}, or ${detailCommand}.`));
|
|
8409
|
+
} else {
|
|
8410
|
+
console.log(chalk2.dim(`
|
|
8411
|
+
Use --verbose or ${detailCommand} for details.`));
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8211
8414
|
function editDistance(a, b) {
|
|
8212
8415
|
const m = a.length, n = b.length;
|
|
8213
8416
|
const dp = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
|
|
@@ -8417,8 +8620,9 @@ Installing hooks (${scope}, ${target})...
|
|
|
8417
8620
|
console.log(chalk2.dim(`
|
|
8418
8621
|
Registered in ${settingsFile}`));
|
|
8419
8622
|
});
|
|
8420
|
-
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("List available or installed hooks").action((options) => {
|
|
8623
|
+
program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available hooks", false).option("-i, --installed", "Show only installed hooks", false).option("-r, --registered", "Show registered hooks", false).option("-g, --global", "Check global settings", false).option("-p, --project", "Check project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith (default: claude)", "claude").option("-n, --limit <n>", "Max rows to show in compact output", "20").option("--verbose", "Show descriptions and full detail columns", false).option("-j, --json", "Output as JSON", false).description("List available or installed hooks").action((options) => {
|
|
8421
8624
|
const scope = resolveScope(options);
|
|
8625
|
+
const limit = options.all ? Number.MAX_SAFE_INTEGER : parseLimit(options.limit, 20, 200);
|
|
8422
8626
|
if (options.registered || options.installed) {
|
|
8423
8627
|
const target = options.target === "gemini" ? "gemini" : options.target === "codewith" ? "codewith" : "claude";
|
|
8424
8628
|
const registered = getRegisteredHooksForTarget(scope, target);
|
|
@@ -8433,13 +8637,18 @@ program2.command("list").alias("ls").option("-c, --category <category>", "Filter
|
|
|
8433
8637
|
console.log(chalk2.dim(`No hooks registered (${scope}, ${target})`));
|
|
8434
8638
|
return;
|
|
8435
8639
|
}
|
|
8640
|
+
const visible2 = registered.slice(0, limit);
|
|
8436
8641
|
console.log(chalk2.bold(`
|
|
8437
|
-
Registered hooks \u2014 ${scope}/${target} (${registered.length}):
|
|
8642
|
+
Registered hooks \u2014 ${scope}/${target} (${registered.length}, showing ${visible2.length}):
|
|
8438
8643
|
`));
|
|
8439
|
-
for (const name of
|
|
8644
|
+
for (const name of visible2) {
|
|
8440
8645
|
const meta = getHook(name);
|
|
8441
|
-
|
|
8646
|
+
if (meta)
|
|
8647
|
+
console.log(hookSummaryLine(meta, { verbose: options.verbose }));
|
|
8648
|
+
else
|
|
8649
|
+
console.log(` ${chalk2.cyan(name)} ${chalk2.dim("[unknown]")}`);
|
|
8442
8650
|
}
|
|
8651
|
+
printDisclosureHint(registered.length - visible2.length, "hooks info <name>", { includeAll: true });
|
|
8443
8652
|
return;
|
|
8444
8653
|
}
|
|
8445
8654
|
if (options.category) {
|
|
@@ -8458,12 +8667,13 @@ Registered hooks \u2014 ${scope}/${target} (${registered.length}):
|
|
|
8458
8667
|
console.log(JSON.stringify(hooks));
|
|
8459
8668
|
return;
|
|
8460
8669
|
}
|
|
8670
|
+
const visible2 = hooks.slice(0, limit);
|
|
8461
8671
|
console.log(chalk2.bold(`
|
|
8462
|
-
${category} (${hooks.length}):
|
|
8672
|
+
${category} (${hooks.length}, showing ${visible2.length}):
|
|
8463
8673
|
`));
|
|
8464
|
-
for (const h of
|
|
8465
|
-
console.log(
|
|
8466
|
-
}
|
|
8674
|
+
for (const h of visible2)
|
|
8675
|
+
console.log(hookSummaryLine(h, { verbose: options.verbose }));
|
|
8676
|
+
printDisclosureHint(hooks.length - visible2.length, "hooks info <name>", { includeAll: true });
|
|
8467
8677
|
return;
|
|
8468
8678
|
}
|
|
8469
8679
|
if (options.json) {
|
|
@@ -8474,19 +8684,15 @@ ${category} (${hooks.length}):
|
|
|
8474
8684
|
console.log(JSON.stringify(result));
|
|
8475
8685
|
return;
|
|
8476
8686
|
}
|
|
8687
|
+
const visible = HOOKS.slice(0, limit);
|
|
8477
8688
|
console.log(chalk2.bold(`
|
|
8478
|
-
Available hooks (${HOOKS.length}):
|
|
8689
|
+
Available hooks (${HOOKS.length}, showing ${visible.length}):
|
|
8479
8690
|
`));
|
|
8480
|
-
for (const
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
for (const h of hooks) {
|
|
8484
|
-
console.log(` ${chalk2.cyan(h.name)} ${chalk2.dim(`[${h.event}]`)} - ${h.description}`);
|
|
8485
|
-
}
|
|
8486
|
-
console.log();
|
|
8487
|
-
}
|
|
8691
|
+
for (const h of visible)
|
|
8692
|
+
console.log(hookSummaryLine(h, { verbose: options.verbose }));
|
|
8693
|
+
printDisclosureHint(HOOKS.length - visible.length, "hooks info <name>", { includeAll: true });
|
|
8488
8694
|
});
|
|
8489
|
-
program2.command("search").argument("<query>", "Search term").option("-j, --json", "Output as JSON", false).description("Search for hooks").action((query, options) => {
|
|
8695
|
+
program2.command("search").argument("<query>", "Search term").option("-n, --limit <n>", "Max rows to show in compact output", "10").option("--verbose", "Show descriptions for search results", false).option("-j, --json", "Output as JSON", false).description("Search for hooks").action((query, options) => {
|
|
8490
8696
|
const results = searchHooks(query);
|
|
8491
8697
|
if (options.json) {
|
|
8492
8698
|
console.log(JSON.stringify(results));
|
|
@@ -8496,13 +8702,14 @@ program2.command("search").argument("<query>", "Search term").option("-j, --json
|
|
|
8496
8702
|
console.log(chalk2.dim(`No hooks found for "${query}"`));
|
|
8497
8703
|
return;
|
|
8498
8704
|
}
|
|
8705
|
+
const limit = parseLimit(options.limit, 10, 100);
|
|
8706
|
+
const visible = results.slice(0, limit);
|
|
8499
8707
|
console.log(chalk2.bold(`
|
|
8500
|
-
Found ${results.length} hook(s):
|
|
8708
|
+
Found ${results.length} hook(s), showing ${visible.length}:
|
|
8501
8709
|
`));
|
|
8502
|
-
for (const h of
|
|
8503
|
-
console.log(
|
|
8504
|
-
|
|
8505
|
-
}
|
|
8710
|
+
for (const h of visible)
|
|
8711
|
+
console.log(hookSummaryLine(h, { verbose: options.verbose }));
|
|
8712
|
+
printDisclosureHint(results.length - visible.length, "hooks info <name>");
|
|
8506
8713
|
});
|
|
8507
8714
|
program2.command("remove").alias("rm").argument("<hook>", "Hook to remove").option("-g, --global", "Remove from global settings", false).option("-p, --project", "Remove from project settings", false).option("-t, --target <target>", "Agent target: claude, gemini, codewith, all (default: claude)", "claude").option("-j, --json", "Output as JSON", false).description("Remove an installed hook").action((hook, options) => {
|
|
8508
8715
|
const scope = resolveScope(options);
|
|
@@ -8698,7 +8905,7 @@ Updating hooks...
|
|
|
8698
8905
|
}
|
|
8699
8906
|
}
|
|
8700
8907
|
});
|
|
8701
|
-
program2.command("docs").argument("[hook]", "Hook name (shows general docs if omitted)").option("-j, --json", "Output as JSON", false).description("Show documentation for hooks").action((hook, options) => {
|
|
8908
|
+
program2.command("docs").argument("[hook]", "Hook name (shows general docs if omitted)").option("--verbose", "Print full hook README content", false).option("-j, --json", "Output as JSON", false).description("Show documentation for hooks").action((hook, options) => {
|
|
8702
8909
|
if (hook) {
|
|
8703
8910
|
const meta = getHook(hook);
|
|
8704
8911
|
if (!meta) {
|
|
@@ -8733,13 +8940,23 @@ ${meta.displayName} v${meta.version}
|
|
|
8733
8940
|
console.log(` hooks install ${meta.name} # global`);
|
|
8734
8941
|
console.log(` hooks install ${meta.name} --project # project only`);
|
|
8735
8942
|
console.log();
|
|
8736
|
-
if (readme) {
|
|
8943
|
+
if (readme && options.verbose) {
|
|
8737
8944
|
console.log(chalk2.bold(` README:
|
|
8738
8945
|
`));
|
|
8739
8946
|
for (const line of readme.split(`
|
|
8740
8947
|
`)) {
|
|
8741
8948
|
console.log(` ${line}`);
|
|
8742
8949
|
}
|
|
8950
|
+
} else if (readme) {
|
|
8951
|
+
const preview = readmePreview(readme);
|
|
8952
|
+
if (preview) {
|
|
8953
|
+
console.log(chalk2.bold(` README Preview:
|
|
8954
|
+
`));
|
|
8955
|
+
console.log(` ${preview}
|
|
8956
|
+
`);
|
|
8957
|
+
}
|
|
8958
|
+
console.log(chalk2.dim(` README has ${readme.split(`
|
|
8959
|
+
`).length} lines. Use hooks docs ${meta.name} --verbose for the full README, or --json for machine-readable output.`));
|
|
8743
8960
|
}
|
|
8744
8961
|
return;
|
|
8745
8962
|
}
|
|
@@ -8816,8 +9033,9 @@ ${meta.displayName} v${meta.version}
|
|
|
8816
9033
|
console.log(chalk2.bold(`
|
|
8817
9034
|
Hook-Specific Docs
|
|
8818
9035
|
`));
|
|
8819
|
-
console.log(` hooks docs <name>
|
|
8820
|
-
console.log(` hooks docs --
|
|
9036
|
+
console.log(` hooks docs <name> Compact hook docs`);
|
|
9037
|
+
console.log(` hooks docs <name> --verbose Full hook README`);
|
|
9038
|
+
console.log(` hooks docs --json Machine-readable documentation`);
|
|
8821
9039
|
console.log();
|
|
8822
9040
|
});
|
|
8823
9041
|
program2.command("upgrade").option("-c, --check", "Check for updates without installing", false).option("-j, --json", "Output as JSON", false).description("Update the @hasna/hooks package to the latest version").action(async (options) => {
|
|
@@ -8963,11 +9181,12 @@ logCmd.command("list").description("List hook events").option("--hook <name>", "
|
|
|
8963
9181
|
`));
|
|
8964
9182
|
for (const row of rows) {
|
|
8965
9183
|
const ts = row.timestamp.slice(0, 19).replace("T", " ");
|
|
8966
|
-
const err = row.error ? chalk2.red(` ERR: ${row.error
|
|
9184
|
+
const err = row.error ? chalk2.red(` ERR: ${truncateText2(row.error, 60)}`) : "";
|
|
8967
9185
|
const tool = row.tool_name ? chalk2.dim(` [${row.tool_name}]`) : "";
|
|
8968
9186
|
console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))}${tool}${err}`);
|
|
8969
9187
|
}
|
|
8970
|
-
console.log(
|
|
9188
|
+
console.log(chalk2.dim(`
|
|
9189
|
+
Compact rows shown. Use --json for full event records or --limit <n> to change row count.`));
|
|
8971
9190
|
});
|
|
8972
9191
|
logCmd.command("search <text>").description("Search hook events by tool_input or error text").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (text, options) => {
|
|
8973
9192
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
@@ -8988,10 +9207,11 @@ logCmd.command("search <text>").description("Search hook events by tool_input or
|
|
|
8988
9207
|
`));
|
|
8989
9208
|
for (const row of rows) {
|
|
8990
9209
|
const ts = row.timestamp.slice(0, 19).replace("T", " ");
|
|
8991
|
-
const snippet = (row.tool_input || row.error || ""
|
|
9210
|
+
const snippet = truncateText2(row.tool_input || row.error || "", 80);
|
|
8992
9211
|
console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))} ${chalk2.dim(snippet)}`);
|
|
8993
9212
|
}
|
|
8994
|
-
console.log(
|
|
9213
|
+
console.log(chalk2.dim(`
|
|
9214
|
+
Compact rows shown. Use --json for full event records or --limit <n> to change row count.`));
|
|
8995
9215
|
});
|
|
8996
9216
|
logCmd.command("tail").description("Show most recent hook events").option("-n <n>", "Number of rows", "20").option("-j, --json", "Output as JSON", false).action(async (options) => {
|
|
8997
9217
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
@@ -9011,11 +9231,12 @@ logCmd.command("tail").description("Show most recent hook events").option("-n <n
|
|
|
9011
9231
|
`));
|
|
9012
9232
|
for (const row of rows) {
|
|
9013
9233
|
const ts = row.timestamp.slice(0, 19).replace("T", " ");
|
|
9014
|
-
const err = row.error ? chalk2.red(` \u2717 ${row.error
|
|
9234
|
+
const err = row.error ? chalk2.red(` \u2717 ${truncateText2(row.error, 60)}`) : "";
|
|
9015
9235
|
const tool = row.tool_name ? chalk2.dim(` [${row.tool_name}]`) : "";
|
|
9016
9236
|
console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))}${tool}${err}`);
|
|
9017
9237
|
}
|
|
9018
|
-
console.log(
|
|
9238
|
+
console.log(chalk2.dim(`
|
|
9239
|
+
Compact rows shown. Use --json for full event records or -n <n> to change row count.`));
|
|
9019
9240
|
});
|
|
9020
9241
|
logCmd.command("errors").description("Show hook events that contain errors").option("--since <duration>", "Only show errors since this duration (e.g. 1h, 30m, 7d)", "24h").option("-n, --limit <n>", "Number of rows to show", "50").option("-j, --json", "Output as JSON", false).action(async (options) => {
|
|
9021
9242
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|
|
@@ -9054,9 +9275,10 @@ logCmd.command("errors").description("Show hook events that contain errors").opt
|
|
|
9054
9275
|
`));
|
|
9055
9276
|
for (const row of rows) {
|
|
9056
9277
|
const ts = row.timestamp.slice(0, 19).replace("T", " ");
|
|
9057
|
-
console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))} ${chalk2.red(row.error
|
|
9278
|
+
console.log(` ${chalk2.dim(ts)} ${chalk2.cyan(row.hook_name.padEnd(14))} ${chalk2.red(truncateText2(row.error, 100))}`);
|
|
9058
9279
|
}
|
|
9059
|
-
console.log(
|
|
9280
|
+
console.log(chalk2.dim(`
|
|
9281
|
+
Compact rows shown. Use --json for full event records or --limit <n> to change row count.`));
|
|
9060
9282
|
});
|
|
9061
9283
|
logCmd.command("clear").description("Delete hook event logs").option("--hook <name>", "Only delete events for this hook").option("-y, --yes", "Skip confirmation prompt", false).action(async (options) => {
|
|
9062
9284
|
const { getDb: getDb2 } = await Promise.resolve().then(() => (init_db(), exports_db));
|