iris-chatbot 0.2.4
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 +21 -0
- package/README.md +49 -0
- package/bin/iris.mjs +267 -0
- package/package.json +61 -0
- package/template/LICENSE +21 -0
- package/template/README.md +49 -0
- package/template/eslint.config.mjs +18 -0
- package/template/next.config.ts +7 -0
- package/template/package-lock.json +9193 -0
- package/template/package.json +46 -0
- package/template/postcss.config.mjs +7 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/next.svg +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/api/chat/route.ts +2445 -0
- package/template/src/app/api/connections/models/route.ts +255 -0
- package/template/src/app/api/connections/test/route.ts +124 -0
- package/template/src/app/api/local-sync/route.ts +74 -0
- package/template/src/app/api/tool-approval/route.ts +47 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +808 -0
- package/template/src/app/layout.tsx +74 -0
- package/template/src/app/page.tsx +444 -0
- package/template/src/components/ChatView.tsx +1537 -0
- package/template/src/components/Composer.tsx +160 -0
- package/template/src/components/MapView.tsx +244 -0
- package/template/src/components/MessageCard.tsx +955 -0
- package/template/src/components/SearchModal.tsx +72 -0
- package/template/src/components/SettingsModal.tsx +1257 -0
- package/template/src/components/Sidebar.tsx +153 -0
- package/template/src/components/TopBar.tsx +164 -0
- package/template/src/lib/connections.ts +275 -0
- package/template/src/lib/data.ts +324 -0
- package/template/src/lib/db.ts +49 -0
- package/template/src/lib/hooks.ts +76 -0
- package/template/src/lib/local-sync.ts +192 -0
- package/template/src/lib/memory.ts +695 -0
- package/template/src/lib/model-presets.ts +251 -0
- package/template/src/lib/store.ts +36 -0
- package/template/src/lib/tooling/approvals.ts +78 -0
- package/template/src/lib/tooling/providers/anthropic.ts +155 -0
- package/template/src/lib/tooling/providers/ollama.ts +73 -0
- package/template/src/lib/tooling/providers/openai.ts +267 -0
- package/template/src/lib/tooling/providers/openai_compatible.ts +16 -0
- package/template/src/lib/tooling/providers/types.ts +44 -0
- package/template/src/lib/tooling/registry.ts +103 -0
- package/template/src/lib/tooling/runtime.ts +189 -0
- package/template/src/lib/tooling/safety.ts +165 -0
- package/template/src/lib/tooling/tools/apps.ts +108 -0
- package/template/src/lib/tooling/tools/apps_plus.ts +153 -0
- package/template/src/lib/tooling/tools/communication.ts +883 -0
- package/template/src/lib/tooling/tools/files.ts +395 -0
- package/template/src/lib/tooling/tools/music.ts +988 -0
- package/template/src/lib/tooling/tools/notes.ts +461 -0
- package/template/src/lib/tooling/tools/notes_plus.ts +294 -0
- package/template/src/lib/tooling/tools/numbers.ts +175 -0
- package/template/src/lib/tooling/tools/schedule.ts +579 -0
- package/template/src/lib/tooling/tools/system.ts +142 -0
- package/template/src/lib/tooling/tools/web.ts +212 -0
- package/template/src/lib/tooling/tools/workflow.ts +218 -0
- package/template/src/lib/tooling/types.ts +27 -0
- package/template/src/lib/types.ts +309 -0
- package/template/src/lib/utils.ts +108 -0
- package/template/tsconfig.json +34 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import type { ApprovalMode, LocalToolsSettings } from "../types";
|
|
5
|
+
import type { ToolRisk } from "./types";
|
|
6
|
+
|
|
7
|
+
const SYSTEM_DENY_PREFIXES = [
|
|
8
|
+
"/System",
|
|
9
|
+
"/Library",
|
|
10
|
+
"/private",
|
|
11
|
+
"/bin",
|
|
12
|
+
"/sbin",
|
|
13
|
+
"/usr",
|
|
14
|
+
"/etc",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
const SENSITIVE_SEGMENTS = [".ssh", ".aws", ".gnupg", ".config"];
|
|
18
|
+
|
|
19
|
+
function isInside(root: string, target: string): boolean {
|
|
20
|
+
const rel = path.relative(root, target);
|
|
21
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function expandUserPath(value: string): string {
|
|
25
|
+
if (!value) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
if (value === "~") {
|
|
29
|
+
return os.homedir();
|
|
30
|
+
}
|
|
31
|
+
if (value.startsWith("~/")) {
|
|
32
|
+
return path.join(os.homedir(), value.slice(2));
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function normalizeAllowedRoots(allowedRoots: string[]): Promise<string[]> {
|
|
38
|
+
const roots: string[] = [];
|
|
39
|
+
for (const rawRoot of allowedRoots) {
|
|
40
|
+
const expanded = expandUserPath(rawRoot).trim();
|
|
41
|
+
if (!expanded) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const absolute = path.isAbsolute(expanded)
|
|
45
|
+
? path.normalize(expanded)
|
|
46
|
+
: path.resolve(process.cwd(), expanded);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
roots.push(await fs.realpath(absolute));
|
|
50
|
+
} catch {
|
|
51
|
+
roots.push(absolute);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return [...new Set(roots)];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function resolveExistingPath(candidate: string): Promise<string> {
|
|
58
|
+
const absolute = path.isAbsolute(candidate)
|
|
59
|
+
? path.normalize(candidate)
|
|
60
|
+
: path.resolve(process.cwd(), candidate);
|
|
61
|
+
return fs.realpath(absolute);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function resolveWriteTarget(candidate: string): Promise<string> {
|
|
65
|
+
const absolute = path.isAbsolute(candidate)
|
|
66
|
+
? path.normalize(candidate)
|
|
67
|
+
: path.resolve(process.cwd(), candidate);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
return await fs.realpath(absolute);
|
|
71
|
+
} catch {
|
|
72
|
+
let cursor = path.dirname(absolute);
|
|
73
|
+
let suffix = path.basename(absolute);
|
|
74
|
+
|
|
75
|
+
while (true) {
|
|
76
|
+
try {
|
|
77
|
+
const ancestorReal = await fs.realpath(cursor);
|
|
78
|
+
return path.join(ancestorReal, suffix);
|
|
79
|
+
} catch {
|
|
80
|
+
const next = path.dirname(cursor);
|
|
81
|
+
if (next === cursor) {
|
|
82
|
+
throw new Error(`Unable to resolve write target path: ${absolute}`);
|
|
83
|
+
}
|
|
84
|
+
suffix = path.join(path.basename(cursor), suffix);
|
|
85
|
+
cursor = next;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function hasSensitiveSegment(target: string): boolean {
|
|
92
|
+
return SENSITIVE_SEGMENTS.some((segment) =>
|
|
93
|
+
target.split(path.sep).includes(segment),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isDeniedBySystemPrefix(target: string): boolean {
|
|
98
|
+
return SYSTEM_DENY_PREFIXES.some(
|
|
99
|
+
(prefix) => target === prefix || target.startsWith(`${prefix}${path.sep}`),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function assertAllowedPath(params: {
|
|
104
|
+
candidate: string;
|
|
105
|
+
localTools: LocalToolsSettings;
|
|
106
|
+
mode: "read" | "write";
|
|
107
|
+
}): Promise<string> {
|
|
108
|
+
const normalizedRoots = await normalizeAllowedRoots(params.localTools.allowedRoots);
|
|
109
|
+
if (normalizedRoots.length === 0) {
|
|
110
|
+
throw new Error("No allowed filesystem roots are configured.");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const resolved =
|
|
114
|
+
params.mode === "read"
|
|
115
|
+
? await resolveExistingPath(expandUserPath(params.candidate))
|
|
116
|
+
: await resolveWriteTarget(expandUserPath(params.candidate));
|
|
117
|
+
|
|
118
|
+
const insideAllowed = normalizedRoots.some((root) => isInside(root, resolved));
|
|
119
|
+
if (!insideAllowed) {
|
|
120
|
+
throw new Error(`Path is outside allowed roots: ${resolved}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (isDeniedBySystemPrefix(resolved)) {
|
|
124
|
+
throw new Error(`Path is blocked by system denylist: ${resolved}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (hasSensitiveSegment(resolved)) {
|
|
128
|
+
throw new Error(`Path contains a blocked sensitive segment: ${resolved}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return resolved;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function requiresApprovalForRisk(risk: ToolRisk, mode: ApprovalMode): boolean {
|
|
135
|
+
if (mode === "trusted_auto") {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (mode === "always_confirm_writes") {
|
|
140
|
+
return risk !== "read";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return risk === "destructive";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function requiresApprovalForSafetyProfile(
|
|
147
|
+
risk: ToolRisk,
|
|
148
|
+
profile: "strict" | "balanced" | "auto",
|
|
149
|
+
): boolean {
|
|
150
|
+
if (profile === "strict") {
|
|
151
|
+
return risk !== "read";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (profile === "balanced") {
|
|
155
|
+
return ["write", "destructive", "external", "system"].includes(risk);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return risk === "destructive" || risk === "external";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function ensureMacOS(feature: string) {
|
|
162
|
+
if (process.platform !== "darwin") {
|
|
163
|
+
throw new Error(`${feature} is only available on macOS.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ensureMacOS } from "../safety";
|
|
2
|
+
import { runCommandSafe } from "../runtime";
|
|
3
|
+
import type { ToolDefinition, ToolExecutionContext } from "../types";
|
|
4
|
+
|
|
5
|
+
type AppInput = {
|
|
6
|
+
appName?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const APP_COMMAND_TIMEOUT_MS = 20_000;
|
|
10
|
+
|
|
11
|
+
function asObject(input: unknown): Record<string, unknown> {
|
|
12
|
+
if (!input || typeof input !== "object") {
|
|
13
|
+
throw new Error("Tool input must be an object.");
|
|
14
|
+
}
|
|
15
|
+
return input as Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function asString(input: unknown, field: string): string {
|
|
19
|
+
if (typeof input !== "string" || !input.trim()) {
|
|
20
|
+
throw new Error(`Missing required string field: ${field}`);
|
|
21
|
+
}
|
|
22
|
+
return input.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function runCommand(command: string, args: string[], signal?: AbortSignal): Promise<string> {
|
|
26
|
+
const { stdout } = await runCommandSafe({
|
|
27
|
+
command,
|
|
28
|
+
args,
|
|
29
|
+
signal,
|
|
30
|
+
timeoutMs: APP_COMMAND_TIMEOUT_MS,
|
|
31
|
+
});
|
|
32
|
+
return stdout;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runAppOpen(input: unknown, context: ToolExecutionContext) {
|
|
36
|
+
const payload = asObject(input) as AppInput;
|
|
37
|
+
const appName = asString(payload.appName, "appName");
|
|
38
|
+
|
|
39
|
+
ensureMacOS("App open automation");
|
|
40
|
+
|
|
41
|
+
if (context.localTools.dryRun) {
|
|
42
|
+
return {
|
|
43
|
+
dryRun: true,
|
|
44
|
+
action: "app_open",
|
|
45
|
+
appName,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await runCommand("open", ["-a", appName], context.signal);
|
|
50
|
+
return {
|
|
51
|
+
opened: true,
|
|
52
|
+
appName,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runAppFocus(input: unknown, context: ToolExecutionContext) {
|
|
57
|
+
const payload = asObject(input) as AppInput;
|
|
58
|
+
const appName = asString(payload.appName, "appName");
|
|
59
|
+
|
|
60
|
+
ensureMacOS("App focus automation");
|
|
61
|
+
|
|
62
|
+
if (context.localTools.dryRun) {
|
|
63
|
+
return {
|
|
64
|
+
dryRun: true,
|
|
65
|
+
action: "app_focus",
|
|
66
|
+
appName,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const script = `tell application ${JSON.stringify(appName)} to activate`;
|
|
71
|
+
await runCommand("osascript", ["-e", script], context.signal);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
focused: true,
|
|
75
|
+
appName,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const appTools: ToolDefinition[] = [
|
|
80
|
+
{
|
|
81
|
+
name: "app_open",
|
|
82
|
+
description: "Open an installed macOS app by name.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
required: ["appName"],
|
|
86
|
+
properties: {
|
|
87
|
+
appName: { type: "string" },
|
|
88
|
+
},
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
risk: "app",
|
|
92
|
+
execute: runAppOpen,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "app_focus",
|
|
96
|
+
description: "Bring an app to the foreground.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
required: ["appName"],
|
|
100
|
+
properties: {
|
|
101
|
+
appName: { type: "string" },
|
|
102
|
+
},
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
},
|
|
105
|
+
risk: "app",
|
|
106
|
+
execute: runAppFocus,
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { ensureMacOS } from "../safety";
|
|
2
|
+
import { runCommandSafe } from "../runtime";
|
|
3
|
+
import type { ToolDefinition, ToolExecutionContext } from "../types";
|
|
4
|
+
|
|
5
|
+
type AppNameInput = {
|
|
6
|
+
appName?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type AppOpenResourceInput = {
|
|
10
|
+
appName?: string;
|
|
11
|
+
pathOrUrl?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type AppWindowActionInput = {
|
|
15
|
+
appName?: string;
|
|
16
|
+
action?: "minimize" | "fullscreen" | "hide" | "unhide" | "close_front_window";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function asObject(input: unknown): Record<string, unknown> {
|
|
20
|
+
if (!input || typeof input !== "object") {
|
|
21
|
+
throw new Error("Tool input must be an object.");
|
|
22
|
+
}
|
|
23
|
+
return input as Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function asString(input: unknown, field: string): string {
|
|
27
|
+
if (typeof input !== "string" || !input.trim()) {
|
|
28
|
+
throw new Error(`Missing required string field: ${field}`);
|
|
29
|
+
}
|
|
30
|
+
return input.trim();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function runAppleScript(script: string, args: string[] = [], signal?: AbortSignal) {
|
|
34
|
+
const { stdout } = await runCommandSafe({
|
|
35
|
+
command: "osascript",
|
|
36
|
+
args: ["-e", script, ...args],
|
|
37
|
+
signal,
|
|
38
|
+
});
|
|
39
|
+
return stdout;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runAppQuit(input: unknown, context: ToolExecutionContext) {
|
|
43
|
+
ensureMacOS("App automation");
|
|
44
|
+
const payload = asObject(input) as AppNameInput;
|
|
45
|
+
const appName = asString(payload.appName, "appName");
|
|
46
|
+
|
|
47
|
+
if (context.localTools.dryRun) {
|
|
48
|
+
return { dryRun: true, action: "app_quit", appName };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await runAppleScript(`tell application ${JSON.stringify(appName)} to quit`, [], context.signal);
|
|
52
|
+
return { quit: true, appName };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runAppOpenResource(input: unknown, context: ToolExecutionContext) {
|
|
56
|
+
ensureMacOS("App automation");
|
|
57
|
+
const payload = asObject(input) as AppOpenResourceInput;
|
|
58
|
+
const pathOrUrl = asString(payload.pathOrUrl, "pathOrUrl");
|
|
59
|
+
const appName = typeof payload.appName === "string" ? payload.appName.trim() : "";
|
|
60
|
+
|
|
61
|
+
if (context.localTools.dryRun) {
|
|
62
|
+
return { dryRun: true, action: "app_open_resource", appName: appName || null, pathOrUrl };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const args = appName ? ["-a", appName, pathOrUrl] : [pathOrUrl];
|
|
66
|
+
await runCommandSafe({
|
|
67
|
+
command: "open",
|
|
68
|
+
args,
|
|
69
|
+
signal: context.signal,
|
|
70
|
+
});
|
|
71
|
+
return { opened: true, appName: appName || null, pathOrUrl };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function runAppWindowAction(input: unknown, context: ToolExecutionContext) {
|
|
75
|
+
ensureMacOS("App automation");
|
|
76
|
+
const payload = asObject(input) as AppWindowActionInput;
|
|
77
|
+
const appName = asString(payload.appName, "appName");
|
|
78
|
+
const action = payload.action;
|
|
79
|
+
if (!action) {
|
|
80
|
+
throw new Error("Missing required string field: action");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (context.localTools.dryRun) {
|
|
84
|
+
return { dryRun: true, action: "app_window_action", appName, windowAction: action };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const actionScript =
|
|
88
|
+
action === "hide"
|
|
89
|
+
? "set visible to false"
|
|
90
|
+
: action === "unhide"
|
|
91
|
+
? "set visible to true"
|
|
92
|
+
: action === "close_front_window"
|
|
93
|
+
? 'if (count of windows) > 0 then close window 1'
|
|
94
|
+
: action === "minimize"
|
|
95
|
+
? 'if (count of windows) > 0 then set miniaturized of window 1 to true'
|
|
96
|
+
: 'if (count of windows) > 0 then set value of attribute "AXFullScreen" of window 1 to true';
|
|
97
|
+
|
|
98
|
+
const script =
|
|
99
|
+
`tell application ${JSON.stringify(appName)}\n` +
|
|
100
|
+
"activate\n" +
|
|
101
|
+
`${actionScript}\n` +
|
|
102
|
+
"end tell";
|
|
103
|
+
await runAppleScript(script, [], context.signal);
|
|
104
|
+
return { appName, action };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const appPlusTools: ToolDefinition[] = [
|
|
108
|
+
{
|
|
109
|
+
name: "app_quit",
|
|
110
|
+
description: "Quit an app by name.",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
required: ["appName"],
|
|
114
|
+
properties: { appName: { type: "string" } },
|
|
115
|
+
additionalProperties: false,
|
|
116
|
+
},
|
|
117
|
+
risk: "app",
|
|
118
|
+
execute: runAppQuit,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "app_open_resource",
|
|
122
|
+
description: "Open a URL or file path, optionally with a specific app.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
required: ["pathOrUrl"],
|
|
126
|
+
properties: {
|
|
127
|
+
appName: { type: "string" },
|
|
128
|
+
pathOrUrl: { type: "string" },
|
|
129
|
+
},
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
},
|
|
132
|
+
risk: "app",
|
|
133
|
+
execute: runAppOpenResource,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "app_window_action",
|
|
137
|
+
description: "Perform a window action for an app.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
required: ["appName", "action"],
|
|
141
|
+
properties: {
|
|
142
|
+
appName: { type: "string" },
|
|
143
|
+
action: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["minimize", "fullscreen", "hide", "unhide", "close_front_window"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
additionalProperties: false,
|
|
149
|
+
},
|
|
150
|
+
risk: "app",
|
|
151
|
+
execute: runAppWindowAction,
|
|
152
|
+
},
|
|
153
|
+
];
|