ltcai 0.1.4 → 0.1.8

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.
@@ -0,0 +1,57 @@
1
+ # Skill: <name>
2
+
3
+ ## 메타데이터
4
+ - **버전**: 0.1.0
5
+ - **카테고리**: [coding | data | document | web | system | analysis]
6
+ - **위험도**: [low | medium | high] <!-- low=읽기, medium=쓰기, high=실행/삭제 -->
7
+ - **필요 권한**: [none | local_read | local_write | exec | admin]
8
+
9
+ ## 설명
10
+ 한 줄 설명.
11
+
12
+ ## 입력 스키마
13
+ ```json
14
+ {
15
+ "required": ["field1"],
16
+ "optional": ["field2"],
17
+ "properties": {
18
+ "field1": { "type": "string", "description": "..." },
19
+ "field2": { "type": "integer", "default": 10 }
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## 출력 스키마
25
+ ```json
26
+ {
27
+ "success": true,
28
+ "result": "...",
29
+ "artifacts": []
30
+ }
31
+ ```
32
+
33
+ ## 실행 조건
34
+ - 사전 조건 (예: 모델 로드 필요, 파일 존재 여부)
35
+ - 후처리 조건
36
+
37
+ ## 예제
38
+
39
+ ### 성공 케이스
40
+ **입력**: `{ "field1": "예시값" }`
41
+ **출력**: `{ "success": true, "result": "..." }`
42
+
43
+ ### 실패 케이스
44
+ **입력**: `{ "field1": "" }`
45
+ **출력**: `{ "success": false, "error": "field1 is required" }`
46
+
47
+ ## 실패 처리
48
+ | 에러 코드 | 원인 | 처리 방법 |
49
+ |-----------|------|-----------|
50
+ | `INVALID_INPUT` | 필수 필드 누락 | 입력 검증 후 재시도 |
51
+ | `PERMISSION_DENIED` | 권한 없음 | 관리자에게 문의 |
52
+ | `TIMEOUT` | 실행 시간 초과 | 작업 분할 후 재시도 |
53
+
54
+ ## 테스트 케이스
55
+ ```python
56
+ # tests/skills/test_<name>.py 참조
57
+ ```
@@ -0,0 +1,76 @@
1
+ # Skill: code_review
2
+
3
+ ## 메타데이터
4
+ - **버전**: 0.1.0
5
+ - **카테고리**: coding
6
+ - **위험도**: low
7
+ - **필요 권한**: local_read
8
+
9
+ ## 설명
10
+ 파일 또는 코드 스니펫을 LLM에 전달해 버그, 보안 이슈, 성능, 스타일을 리뷰한다.
11
+
12
+ ## 입력 스키마
13
+ ```json
14
+ {
15
+ "required": ["target"],
16
+ "optional": ["focus", "lang", "max_lines"],
17
+ "properties": {
18
+ "target": { "type": "string", "description": "절대 파일 경로 또는 코드 스니펫 문자열" },
19
+ "focus": { "type": "array", "items": { "type": "string", "enum": ["bug", "security", "performance", "style"] }, "default": ["bug", "security"], "description": "리뷰 초점 목록" },
20
+ "lang": { "type": "string", "description": "언어 힌트 (python, js, go …). 미입력시 자동 감지" },
21
+ "max_lines": { "type": "integer", "default": 500, "description": "분석할 최대 줄 수" }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## 출력 스키마
27
+ ```json
28
+ {
29
+ "success": true,
30
+ "result": {
31
+ "summary": "...",
32
+ "issues": [
33
+ { "severity": "high|medium|low", "line": 42, "category": "security", "message": "..." }
34
+ ],
35
+ "score": 85
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## 실행 조건
41
+ - LLM 모델이 로드되어 있어야 함 (`/mode` 응답의 `model` 필드 비어있지 않음)
42
+ - 파일 대상인 경우 파일이 존재해야 함
43
+
44
+ ## 예제
45
+
46
+ ### 성공 케이스
47
+ **입력**: `{ "target": "~/project/server.py", "focus": ["security"] }`
48
+ **출력**:
49
+ ```json
50
+ {
51
+ "success": true,
52
+ "result": {
53
+ "summary": "1개의 심각한 보안 이슈 발견",
54
+ "issues": [{ "severity": "high", "line": 102, "category": "security", "message": "SQL 쿼리에 사용자 입력이 직접 삽입됨" }],
55
+ "score": 60
56
+ }
57
+ }
58
+ ```
59
+
60
+ ### 실패 케이스
61
+ **입력**: `{ "target": "" }`
62
+ **출력**: `{ "success": false, "error": "INVALID_INPUT", "message": "target is required" }`
63
+
64
+ ## 실패 처리
65
+ | 에러 코드 | 원인 | 처리 방법 |
66
+ |-----------|------|-----------|
67
+ | `INVALID_INPUT` | target 비어 있음 | 파일 경로 또는 코드 입력 |
68
+ | `FILE_NOT_FOUND` | 파일 경로 존재하지 않음 | 경로 확인 |
69
+ | `MODEL_NOT_LOADED` | LLM 미로드 | `/model` 명령으로 모델 선택 |
70
+ | `SIZE_LIMIT` | max_lines 초과 파일 | max_lines 값 조정 또는 파일 분할 |
71
+
72
+ ## 테스트 케이스
73
+ ```python
74
+ # tests/unit/test_tools.py::test_code_review_snippet
75
+ # tests/integration/test_api.py::test_agent_code_review_file
76
+ ```
@@ -0,0 +1,79 @@
1
+ # Skill: data_analysis
2
+
3
+ ## 메타데이터
4
+ - **버전**: 0.1.0
5
+ - **카테고리**: data
6
+ - **위험도**: low
7
+ - **필요 권한**: local_read
8
+
9
+ ## 설명
10
+ CSV/Excel/JSON 파일을 읽어 기초 통계, 컬럼 요약, 이상치 탐지를 수행하고 인사이트를 제공한다.
11
+
12
+ ## 입력 스키마
13
+ ```json
14
+ {
15
+ "required": ["path"],
16
+ "optional": ["columns", "analysis_type", "max_rows"],
17
+ "properties": {
18
+ "path": { "type": "string", "description": "분석할 파일 절대 경로 (.csv, .xlsx, .json)" },
19
+ "columns": { "type": "array", "items": { "type": "string" }, "description": "분석할 컬럼 목록. 미입력시 전체" },
20
+ "analysis_type": { "type": "array", "items": { "type": "string", "enum": ["summary", "outlier", "correlation", "trend"] }, "default": ["summary"], "description": "수행할 분석 유형" },
21
+ "max_rows": { "type": "integer", "default": 10000, "description": "처리할 최대 행 수" }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ## 출력 스키마
27
+ ```json
28
+ {
29
+ "success": true,
30
+ "result": {
31
+ "shape": [100, 5],
32
+ "columns": ["col1", "col2"],
33
+ "summary": { "col1": { "mean": 42.0, "std": 3.1, "min": 10, "max": 99 } },
34
+ "outliers": { "col1": [99, 10] },
35
+ "insights": "..."
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## 실행 조건
41
+ - pandas, openpyxl 패키지 설치 필요 (requirements.txt 포함)
42
+ - 파일이 존재해야 하며 .csv/.xlsx/.json 형식이어야 함
43
+
44
+ ## 예제
45
+
46
+ ### 성공 케이스
47
+ **입력**: `{ "path": "~/data/sales.csv", "analysis_type": ["summary", "outlier"] }`
48
+ **출력**:
49
+ ```json
50
+ {
51
+ "success": true,
52
+ "result": {
53
+ "shape": [500, 4],
54
+ "columns": ["date", "revenue", "units", "region"],
55
+ "summary": { "revenue": { "mean": 15000.0, "std": 4200.0, "min": 200, "max": 89000 } },
56
+ "outliers": { "revenue": [89000] },
57
+ "insights": "revenue 컬럼에서 1개의 이상치(89000) 발견. 평균 대비 17.6 표준편차."
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### 실패 케이스
63
+ **입력**: `{ "path": "~/data/photo.png" }`
64
+ **출력**: `{ "success": false, "error": "UNSUPPORTED_FORMAT", "message": "Supported formats: csv, xlsx, json" }`
65
+
66
+ ## 실패 처리
67
+ | 에러 코드 | 원인 | 처리 방법 |
68
+ |-----------|------|-----------|
69
+ | `INVALID_INPUT` | path 누락 | 파일 경로 입력 |
70
+ | `FILE_NOT_FOUND` | 경로에 파일 없음 | 경로 확인 |
71
+ | `UNSUPPORTED_FORMAT` | csv/xlsx/json 이외 형식 | 지원 형식으로 변환 후 재시도 |
72
+ | `PARSE_ERROR` | 파일 파싱 실패 | 파일 인코딩/형식 확인 |
73
+ | `SIZE_LIMIT` | max_rows 초과 | max_rows 값 조정 |
74
+
75
+ ## 테스트 케이스
76
+ ```python
77
+ # tests/unit/test_tools.py::test_data_analysis_csv_summary
78
+ # tests/unit/test_tools.py::test_data_analysis_unsupported_format
79
+ ```
@@ -0,0 +1,68 @@
1
+ # Skill: file_edit
2
+
3
+ ## 메타데이터
4
+ - **버전**: 0.1.0
5
+ - **카테고리**: system
6
+ - **위험도**: medium
7
+ - **필요 권한**: local_write
8
+
9
+ ## 설명
10
+ 로컬 파일을 읽고 특정 범위를 편집한 뒤 저장한다. 원본 백업을 `.bak` 파일로 생성한다.
11
+
12
+ ## 입력 스키마
13
+ ```json
14
+ {
15
+ "required": ["path", "new_content"],
16
+ "optional": ["start_line", "end_line", "backup"],
17
+ "properties": {
18
+ "path": { "type": "string", "description": "절대 경로 또는 ~/로 시작하는 경로" },
19
+ "new_content": { "type": "string", "description": "교체할 새 내용" },
20
+ "start_line": { "type": "integer", "description": "편집 시작 줄 번호 (1-indexed). 없으면 전체 교체" },
21
+ "end_line": { "type": "integer", "description": "편집 종료 줄 번호 (포함). 없으면 start_line 단일 줄" },
22
+ "backup": { "type": "boolean", "default": true, "description": "false면 .bak 파일 생성 생략" }
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## 출력 스키마
28
+ ```json
29
+ {
30
+ "success": true,
31
+ "result": {
32
+ "path": "...",
33
+ "lines_changed": 3,
34
+ "backup_path": "....bak"
35
+ }
36
+ }
37
+ ```
38
+
39
+ ## 실행 조건
40
+ - 대상 파일이 존재해야 함
41
+ - BINARY_EXTS(png, jpg, zip 등) 파일은 거부
42
+ - 파일 크기 10 MB 이하
43
+
44
+ ## 예제
45
+
46
+ ### 성공 케이스
47
+ **입력**: `{ "path": "~/project/config.py", "new_content": "DEBUG = False\n", "start_line": 5, "end_line": 5 }`
48
+ **출력**: `{ "success": true, "result": { "path": "...", "lines_changed": 1, "backup_path": "...config.py.bak" } }`
49
+
50
+ ### 실패 케이스
51
+ **입력**: `{ "path": "~/photo.png", "new_content": "..." }`
52
+ **출력**: `{ "success": false, "error": "BINARY_FILE", "message": "Binary files cannot be edited as text" }`
53
+
54
+ ## 실패 처리
55
+ | 에러 코드 | 원인 | 처리 방법 |
56
+ |-----------|------|-----------|
57
+ | `INVALID_INPUT` | path 또는 new_content 누락 | 필드 확인 후 재시도 |
58
+ | `FILE_NOT_FOUND` | 경로에 파일 없음 | 경로 확인 |
59
+ | `BINARY_FILE` | 바이너리 파일 편집 시도 | 텍스트 파일만 지원 |
60
+ | `PERMISSION_DENIED` | 파일 쓰기 권한 없음 | 관리자 권한 확인 |
61
+ | `SIZE_LIMIT` | 파일 10 MB 초과 | 파일 분할 후 재시도 |
62
+
63
+ ## 테스트 케이스
64
+ ```python
65
+ # tests/unit/test_tools.py::test_file_edit_full_replace
66
+ # tests/unit/test_tools.py::test_file_edit_line_range
67
+ # tests/unit/test_tools.py::test_file_edit_binary_rejected
68
+ ```
@@ -0,0 +1,74 @@
1
+ # Skill: web_search
2
+
3
+ ## 메타데이터
4
+ - **버전**: 0.1.0
5
+ - **카테고리**: web
6
+ - **위험도**: low
7
+ - **필요 권한**: none
8
+
9
+ ## 설명
10
+ 외부 검색 엔진(DuckDuckGo/Brave)을 통해 웹 검색을 수행하고 상위 결과를 반환한다.
11
+
12
+ ## 입력 스키마
13
+ ```json
14
+ {
15
+ "required": ["query"],
16
+ "optional": ["num_results", "lang"],
17
+ "properties": {
18
+ "query": { "type": "string", "description": "검색어" },
19
+ "num_results": { "type": "integer", "default": 5, "description": "반환할 결과 수 (1-20)" },
20
+ "lang": { "type": "string", "default": "ko-KR", "description": "검색 언어 로케일" }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ## 출력 스키마
26
+ ```json
27
+ {
28
+ "success": true,
29
+ "result": {
30
+ "query": "...",
31
+ "results": [
32
+ { "title": "...", "url": "...", "snippet": "..." }
33
+ ]
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## 실행 조건
39
+ - 네트워크 연결 필요
40
+ - 외부 API 키 불필요 (DuckDuckGo instant answer API 사용)
41
+
42
+ ## 예제
43
+
44
+ ### 성공 케이스
45
+ **입력**: `{ "query": "FastAPI 비동기 처리", "num_results": 3 }`
46
+ **출력**:
47
+ ```json
48
+ {
49
+ "success": true,
50
+ "result": {
51
+ "query": "FastAPI 비동기 처리",
52
+ "results": [
53
+ { "title": "FastAPI - Async", "url": "https://fastapi.tiangolo.com/async/", "snippet": "..." }
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### 실패 케이스
60
+ **입력**: `{ "query": "" }`
61
+ **출력**: `{ "success": false, "error": "INVALID_INPUT", "message": "query is required" }`
62
+
63
+ ## 실패 처리
64
+ | 에러 코드 | 원인 | 처리 방법 |
65
+ |-----------|------|-----------|
66
+ | `INVALID_INPUT` | query 비어 있음 | 검색어 입력 후 재시도 |
67
+ | `NETWORK_ERROR` | 외부 API 연결 실패 | 잠시 후 재시도 |
68
+ | `TIMEOUT` | 5초 초과 | 검색어 단순화 후 재시도 |
69
+
70
+ ## 테스트 케이스
71
+ ```python
72
+ # tests/unit/test_tools.py::test_web_search_returns_results
73
+ # tests/integration/test_api.py::test_agent_web_search
74
+ ```
@@ -2,8 +2,13 @@
2
2
  <html lang="ko">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
6
  <title>Lattice AI</title>
7
+ <link rel="manifest" href="/manifest.json">
8
+ <meta name="theme-color" content="#2d5a3d">
9
+ <meta name="apple-mobile-web-app-capable" content="yes">
10
+ <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
11
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
7
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
8
13
  <style>
9
14
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
@@ -300,7 +305,12 @@
300
305
 
301
306
  <script>
302
307
  const API_BASE = window.location.protocol === 'file:' ? 'http://localhost:4825' : '';
303
- function apiFetch(path, opts = {}) { return fetch(API_BASE + path, opts); }
308
+ function apiFetch(path, opts = {}) {
309
+ const headers = { ...(opts.headers || {}) };
310
+ const token = localStorage.getItem('ltcai_session_token') || '';
311
+ if (token && !headers.Authorization) headers.Authorization = `Bearer ${token}`;
312
+ return fetch(API_BASE + path, { credentials: 'include', ...opts, headers });
313
+ }
304
314
 
305
315
  // ── i18n ──────────────────────────────────────────────
306
316
  const I18N = {
@@ -431,6 +441,7 @@
431
441
  localStorage.setItem('ltcai_user_email', data.email);
432
442
  localStorage.setItem('ltcai_user_nickname', data.nickname || data.name || data.email);
433
443
  localStorage.setItem('ltcai_is_admin', data.is_admin ? 'true' : 'false');
444
+ if (data.token) localStorage.setItem('ltcai_session_token', data.token);
434
445
  window.location.href = '/chat';
435
446
  } else {
436
447
  const data = await res.json().catch(() => ({}));
@@ -473,6 +484,7 @@
473
484
  localStorage.setItem('ltcai_user_email', data.email);
474
485
  localStorage.setItem('ltcai_user_nickname', data.nickname || data.name || data.email);
475
486
  localStorage.setItem('ltcai_is_admin', data.is_admin ? 'true' : 'false');
487
+ if (data.token) localStorage.setItem('ltcai_session_token', data.token);
476
488
  window.location.href = '/chat';
477
489
  }
478
490
  });