@bahulam/code 2.6.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/README.md +80 -0
- package/package.json +49 -0
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/benchmark/route.ts +113 -0
- package/pulse/app/api/benchmarks/route.ts +195 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +50 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/benchmarks/page.tsx +224 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +143 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +164 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +53 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +183 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/bahulam-paths.ts +23 -0
- package/pulse/lib/claude-reader.ts +592 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +94 -0
- package/src/agents/multi_workflow_loader.mjs +330 -0
- package/src/agents/parser.mjs +205 -0
- package/src/agents/scaffold.mjs +222 -0
- package/src/agents/teams.mjs +123 -0
- package/src/agents/workflow_loader.mjs +122 -0
- package/src/agents/workflow_scaffold.mjs +249 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +306 -0
- package/src/commands/agent.mjs +220 -0
- package/src/commands/workflow.mjs +581 -0
- package/src/config/cli-args.mjs +200 -0
- package/src/config/env.mjs +263 -0
- package/src/config/hook-runner.mjs +100 -0
- package/src/config/memory-loader.mjs +32 -0
- package/src/config/settings-loader.mjs +45 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +308 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/context/symbol-indexer.mjs +375 -0
- package/src/core/agent-history.mjs +111 -0
- package/src/core/agent-loop.mjs +486 -0
- package/src/core/approval-log.mjs +104 -0
- package/src/core/approval.mjs +476 -0
- package/src/core/attachments.mjs +380 -0
- package/src/core/backend-url.mjs +55 -0
- package/src/core/cache-control.mjs +92 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +180 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/compact-history.mjs +127 -0
- package/src/core/context-envelope.mjs +54 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/error-guidance.mjs +311 -0
- package/src/core/file-diff.mjs +217 -0
- package/src/core/headless.mjs +448 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +449 -0
- package/src/core/local-agent.mjs +537 -0
- package/src/core/local-store.mjs +836 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +190 -0
- package/src/core/policy-resolver.mjs +156 -0
- package/src/core/pricing.mjs +336 -0
- package/src/core/project-artifacts.mjs +39 -0
- package/src/core/project-context-loader.mjs +139 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limit-display.mjs +121 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/resume-mode.mjs +192 -0
- package/src/core/risk-tier.mjs +337 -0
- package/src/core/safety.mjs +203 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +360 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +829 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +140 -0
- package/src/core/tasks.mjs +196 -0
- package/src/core/tool-executor.mjs +1950 -0
- package/src/core/trust.mjs +158 -0
- package/src/core/work-scope.mjs +248 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +426 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/onboarding/preflight.mjs +360 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +652 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +252 -0
- package/src/skills/runner.mjs +55 -0
- package/src/state/orbit.mjs +263 -0
- package/src/state/verbosity.mjs +99 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +695 -0
- package/src/terminal/init.mjs +145 -0
- package/src/terminal/main.mjs +269 -0
- package/src/terminal/repl-explore.mjs +35 -0
- package/src/terminal/repl-format.mjs +257 -0
- package/src/terminal/repl-render.mjs +561 -0
- package/src/terminal/repl-resume.mjs +625 -0
- package/src/terminal/repl-state.mjs +103 -0
- package/src/terminal/repl-utils.mjs +34 -0
- package/src/terminal/repl.mjs +3832 -0
- package/src/terminal/skills.mjs +54 -0
- package/src/terminal/tool-display.mjs +240 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +231 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/project-overview.mjs +641 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +116 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/approval.mjs +263 -0
- package/src/ui/banner.mjs +235 -0
- package/src/ui/commands.mjs +537 -0
- package/src/ui/formatter.mjs +409 -0
- package/src/ui/icons.mjs +164 -0
- package/src/ui/input-dock.mjs +444 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/mission-report.mjs +296 -0
- package/src/ui/palette.mjs +189 -0
- package/src/ui/slash-commands.mjs +245 -0
- package/src/ui/spinner.mjs +116 -0
- package/src/ui/sub-agent.mjs +152 -0
- package/src/ui/term.mjs +159 -0
- package/src/ui/text-layout.mjs +127 -0
- package/src/ui/tool-card.mjs +463 -0
- package/src/ui/tool-details.mjs +312 -0
- package/src/ui/transcript-block.mjs +21 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Fetch Tool โ fetch URL content using built-in Node.js fetch.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const WebFetchTool = {
|
|
6
|
+
name: 'WebFetch',
|
|
7
|
+
description: 'Fetch content from a URL. Returns the response body as text.',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
url: { type: 'string', description: 'The URL to fetch' },
|
|
12
|
+
headers: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
description: 'Optional HTTP headers',
|
|
15
|
+
},
|
|
16
|
+
max_length: {
|
|
17
|
+
type: 'number',
|
|
18
|
+
description: 'Max response length in characters (default: 50000)',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ['url'],
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
validateInput(input) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
if (!input.url) errors.push('url is required');
|
|
27
|
+
try {
|
|
28
|
+
new URL(input.url);
|
|
29
|
+
} catch {
|
|
30
|
+
errors.push('url must be a valid URL');
|
|
31
|
+
}
|
|
32
|
+
return errors;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async call(input) {
|
|
36
|
+
const maxLength = input.max_length || 50000;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
41
|
+
|
|
42
|
+
const res = await fetch(input.url, {
|
|
43
|
+
headers: input.headers || {},
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
redirect: 'follow',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
return `HTTP ${res.status}: ${res.statusText}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const contentType = res.headers.get('content-type') || '';
|
|
55
|
+
const text = await res.text();
|
|
56
|
+
const truncated = text.length > maxLength
|
|
57
|
+
? text.slice(0, maxLength) + `\n...[truncated at ${maxLength} chars]`
|
|
58
|
+
: text;
|
|
59
|
+
|
|
60
|
+
return `Content-Type: ${contentType}\nLength: ${text.length}\n\n${truncated}`;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return `Fetch error: ${err.message}`;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Search Tool โ search the web via configurable API backends.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Brave Search API (BRAVE_API_KEY)
|
|
6
|
+
* - SearXNG (self-hosted, SEARXNG_URL)
|
|
7
|
+
* - Fallback: returns instructions to use web-fetch instead
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const WebSearchTool = {
|
|
11
|
+
name: 'WebSearch',
|
|
12
|
+
description: 'Search the web for information. Requires BRAVE_API_KEY or SEARXNG_URL.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
query: { type: 'string', description: 'Search query' },
|
|
17
|
+
limit: { type: 'number', description: 'Max results (default: 5)' },
|
|
18
|
+
},
|
|
19
|
+
required: ['query'],
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
validateInput(input) {
|
|
23
|
+
return input.query ? [] : ['query is required'];
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
async call(input) {
|
|
27
|
+
const limit = input.limit || 5;
|
|
28
|
+
|
|
29
|
+
// Try Brave Search API
|
|
30
|
+
const braveKey = process.env.BRAVE_API_KEY;
|
|
31
|
+
if (braveKey) {
|
|
32
|
+
return searchBrave(input.query, limit, braveKey);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Try SearXNG
|
|
36
|
+
const searxUrl = process.env.SEARXNG_URL;
|
|
37
|
+
if (searxUrl) {
|
|
38
|
+
return searchSearxng(input.query, limit, searxUrl);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 'No search API configured. Set BRAVE_API_KEY or SEARXNG_URL environment variable. ' +
|
|
42
|
+
'Alternatively, use the WebFetch tool to fetch specific URLs directly.';
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
async function searchBrave(query, limit, apiKey) {
|
|
47
|
+
try {
|
|
48
|
+
const url = `https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=${limit}`;
|
|
49
|
+
const res = await fetch(url, {
|
|
50
|
+
headers: {
|
|
51
|
+
'Accept': 'application/json',
|
|
52
|
+
'X-Subscription-Token': apiKey,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!res.ok) return `Brave API error: ${res.status}`;
|
|
57
|
+
|
|
58
|
+
const data = await res.json();
|
|
59
|
+
const results = (data.web?.results || []).slice(0, limit);
|
|
60
|
+
|
|
61
|
+
if (results.length === 0) return 'No results found.';
|
|
62
|
+
|
|
63
|
+
return results.map((r, i) =>
|
|
64
|
+
`${i + 1}. ${r.title}\n ${r.url}\n ${r.description || ''}`
|
|
65
|
+
).join('\n\n');
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return `Search error: ${err.message}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function searchSearxng(query, limit, baseUrl) {
|
|
72
|
+
try {
|
|
73
|
+
const url = `${baseUrl}/search?q=${encodeURIComponent(query)}&format=json&categories=general`;
|
|
74
|
+
const res = await fetch(url);
|
|
75
|
+
|
|
76
|
+
if (!res.ok) return `SearXNG error: ${res.status}`;
|
|
77
|
+
|
|
78
|
+
const data = await res.json();
|
|
79
|
+
const results = (data.results || []).slice(0, limit);
|
|
80
|
+
|
|
81
|
+
if (results.length === 0) return 'No results found.';
|
|
82
|
+
|
|
83
|
+
return results.map((r, i) =>
|
|
84
|
+
`${i + 1}. ${r.title}\n ${r.url}\n ${r.content || ''}`
|
|
85
|
+
).join('\n\n');
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return `Search error: ${err.message}`;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write Tool โ matches Claude Code's exact behavior.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Creates parent directories if needed
|
|
6
|
+
* - Requires Read first for existing file overwrites
|
|
7
|
+
* - No README creation unless explicitly asked
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { hasBeenRead, markRead } from './read.mjs';
|
|
12
|
+
|
|
13
|
+
export const WriteTool = {
|
|
14
|
+
name: 'Write',
|
|
15
|
+
description: 'Write content to a file. Creates parent dirs if needed.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
file_path: { type: 'string', description: 'Absolute path to the file' },
|
|
20
|
+
content: { type: 'string', description: 'The content to write' },
|
|
21
|
+
},
|
|
22
|
+
required: ['file_path', 'content'],
|
|
23
|
+
},
|
|
24
|
+
validateInput(input) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
if (!input.file_path) errors.push('file_path required');
|
|
27
|
+
return errors;
|
|
28
|
+
},
|
|
29
|
+
async call(input) {
|
|
30
|
+
const filePath = path.resolve(input.file_path);
|
|
31
|
+
|
|
32
|
+
// Check if file already exists โ require Read first for overwrites
|
|
33
|
+
if (fs.existsSync(filePath)) {
|
|
34
|
+
if (!hasBeenRead(filePath)) {
|
|
35
|
+
return `Error: File ${filePath} already exists. You must Read it first before overwriting.`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create parent directory if it doesn't exist
|
|
40
|
+
const dir = path.dirname(filePath);
|
|
41
|
+
try {
|
|
42
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return `Error creating directory ${dir}: ${e.message}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(filePath, input.content);
|
|
49
|
+
markRead(filePath); // Mark as read after writing
|
|
50
|
+
return `File written: ${filePath}`;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return `Error writing file: ${e.message}`;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Approval prompt UI โ Mission Control (PRD-055 ยง8.2).
|
|
3
|
+
*
|
|
4
|
+
* โโ โ APPROVAL ยท SHELL-MEDIUM ยท shell โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
5
|
+
* โ๏ธ Running git add package.json && git status --short
|
|
6
|
+
* Decision
|
|
7
|
+
* โธ [y] approve once run this call
|
|
8
|
+
* [t] always allow auto-approve future calls to this tool
|
|
9
|
+
* [n] cancel do not run
|
|
10
|
+
* โโ move ยท Enter pick ยท letter shortcut ยท Esc cancel
|
|
11
|
+
*
|
|
12
|
+
* The rule colour is `brand.accent` (magenta) for explicit-approval
|
|
13
|
+
* tiers; safe-default prompts use `brand.data` so they read as advisory.
|
|
14
|
+
*
|
|
15
|
+
* Pure โ caller writes the returned string to stderr.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { paint, width as visibleWidth } from './palette.mjs';
|
|
19
|
+
import { icon } from './icons.mjs';
|
|
20
|
+
import { shellCommandDisplay, toolDisplayLabel, toolDisplaySummary } from '../terminal/tool-display.mjs';
|
|
21
|
+
import { label as tierLabel, requiresExplicitApproval, TIERS } from '../core/risk-tier.mjs';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default option set per tier. Caller can override via `opts.options`.
|
|
25
|
+
*
|
|
26
|
+
* Each option is `{ key, label, value, hint? }`:
|
|
27
|
+
* key โ single-letter shortcut (case-insensitive)
|
|
28
|
+
* label โ text shown in the menu
|
|
29
|
+
* value โ return value from the menu loop
|
|
30
|
+
* hint โ secondary description shown to the right of the label
|
|
31
|
+
*/
|
|
32
|
+
export function defaultOptions(tier) {
|
|
33
|
+
const approve = { key: 'y', label: 'approve once', value: 'approve', hint: 'run this call' };
|
|
34
|
+
const cancel = { key: 'n', label: 'cancel', value: 'reject', hint: 'do not run' };
|
|
35
|
+
if (requiresExplicitApproval(tier)) {
|
|
36
|
+
return [approve, cancel];
|
|
37
|
+
}
|
|
38
|
+
return [
|
|
39
|
+
approve,
|
|
40
|
+
{ key: 't', label: 'always allow', value: 'allow-type', hint: 'auto-approve future calls to this tool' },
|
|
41
|
+
cancel,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render the compact horizontal prompt with arrow-navigable options.
|
|
47
|
+
*
|
|
48
|
+
* โโ โ APPROVAL ยท SHELL-MEDIUM ยท shell โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
49
|
+
* โ๏ธ Running rm -rf node_modules && npm install
|
|
50
|
+
* Decision
|
|
51
|
+
* โธ [y] approve once
|
|
52
|
+
* [t] always allow
|
|
53
|
+
* [n] cancel
|
|
54
|
+
* โโ move ยท Enter pick ยท letter shortcut ยท Esc cancel
|
|
55
|
+
* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
56
|
+
*
|
|
57
|
+
* @param {object} opts
|
|
58
|
+
* @param {string} opts.tool
|
|
59
|
+
* @param {object} opts.args
|
|
60
|
+
* @param {string} opts.tier
|
|
61
|
+
* @param {string} [opts.why]
|
|
62
|
+
* @param {number} [opts.width]
|
|
63
|
+
* @param {Array} [opts.options] โ override default option set
|
|
64
|
+
* @param {number} [opts.selected] โ index of the highlighted option
|
|
65
|
+
*/
|
|
66
|
+
export function renderApprovalPrompt({
|
|
67
|
+
tool, args = {}, tier, why: _why = '', width,
|
|
68
|
+
options, selected = 0,
|
|
69
|
+
} = {}) {
|
|
70
|
+
const cols = Math.max(60, Math.min(width || process.stderr.columns || 96, 120));
|
|
71
|
+
const explicit = requiresExplicitApproval(tier);
|
|
72
|
+
const accent = explicit ? paint.brand.accent : paint.brand.data;
|
|
73
|
+
const opts = options || defaultOptions(tier);
|
|
74
|
+
const title = `${riskIcon(tier)} ${approvalTitle(tier)} ยท ${tierLabel(tier)} ยท ${tool || 'tool'}`;
|
|
75
|
+
|
|
76
|
+
const lines = [
|
|
77
|
+
horizontalRule(title, cols, accent),
|
|
78
|
+
...subjectRows(tool, args, cols, accent),
|
|
79
|
+
...decisionRows(opts, selected, accent),
|
|
80
|
+
` ${paint.text.dim('โโ move ยท Enter pick ยท letter shortcut ยท Esc cancel')}`,
|
|
81
|
+
horizontalRule('', cols, accent),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
return '\n' + lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function renderTrustedApproval({ tool, args = {}, scope = 'session', ruleId = '', delaySeconds = 0 } = {}) {
|
|
88
|
+
const summary = approvalSubjectSummary(tool, args);
|
|
89
|
+
const subject = `${tool || 'tool'}${summary ? ` "${truncate(summary, 80)}"` : ''}`;
|
|
90
|
+
const rule = ruleId ? ` ยท rule ${ruleId}` : '';
|
|
91
|
+
const delay = delaySeconds > 0 ? ` ยท auto-approved after ${delaySeconds}s` : '';
|
|
92
|
+
return ` ${paint.state.success('โ')} ${paint.text.primary(subject)} ${paint.text.dim(`ยท pre-approved (${String(scope || 'session').toLowerCase()}${rule})${delay}`)}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function truncate(text, n) {
|
|
96
|
+
const s = String(text || '');
|
|
97
|
+
if (s.length <= n) return s;
|
|
98
|
+
return s.slice(0, Math.max(0, n - 1)) + 'โฆ';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// โโ Compatibility wrapper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render the unified approval prompt. Kept as a named export for older call
|
|
105
|
+
* sites/tests that imported the previous inline renderer.
|
|
106
|
+
*/
|
|
107
|
+
export function renderInlinePrompt({ tool, args = {}, tier, why = '' } = {}) {
|
|
108
|
+
return renderApprovalPrompt({ tool, args, tier, why });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { TIERS };
|
|
112
|
+
|
|
113
|
+
function riskIcon(tier) {
|
|
114
|
+
switch (tier) {
|
|
115
|
+
case TIERS.SENSITIVE_READ:
|
|
116
|
+
return '๐';
|
|
117
|
+
case TIERS.SHELL_DANGEROUS:
|
|
118
|
+
case TIERS.DESTRUCTIVE:
|
|
119
|
+
return '๐ด';
|
|
120
|
+
case TIERS.SHELL_MEDIUM:
|
|
121
|
+
return 'โ ';
|
|
122
|
+
case TIERS.NETWORK:
|
|
123
|
+
return '๐';
|
|
124
|
+
case TIERS.LOCAL_EDIT:
|
|
125
|
+
return 'โ';
|
|
126
|
+
default:
|
|
127
|
+
return 'โ';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function tierTitle(tier) {
|
|
132
|
+
switch (tier) {
|
|
133
|
+
case TIERS.SENSITIVE_READ: return 'SENSITIVE';
|
|
134
|
+
case TIERS.SHELL_DANGEROUS: return 'DANGEROUS';
|
|
135
|
+
case TIERS.DESTRUCTIVE: return 'DESTRUCTIVE';
|
|
136
|
+
case TIERS.SHELL_MEDIUM: return 'MEDIUM';
|
|
137
|
+
case TIERS.NETWORK: return 'NETWORK';
|
|
138
|
+
case TIERS.LOCAL_EDIT: return 'EDIT';
|
|
139
|
+
case TIERS.SHELL_SAFE: return 'SAFE';
|
|
140
|
+
case TIERS.READ: return 'READ';
|
|
141
|
+
default: return tierLabel(tier);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function approvalTitle(tier) {
|
|
146
|
+
switch (tier) {
|
|
147
|
+
case TIERS.SENSITIVE_READ:
|
|
148
|
+
case TIERS.SHELL_DANGEROUS:
|
|
149
|
+
case TIERS.DESTRUCTIVE:
|
|
150
|
+
return tierTitle(tier);
|
|
151
|
+
default:
|
|
152
|
+
return 'APPROVAL';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function horizontalRule(title, cols, accent) {
|
|
157
|
+
if (!title) return ` ${accent('โ'.repeat(Math.max(24, cols - 2)))}`;
|
|
158
|
+
const label = ` ${paint.bold(accent(title))} `;
|
|
159
|
+
const remaining = Math.max(8, cols - 2 - visibleWidth(label) - 2);
|
|
160
|
+
return ` ${accent('โ')} ${label}${accent('โ'.repeat(remaining))}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function subjectRows(tool, args, cols, accent) {
|
|
164
|
+
const rows = [];
|
|
165
|
+
const available = Math.max(24, cols - 4);
|
|
166
|
+
const summary = toolDisplaySummary(tool, args, {});
|
|
167
|
+
const label = `${icon(tool)} ${paint.text.primary(toolDisplayLabel(tool))}`;
|
|
168
|
+
const details = subjectDetails(tool, args, summary, Math.max(24, available - visibleWidth(label) - 1));
|
|
169
|
+
|
|
170
|
+
if (details.length === 1 && visibleWidth(`${label} ${details[0]}`) <= available) {
|
|
171
|
+
rows.push(` ${label} ${paint.text.primary(details[0])}`);
|
|
172
|
+
return rows;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
rows.push(` ${label}`);
|
|
176
|
+
for (const line of subjectDetails(tool, args, summary, Math.max(24, available - 2))) {
|
|
177
|
+
rows.push(` ${paint.text.primary(line)}`);
|
|
178
|
+
}
|
|
179
|
+
return rows;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function decisionRows(opts, selected, accent) {
|
|
183
|
+
const rows = [` ${paint.text.dim('Decision')}`];
|
|
184
|
+
for (let i = 0; i < opts.length; i++) {
|
|
185
|
+
rows.push(` ${optionToken(opts[i], i === selected, accent)}`);
|
|
186
|
+
}
|
|
187
|
+
return rows;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function optionToken(option, selected, accent) {
|
|
191
|
+
const cursor = selected ? accent('โธ ') : paint.text.dim(' ');
|
|
192
|
+
const keyTag = paint.text.dim('[') + (selected ? accent(option.key) : paint.brand.data(option.key)) + paint.text.dim('] ');
|
|
193
|
+
const label = selected ? paint.bold(accent(option.label)) : paint.text.primary(option.label);
|
|
194
|
+
const hint = option.hint ? ` ${paint.text.muted(option.hint)}` : '';
|
|
195
|
+
return `${cursor}${keyTag}${label}${hint}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function subjectDetails(tool, args = {}, summary = '', available = 72) {
|
|
199
|
+
if (tool === 'shell') {
|
|
200
|
+
const display = shellCommandDisplay(args.command || args.cmd || summary || '');
|
|
201
|
+
const lines = wrapText(display.command, available);
|
|
202
|
+
if (display.cwdLabel) lines.push(`in ${display.cwdLabel}`);
|
|
203
|
+
return lines.length ? lines : ['(empty command)'];
|
|
204
|
+
}
|
|
205
|
+
if (tool === 'write_file') {
|
|
206
|
+
const file = args.file_path || args.path || summary || '';
|
|
207
|
+
const lineCount = typeof args.content === 'string' ? args.content.split('\n').length : null;
|
|
208
|
+
return [`${file}${lineCount ? ` ยท ${lineCount} lines` : ''}`];
|
|
209
|
+
}
|
|
210
|
+
if (tool === 'edit_file') {
|
|
211
|
+
const file = args.file_path || args.path || '';
|
|
212
|
+
const search = String(args.search || args.old_string || '').trim();
|
|
213
|
+
const replacement = String(args.replace || args.new_string || '').trim();
|
|
214
|
+
const details = [`${file || summary}`];
|
|
215
|
+
if (search) details.push(`match: ${truncate(search, available - 7)}`);
|
|
216
|
+
if (replacement) details.push(`replace: ${truncate(replacement, available - 9)}`);
|
|
217
|
+
return details.slice(0, 4);
|
|
218
|
+
}
|
|
219
|
+
if (tool === 'delete_file') {
|
|
220
|
+
return [args.file_path || args.path || summary || ''];
|
|
221
|
+
}
|
|
222
|
+
if (tool === 'WebFetch' || tool === 'fetch_url') {
|
|
223
|
+
const url = args.url || summary || '';
|
|
224
|
+
return [`Target ${hostFromUrl(url) || url}`];
|
|
225
|
+
}
|
|
226
|
+
return wrapText(summary || JSON.stringify(args || {}), available).slice(0, 4);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function approvalSubjectSummary(tool, args = {}) {
|
|
230
|
+
const summary = toolDisplaySummary(tool, args, {});
|
|
231
|
+
if (tool !== 'shell') return summary;
|
|
232
|
+
const display = shellCommandDisplay(args.command || args.cmd || summary || '');
|
|
233
|
+
return display.cwdLabel ? `${display.command} in ${display.cwdLabel}` : display.command;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function hostFromUrl(url) {
|
|
237
|
+
try {
|
|
238
|
+
return new URL(String(url)).host;
|
|
239
|
+
} catch {
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function wrapText(text, width) {
|
|
245
|
+
const words = String(text || '').split(/\s+/).filter(Boolean);
|
|
246
|
+
if (!words.length) return [''];
|
|
247
|
+
const lines = [];
|
|
248
|
+
let line = '';
|
|
249
|
+
for (const word of words) {
|
|
250
|
+
if (!line) {
|
|
251
|
+
line = word;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if ((line + ' ' + word).length <= width) {
|
|
255
|
+
line += ' ' + word;
|
|
256
|
+
} else {
|
|
257
|
+
lines.push(line);
|
|
258
|
+
line = word;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (line) lines.push(line);
|
|
262
|
+
return lines;
|
|
263
|
+
}
|