@valni/cli-darwin-x64 0.0.0-stage → 0.1.3
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/LICENSE +52 -0
- package/package.json +18 -5
- package/runtime/LICENSE +52 -0
- package/runtime/assets/clankolas.png +0 -0
- package/runtime/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/runtime/examples/extensions/bash-spawn-hook.ts +30 -0
- package/runtime/examples/extensions/bookmark.ts +50 -0
- package/runtime/examples/extensions/border-status-editor.ts +145 -0
- package/runtime/examples/extensions/built-in-tool-renderer.ts +249 -0
- package/runtime/examples/extensions/claude-rules.ts +86 -0
- package/runtime/examples/extensions/commands.ts +72 -0
- package/runtime/examples/extensions/confirm-destructive.ts +59 -0
- package/runtime/examples/extensions/custom-footer.ts +64 -0
- package/runtime/examples/extensions/custom-header.ts +73 -0
- package/runtime/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/runtime/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/runtime/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/runtime/examples/extensions/dirty-repo-guard.ts +56 -0
- package/runtime/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/runtime/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/runtime/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/runtime/examples/extensions/doom-overlay/doom/doomgeneric_valni.c +72 -0
- package/runtime/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/runtime/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/runtime/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/runtime/examples/extensions/doom-overlay/index.ts +74 -0
- package/runtime/examples/extensions/doom-overlay/wad-finder.ts +51 -0
- package/runtime/examples/extensions/dynamic-resources/dynamic.json +78 -0
- package/runtime/examples/extensions/dynamic-resources/index.ts +13 -0
- package/runtime/examples/extensions/dynamic-tools.ts +74 -0
- package/runtime/examples/extensions/entry-renderer.ts +41 -0
- package/runtime/examples/extensions/event-bus.ts +43 -0
- package/runtime/examples/extensions/file-trigger.ts +41 -0
- package/runtime/examples/extensions/git-checkpoint.ts +53 -0
- package/runtime/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/runtime/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/runtime/examples/extensions/hello.ts +26 -0
- package/runtime/examples/extensions/hidden-thinking-label.ts +53 -0
- package/runtime/examples/extensions/inline-bash.ts +94 -0
- package/runtime/examples/extensions/input-transform-streaming.ts +39 -0
- package/runtime/examples/extensions/input-transform.ts +43 -0
- package/runtime/examples/extensions/interactive-shell.ts +196 -0
- package/runtime/examples/extensions/mac-system-theme.ts +47 -0
- package/runtime/examples/extensions/message-renderer.ts +59 -0
- package/runtime/examples/extensions/minimal-mode.ts +426 -0
- package/runtime/examples/extensions/modal-editor.ts +85 -0
- package/runtime/examples/extensions/model-status.ts +31 -0
- package/runtime/examples/extensions/notify.ts +55 -0
- package/runtime/examples/extensions/overlay-qa-tests.ts +1450 -0
- package/runtime/examples/extensions/overlay-test.ts +153 -0
- package/runtime/examples/extensions/permission-gate.ts +34 -0
- package/runtime/examples/extensions/pirate.ts +47 -0
- package/runtime/examples/extensions/plan-mode/index.ts +390 -0
- package/runtime/examples/extensions/plan-mode/utils.ts +168 -0
- package/runtime/examples/extensions/preset.ts +436 -0
- package/runtime/examples/extensions/project-trust.ts +64 -0
- package/runtime/examples/extensions/prompt-customizer.ts +97 -0
- package/runtime/examples/extensions/protected-paths.ts +30 -0
- package/runtime/examples/extensions/provider-payload.ts +18 -0
- package/runtime/examples/extensions/question.ts +278 -0
- package/runtime/examples/extensions/questionnaire.ts +440 -0
- package/runtime/examples/extensions/rainbow-editor.ts +88 -0
- package/runtime/examples/extensions/reload-runtime.ts +37 -0
- package/runtime/examples/extensions/rpc-demo.ts +118 -0
- package/runtime/examples/extensions/sandbox/index.ts +321 -0
- package/runtime/examples/extensions/sandbox/package-lock.json +92 -0
- package/runtime/examples/extensions/sandbox/package.json +19 -0
- package/runtime/examples/extensions/send-user-message.ts +97 -0
- package/runtime/examples/extensions/session-name.ts +27 -0
- package/runtime/examples/extensions/shutdown-command.ts +63 -0
- package/runtime/examples/extensions/snake.ts +343 -0
- package/runtime/examples/extensions/space-invaders.ts +560 -0
- package/runtime/examples/extensions/ssh.ts +220 -0
- package/runtime/examples/extensions/status-line.ts +32 -0
- package/runtime/examples/extensions/structured-output.ts +65 -0
- package/runtime/examples/extensions/subagent/agents.ts +126 -0
- package/runtime/examples/extensions/subagent/index.ts +1009 -0
- package/runtime/examples/extensions/system-prompt-header.ts +17 -0
- package/runtime/examples/extensions/tic-tac-toe.ts +1008 -0
- package/runtime/examples/extensions/timed-confirm.ts +70 -0
- package/runtime/examples/extensions/titlebar-spinner.ts +58 -0
- package/runtime/examples/extensions/todo.ts +297 -0
- package/runtime/examples/extensions/tool-override.ts +144 -0
- package/runtime/examples/extensions/tools.ts +146 -0
- package/runtime/examples/extensions/trigger-compact.ts +50 -0
- package/runtime/examples/extensions/truncated-tool.ts +195 -0
- package/runtime/examples/extensions/widget-placement.ts +9 -0
- package/runtime/examples/extensions/with-deps/index.ts +32 -0
- package/runtime/examples/extensions/with-deps/package-lock.json +31 -0
- package/runtime/examples/extensions/with-deps/package.json +22 -0
- package/runtime/examples/extensions/working-indicator.ts +123 -0
- package/runtime/examples/extensions/working-message-test.ts +25 -0
- package/runtime/examples/rpc-extension-ui.ts +632 -0
- package/runtime/examples/sdk/01-minimal.ts +26 -0
- package/runtime/examples/sdk/02-custom-model.ts +43 -0
- package/runtime/examples/sdk/03-custom-prompt.ts +70 -0
- package/runtime/examples/sdk/04-skills.ts +55 -0
- package/runtime/examples/sdk/05-tools.ts +48 -0
- package/runtime/examples/sdk/06-extensions.ts +94 -0
- package/runtime/examples/sdk/07-context-files.ts +42 -0
- package/runtime/examples/sdk/08-prompt-templates.ts +51 -0
- package/runtime/examples/sdk/09-api-keys-and-oauth.ts +34 -0
- package/runtime/examples/sdk/10-settings.ts +53 -0
- package/runtime/examples/sdk/11-sessions.ts +52 -0
- package/runtime/examples/sdk/12-full-control.ts +71 -0
- package/runtime/examples/sdk/13-session-runtime.ts +67 -0
- package/runtime/export-html/ansi-to-html.js +249 -0
- package/runtime/export-html/index.js +226 -0
- package/runtime/export-html/template.css +1066 -0
- package/runtime/export-html/template.html +55 -0
- package/runtime/export-html/template.js +1864 -0
- package/runtime/export-html/tool-renderer.js +108 -0
- package/runtime/export-html/vendor/highlight.min.js +1213 -0
- package/runtime/export-html/vendor/marked.min.js +78 -0
- package/runtime/extensions/pi-switchboard/.github/workflows/ci.yml +51 -0
- package/runtime/extensions/pi-switchboard/LICENSE +22 -0
- package/runtime/extensions/pi-switchboard/README.md +85 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/catalog.ts +183 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/config.ts +97 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/connections.ts +386 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/constants.ts +12 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/device-auth.ts +127 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/envelope.ts +199 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/errors.ts +180 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/index.ts +125 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/participant.ts +17 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/sessionStream.ts +131 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/toolProxy.ts +136 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/tools.ts +16 -0
- package/runtime/extensions/pi-switchboard/extensions/switchboard/types.ts +37 -0
- package/runtime/extensions/pi-switchboard/package.json +30 -0
- package/runtime/extensions/pi-switchboard/tsconfig.json +13 -0
- package/runtime/extensions/switchboard-local/.github/workflows/ci.yml +80 -0
- package/runtime/extensions/switchboard-local/.github/workflows/release.yml +46 -0
- package/runtime/extensions/switchboard-local/README.md +98 -0
- package/runtime/extensions/switchboard-local/extensions/local/engine.ts +28 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/gguf.ts +324 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/llamaBuild.ts +62 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/mlx.ts +245 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/select.ts +38 -0
- package/runtime/extensions/switchboard-local/extensions/local/engines/support.ts +151 -0
- package/runtime/extensions/switchboard-local/extensions/local/hf.ts +106 -0
- package/runtime/extensions/switchboard-local/extensions/local/index.ts +67 -0
- package/runtime/extensions/switchboard-local/extensions/local/nativeStream.ts +602 -0
- package/runtime/extensions/switchboard-local/extensions/local/paths.ts +44 -0
- package/runtime/extensions/switchboard-local/extensions/local/provider.ts +57 -0
- package/runtime/extensions/switchboard-local/extensions/local/store.ts +82 -0
- package/runtime/extensions/switchboard-local/extensions/local/ui.ts +60 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/apiTypes.js +74 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/client.js +292 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/deviceAuth.js +155 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/errors.js +95 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/index.js +8 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/messages.js +253 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/models.js +19 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/native.js +886 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/dist/wire.js +85 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard/package.json +39 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/package.json +29 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/anthropic.ts +127 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/google.ts +111 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/index.ts +8 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.test.ts +25 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/modelRecord.ts +57 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiGeneric.ts +103 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/openaiPro.ts +89 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/profile.ts +120 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.test.ts +82 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/routing.ts +74 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.google.test.ts +137 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.openai.test.ts +241 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.test.ts +174 -0
- package/runtime/extensions/switchboard-local/node_modules/@valni/switchboard-native/src/validateConfig.ts +438 -0
- package/runtime/extensions/switchboard-local/package.json +34 -0
- package/runtime/extensions/switchboard-local/tsconfig.json +13 -0
- package/runtime/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/runtime/package.json +98 -0
- package/runtime/photon_rs_bg.wasm +0 -0
- package/runtime/theme/dark.json +86 -0
- package/runtime/theme/light.json +85 -0
- package/runtime/theme/theme-schema.json +340 -0
- package/runtime/valni +4 -0
- package/README.md +0 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Checkpoint Extension
|
|
3
|
+
*
|
|
4
|
+
* Creates git stash checkpoints at each turn so /fork can restore code state.
|
|
5
|
+
* When forking, offers to restore code to that point in history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
9
|
+
|
|
10
|
+
export default function (valni: ExtensionAPI) {
|
|
11
|
+
const checkpoints = new Map<string, string>();
|
|
12
|
+
let currentEntryId: string | undefined;
|
|
13
|
+
|
|
14
|
+
// Track the current entry ID when user messages are saved
|
|
15
|
+
valni.on("tool_result", async (_event, ctx) => {
|
|
16
|
+
const leaf = ctx.sessionManager.getLeafEntry();
|
|
17
|
+
if (leaf) currentEntryId = leaf.id;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
valni.on("turn_start", async () => {
|
|
21
|
+
// Create a git stash entry before LLM makes changes
|
|
22
|
+
const { stdout } = await valni.exec("git", ["stash", "create"]);
|
|
23
|
+
const ref = stdout.trim();
|
|
24
|
+
if (ref && currentEntryId) {
|
|
25
|
+
checkpoints.set(currentEntryId, ref);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
valni.on("session_before_fork", async (event, ctx) => {
|
|
30
|
+
const ref = checkpoints.get(event.entryId);
|
|
31
|
+
if (!ref) return;
|
|
32
|
+
|
|
33
|
+
if (!ctx.hasUI) {
|
|
34
|
+
// In non-interactive mode, don't restore automatically
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const choice = await ctx.ui.select("Restore code state?", [
|
|
39
|
+
"Yes, restore code to that point",
|
|
40
|
+
"No, keep current code",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
if (choice?.startsWith("Yes")) {
|
|
44
|
+
await valni.exec("git", ["stash", "apply", ref]);
|
|
45
|
+
ctx.ui.notify("Code restored to checkpoint", "info");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
valni.on("agent_end", async () => {
|
|
50
|
+
// Clear checkpoints after agent completes
|
|
51
|
+
checkpoints.clear();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge and Resolve
|
|
3
|
+
*
|
|
4
|
+
* Keeps the working branch up to date with its upstream tracking ref.
|
|
5
|
+
* After each agent turn, fetches and merges. Clean merges complete
|
|
6
|
+
* silently. When conflicts arise, the working tree is left dirty and
|
|
7
|
+
* the agent receives a follow-up message listing each conflict block
|
|
8
|
+
* with file, line range, and ours/theirs sections so it can resolve them.
|
|
9
|
+
* Also re-sends unresolved conflicts from a previous incomplete merge.
|
|
10
|
+
*
|
|
11
|
+
* Start valni with this extension:
|
|
12
|
+
* valni -e ./examples/extensions/git-merge-and-resolve.ts
|
|
13
|
+
*/
|
|
14
|
+
import { createReadStream } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { createInterface } from "node:readline";
|
|
17
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
18
|
+
|
|
19
|
+
interface ConflictBlock {
|
|
20
|
+
file: string;
|
|
21
|
+
startLine: number;
|
|
22
|
+
separatorLine: number;
|
|
23
|
+
endLine: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Parse conflict markers from working tree files with unmerged paths. */
|
|
27
|
+
async function findConflicts(valni: ExtensionAPI, cwd: string): Promise<ConflictBlock[]> {
|
|
28
|
+
const { stdout, code } = await valni.exec("git", ["diff", "--name-only", "--diff-filter=U"]);
|
|
29
|
+
if (code !== 0 || !stdout.trim()) return [];
|
|
30
|
+
|
|
31
|
+
const blocks: ConflictBlock[] = [];
|
|
32
|
+
for (const file of stdout.trim().split("\n")) {
|
|
33
|
+
try {
|
|
34
|
+
const rl = createInterface({ input: createReadStream(join(cwd, file), "utf-8") });
|
|
35
|
+
let lineNo = 0;
|
|
36
|
+
let blockStart: number | undefined;
|
|
37
|
+
let separatorLine: number | undefined;
|
|
38
|
+
for await (const line of rl) {
|
|
39
|
+
lineNo++;
|
|
40
|
+
if (line.startsWith("<<<<<<<")) {
|
|
41
|
+
blockStart = lineNo;
|
|
42
|
+
separatorLine = undefined;
|
|
43
|
+
} else if (line.startsWith("=======") && blockStart !== undefined) {
|
|
44
|
+
separatorLine = lineNo;
|
|
45
|
+
} else if (line.startsWith(">>>>>>>") && blockStart !== undefined && separatorLine !== undefined) {
|
|
46
|
+
blocks.push({ file, startLine: blockStart, separatorLine, endLine: lineNo });
|
|
47
|
+
blockStart = undefined;
|
|
48
|
+
separatorLine = undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
return blocks;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatRange(start: number, end: number): string {
|
|
57
|
+
if (start > end) return "empty";
|
|
58
|
+
if (start === end) return `${start}`;
|
|
59
|
+
return `${start}-${end}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function formatConflicts(ref: string, blocks: ConflictBlock[]): string {
|
|
63
|
+
const lines = [`Merged ${ref} with conflicts:`, ""];
|
|
64
|
+
for (const b of blocks) {
|
|
65
|
+
const ours = formatRange(b.startLine + 1, b.separatorLine - 1);
|
|
66
|
+
const theirs = formatRange(b.separatorLine + 1, b.endLine - 1);
|
|
67
|
+
lines.push(` ${b.file}:${b.startLine}-${b.endLine} (ours ${ours}, theirs ${theirs})`);
|
|
68
|
+
}
|
|
69
|
+
lines.push("", "Resolve these conflicts.");
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default function (valni: ExtensionAPI) {
|
|
74
|
+
valni.on("agent_end", async (_event, ctx) => {
|
|
75
|
+
const { code: revParseCode } = await valni.exec("git", ["rev-parse", "--git-dir"]);
|
|
76
|
+
if (revParseCode !== 0) return;
|
|
77
|
+
|
|
78
|
+
let ref = "MERGE_HEAD";
|
|
79
|
+
|
|
80
|
+
// If not already in a merge, attempt one
|
|
81
|
+
const { code: mergeHeadCode } = await valni.exec("git", ["rev-parse", "MERGE_HEAD"]);
|
|
82
|
+
if (mergeHeadCode !== 0) {
|
|
83
|
+
// Only attempt a new merge if the working tree is clean
|
|
84
|
+
const { stdout: status } = await valni.exec("git", ["status", "--porcelain"]);
|
|
85
|
+
if (status.trim()) return;
|
|
86
|
+
|
|
87
|
+
const { stdout: upstream, code: upstreamCode } = await valni.exec("git", [
|
|
88
|
+
"rev-parse",
|
|
89
|
+
"--abbrev-ref",
|
|
90
|
+
"--symbolic-full-name",
|
|
91
|
+
"@{u}",
|
|
92
|
+
]);
|
|
93
|
+
if (upstreamCode !== 0) return;
|
|
94
|
+
|
|
95
|
+
ref = upstream.trim();
|
|
96
|
+
const remote = ref.split("/")[0];
|
|
97
|
+
ctx.ui.notify(`git-merge-and-resolve: fetching ${remote}, merging ${ref}`, "info");
|
|
98
|
+
|
|
99
|
+
const { code: fetchCode, stderr: fetchErr } = await valni.exec("git", ["fetch", remote]);
|
|
100
|
+
if (fetchCode !== 0) {
|
|
101
|
+
ctx.ui.notify(`git-merge-and-resolve: fetch failed: ${fetchErr.trim()}`, "warning");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { code: mergeCode } = await valni.exec("git", ["merge", "--no-ff", ref]);
|
|
106
|
+
if (mergeCode === 0) return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Either we just merged with conflicts, or we were already in an unfinished merge
|
|
110
|
+
const conflicts = await findConflicts(valni, ctx.cwd);
|
|
111
|
+
if (conflicts.length === 0) return;
|
|
112
|
+
|
|
113
|
+
valni.sendUserMessage(formatConflicts(ref, conflicts), { deliverAs: "followUp" });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Requires GitHub CLI (`gh`) and a GitHub repository checkout.
|
|
2
|
+
// Preloads the latest open issues once per session, then filters them locally for fast `#...` completion.
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
5
|
+
import {
|
|
6
|
+
type AutocompleteItem,
|
|
7
|
+
type AutocompleteProvider,
|
|
8
|
+
type AutocompleteSuggestions,
|
|
9
|
+
fuzzyFilter,
|
|
10
|
+
} from "@valni/tui";
|
|
11
|
+
|
|
12
|
+
type GitHubIssue = {
|
|
13
|
+
number: number;
|
|
14
|
+
title: string;
|
|
15
|
+
state: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type RepoResolution = { ok: true; repo: string } | { ok: false; error: string };
|
|
19
|
+
|
|
20
|
+
const MAX_ISSUES = 100;
|
|
21
|
+
const MAX_SUGGESTIONS = 20;
|
|
22
|
+
|
|
23
|
+
function extractIssueToken(textBeforeCursor: string): string | undefined {
|
|
24
|
+
const match = textBeforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
|
|
25
|
+
return match?.[1];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseGitHubRepo(remoteUrl: string): string | undefined {
|
|
29
|
+
const sshMatch = remoteUrl.match(/^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
30
|
+
if (sshMatch) {
|
|
31
|
+
return sshMatch[1];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const httpsMatch = remoteUrl.match(/^https?:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
35
|
+
if (httpsMatch) {
|
|
36
|
+
return httpsMatch[1];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function resolveGitHubRepo(valni: ExtensionAPI, cwd: string): Promise<RepoResolution> {
|
|
43
|
+
const result = await valni.exec("git", ["remote", "-v"], { cwd, timeout: 5_000 });
|
|
44
|
+
if (result.code !== 0) {
|
|
45
|
+
return { ok: false, error: "github-issue-autocomplete: cwd is not a git repository" };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const line of result.stdout.split("\n")) {
|
|
49
|
+
const columns = line.trim().split(/\s+/);
|
|
50
|
+
const remoteUrl = columns[1];
|
|
51
|
+
if (!remoteUrl) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const repo = parseGitHubRepo(remoteUrl);
|
|
55
|
+
if (repo) {
|
|
56
|
+
return { ok: true, repo };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { ok: false, error: "github-issue-autocomplete: cwd is not a GitHub repository" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatIssueItem(issue: GitHubIssue): AutocompleteItem {
|
|
64
|
+
return {
|
|
65
|
+
value: `#${issue.number}`,
|
|
66
|
+
label: `#${issue.number}`,
|
|
67
|
+
description: `[${issue.state.toLowerCase()}] ${issue.title}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function filterIssues(issues: GitHubIssue[], query: string): AutocompleteItem[] {
|
|
72
|
+
if (!query.trim()) {
|
|
73
|
+
return issues.slice(0, MAX_SUGGESTIONS).map(formatIssueItem);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (/^\d+$/.test(query)) {
|
|
77
|
+
const numericMatches = issues
|
|
78
|
+
.filter((issue) => String(issue.number).startsWith(query))
|
|
79
|
+
.slice(0, MAX_SUGGESTIONS)
|
|
80
|
+
.map(formatIssueItem);
|
|
81
|
+
if (numericMatches.length > 0) {
|
|
82
|
+
return numericMatches;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return fuzzyFilter(issues, query, (issue) => `${issue.number} ${issue.title}`)
|
|
87
|
+
.slice(0, MAX_SUGGESTIONS)
|
|
88
|
+
.map(formatIssueItem);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createIssueAutocompleteProvider(
|
|
92
|
+
current: AutocompleteProvider,
|
|
93
|
+
getIssues: () => Promise<GitHubIssue[] | undefined>,
|
|
94
|
+
): AutocompleteProvider {
|
|
95
|
+
return {
|
|
96
|
+
async getSuggestions(lines, cursorLine, cursorCol, options): Promise<AutocompleteSuggestions | null> {
|
|
97
|
+
const currentLine = lines[cursorLine] ?? "";
|
|
98
|
+
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
|
99
|
+
const token = extractIssueToken(textBeforeCursor);
|
|
100
|
+
if (token === undefined) {
|
|
101
|
+
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const issues = await getIssues();
|
|
105
|
+
if (options.signal.aborted || !issues || issues.length === 0) {
|
|
106
|
+
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const suggestions = filterIssues(issues, token);
|
|
110
|
+
if (suggestions.length === 0) {
|
|
111
|
+
return current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
items: suggestions,
|
|
116
|
+
prefix: `#${token}`,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
|
|
121
|
+
return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
|
|
125
|
+
return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default function (valni: ExtensionAPI): void {
|
|
131
|
+
valni.on("session_start", async (_event, ctx) => {
|
|
132
|
+
const resolvedRepo = await resolveGitHubRepo(valni, ctx.cwd);
|
|
133
|
+
if (!resolvedRepo.ok) {
|
|
134
|
+
ctx.ui.notify(resolvedRepo.error, "error");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const repo = resolvedRepo.repo;
|
|
139
|
+
let issuesPromise: Promise<GitHubIssue[] | undefined> | undefined;
|
|
140
|
+
let loadErrorShown = false;
|
|
141
|
+
|
|
142
|
+
const getIssues = async (): Promise<GitHubIssue[] | undefined> => {
|
|
143
|
+
issuesPromise ||= (async () => {
|
|
144
|
+
const result = await valni.exec(
|
|
145
|
+
"gh",
|
|
146
|
+
[
|
|
147
|
+
"issue",
|
|
148
|
+
"list",
|
|
149
|
+
"--repo",
|
|
150
|
+
repo,
|
|
151
|
+
"--state",
|
|
152
|
+
"open",
|
|
153
|
+
"--limit",
|
|
154
|
+
String(MAX_ISSUES),
|
|
155
|
+
"--json",
|
|
156
|
+
"number,title,state",
|
|
157
|
+
],
|
|
158
|
+
{ cwd: ctx.cwd, timeout: 5_000 },
|
|
159
|
+
);
|
|
160
|
+
if (result.code !== 0) {
|
|
161
|
+
if (!loadErrorShown) {
|
|
162
|
+
loadErrorShown = true;
|
|
163
|
+
const details = result.stderr.trim() || `exit code ${result.code}`;
|
|
164
|
+
ctx.ui.notify(`github-issue-autocomplete: failed to load issues: ${details}`, "error");
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
return JSON.parse(result.stdout) as GitHubIssue[];
|
|
171
|
+
} catch {
|
|
172
|
+
if (!loadErrorShown) {
|
|
173
|
+
loadErrorShown = true;
|
|
174
|
+
ctx.ui.notify("github-issue-autocomplete: failed to parse gh issue list output", "error");
|
|
175
|
+
}
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
return issuesPromise;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
void getIssues();
|
|
183
|
+
ctx.ui.addAutocompleteProvider((current) => createIssueAutocompleteProvider(current, getIssues));
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hello Tool - Minimal custom tool example
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Type } from "@valni/ai";
|
|
6
|
+
import { defineTool, type ExtensionAPI } from "@valni/cli";
|
|
7
|
+
|
|
8
|
+
const helloTool = defineTool({
|
|
9
|
+
name: "hello",
|
|
10
|
+
label: "Hello",
|
|
11
|
+
description: "A simple greeting tool",
|
|
12
|
+
parameters: Type.Object({
|
|
13
|
+
name: Type.String({ description: "Name to greet" }),
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Hello, ${params.name}!` }],
|
|
19
|
+
details: { greeted: params.name },
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default function (valni: ExtensionAPI) {
|
|
25
|
+
valni.registerTool(helloTool);
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hidden Thinking Label Extension
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates `ctx.ui.setHiddenThinkingLabel()` for customizing the label shown
|
|
5
|
+
* when thinking blocks are hidden.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* valni --extension examples/extensions/hidden-thinking-label.ts
|
|
9
|
+
*
|
|
10
|
+
* Test:
|
|
11
|
+
* 1. Load this extension
|
|
12
|
+
* 2. Hide thinking blocks with Ctrl+T
|
|
13
|
+
* 3. Ask for something that produces reasoning output
|
|
14
|
+
* 4. The collapsed thinking block label will show the custom text
|
|
15
|
+
*
|
|
16
|
+
* Commands:
|
|
17
|
+
* /thinking-label <text> Set a custom hidden thinking label
|
|
18
|
+
* /thinking-label Reset to the default label
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ExtensionAPI, ExtensionContext } from "@valni/cli";
|
|
22
|
+
|
|
23
|
+
const DEFAULT_LABEL = "Pondering...";
|
|
24
|
+
|
|
25
|
+
export default function (valni: ExtensionAPI) {
|
|
26
|
+
let label = DEFAULT_LABEL;
|
|
27
|
+
|
|
28
|
+
const applyLabel = (ctx: ExtensionContext) => {
|
|
29
|
+
ctx.ui.setHiddenThinkingLabel(label);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
valni.on("session_start", async (_event, ctx) => {
|
|
33
|
+
applyLabel(ctx);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
valni.registerCommand("thinking-label", {
|
|
37
|
+
description: "Set the hidden thinking label. Use without args to reset.",
|
|
38
|
+
handler: async (args, ctx) => {
|
|
39
|
+
const nextLabel = args.trim();
|
|
40
|
+
|
|
41
|
+
if (!nextLabel) {
|
|
42
|
+
label = DEFAULT_LABEL;
|
|
43
|
+
ctx.ui.setHiddenThinkingLabel();
|
|
44
|
+
ctx.ui.notify(`Hidden thinking label reset to: ${DEFAULT_LABEL}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
label = nextLabel;
|
|
49
|
+
ctx.ui.setHiddenThinkingLabel(label);
|
|
50
|
+
ctx.ui.notify(`Hidden thinking label set to: ${label}`);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Bash Extension - expands inline bash commands in user prompts.
|
|
3
|
+
*
|
|
4
|
+
* Start valni with this extension:
|
|
5
|
+
* valni -e ./examples/extensions/inline-bash.ts
|
|
6
|
+
*
|
|
7
|
+
* Then type prompts with inline bash:
|
|
8
|
+
* What's in !{pwd}?
|
|
9
|
+
* The current branch is !{git branch --show-current} and status: !{git status --short}
|
|
10
|
+
* My node version is !{node --version}
|
|
11
|
+
*
|
|
12
|
+
* The !{command} patterns are executed and replaced with their output before
|
|
13
|
+
* the prompt is sent to the agent.
|
|
14
|
+
*
|
|
15
|
+
* Note: Regular !command syntax (whole-line bash) is preserved and works as before.
|
|
16
|
+
*/
|
|
17
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
18
|
+
|
|
19
|
+
export default function (valni: ExtensionAPI) {
|
|
20
|
+
const PATTERN = /!\{([^}]+)\}/g;
|
|
21
|
+
const TIMEOUT_MS = 30000;
|
|
22
|
+
|
|
23
|
+
valni.on("input", async (event, ctx) => {
|
|
24
|
+
const text = event.text;
|
|
25
|
+
|
|
26
|
+
// Don't process if it's a whole-line bash command (starts with !)
|
|
27
|
+
// This preserves the existing !command behavior
|
|
28
|
+
if (text.trimStart().startsWith("!") && !text.trimStart().startsWith("!{")) {
|
|
29
|
+
return { action: "continue" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if there are any inline bash patterns
|
|
33
|
+
if (!PATTERN.test(text)) {
|
|
34
|
+
return { action: "continue" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Reset regex state after test()
|
|
38
|
+
PATTERN.lastIndex = 0;
|
|
39
|
+
|
|
40
|
+
let result = text;
|
|
41
|
+
const expansions: Array<{ command: string; output: string; error?: string }> = [];
|
|
42
|
+
|
|
43
|
+
// Find all matches first (to avoid issues with replacing while iterating)
|
|
44
|
+
const matches: Array<{ full: string; command: string }> = [];
|
|
45
|
+
let match = PATTERN.exec(text);
|
|
46
|
+
while (match) {
|
|
47
|
+
matches.push({ full: match[0], command: match[1] });
|
|
48
|
+
match = PATTERN.exec(text);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Execute each command and collect results
|
|
52
|
+
for (const { full, command } of matches) {
|
|
53
|
+
try {
|
|
54
|
+
const bashResult = await valni.exec("bash", ["-c", command], {
|
|
55
|
+
timeout: TIMEOUT_MS,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const output = bashResult.stdout || bashResult.stderr || "";
|
|
59
|
+
const trimmed = output.trim();
|
|
60
|
+
|
|
61
|
+
if (bashResult.code !== 0 && bashResult.stderr) {
|
|
62
|
+
expansions.push({
|
|
63
|
+
command,
|
|
64
|
+
output: trimmed,
|
|
65
|
+
error: `exit code ${bashResult.code}`,
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
expansions.push({ command, output: trimmed });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
result = result.replace(full, trimmed);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
expansions.push({ command, output: "", error: errorMsg });
|
|
75
|
+
result = result.replace(full, `[error: ${errorMsg}]`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Show what was expanded (if UI available)
|
|
80
|
+
if (ctx.hasUI && expansions.length > 0) {
|
|
81
|
+
const summary = expansions
|
|
82
|
+
.map((e) => {
|
|
83
|
+
const status = e.error ? ` (${e.error})` : "";
|
|
84
|
+
const preview = e.output.length > 50 ? `${e.output.slice(0, 50)}...` : e.output;
|
|
85
|
+
return `!{${e.command}}${status} -> "${preview}"`;
|
|
86
|
+
})
|
|
87
|
+
.join("\n");
|
|
88
|
+
|
|
89
|
+
ctx.ui.notify(`Expanded ${expansions.length} inline command(s):\n${summary}`, "info");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { action: "transform", text: result, images: event.images };
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streaming-Aware Input Gate
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates `event.streamingBehavior` to skip expensive pre-processing
|
|
5
|
+
* during mid-stream steering, where low latency matters.
|
|
6
|
+
*
|
|
7
|
+
* This extension prepends `git diff --stat` output when the user mentions
|
|
8
|
+
* file changes, giving the model immediate context. During steering the
|
|
9
|
+
* exec call is skipped so the correction reaches the model without delay.
|
|
10
|
+
*
|
|
11
|
+
* Start valni with this extension:
|
|
12
|
+
* valni -e ./examples/extensions/input-transform-streaming.ts
|
|
13
|
+
*/
|
|
14
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
15
|
+
|
|
16
|
+
const TRIGGER = /\b(changes?|diff|modified)\b/i;
|
|
17
|
+
|
|
18
|
+
export default function (valni: ExtensionAPI) {
|
|
19
|
+
valni.on("input", async (event) => {
|
|
20
|
+
// During steering, skip the exec call — corrections should be fast
|
|
21
|
+
if (event.streamingBehavior === "steer") {
|
|
22
|
+
return { action: "continue" };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!TRIGGER.test(event.text)) {
|
|
26
|
+
return { action: "continue" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { stdout, code } = await valni.exec("git", ["diff", "--stat"]);
|
|
30
|
+
if (code !== 0 || !stdout.trim()) {
|
|
31
|
+
return { action: "continue" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
action: "transform",
|
|
36
|
+
text: `${event.text}\n\nCurrent uncommitted changes:\n\`\`\`\n${stdout.trim()}\n\`\`\``,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Transform Example - demonstrates the `input` event for intercepting user input.
|
|
3
|
+
*
|
|
4
|
+
* Start valni with this extension:
|
|
5
|
+
* valni -e ./examples/extensions/input-transform.ts
|
|
6
|
+
*
|
|
7
|
+
* Then type these inside valni:
|
|
8
|
+
* ?quick What is TypeScript? → "Respond briefly: What is TypeScript?"
|
|
9
|
+
* ping → "pong" (instant, no LLM)
|
|
10
|
+
* time → current time (instant, no LLM)
|
|
11
|
+
*/
|
|
12
|
+
import type { ExtensionAPI } from "@valni/cli";
|
|
13
|
+
|
|
14
|
+
export default function (valni: ExtensionAPI) {
|
|
15
|
+
valni.on("input", async (event, ctx) => {
|
|
16
|
+
// Source-based logic: skip processing for extension-injected messages
|
|
17
|
+
if (event.source === "extension") {
|
|
18
|
+
return { action: "continue" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Transform: ?quick prefix for brief responses
|
|
22
|
+
if (event.text.startsWith("?quick ")) {
|
|
23
|
+
const query = event.text.slice(7).trim();
|
|
24
|
+
if (!query) {
|
|
25
|
+
ctx.ui.notify("Usage: ?quick <question>", "warning");
|
|
26
|
+
return { action: "handled" };
|
|
27
|
+
}
|
|
28
|
+
return { action: "transform", text: `Respond briefly in 1-2 sentences: ${query}` };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle: instant responses without LLM (extension shows its own feedback)
|
|
32
|
+
if (event.text.toLowerCase() === "ping") {
|
|
33
|
+
ctx.ui.notify("pong", "info");
|
|
34
|
+
return { action: "handled" };
|
|
35
|
+
}
|
|
36
|
+
if (event.text.toLowerCase() === "time") {
|
|
37
|
+
ctx.ui.notify(new Date().toLocaleString(), "info");
|
|
38
|
+
return { action: "handled" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return { action: "continue" };
|
|
42
|
+
});
|
|
43
|
+
}
|