@nomadamas/k-skill 0.1.0 → 0.2.1
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/bin/k-skill.js +35 -5
- package/package.json +2 -2
- package/skills/assembly-bill-vote-search/skill.json +0 -1
- package/skills/biz-health-check/instruction.md +2 -2
- package/skills/biz-health-check/skill.json +0 -1
- package/skills/bok-ecos-stats/instruction.md +8 -8
- package/skills/bok-ecos-stats/skill.json +0 -1
- package/skills/building-register-search/instruction.md +6 -6
- package/skills/building-register-search/scripts/building_register.py +0 -0
- package/skills/cheap-gas-nearby/skill.json +0 -1
- package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +0 -0
- package/skills/corporate-registration-consulting/skill.json +3 -1
- package/skills/coupang-product-search/instruction.md +10 -10
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +0 -0
- package/skills/court-auction-notice-search/skill.json +2 -1
- package/skills/court-payment-order-assistant/skill.json +1 -1
- package/skills/d2b-notice-search/skill.json +2 -1
- package/skills/daangn-cars-search/instruction.md +3 -3
- package/skills/daangn-cars-search/scripts/daangn_cars.py +0 -0
- package/skills/daangn-jobs-search/instruction.md +2 -2
- package/skills/daangn-jobs-search/scripts/daangn_jobs.py +0 -0
- package/skills/daangn-jobs-search/skill.json +3 -1
- package/skills/daangn-realty-search/instruction.md +1 -1
- package/skills/daangn-realty-search/scripts/daangn_realty.py +0 -0
- package/skills/daangn-used-goods-search/instruction.md +2 -2
- package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +0 -0
- package/skills/daishin-report-search/skill.json +0 -1
- package/skills/danawa-price-search/instruction.md +4 -4
- package/skills/danawa-price-search/scripts/danawa_search.py +0 -0
- package/skills/ev-charger-nearby/instruction.md +5 -5
- package/skills/ev-charger-nearby/skill.json +1 -2
- package/skills/express-bus-booking/instruction.md +2 -2
- package/skills/express-bus-booking/scripts/kobus_express_booking.py +0 -0
- package/skills/express-bus-booking/skill.json +2 -0
- package/skills/fine-dust-location/instruction.md +1 -1
- package/skills/fine-dust-location/scripts/fine_dust.py +549 -0
- package/skills/fine-dust-location/skill.json +7 -1
- package/skills/flight-ticket-search/instruction.md +7 -7
- package/skills/foresttrip-vacancy/instruction.md +7 -7
- package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +0 -0
- package/skills/fsc-corporate-info/instruction.md +1 -1
- package/skills/fsc-corporate-info/skill.json +0 -1
- package/skills/g2b-order-plan-search/instruction.md +2 -2
- package/skills/g2b-order-plan-search/skill.json +2 -1
- package/skills/g2b-sanctioned-supplier/instruction.md +1 -1
- package/skills/g2b-sanctioned-supplier/skill.json +0 -1
- package/skills/geeknews-search/instruction.md +3 -3
- package/skills/geeknews-search/scripts/geeknews_search.py +0 -0
- package/skills/gov-overseas-trip-report/instruction.md +12 -12
- package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +0 -0
- package/skills/han-river-water-level/skill.json +0 -1
- package/skills/highway-traffic-status/instruction.md +2 -2
- package/skills/highway-traffic-status/skill.json +0 -1
- package/skills/household-waste-info/skill.json +0 -1
- package/skills/intercity-bus-booking/instruction.md +3 -3
- package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +0 -0
- package/skills/intercity-bus-booking/skill.json +2 -0
- package/skills/iros-registry-automation/instruction.md +2 -2
- package/skills/iros-registry-automation/skill.json +2 -1
- package/skills/job-posting-match/instruction.md +2 -2
- package/skills/job-posting-match/skill.json +1 -1
- package/skills/jobkorea-talent-search/instruction.md +1 -1
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +0 -0
- package/skills/jobkorea-talent-search/skill.json +2 -1
- package/skills/joseon-sillok-search/instruction.md +6 -6
- package/skills/k-skill-cleaner/instruction.md +2 -2
- package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +0 -0
- package/skills/k-skill-setup/instruction.md +1 -1
- package/skills/k-skill-setup/scripts/check-setup.sh +29 -0
- package/skills/k-skill-setup/skill.json +8 -2
- package/skills/kakaotalk-mac/skill.json +1 -1
- package/skills/keris-academic-search/instruction.md +4 -4
- package/skills/korea-weather/skill.json +0 -1
- package/skills/korean-character-count/instruction.md +8 -8
- package/skills/korean-heritage-search/instruction.md +5 -5
- package/skills/korean-humanizer/instruction.md +1 -1
- package/skills/korean-jangbu-for/instruction.md +3 -3
- package/skills/korean-jangbu-for/scripts/install.sh +0 -0
- package/skills/korean-middle-korean/instruction.md +6 -6
- package/skills/korean-middle-korean/scripts/korean_middle_korean.js +0 -0
- package/skills/korean-patent-search/instruction.md +4 -4
- package/skills/korean-privacy-terms/instruction.md +4 -4
- package/skills/korean-privacy-terms/scripts/install.sh +0 -0
- package/skills/korean-scholarship-search/instruction.md +10 -10
- package/skills/korean-slang-writing/instruction.md +8 -8
- package/skills/korean-spell-check/instruction.md +3 -3
- package/skills/korean-spell-check/skill.json +0 -1
- package/skills/korean-stock-search/skill.json +0 -1
- package/skills/korean-transit-route/skill.json +0 -1
- package/skills/kosis-stats/instruction.md +13 -26
- package/skills/kr-whois-lookup/skill.json +1 -2
- package/skills/kstartup-search/instruction.md +8 -8
- package/skills/ktx-booking/instruction.md +15 -15
- package/skills/ktx-booking/scripts/ktx_booking.py +1316 -0
- package/skills/ktx-booking/skill.json +7 -1
- package/skills/lck-analytics/instruction.md +4 -4
- package/skills/lck-analytics/scripts/_lib.js +0 -0
- package/skills/lck-analytics/scripts/analyze-live-game.js +0 -0
- package/skills/lck-analytics/scripts/build-match-report.js +0 -0
- package/skills/lck-analytics/scripts/sync-oracle.js +0 -0
- package/skills/localdata-business-status/instruction.md +2 -2
- package/skills/mfds-drug-safety/instruction.md +3 -3
- package/skills/mfds-drug-safety/skill.json +0 -1
- package/skills/mfds-food-safety/instruction.md +6 -6
- package/skills/mfds-food-safety/skill.json +0 -1
- package/skills/myrealtrip-search/instruction.md +11 -11
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +0 -0
- package/skills/national-pension-workplace/instruction.md +1 -1
- package/skills/national-pension-workplace/skill.json +0 -1
- package/skills/naver-ad-performance/instruction.md +7 -7
- package/skills/naver-blog-research/instruction.md +4 -4
- package/skills/naver-news-search/skill.json +0 -1
- package/skills/nhis-care-checkup-search/skill.json +1 -2
- package/skills/nts-business-registration/instruction.md +3 -3
- package/skills/nts-tax-delinquency/instruction.md +1 -1
- package/skills/ohou-today-deal/instruction.md +4 -4
- package/skills/ohou-today-deal/scripts/ohou_today_deal.py +0 -0
- package/skills/popbill/instruction.md +9 -9
- package/skills/real-estate-search/skill.json +0 -1
- package/skills/s2b-notice-search/skill.json +2 -1
- package/skills/saramin-talent-search/skill.json +2 -1
- package/skills/seoul-bike/instruction.md +3 -3
- package/skills/seoul-bike/scripts/seoul_bike.py +0 -0
- package/skills/seoul-bike/skill.json +0 -1
- package/skills/seoul-density/instruction.md +4 -4
- package/skills/seoul-density/skill.json +0 -1
- package/skills/srt-booking/instruction.md +4 -4
- package/skills/subway-lost-property/instruction.md +2 -2
- package/skills/subway-lost-property/scripts/subway_lost_property.py +0 -0
- package/skills/subway-lost-property/skill.json +1 -1
- package/skills/ticket-availability/instruction.md +3 -3
- package/skills/ticket-availability/skill.json +2 -1
- package/skills/yebigun-training/skill.json +2 -1
- package/skills/zipcode-search/instruction.md +2 -2
- package/skills/zipcode-search/scripts/zipcode_search.py +0 -0
- package/src/assemble.js +148 -4
- package/src/execute.js +81 -0
- package/templates/action-recruiting.md +5 -0
- package/templates/legal.md +6 -0
- package/templates/operations.md +3 -0
- package/templates/action-communication.md +0 -5
- package/templates/hard-boundary.md +0 -3
|
@@ -66,7 +66,7 @@ python3 -m pip install SRTrain
|
|
|
66
66
|
먼저 helper 로 조회해서 후보를 요약한다.
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
|
|
69
|
+
npx -y @nomadamas/k-skill@0 exec srt-booking scripts/srt_booking.py -- search 수서 부산 20260328 080000 --time-limit 120000 --limit 5
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
### 3. Summarize options before side effects
|
|
@@ -82,19 +82,19 @@ python3 scripts/srt_booking.py search 수서 부산 20260328 080000 --time-limit
|
|
|
82
82
|
`search` 의 좌석 가능 여부는 열차 단위 플래그다. 사용자가 "남은 좌석 번호", "호차별 좌석", "특정 좌석", "창측/순방향 자리", "예약 전에 자리 확인"처럼 구체적인 좌석을 물으면 예약 전에 `seats` 를 호출한다.
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
|
|
85
|
+
npx -y @nomadamas/k-skill@0 exec srt-booking scripts/srt_booking.py -- seats 수서 부산 20260328 080000 --train-id <train_id>
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
특정 호차의 빈 좌석만 확인하려면 `--car-no` 와 `--available-only` 를 쓴다.
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
|
|
91
|
+
npx -y @nomadamas/k-skill@0 exec srt-booking scripts/srt_booking.py -- seats 수서 부산 20260328 080000 --train-id <train_id> --car-no 5 --available-only
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
특정 좌석이 비었는지 확인하려면 `--seat` 를 붙인다.
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
|
|
97
|
+
npx -y @nomadamas/k-skill@0 exec srt-booking scripts/srt_booking.py -- seats 수서 부산 20260328 080000 --train-id <train_id> --car-no 5 --seat 11A
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
특정 호차를 지정하지 않으면 가운데 호차부터 탐색한다. `--car-priority center|low|high` 로 호차 탐색 순서를 바꾸고, `--seat-priority forward-window|window-forward|row-low` 로 좌석 정렬 우선순위를 바꾼다.
|
|
@@ -47,7 +47,7 @@ LOST112 검색 폼에서 확인되는 핵심 필드는 아래와 같다.
|
|
|
47
47
|
repo helper를 그대로 써도 된다.
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
|
-
|
|
50
|
+
npx -y @nomadamas/k-skill@0 exec subway-lost-property scripts/subway_lost_property.py -- \
|
|
51
51
|
--station 강남역 \
|
|
52
52
|
--item 지갑 \
|
|
53
53
|
--days 14
|
|
@@ -58,7 +58,7 @@ helper는 기본적으로 `SITE=V` 를 사용하고, 역명/물품명/기간을
|
|
|
58
58
|
### 3) Optionally verify live reachability
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
|
|
61
|
+
npx -y @nomadamas/k-skill@0 exec subway-lost-property scripts/subway_lost_property.py -- \
|
|
62
62
|
--station 강남역 \
|
|
63
63
|
--item 지갑 \
|
|
64
64
|
--days 14 \
|
|
File without changes
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "subway-lost-property",
|
|
3
3
|
"description": "Use when the user asks how to look up 지하철 분실물 or 유실물 by 역명/물품명. v1 is an 안내형/하이브리드 skill that structures the official LOST112 + 서울교통공사 flow conservatively. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
4
|
"profiles": [
|
|
5
|
-
"
|
|
5
|
+
"lookup"
|
|
6
6
|
],
|
|
7
7
|
"frontmatter": "name: subway-lost-property\ndescription: Use when the user asks how to look up 지하철 분실물 or 유실물 by 역명/물품명. v1 is an 안내형/하이브리드 skill that structures the official LOST112 + 서울교통공사 flow conservatively. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: transit\n locale: ko-KR\n phase: v1"
|
|
8
8
|
}
|
|
@@ -59,7 +59,7 @@ pip install httpx
|
|
|
59
59
|
### 2. 일정 조회 (`schedule`)
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
|
|
62
|
+
npx -y @nomadamas/k-skill@0 exec ticket-availability scripts/ticket_availability.py -- schedule "https://tickets.interpark.com/goods/26000541"
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
응답 — Interpark:
|
|
@@ -90,7 +90,7 @@ YES24 는 기본 3주 윈도우. 6개월 전체는 `--all-dates` 추가.
|
|
|
90
90
|
### 3. 잔여석 조회 (`seats`)
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
|
|
93
|
+
npx -y @nomadamas/k-skill@0 exec ticket-availability scripts/ticket_availability.py -- seats "interpark:26000541"
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
응답:
|
|
@@ -120,7 +120,7 @@ YES24 응답은 등급별 `price` (노출가) 도 포함:
|
|
|
120
120
|
### 4. 헬스체크 (`health`)
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
|
|
123
|
+
npx -y @nomadamas/k-skill@0 exec ticket-availability scripts/ticket_availability.py -- health
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
응답:
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
"name": "ticket-availability",
|
|
3
3
|
"description": "YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음). 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.",
|
|
4
4
|
"profiles": [
|
|
5
|
+
"vault",
|
|
5
6
|
"browser",
|
|
6
|
-
"
|
|
7
|
+
"action:booking"
|
|
7
8
|
],
|
|
8
9
|
"frontmatter": "name: ticket-availability\ndescription: YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음). 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.\nlicense: MIT\nmetadata:\n category: lifestyle\n subcategory: ticket\n locale: ko-KR\n phase: v1"
|
|
9
10
|
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
"name": "yebigun-training",
|
|
3
3
|
"description": "사용자가 직접 로그인한 예비군 홈페이지(yebigun1.mil.kr) Chrome 세션을 재사용해 이번 훈련 일정/장소를 조회하고, 작년 이전 기록과 비교해 무엇이 달라졌는지 알려준다. 조회 전용이며 로그인이나 연기·보류 신청은 절대 자동화하지 않는다. 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.",
|
|
4
4
|
"profiles": [
|
|
5
|
+
"vault",
|
|
5
6
|
"browser",
|
|
6
|
-
"
|
|
7
|
+
"legal"
|
|
7
8
|
],
|
|
8
9
|
"frontmatter": "name: yebigun-training\ndescription: 사용자가 직접 로그인한 예비군 홈페이지(yebigun1.mil.kr) Chrome 세션을 재사용해 이번 훈련 일정/장소를 조회하고, 작년 이전 기록과 비교해 무엇이 달라졌는지 알려준다. 조회 전용이며 로그인이나 연기·보류 신청은 절대 자동화하지 않는다. 돌쇠에서는 가능한 가장 가까운 합법적 공식 단계까지 준비한다.\nlicense: MIT\nmetadata:\n category: legal\n locale: ko-KR\n phase: v1"
|
|
9
10
|
}
|
|
@@ -98,8 +98,8 @@ PY
|
|
|
98
98
|
저장소에는 같은 흐름을 감싼 실행 가능한 helper가 포함되어 있다.
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
npx -y @nomadamas/k-skill@0 exec zipcode-search scripts/zipcode_search.py -- "서울특별시 강남구 테헤란로 123"
|
|
102
|
+
npx -y @nomadamas/k-skill@0 exec zipcode-search scripts/zipcode_search.py -- "서울특별시 강남구 테헤란로 123"
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
예시 출력:
|
|
File without changes
|
package/src/assemble.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require("node:path");
|
|
|
6
6
|
const packageRoot = path.join(__dirname, "..");
|
|
7
7
|
const templatesDir = path.join(packageRoot, "templates");
|
|
8
8
|
const skillsDir = path.join(packageRoot, "skills");
|
|
9
|
+
const CLI_INVOCATION = "npx -y @nomadamas/k-skill@0";
|
|
9
10
|
|
|
10
11
|
const KNOWN_PROFILES = [
|
|
11
12
|
"core",
|
|
@@ -14,12 +15,13 @@ const KNOWN_PROFILES = [
|
|
|
14
15
|
"vault",
|
|
15
16
|
"action:booking",
|
|
16
17
|
"action:commerce",
|
|
17
|
-
"action:communication",
|
|
18
18
|
"action:submission",
|
|
19
19
|
"action:account",
|
|
20
|
-
"
|
|
20
|
+
"action:recruiting",
|
|
21
|
+
"legal",
|
|
21
22
|
"lookup",
|
|
22
23
|
"local",
|
|
24
|
+
"operations",
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
function profileTemplatePath(profile) {
|
|
@@ -64,6 +66,133 @@ function loadSkill(skillName) {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
function assetRelativePaths(skillName) {
|
|
70
|
+
const { dir } = loadSkill(skillName);
|
|
71
|
+
return bundledFiles(skillName).map((filePath) => path.relative(dir, filePath).split(path.sep).join("/"));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveBundledAsset(skillName, relativePath, allowedRoots = ["scripts", "references", "tests"]) {
|
|
75
|
+
const { dir } = loadSkill(skillName);
|
|
76
|
+
const normalized = path.posix.normalize(String(relativePath).replaceAll("\\", "/"));
|
|
77
|
+
const root = normalized.split("/")[0];
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
normalized === "." ||
|
|
81
|
+
normalized.startsWith("../") ||
|
|
82
|
+
path.posix.isAbsolute(normalized) ||
|
|
83
|
+
!allowedRoots.includes(root)
|
|
84
|
+
) {
|
|
85
|
+
const error = new Error(
|
|
86
|
+
`asset path "${relativePath}" must stay inside ${allowedRoots.join(", ")} for skill "${skillName}"`,
|
|
87
|
+
);
|
|
88
|
+
error.code = "EASSETPATH";
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const candidate = path.resolve(dir, ...normalized.split("/"));
|
|
93
|
+
if (!fs.existsSync(candidate) || !fs.statSync(candidate).isFile()) {
|
|
94
|
+
const error = new Error(`asset "${normalized}" is not bundled for skill "${skillName}"`);
|
|
95
|
+
error.code = "EASSETNOTFOUND";
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const realDir = fs.realpathSync(dir);
|
|
100
|
+
const realCandidate = fs.realpathSync(candidate);
|
|
101
|
+
if (!realCandidate.startsWith(`${realDir}${path.sep}`)) {
|
|
102
|
+
const error = new Error(`asset "${normalized}" escapes skill "${skillName}"`);
|
|
103
|
+
error.code = "EASSETPATH";
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return realCandidate;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function readBundledAsset(skillName, relativePath) {
|
|
111
|
+
return fs.readFileSync(resolveBundledAsset(skillName, relativePath, ["references", "scripts"]), "utf8");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function escapeRegex(value) {
|
|
115
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function scriptPathPattern(skillName, relativePath) {
|
|
119
|
+
const rel = escapeRegex(relativePath);
|
|
120
|
+
const skill = escapeRegex(skillName);
|
|
121
|
+
return `(?:"?\\$SKILL_DIR/|\\./${skill}/|${skill}/|\\./)?${rel}"?`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function rewriteAssetCommandReferences(raw, skillName) {
|
|
125
|
+
let output = raw;
|
|
126
|
+
const assets = assetRelativePaths(skillName);
|
|
127
|
+
const scripts = assets.filter((relativePath) => relativePath.startsWith("scripts/"));
|
|
128
|
+
const references = assets.filter((relativePath) => relativePath.startsWith("references/"));
|
|
129
|
+
|
|
130
|
+
for (const relativePath of scripts.sort((a, b) => b.length - a.length)) {
|
|
131
|
+
const assetPattern = scriptPathPattern(skillName, relativePath);
|
|
132
|
+
const execCommand = `${CLI_INVOCATION} exec ${skillName} ${relativePath} --`;
|
|
133
|
+
|
|
134
|
+
output = output.replace(
|
|
135
|
+
new RegExp(`(?:python3?|node|bash|uv\\s+run)\\s+${assetPattern}`, "g"),
|
|
136
|
+
execCommand,
|
|
137
|
+
);
|
|
138
|
+
output = output.replace(
|
|
139
|
+
new RegExp(`(^|[|;&]\\s*)${assetPattern}(?=\\s|$)`, "gm"),
|
|
140
|
+
`$1${execCommand}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const relativePath of assets.filter((item) => /\.(?:md|txt|pin)$/i.test(item))) {
|
|
145
|
+
const assetPattern = scriptPathPattern(skillName, relativePath);
|
|
146
|
+
const readCommand = `${CLI_INVOCATION} read ${skillName} ${relativePath}`;
|
|
147
|
+
|
|
148
|
+
output = output.replace(new RegExp(`cat\\s+${assetPattern}`, "g"), readCommand);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const relativePath of references.sort((a, b) => b.length - a.length)) {
|
|
152
|
+
const escaped = escapeRegex(relativePath);
|
|
153
|
+
const readCommand = `${CLI_INVOCATION} read ${skillName} ${relativePath}`;
|
|
154
|
+
|
|
155
|
+
output = output.replace(
|
|
156
|
+
new RegExp(`\\[([^\\]]+)\\]\\((?:\\./)?${escaped}\\)`, "g"),
|
|
157
|
+
(_match, label) =>
|
|
158
|
+
label.includes(relativePath) ? `\`${readCommand}\`` : `${label} (\`${readCommand}\`)`,
|
|
159
|
+
);
|
|
160
|
+
output = output.replace(
|
|
161
|
+
new RegExp(`\`(?:\\./)?${escaped}\``, "g"),
|
|
162
|
+
`\`${readCommand}\``,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Source instructions are migrated to the CLI surface, and assembly repeats
|
|
170
|
+
// the normalization defensively so a newly added relative command cannot leak
|
|
171
|
+
// into a released instruction before CI catches it.
|
|
172
|
+
function rewriteBundledAssetInstructions(raw, skillName) {
|
|
173
|
+
const assets = assetRelativePaths(skillName);
|
|
174
|
+
const scripts = assets.filter((relativePath) => relativePath.startsWith("scripts/"));
|
|
175
|
+
const references = assets.filter((relativePath) => relativePath.startsWith("references/"));
|
|
176
|
+
let output = rewriteAssetCommandReferences(raw, skillName);
|
|
177
|
+
|
|
178
|
+
if (scripts.length || references.length) {
|
|
179
|
+
const accessRules = [
|
|
180
|
+
"## Bundled asset access",
|
|
181
|
+
"",
|
|
182
|
+
`- Execute bundled helpers only through \`${CLI_INVOCATION} exec ${skillName} scripts/<file> -- <args>\`; do not assume a repository-relative or installed-skill-relative path.`,
|
|
183
|
+
`- Resolve an asset path with \`${CLI_INVOCATION} path ${skillName} <relative-path>\` only when another tool explicitly requires a filesystem path.`,
|
|
184
|
+
];
|
|
185
|
+
if (references.length) {
|
|
186
|
+
accessRules.push(
|
|
187
|
+
`- Read bundled references through \`${CLI_INVOCATION} read ${skillName} references/<file>\`.`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
output = `${accessRules.join("\n")}\n\n${output}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return output;
|
|
194
|
+
}
|
|
195
|
+
|
|
67
196
|
// Template files interleave `<!-- mode:always -->`, `<!-- mode:dolshoi -->`,
|
|
68
197
|
// and `<!-- mode:generic -->` markers. Only the sections matching the detected
|
|
69
198
|
// mode (plus always-sections) are emitted.
|
|
@@ -107,7 +236,8 @@ function assemble(skillName, runtime) {
|
|
|
107
236
|
].join("\n");
|
|
108
237
|
|
|
109
238
|
// instruction.md may also use mode markers for mode-specific sections.
|
|
110
|
-
|
|
239
|
+
const renderedInstruction = renderTemplate(instruction, runtime.mode);
|
|
240
|
+
return `${header}\n\n${rewriteBundledAssetInstructions(renderedInstruction, skillName)}\n`;
|
|
111
241
|
}
|
|
112
242
|
|
|
113
243
|
function bundledFiles(skillName) {
|
|
@@ -121,6 +251,7 @@ function bundledFiles(skillName) {
|
|
|
121
251
|
while (stack.length) {
|
|
122
252
|
const currentDir = stack.pop();
|
|
123
253
|
for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
|
|
254
|
+
if (entry.name === "__pycache__" || entry.name.startsWith(".") || entry.name.endsWith(".pyc")) continue;
|
|
124
255
|
const full = path.join(currentDir, entry.name);
|
|
125
256
|
if (entry.isDirectory()) stack.push(full);
|
|
126
257
|
else results.push(full);
|
|
@@ -131,4 +262,17 @@ function bundledFiles(skillName) {
|
|
|
131
262
|
return results.sort();
|
|
132
263
|
}
|
|
133
264
|
|
|
134
|
-
module.exports = {
|
|
265
|
+
module.exports = {
|
|
266
|
+
CLI_INVOCATION,
|
|
267
|
+
KNOWN_PROFILES,
|
|
268
|
+
assemble,
|
|
269
|
+
assetRelativePaths,
|
|
270
|
+
bundledFiles,
|
|
271
|
+
listSkills,
|
|
272
|
+
loadSkill,
|
|
273
|
+
readBundledAsset,
|
|
274
|
+
renderTemplate,
|
|
275
|
+
resolveBundledAsset,
|
|
276
|
+
rewriteAssetCommandReferences,
|
|
277
|
+
rewriteBundledAssetInstructions,
|
|
278
|
+
};
|
package/src/execute.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const childProcess = require("node:child_process");
|
|
6
|
+
|
|
7
|
+
const { resolveBundledAsset } = require("./assemble");
|
|
8
|
+
|
|
9
|
+
function splitShebang(value) {
|
|
10
|
+
return value.trim().split(/\s+/).filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function resolveRunner(scriptPath, env = process.env) {
|
|
14
|
+
const firstLine = fs.readFileSync(scriptPath, "utf8").split(/\r?\n/, 1)[0];
|
|
15
|
+
|
|
16
|
+
if (firstLine.startsWith("#!")) {
|
|
17
|
+
const parts = splitShebang(firstLine.slice(2));
|
|
18
|
+
if (parts[0] === "/usr/bin/env") {
|
|
19
|
+
const args = parts.slice(parts[1] === "-S" ? 2 : 1);
|
|
20
|
+
if (args.length) {
|
|
21
|
+
const [command, ...prefixArgs] = args;
|
|
22
|
+
return {
|
|
23
|
+
command: command === "node" ? process.execPath : command,
|
|
24
|
+
args: [...prefixArgs, scriptPath],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (parts[0]) {
|
|
30
|
+
return { command: parts[0], args: [...parts.slice(1), scriptPath] };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
switch (path.extname(scriptPath).toLowerCase()) {
|
|
35
|
+
case ".py":
|
|
36
|
+
return { command: env.KSKILL_PYTHON || "python3", args: [scriptPath] };
|
|
37
|
+
case ".js":
|
|
38
|
+
case ".cjs":
|
|
39
|
+
case ".mjs":
|
|
40
|
+
return { command: process.execPath, args: [scriptPath] };
|
|
41
|
+
case ".sh":
|
|
42
|
+
return { command: env.KSKILL_BASH || "bash", args: [scriptPath] };
|
|
43
|
+
default: {
|
|
44
|
+
const error = new Error(
|
|
45
|
+
`cannot determine a runner for "${path.basename(scriptPath)}"; use a supported shebang or .py/.js/.sh extension`,
|
|
46
|
+
);
|
|
47
|
+
error.code = "EUNSUPPORTEDSCRIPT";
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runBundledScript(skillName, relativePath, args = [], options = {}) {
|
|
54
|
+
const scriptPath = resolveBundledAsset(skillName, relativePath, ["scripts"]);
|
|
55
|
+
const runner = resolveRunner(scriptPath, options.env || process.env);
|
|
56
|
+
const result = childProcess.spawnSync(runner.command, [...runner.args, ...args], {
|
|
57
|
+
cwd: options.cwd || process.cwd(),
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
...options.env,
|
|
61
|
+
KSKILL_BUNDLED_SKILL_DIR: path.dirname(path.dirname(scriptPath)),
|
|
62
|
+
},
|
|
63
|
+
encoding: options.encoding,
|
|
64
|
+
stdio: options.stdio || "inherit",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (result.error) throw result.error;
|
|
68
|
+
if (result.signal) {
|
|
69
|
+
const error = new Error(`script terminated by signal ${result.signal}`);
|
|
70
|
+
error.code = "ESCRIPTSIGNAL";
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
status: result.status ?? 1,
|
|
76
|
+
stdout: result.stdout,
|
|
77
|
+
stderr: result.stderr,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { resolveRunner, runBundledScript };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- mode:dolshoi -->
|
|
2
|
+
- Use the official employer account surface to search and compare candidates, keeping masked/private data protected until the requested candidate is fixed.
|
|
3
|
+
- Paid resume unlocks, contact-detail reveals, credit use, proposal delivery, shortlist mutation, and candidate-status changes are separate external effects. Immediately before each such action, call `clarify` with the candidate, cost/credit impact, disclosed data, message payload, and resulting state; execute only the approved action and verify it.
|
|
4
|
+
<!-- mode:generic -->
|
|
5
|
+
- Search and compare visible candidate information through the documented logged-in browser workflow. Prepare the shortlist and proposal draft, then hand paid unlocks, private-data reveals, credit use, and proposal delivery to the user on the official surface.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- mode:dolshoi -->
|
|
2
|
+
- Use the official legal/government surface with CloakBrowser and vault-backed login. Proceed through ordinary login, supported identity verification, document upload, form filling, fee calculation, and reversible preparation when the official surface and available capabilities support them.
|
|
3
|
+
- Do not evade CAPTCHA, impersonate the user, forge identity evidence, or bypass an authentication/security control. If the official flow presents a user-presence-only verification or electronic-signature prompt, open the exact prompt and let the user complete that control, then resume the same workflow immediately afterward.
|
|
4
|
+
- Immediately before filing, bidding, paying a government fee, signing, cancelling, or another irreversible legal effect, call `clarify` with the exact case/form, parties, documents, amount, and legal effect. Execute after approval and verify the official receipt or resulting status.
|
|
5
|
+
<!-- mode:generic -->
|
|
6
|
+
- Navigate the official legal/government surface and complete every supported reversible preparation step. Keep login, identity verification, CAPTCHA, electronic signature, filing, bidding, payment, and cancellation on the official user-controlled surface unless the host provides an explicit capability for that step.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<!-- mode:always -->
|
|
2
|
+
- This skill operates or configures the k-skill installation itself. Inspect current state, apply the requested reversible setup/update/repair operation, and verify the installed CLI, skill directories, browser runtime, proxy connectivity, and credential-provider integration that the request covers.
|
|
3
|
+
- Do not change unrelated user configuration. Before deleting data, replacing user-managed files, changing persistent update automation, or performing another irreversible operation, call `clarify` with the exact paths and effect.
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
<!-- mode:dolshoi -->
|
|
2
|
-
- When sending is requested, continue past lookup: identify the exact recipient and payload, prepare the draft on the official surface, and keep it unsent.
|
|
3
|
-
- Immediately before actual delivery (message, application, inquiry), call `clarify` with the exact recipient and full payload. After approval, send and verify the sent state.
|
|
4
|
-
<!-- mode:generic -->
|
|
5
|
-
- Prepare the recipient, draft, and official surface link that the documented portable workflow supports, then hand the final send action to the user. Do not automate delivery here.
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
<!-- mode:always -->
|
|
2
|
-
- This skill has a hard boundary (law, mandatory physical presence, CAPTCHA, identity proofing, or electronic signature). Navigate to the exact official case/form/item, complete every lawful reversible preparation step, and prefill all safe fields — but never automate the bounded final step. Completion means the exact official screen is open, safe inputs are prepared, and the single user-only step is stated precisely.
|
|
3
|
-
- If the remaining official step is merely irreversible (not identity-bound or prohibited), call `clarify` with the exact target and effect, then execute after approval.
|