assistme 0.2.2 → 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.
@@ -1,153 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import {
3
- isValidUrl,
4
- isValidApiKey,
5
- isValidPort,
6
- isValidMaxTurns,
7
- validateConfig,
8
- sanitizeForIlike,
9
- sanitizeSelector,
10
- } from "./validation.js";
11
-
12
- describe("isValidUrl", () => {
13
- it("accepts valid HTTPS URLs", () => {
14
- expect(isValidUrl("https://example.supabase.co")).toBe(true);
15
- expect(isValidUrl("https://localhost:3000")).toBe(true);
16
- });
17
-
18
- it("accepts HTTP URLs", () => {
19
- expect(isValidUrl("http://localhost:54321")).toBe(true);
20
- });
21
-
22
- it("rejects invalid URLs", () => {
23
- expect(isValidUrl("not-a-url")).toBe(false);
24
- expect(isValidUrl("ftp://files.example.com")).toBe(false);
25
- expect(isValidUrl("")).toBe(false);
26
- });
27
- });
28
-
29
- describe("isValidApiKey", () => {
30
- it("accepts valid Anthropic keys", () => {
31
- expect(isValidApiKey("sk-ant-api03-aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj")).toBe(true);
32
- });
33
-
34
- it("rejects invalid keys", () => {
35
- expect(isValidApiKey("sk-1234567890")).toBe(false);
36
- expect(isValidApiKey("")).toBe(false);
37
- expect(isValidApiKey("sk-ant-short")).toBe(false);
38
- });
39
- });
40
-
41
- describe("isValidPort", () => {
42
- it("accepts valid ports", () => {
43
- expect(isValidPort(9222)).toBe(true);
44
- expect(isValidPort(1)).toBe(true);
45
- expect(isValidPort(65535)).toBe(true);
46
- });
47
-
48
- it("rejects invalid ports", () => {
49
- expect(isValidPort(0)).toBe(false);
50
- expect(isValidPort(65536)).toBe(false);
51
- expect(isValidPort(-1)).toBe(false);
52
- expect(isValidPort(3.5)).toBe(false);
53
- });
54
- });
55
-
56
- describe("isValidMaxTurns", () => {
57
- it("accepts valid turn counts", () => {
58
- expect(isValidMaxTurns(50)).toBe(true);
59
- expect(isValidMaxTurns(1)).toBe(true);
60
- expect(isValidMaxTurns(500)).toBe(true);
61
- });
62
-
63
- it("rejects invalid turn counts", () => {
64
- expect(isValidMaxTurns(0)).toBe(false);
65
- expect(isValidMaxTurns(501)).toBe(false);
66
- expect(isValidMaxTurns(-10)).toBe(false);
67
- });
68
- });
69
-
70
- describe("validateConfig", () => {
71
- const validConfig = {
72
- supabaseUrl: "https://example.supabase.co",
73
- supabaseAnonKey: "some-anon-key",
74
- anthropicApiKey: "sk-ant-api03-aaaabbbbccccddddeeeeffffgggghhhhiiiijjjj",
75
- workspacePath: "/home/user/workspace",
76
- maxTurns: 50,
77
- };
78
-
79
- it("validates a correct config", () => {
80
- const result = validateConfig(validConfig);
81
- expect(result.valid).toBe(true);
82
- expect(result.errors).toHaveLength(0);
83
- });
84
-
85
- it("catches missing supabaseUrl", () => {
86
- const result = validateConfig({ ...validConfig, supabaseUrl: "" });
87
- expect(result.valid).toBe(false);
88
- expect(result.errors.some((e) => e.includes("supabaseUrl"))).toBe(true);
89
- });
90
-
91
- it("catches invalid URL format", () => {
92
- const result = validateConfig({ ...validConfig, supabaseUrl: "not-url" });
93
- expect(result.valid).toBe(false);
94
- expect(result.errors.some((e) => e.includes("not a valid URL"))).toBe(true);
95
- });
96
-
97
- it("catches missing API key", () => {
98
- const result = validateConfig({ ...validConfig, anthropicApiKey: "" });
99
- expect(result.valid).toBe(false);
100
- expect(result.errors.some((e) => e.includes("anthropicApiKey"))).toBe(true);
101
- });
102
-
103
- it("catches invalid API key format", () => {
104
- const result = validateConfig({ ...validConfig, anthropicApiKey: "bad-key" });
105
- expect(result.valid).toBe(false);
106
- expect(result.errors.some((e) => e.includes("sk-ant-"))).toBe(true);
107
- });
108
-
109
- it("catches invalid maxTurns", () => {
110
- const result = validateConfig({ ...validConfig, maxTurns: 0 });
111
- expect(result.valid).toBe(false);
112
- expect(result.errors.some((e) => e.includes("maxTurns"))).toBe(true);
113
- });
114
-
115
- it("collects multiple errors", () => {
116
- const result = validateConfig({
117
- supabaseUrl: "",
118
- supabaseAnonKey: "",
119
- anthropicApiKey: "",
120
- workspacePath: "",
121
- maxTurns: -1,
122
- });
123
- expect(result.valid).toBe(false);
124
- expect(result.errors.length).toBeGreaterThanOrEqual(4);
125
- });
126
- });
127
-
128
- describe("sanitizeForIlike", () => {
129
- it("escapes % and _ wildcards", () => {
130
- expect(sanitizeForIlike("test%value_here")).toBe("test\\%value\\_here");
131
- });
132
-
133
- it("leaves normal strings unchanged", () => {
134
- expect(sanitizeForIlike("hello world")).toBe("hello world");
135
- });
136
- });
137
-
138
- describe("sanitizeSelector", () => {
139
- it("removes script tags", () => {
140
- expect(sanitizeSelector("<script>alert(1)</script>#btn")).toBe(
141
- "alert(1)#btn"
142
- );
143
- });
144
-
145
- it("removes event handlers", () => {
146
- expect(sanitizeSelector("div[onclick=evil]")).toBe("div[evil]");
147
- });
148
-
149
- it("passes through valid selectors", () => {
150
- expect(sanitizeSelector("#submit-btn")).toBe("#submit-btn");
151
- expect(sanitizeSelector("a.nav-link")).toBe("a.nav-link");
152
- });
153
- });
@@ -1,101 +0,0 @@
1
- /**
2
- * Input validation helpers for CLI configuration and tool inputs.
3
- */
4
-
5
- export interface ValidationResult {
6
- valid: boolean;
7
- errors: string[];
8
- }
9
-
10
- /**
11
- * Validate that a URL is well-formed (https:// or http://).
12
- */
13
- export function isValidUrl(url: string): boolean {
14
- try {
15
- const parsed = new URL(url);
16
- return parsed.protocol === "https:" || parsed.protocol === "http:";
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- /**
23
- * Validate an Anthropic API key format.
24
- * Real keys start with "sk-ant-" and are at least 40 chars.
25
- */
26
- export function isValidApiKey(key: string): boolean {
27
- return key.startsWith("sk-ant-") && key.length >= 40;
28
- }
29
-
30
- /**
31
- * Validate a port number is in valid range.
32
- */
33
- export function isValidPort(port: number): boolean {
34
- return Number.isInteger(port) && port >= 1 && port <= 65535;
35
- }
36
-
37
- /**
38
- * Validate maxTurns is a reasonable number.
39
- */
40
- export function isValidMaxTurns(turns: number): boolean {
41
- return Number.isInteger(turns) && turns >= 1 && turns <= 500;
42
- }
43
-
44
- /**
45
- * Validate the full CLI configuration.
46
- * Returns errors for any required/invalid fields.
47
- */
48
- export function validateConfig(config: {
49
- supabaseUrl: string;
50
- supabaseAnonKey: string;
51
- anthropicApiKey: string;
52
- workspacePath: string;
53
- maxTurns: number;
54
- }): ValidationResult {
55
- const errors: string[] = [];
56
-
57
- if (!config.supabaseUrl) {
58
- errors.push("supabaseUrl is required. Run: assistme config set supabaseUrl <url>");
59
- } else if (!isValidUrl(config.supabaseUrl)) {
60
- errors.push(`supabaseUrl is not a valid URL: "${config.supabaseUrl}"`);
61
- }
62
-
63
- if (!config.supabaseAnonKey) {
64
- errors.push("supabaseAnonKey is required. Run: assistme config set supabaseAnonKey <key>");
65
- }
66
-
67
- if (!config.anthropicApiKey) {
68
- errors.push("anthropicApiKey is required. Set ANTHROPIC_API_KEY env var or run: assistme config set anthropicApiKey <key>");
69
- } else if (!isValidApiKey(config.anthropicApiKey)) {
70
- errors.push("anthropicApiKey does not look like a valid Anthropic key (expected sk-ant-...)");
71
- }
72
-
73
- if (!config.workspacePath) {
74
- errors.push("workspacePath is required");
75
- }
76
-
77
- if (!isValidMaxTurns(config.maxTurns)) {
78
- errors.push(`maxTurns must be between 1 and 500, got: ${config.maxTurns}`);
79
- }
80
-
81
- return { valid: errors.length === 0, errors };
82
- }
83
-
84
- /**
85
- * Sanitize user-provided strings for safe use in ILIKE queries.
86
- * Escapes SQL wildcards % and _.
87
- */
88
- export function sanitizeForIlike(input: string): string {
89
- return input.replace(/[%_]/g, "\\$&");
90
- }
91
-
92
- /**
93
- * Validate and sanitize a CSS selector to prevent obvious injection.
94
- */
95
- export function sanitizeSelector(selector: string): string {
96
- // Remove any embedded script tags (opening and closing) or event handlers
97
- return selector
98
- .replace(/<\/?script[^>]*>/gi, "")
99
- .replace(/on\w+\s*=/gi, "")
100
- .trim();
101
- }