elliot-stack 1.0.29 → 1.0.33
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 -21
- package/README.md +5 -0
- package/bin/install.cjs +981 -950
- package/hooks/repo-search-nudge.js +32 -32
- package/package.json +1 -1
- package/skills/estack-active-learning-tutor/SKILL.md +339 -339
- package/skills/estack-better-title/SKILL.md +64 -64
- package/skills/estack-better-title/scripts/rename.sh +55 -55
- package/skills/estack-chris-voss/SKILL.md +80 -80
- package/skills/estack-chris-voss/references/elliot-notes.md +120 -120
- package/skills/estack-chris-voss/references/voss-principles.md +210 -210
- package/skills/estack-customer-discovery/SKILL.md +60 -60
- package/skills/estack-flight-planner/SKILL.md +332 -332
- package/skills/estack-flight-planner/references/config_schema.md +156 -156
- package/skills/estack-flight-planner/references/flight_history_schema.md +97 -97
- package/skills/estack-flight-planner/references/shuttle_schedules.md +98 -98
- package/skills/estack-flight-planner/scripts/check_setup.sh +89 -89
- package/skills/estack-flight-planner/scripts/fetch_flights.py +99 -99
- package/skills/estack-flight-planner/scripts/filter_flights.py +265 -265
- package/skills/estack-flight-planner/scripts/pair_shuttles.py +173 -173
- package/skills/estack-github-issue-tracker/SKILL.md +322 -322
- package/skills/estack-github-issue-tracker/bin/tracker-tools.cjs +1358 -1358
- package/skills/estack-github-issue-tracker/references/gh-cli-patterns.md +124 -124
- package/skills/estack-github-issue-tracker/references/result-file-schema.md +156 -156
- package/skills/estack-github-issue-tracker/references/tracker-schema.md +96 -96
- package/skills/estack-github-issue-tracker/tracker-template.md +58 -58
- package/skills/estack-leadership-coach/SKILL.md +235 -0
- package/skills/estack-leadership-coach/adding-references.md +280 -0
- package/skills/estack-leadership-coach/frameworks/delegation/flows/post-mortem.md +120 -0
- package/skills/estack-leadership-coach/frameworks/delegation/flows/pre-delegation.md +138 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/1-intake.md +145 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/2-trm-assessment.md +119 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/3-enrollment.md +132 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/4-build-brief.md +171 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/5-monitoring.md +134 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/6-reverse-delegation.md +118 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/7-diagnose.md +200 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__deci-olafsen-ryan-2017-self-determination-theory-in-work-organizations.md +1881 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__gagne-deci-2005-self-determination-theory-and-work-motivation.md +2058 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__selfdeterminationtheory-org-theory-overview-page.md +61 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-3-key-insights-into-the-global-workplace-2024.md +57 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-managers-account-for-70-percent-of-variance-in-employee-engagement-2015.md +40 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-state-of-the-global-workplace-2026-global-data-summary.md +73 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-state-of-the-global-workplace-2026-report-landing.md +42 -0
- package/skills/estack-leadership-coach/references/.source-files/hormozi-leila_4-stages__leila-hormozi-the-art-of-delegation-blog-post.md +91 -0
- package/skills/estack-leadership-coach/references/.source-files/oncken-wass_monkeys-hbr-1974__oncken-wass-management-time-whos-got-the-monkey-hbr-classic-1974.md +969 -0
- package/skills/estack-leadership-coach/references/.source-files/sanchez_main-street-millionaire__codie-sanchez-afford-anything-podcast-ep-565-show-notes.md +89 -0
- package/skills/estack-leadership-coach/references/.source-files/sullivan_who-not-how__dan-sullivan-impact-filter-tool-and-guide-booklet.md +565 -0
- package/skills/estack-leadership-coach/references/.source-files/van-edwards_cues__vanessa-van-edwards-lewis-howes-school-of-greatness-ep-1231-show-notes.md +122 -0
- package/skills/estack-leadership-coach/references/.source-files/van-edwards_cues__vanessa-van-edwards-roger-dooley-cues-interview.md +194 -0
- package/skills/estack-leadership-coach/references/deci-ryan_self-determination-theory.md +166 -0
- package/skills/estack-leadership-coach/references/doerr_measure-what-matters.md +154 -0
- package/skills/estack-leadership-coach/references/ferriss_4hww.md +189 -0
- package/skills/estack-leadership-coach/references/gallup_engagement-research.md +105 -0
- package/skills/estack-leadership-coach/references/gerber_e-myth-revisited.md +118 -0
- package/skills/estack-leadership-coach/references/grove_high-output-management.md +95 -0
- package/skills/estack-leadership-coach/references/hormozi-alex_followthrough.md +152 -0
- package/skills/estack-leadership-coach/references/hormozi-leila_4-stages.md +146 -0
- package/skills/estack-leadership-coach/references/oncken-wass_monkeys-hbr-1974.md +128 -0
- package/skills/estack-leadership-coach/references/sanchez_main-street-millionaire.md +196 -0
- package/skills/estack-leadership-coach/references/sullivan_who-not-how.md +137 -0
- package/skills/estack-leadership-coach/references/van-edwards_cues.md +189 -0
- package/skills/estack-migrate-claude-session-history/SKILL.md +226 -0
- package/skills/estack-migrate-claude-session-history/references/path-encoding.md +55 -0
- package/skills/estack-migrate-claude-session-history/references/troubleshooting.md +96 -0
- package/skills/estack-migrate-claude-session-history/scripts/migrate-claude-history.js +1123 -0
- package/skills/estack-migrate-claude-session-history/scripts/test-append-note.js +48 -0
- package/skills/estack-migrate-claude-session-history/scripts/test-validate-migration.py +326 -0
- package/skills/estack-migrate-claude-session-history/scripts/validate-migration.py +493 -0
- package/skills/estack-pdf-to-md/SKILL.md +180 -0
- package/skills/estack-pdf-to-md/scripts/pdf_to_md.py +596 -0
- package/skills/estack-productivity-prioritization-coach/SKILL.md +124 -0
- package/skills/estack-productivity-prioritization-coach/sources/01-tony-robbins-rpm.md +39 -0
- package/skills/estack-productivity-prioritization-coach/sources/02-justin-sung-task-prioritization.md +34 -0
- package/skills/estack-prompt-builder-coach/SKILL.md +81 -81
- package/skills/estack-prompt-builder-coach/definition-of-done-generator.md +42 -42
- package/skills/estack-prompt-builder-coach/prompt-builder.md +37 -37
- package/skills/estack-prompt-builder-coach/task-shaper.md +36 -36
- package/skills/estack-prompt-builder-coach/vague-ask-auditor.md +37 -37
- package/skills/estack-read-claude-session-history/SKILL.md +204 -204
- package/skills/estack-read-claude-session-history/references/jsonl-schema.md +126 -126
- package/skills/estack-read-claude-session-history/references/modes.md +423 -423
- package/skills/estack-read-claude-session-history/references/recipes.md +271 -271
- package/skills/estack-read-claude-session-history/scripts/lib/__init__.py +1 -1
- package/skills/estack-read-claude-session-history/scripts/lib/parser.py +460 -460
- package/skills/estack-read-claude-session-history/scripts/lib/paths.py +234 -234
- package/skills/estack-read-claude-session-history/scripts/lib/search.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/lib/subagents.py +88 -88
- package/skills/estack-read-claude-session-history/scripts/lib/tools.py +144 -144
- package/skills/estack-read-claude-session-history/scripts/read_transcript.py +1776 -1776
- package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +40 -40
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +20 -20
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +9 -9
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +7 -7
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +8 -8
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +1 -1
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +6 -6
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +10 -10
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +56 -56
- package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +239 -239
- package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +201 -201
- package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +199 -199
- package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +195 -195
- package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +133 -133
- package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +78 -78
- package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +43 -43
- package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +212 -212
- package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +80 -80
- package/skills/estack-repo-search/SKILL.md +65 -65
- package/skills/estack-vscode-file-recovery/SKILL.md +188 -0
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# Shuttle Schedules — Template & Format Reference
|
|
2
|
-
|
|
3
|
-
This skill optionally pairs flights with a ground shuttle service. Pairing is **off by default** — the skill only runs it if the user's config has a `shuttle_service` block set.
|
|
4
|
-
|
|
5
|
-
This file describes:
|
|
6
|
-
1. How to configure your shuttle service in `~/.flight-planner/config.json`
|
|
7
|
-
2. The JSON format the pairing script (`scripts/pair_shuttles.py`) expects
|
|
8
|
-
3. How the skill builds that JSON from your shuttle company's website each run
|
|
9
|
-
|
|
10
|
-
## 1. Config block
|
|
11
|
-
|
|
12
|
-
Add this to `~/.flight-planner/config.json` if you regularly use a shuttle:
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
"shuttle_service": {
|
|
16
|
-
"name": "Acme Airport Express",
|
|
17
|
-
"schedule_urls": [
|
|
18
|
-
"https://acmeairport.example.com/schedules/ord",
|
|
19
|
-
"https://acmeairport.example.com/schedules/mdw"
|
|
20
|
-
],
|
|
21
|
-
"costs": {
|
|
22
|
-
"ORD": 60,
|
|
23
|
-
"MDW": 55
|
|
24
|
-
},
|
|
25
|
-
"home_timezone": "America/New_York",
|
|
26
|
-
"airport_timezones": {
|
|
27
|
-
"ORD": "America/Chicago",
|
|
28
|
-
"MDW": "America/Chicago"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Fields:**
|
|
34
|
-
|
|
35
|
-
- `name` — display name for the company
|
|
36
|
-
- `schedule_urls` — one or more URLs the skill will fetch with WebFetch each run. Should be public schedule pages.
|
|
37
|
-
- `costs` — one-way USD cost per destination airport (IATA code)
|
|
38
|
-
- `home_timezone` — IANA timezone string for the city/town the shuttle picks you up from
|
|
39
|
-
- `airport_timezones` — IANA timezone string per destination airport
|
|
40
|
-
|
|
41
|
-
Set `shuttle_service: null` (or omit the field entirely) if you don't use a shuttle.
|
|
42
|
-
|
|
43
|
-
## 2. JSON the pairing script expects
|
|
44
|
-
|
|
45
|
-
After fetching schedule URLs, the skill assembles this JSON and saves it as `shuttles.json`:
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
{
|
|
49
|
-
"shuttles": [
|
|
50
|
-
{
|
|
51
|
-
"company": "Acme Airport Express",
|
|
52
|
-
"from": "Home",
|
|
53
|
-
"to": "ORD",
|
|
54
|
-
"pickup_location": "Downtown station",
|
|
55
|
-
"departs_local": "06:00",
|
|
56
|
-
"arrives_local": "08:30"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"company": "Acme Airport Express",
|
|
60
|
-
"from": "Home",
|
|
61
|
-
"to": "ORD",
|
|
62
|
-
"pickup_location": "Downtown station",
|
|
63
|
-
"departs_local": "10:00",
|
|
64
|
-
"arrives_local": "12:30"
|
|
65
|
-
}
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**Field rules:**
|
|
71
|
-
|
|
72
|
-
- `from` — descriptive label for the pickup region (not used for matching)
|
|
73
|
-
- `to` — destination airport IATA code (used to match flights)
|
|
74
|
-
- `departs_local` — pickup time in **home timezone**, HH:MM 24-hour
|
|
75
|
-
- `arrives_local` — arrival time in **destination airport's local timezone**, HH:MM 24-hour
|
|
76
|
-
- `pickup_location` — optional, shown in output
|
|
77
|
-
|
|
78
|
-
The pairing script uses `--tz-offsets` to translate `arrives_local` into the home timezone for buffer math. The skill computes those offsets from `home_timezone` and `airport_timezones` in your config.
|
|
79
|
-
|
|
80
|
-
## 3. Extracting schedules from a website
|
|
81
|
-
|
|
82
|
-
Most shuttle companies post fixed weekly schedules on their site. To turn a schedule page into the JSON above:
|
|
83
|
-
|
|
84
|
-
1. Use WebFetch on each URL in `schedule_urls`. Prompt: "extract every shuttle run with pickup time, destination airport, and arrival time."
|
|
85
|
-
2. For each run, build one object with `from`, `to`, `departs_local`, `arrives_local`.
|
|
86
|
-
3. Append all objects to a single `"shuttles"` array.
|
|
87
|
-
|
|
88
|
-
If the company has multiple runs to the same airport on different days, treat each as a separate entry. If schedules change on weekends, fetch the relevant day's schedule when the user's flight date is a weekend.
|
|
89
|
-
|
|
90
|
-
## 4. Cost overrides for a single run
|
|
91
|
-
|
|
92
|
-
If the user wants to override shuttle costs for one run only (e.g., a friend is driving them for free), the skill passes `--shuttle-costs "ORD:0"` instead of the config value. This doesn't modify the saved config.
|
|
93
|
-
|
|
94
|
-
## 5. Tightness tuning
|
|
95
|
-
|
|
96
|
-
`--min-buffer-min` (default 90) sets the floor for "viable" — flights with less buffer than this between shuttle arrival and flight departure are marked TOO_TIGHT. Increase to 120 if your airport has long security lines, decrease to 60 if you trust your shuttle and have TSA PreCheck.
|
|
97
|
-
|
|
98
|
-
`--max-wait-min` (default 240) caps how much pre-flight wait time is acceptable. Pairings beyond this get a LONG_WAIT label but still appear in the output.
|
|
1
|
+
# Shuttle Schedules — Template & Format Reference
|
|
2
|
+
|
|
3
|
+
This skill optionally pairs flights with a ground shuttle service. Pairing is **off by default** — the skill only runs it if the user's config has a `shuttle_service` block set.
|
|
4
|
+
|
|
5
|
+
This file describes:
|
|
6
|
+
1. How to configure your shuttle service in `~/.flight-planner/config.json`
|
|
7
|
+
2. The JSON format the pairing script (`scripts/pair_shuttles.py`) expects
|
|
8
|
+
3. How the skill builds that JSON from your shuttle company's website each run
|
|
9
|
+
|
|
10
|
+
## 1. Config block
|
|
11
|
+
|
|
12
|
+
Add this to `~/.flight-planner/config.json` if you regularly use a shuttle:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
"shuttle_service": {
|
|
16
|
+
"name": "Acme Airport Express",
|
|
17
|
+
"schedule_urls": [
|
|
18
|
+
"https://acmeairport.example.com/schedules/ord",
|
|
19
|
+
"https://acmeairport.example.com/schedules/mdw"
|
|
20
|
+
],
|
|
21
|
+
"costs": {
|
|
22
|
+
"ORD": 60,
|
|
23
|
+
"MDW": 55
|
|
24
|
+
},
|
|
25
|
+
"home_timezone": "America/New_York",
|
|
26
|
+
"airport_timezones": {
|
|
27
|
+
"ORD": "America/Chicago",
|
|
28
|
+
"MDW": "America/Chicago"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Fields:**
|
|
34
|
+
|
|
35
|
+
- `name` — display name for the company
|
|
36
|
+
- `schedule_urls` — one or more URLs the skill will fetch with WebFetch each run. Should be public schedule pages.
|
|
37
|
+
- `costs` — one-way USD cost per destination airport (IATA code)
|
|
38
|
+
- `home_timezone` — IANA timezone string for the city/town the shuttle picks you up from
|
|
39
|
+
- `airport_timezones` — IANA timezone string per destination airport
|
|
40
|
+
|
|
41
|
+
Set `shuttle_service: null` (or omit the field entirely) if you don't use a shuttle.
|
|
42
|
+
|
|
43
|
+
## 2. JSON the pairing script expects
|
|
44
|
+
|
|
45
|
+
After fetching schedule URLs, the skill assembles this JSON and saves it as `shuttles.json`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"shuttles": [
|
|
50
|
+
{
|
|
51
|
+
"company": "Acme Airport Express",
|
|
52
|
+
"from": "Home",
|
|
53
|
+
"to": "ORD",
|
|
54
|
+
"pickup_location": "Downtown station",
|
|
55
|
+
"departs_local": "06:00",
|
|
56
|
+
"arrives_local": "08:30"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"company": "Acme Airport Express",
|
|
60
|
+
"from": "Home",
|
|
61
|
+
"to": "ORD",
|
|
62
|
+
"pickup_location": "Downtown station",
|
|
63
|
+
"departs_local": "10:00",
|
|
64
|
+
"arrives_local": "12:30"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Field rules:**
|
|
71
|
+
|
|
72
|
+
- `from` — descriptive label for the pickup region (not used for matching)
|
|
73
|
+
- `to` — destination airport IATA code (used to match flights)
|
|
74
|
+
- `departs_local` — pickup time in **home timezone**, HH:MM 24-hour
|
|
75
|
+
- `arrives_local` — arrival time in **destination airport's local timezone**, HH:MM 24-hour
|
|
76
|
+
- `pickup_location` — optional, shown in output
|
|
77
|
+
|
|
78
|
+
The pairing script uses `--tz-offsets` to translate `arrives_local` into the home timezone for buffer math. The skill computes those offsets from `home_timezone` and `airport_timezones` in your config.
|
|
79
|
+
|
|
80
|
+
## 3. Extracting schedules from a website
|
|
81
|
+
|
|
82
|
+
Most shuttle companies post fixed weekly schedules on their site. To turn a schedule page into the JSON above:
|
|
83
|
+
|
|
84
|
+
1. Use WebFetch on each URL in `schedule_urls`. Prompt: "extract every shuttle run with pickup time, destination airport, and arrival time."
|
|
85
|
+
2. For each run, build one object with `from`, `to`, `departs_local`, `arrives_local`.
|
|
86
|
+
3. Append all objects to a single `"shuttles"` array.
|
|
87
|
+
|
|
88
|
+
If the company has multiple runs to the same airport on different days, treat each as a separate entry. If schedules change on weekends, fetch the relevant day's schedule when the user's flight date is a weekend.
|
|
89
|
+
|
|
90
|
+
## 4. Cost overrides for a single run
|
|
91
|
+
|
|
92
|
+
If the user wants to override shuttle costs for one run only (e.g., a friend is driving them for free), the skill passes `--shuttle-costs "ORD:0"` instead of the config value. This doesn't modify the saved config.
|
|
93
|
+
|
|
94
|
+
## 5. Tightness tuning
|
|
95
|
+
|
|
96
|
+
`--min-buffer-min` (default 90) sets the floor for "viable" — flights with less buffer than this between shuttle arrival and flight departure are marked TOO_TIGHT. Increase to 120 if your airport has long security lines, decrease to 60 if you trust your shuttle and have TSA PreCheck.
|
|
97
|
+
|
|
98
|
+
`--max-wait-min` (default 240) caps how much pre-flight wait time is acceptable. Pairings beyond this get a LONG_WAIT label but still appear in the output.
|
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Deterministic setup check. Run at skill load time via the ```! fence in SKILL.md.
|
|
3
|
-
# Outputs a human-readable report of:
|
|
4
|
-
# - whether ~/.flight-planner/config.json exists and its contents (key masked)
|
|
5
|
-
# - whether ~/.flight-planner/flight_history.json exists and its entry count
|
|
6
|
-
# - today's date (for resolving relative dates in Phase 1)
|
|
7
|
-
# - whether SERPAPI_KEY env var is set
|
|
8
|
-
# No side effects. Safe to run on every skill invocation.
|
|
9
|
-
|
|
10
|
-
set -u
|
|
11
|
-
|
|
12
|
-
CONFIG_DIR="$HOME/.flight-planner"
|
|
13
|
-
CONFIG_FILE="$CONFIG_DIR/config.json"
|
|
14
|
-
HISTORY_FILE="$CONFIG_DIR/flight_history.json"
|
|
15
|
-
|
|
16
|
-
echo "=== Flight Planner Setup ==="
|
|
17
|
-
echo "Today: $(date +%Y-%m-%d) (timezone: $(date +%Z))"
|
|
18
|
-
echo ""
|
|
19
|
-
|
|
20
|
-
# --- Config ---
|
|
21
|
-
if [ -f "$CONFIG_FILE" ]; then
|
|
22
|
-
echo "Config: $CONFIG_FILE (exists)"
|
|
23
|
-
echo ""
|
|
24
|
-
echo "--- Saved preferences ---"
|
|
25
|
-
# Mask serpapi_key value: show as "set" or "null" but never the actual key
|
|
26
|
-
python -c "
|
|
27
|
-
import json, sys
|
|
28
|
-
try:
|
|
29
|
-
with open(r'''$CONFIG_FILE''', encoding='utf-8') as f:
|
|
30
|
-
c = json.load(f)
|
|
31
|
-
except Exception as e:
|
|
32
|
-
print(f' ERROR reading config: {e}')
|
|
33
|
-
sys.exit(0)
|
|
34
|
-
|
|
35
|
-
key = c.get('serpapi_key')
|
|
36
|
-
print(' SerpAPI key: ' + ('set' if key else 'null (will use WebSearch fallback)'))
|
|
37
|
-
print(' Budget: \$' + str(c.get('budget_usd', '?')) + ' (' + str(c.get('budget_strength', '?')) + ')')
|
|
38
|
-
airlines = c.get('airline_preferences') or []
|
|
39
|
-
print(' Airlines: ' + (', '.join(airlines) if airlines else 'any') + ' (' + str(c.get('airline_preference_strength', '?')) + ')')
|
|
40
|
-
np = c.get('nonstop_preference', '?')
|
|
41
|
-
ns = c.get('nonstop_strength', '?')
|
|
42
|
-
print(' Nonstop: ' + str(np) + ' (' + str(ns) + ')')
|
|
43
|
-
bands = c.get('time_priority_bands') or []
|
|
44
|
-
print(' Time priority: ' + (', '.join(bands) if bands else 'none') + ' (' + str(c.get('time_priority_strength', '?')) + ')')
|
|
45
|
-
print(' Home airport: ' + str(c.get('home_airport') or 'not set'))
|
|
46
|
-
freq = c.get('frequent_destinations') or []
|
|
47
|
-
print(' Frequent destinations: ' + (', '.join(freq) if freq else 'not set'))
|
|
48
|
-
shuttle = c.get('shuttle_service')
|
|
49
|
-
if shuttle:
|
|
50
|
-
name = shuttle.get('name', '?')
|
|
51
|
-
costs = shuttle.get('costs', {}) or {}
|
|
52
|
-
cost_str = ', '.join(f'{k}: \${v}' for k, v in costs.items()) if costs else 'no costs configured'
|
|
53
|
-
print(' Shuttle service: ' + str(name) + ' (' + cost_str + ')')
|
|
54
|
-
else:
|
|
55
|
-
print(' Shuttle service: none (pairing step will be skipped)')
|
|
56
|
-
" 2>&1 || echo " (python not available; cannot parse config — read the file directly)"
|
|
57
|
-
else
|
|
58
|
-
echo "Config: $CONFIG_FILE (NOT FOUND)"
|
|
59
|
-
echo ""
|
|
60
|
-
echo "First-run setup is needed. The skill will walk the user through Phase 2"
|
|
61
|
-
echo "to create this file."
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
echo ""
|
|
65
|
-
|
|
66
|
-
# --- Environment SERPAPI_KEY ---
|
|
67
|
-
if [ -n "${SERPAPI_KEY:-}" ]; then
|
|
68
|
-
echo "SERPAPI_KEY: set in environment (will be used if config key is null)"
|
|
69
|
-
else
|
|
70
|
-
echo "SERPAPI_KEY: not set in environment"
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
echo ""
|
|
74
|
-
|
|
75
|
-
# --- Flight history ---
|
|
76
|
-
if [ -f "$HISTORY_FILE" ]; then
|
|
77
|
-
count=$(python -c "
|
|
78
|
-
import json
|
|
79
|
-
try:
|
|
80
|
-
with open(r'''$HISTORY_FILE''', encoding='utf-8') as f:
|
|
81
|
-
data = json.load(f)
|
|
82
|
-
print(len(data) if isinstance(data, list) else 0)
|
|
83
|
-
except Exception:
|
|
84
|
-
print(0)
|
|
85
|
-
" 2>/dev/null || echo "?")
|
|
86
|
-
echo "History: $HISTORY_FILE ($count entries)"
|
|
87
|
-
else
|
|
88
|
-
echo "History: $HISTORY_FILE (not created yet — first search will create it)"
|
|
89
|
-
fi
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Deterministic setup check. Run at skill load time via the ```! fence in SKILL.md.
|
|
3
|
+
# Outputs a human-readable report of:
|
|
4
|
+
# - whether ~/.flight-planner/config.json exists and its contents (key masked)
|
|
5
|
+
# - whether ~/.flight-planner/flight_history.json exists and its entry count
|
|
6
|
+
# - today's date (for resolving relative dates in Phase 1)
|
|
7
|
+
# - whether SERPAPI_KEY env var is set
|
|
8
|
+
# No side effects. Safe to run on every skill invocation.
|
|
9
|
+
|
|
10
|
+
set -u
|
|
11
|
+
|
|
12
|
+
CONFIG_DIR="$HOME/.flight-planner"
|
|
13
|
+
CONFIG_FILE="$CONFIG_DIR/config.json"
|
|
14
|
+
HISTORY_FILE="$CONFIG_DIR/flight_history.json"
|
|
15
|
+
|
|
16
|
+
echo "=== Flight Planner Setup ==="
|
|
17
|
+
echo "Today: $(date +%Y-%m-%d) (timezone: $(date +%Z))"
|
|
18
|
+
echo ""
|
|
19
|
+
|
|
20
|
+
# --- Config ---
|
|
21
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
22
|
+
echo "Config: $CONFIG_FILE (exists)"
|
|
23
|
+
echo ""
|
|
24
|
+
echo "--- Saved preferences ---"
|
|
25
|
+
# Mask serpapi_key value: show as "set" or "null" but never the actual key
|
|
26
|
+
python -c "
|
|
27
|
+
import json, sys
|
|
28
|
+
try:
|
|
29
|
+
with open(r'''$CONFIG_FILE''', encoding='utf-8') as f:
|
|
30
|
+
c = json.load(f)
|
|
31
|
+
except Exception as e:
|
|
32
|
+
print(f' ERROR reading config: {e}')
|
|
33
|
+
sys.exit(0)
|
|
34
|
+
|
|
35
|
+
key = c.get('serpapi_key')
|
|
36
|
+
print(' SerpAPI key: ' + ('set' if key else 'null (will use WebSearch fallback)'))
|
|
37
|
+
print(' Budget: \$' + str(c.get('budget_usd', '?')) + ' (' + str(c.get('budget_strength', '?')) + ')')
|
|
38
|
+
airlines = c.get('airline_preferences') or []
|
|
39
|
+
print(' Airlines: ' + (', '.join(airlines) if airlines else 'any') + ' (' + str(c.get('airline_preference_strength', '?')) + ')')
|
|
40
|
+
np = c.get('nonstop_preference', '?')
|
|
41
|
+
ns = c.get('nonstop_strength', '?')
|
|
42
|
+
print(' Nonstop: ' + str(np) + ' (' + str(ns) + ')')
|
|
43
|
+
bands = c.get('time_priority_bands') or []
|
|
44
|
+
print(' Time priority: ' + (', '.join(bands) if bands else 'none') + ' (' + str(c.get('time_priority_strength', '?')) + ')')
|
|
45
|
+
print(' Home airport: ' + str(c.get('home_airport') or 'not set'))
|
|
46
|
+
freq = c.get('frequent_destinations') or []
|
|
47
|
+
print(' Frequent destinations: ' + (', '.join(freq) if freq else 'not set'))
|
|
48
|
+
shuttle = c.get('shuttle_service')
|
|
49
|
+
if shuttle:
|
|
50
|
+
name = shuttle.get('name', '?')
|
|
51
|
+
costs = shuttle.get('costs', {}) or {}
|
|
52
|
+
cost_str = ', '.join(f'{k}: \${v}' for k, v in costs.items()) if costs else 'no costs configured'
|
|
53
|
+
print(' Shuttle service: ' + str(name) + ' (' + cost_str + ')')
|
|
54
|
+
else:
|
|
55
|
+
print(' Shuttle service: none (pairing step will be skipped)')
|
|
56
|
+
" 2>&1 || echo " (python not available; cannot parse config — read the file directly)"
|
|
57
|
+
else
|
|
58
|
+
echo "Config: $CONFIG_FILE (NOT FOUND)"
|
|
59
|
+
echo ""
|
|
60
|
+
echo "First-run setup is needed. The skill will walk the user through Phase 2"
|
|
61
|
+
echo "to create this file."
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
|
|
66
|
+
# --- Environment SERPAPI_KEY ---
|
|
67
|
+
if [ -n "${SERPAPI_KEY:-}" ]; then
|
|
68
|
+
echo "SERPAPI_KEY: set in environment (will be used if config key is null)"
|
|
69
|
+
else
|
|
70
|
+
echo "SERPAPI_KEY: not set in environment"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo ""
|
|
74
|
+
|
|
75
|
+
# --- Flight history ---
|
|
76
|
+
if [ -f "$HISTORY_FILE" ]; then
|
|
77
|
+
count=$(python -c "
|
|
78
|
+
import json
|
|
79
|
+
try:
|
|
80
|
+
with open(r'''$HISTORY_FILE''', encoding='utf-8') as f:
|
|
81
|
+
data = json.load(f)
|
|
82
|
+
print(len(data) if isinstance(data, list) else 0)
|
|
83
|
+
except Exception:
|
|
84
|
+
print(0)
|
|
85
|
+
" 2>/dev/null || echo "?")
|
|
86
|
+
echo "History: $HISTORY_FILE ($count entries)"
|
|
87
|
+
else
|
|
88
|
+
echo "History: $HISTORY_FILE (not created yet — first search will create it)"
|
|
89
|
+
fi
|
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Fetch flight data from SerpAPI Google Flights.
|
|
3
|
-
|
|
4
|
-
Saves raw JSON responses to a platform-aware temp directory, one file per
|
|
5
|
-
route x date combination. Prints the temp directory path on stdout so the
|
|
6
|
-
caller can pipe it to filter_flights.py.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python fetch_flights.py --dates 2026-05-09,2026-05-10 --routes IND-EWR,ORD-LGA
|
|
10
|
-
python fetch_flights.py --dates 2026-05-09 --routes IND-EWR --airlines UA,DL --stops 1
|
|
11
|
-
python fetch_flights.py --dates 2026-05-09 --routes IND-EWR --output-dir /tmp/x
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
--airlines Optional comma-separated IATA airline codes (e.g. UA,DL). Omit for any airline.
|
|
15
|
-
--stops Optional. 0=any (default), 1=nonstop only, 2=one-stop-max.
|
|
16
|
-
|
|
17
|
-
Auth:
|
|
18
|
-
Reads SERPAPI_KEY from environment. Override with --api-key.
|
|
19
|
-
"""
|
|
20
|
-
import argparse
|
|
21
|
-
import os
|
|
22
|
-
import sys
|
|
23
|
-
import tempfile
|
|
24
|
-
import urllib.parse
|
|
25
|
-
import urllib.request
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
|
|
28
|
-
ENDPOINT = "https://serpapi.com/search"
|
|
29
|
-
BASE_PARAMS = {
|
|
30
|
-
"engine": "google_flights",
|
|
31
|
-
"type": "2", # one-way
|
|
32
|
-
"sort_by": "2", # price
|
|
33
|
-
"currency": "USD",
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def default_output_dir() -> Path:
|
|
38
|
-
base = Path(tempfile.gettempdir()) / "estack-flight-planner"
|
|
39
|
-
base.mkdir(parents=True, exist_ok=True)
|
|
40
|
-
return base
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def fetch_one(api_key: str, dep: str, arr: str, date: str, out_path: Path,
|
|
44
|
-
airlines: str = None, stops: str = None) -> int:
|
|
45
|
-
params = {**BASE_PARAMS, "departure_id": dep, "arrival_id": arr,
|
|
46
|
-
"outbound_date": date, "api_key": api_key}
|
|
47
|
-
if airlines:
|
|
48
|
-
params["include_airlines"] = airlines
|
|
49
|
-
if stops and stops != "0":
|
|
50
|
-
params["stops"] = stops
|
|
51
|
-
url = f"{ENDPOINT}?{urllib.parse.urlencode(params)}"
|
|
52
|
-
with urllib.request.urlopen(url, timeout=60) as resp:
|
|
53
|
-
data = resp.read()
|
|
54
|
-
out_path.write_bytes(data)
|
|
55
|
-
return len(data)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def main() -> int:
|
|
59
|
-
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
60
|
-
p.add_argument("--dates", required=True, help="Comma-separated YYYY-MM-DD")
|
|
61
|
-
p.add_argument("--routes", required=True, help="Comma-separated DEP-ARR (e.g. IND-EWR,ORD-LGA)")
|
|
62
|
-
p.add_argument("--airlines", default=None,
|
|
63
|
-
help="Optional comma-separated IATA codes (e.g. UA,DL). Omit for any airline.")
|
|
64
|
-
p.add_argument("--stops", default="0",
|
|
65
|
-
help="0=any (default), 1=nonstop only, 2=one-stop-max")
|
|
66
|
-
p.add_argument("--output-dir", default=None, help="Override temp dir")
|
|
67
|
-
p.add_argument("--api-key", default=None, help="SerpAPI key (else uses $SERPAPI_KEY)")
|
|
68
|
-
args = p.parse_args()
|
|
69
|
-
|
|
70
|
-
api_key = args.api_key or os.environ.get("SERPAPI_KEY")
|
|
71
|
-
if not api_key:
|
|
72
|
-
print("ERROR: provide --api-key or set SERPAPI_KEY env var", file=sys.stderr)
|
|
73
|
-
return 2
|
|
74
|
-
|
|
75
|
-
out_dir = Path(args.output_dir) if args.output_dir else default_output_dir()
|
|
76
|
-
out_dir.mkdir(parents=True, exist_ok=True)
|
|
77
|
-
|
|
78
|
-
dates = [d.strip() for d in args.dates.split(",") if d.strip()]
|
|
79
|
-
routes = [tuple(r.strip().split("-")) for r in args.routes.split(",") if r.strip()]
|
|
80
|
-
|
|
81
|
-
total = 0
|
|
82
|
-
for dep, arr in routes:
|
|
83
|
-
for date in dates:
|
|
84
|
-
fname = out_dir / f"{dep}_{arr}_{date}.json"
|
|
85
|
-
try:
|
|
86
|
-
size = fetch_one(api_key, dep, arr, date, fname,
|
|
87
|
-
airlines=args.airlines, stops=args.stops)
|
|
88
|
-
print(f" {dep}->{arr} {date}: {size} bytes", file=sys.stderr)
|
|
89
|
-
total += 1
|
|
90
|
-
except Exception as e:
|
|
91
|
-
print(f" {dep}->{arr} {date}: FAILED ({e})", file=sys.stderr)
|
|
92
|
-
|
|
93
|
-
print(f"Fetched {total} files to:", file=sys.stderr)
|
|
94
|
-
print(out_dir) # stdout = the path, for piping
|
|
95
|
-
return 0
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if __name__ == "__main__":
|
|
99
|
-
sys.exit(main())
|
|
1
|
+
"""
|
|
2
|
+
Fetch flight data from SerpAPI Google Flights.
|
|
3
|
+
|
|
4
|
+
Saves raw JSON responses to a platform-aware temp directory, one file per
|
|
5
|
+
route x date combination. Prints the temp directory path on stdout so the
|
|
6
|
+
caller can pipe it to filter_flights.py.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python fetch_flights.py --dates 2026-05-09,2026-05-10 --routes IND-EWR,ORD-LGA
|
|
10
|
+
python fetch_flights.py --dates 2026-05-09 --routes IND-EWR --airlines UA,DL --stops 1
|
|
11
|
+
python fetch_flights.py --dates 2026-05-09 --routes IND-EWR --output-dir /tmp/x
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
--airlines Optional comma-separated IATA airline codes (e.g. UA,DL). Omit for any airline.
|
|
15
|
+
--stops Optional. 0=any (default), 1=nonstop only, 2=one-stop-max.
|
|
16
|
+
|
|
17
|
+
Auth:
|
|
18
|
+
Reads SERPAPI_KEY from environment. Override with --api-key.
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
import urllib.parse
|
|
25
|
+
import urllib.request
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
ENDPOINT = "https://serpapi.com/search"
|
|
29
|
+
BASE_PARAMS = {
|
|
30
|
+
"engine": "google_flights",
|
|
31
|
+
"type": "2", # one-way
|
|
32
|
+
"sort_by": "2", # price
|
|
33
|
+
"currency": "USD",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def default_output_dir() -> Path:
|
|
38
|
+
base = Path(tempfile.gettempdir()) / "estack-flight-planner"
|
|
39
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
return base
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def fetch_one(api_key: str, dep: str, arr: str, date: str, out_path: Path,
|
|
44
|
+
airlines: str = None, stops: str = None) -> int:
|
|
45
|
+
params = {**BASE_PARAMS, "departure_id": dep, "arrival_id": arr,
|
|
46
|
+
"outbound_date": date, "api_key": api_key}
|
|
47
|
+
if airlines:
|
|
48
|
+
params["include_airlines"] = airlines
|
|
49
|
+
if stops and stops != "0":
|
|
50
|
+
params["stops"] = stops
|
|
51
|
+
url = f"{ENDPOINT}?{urllib.parse.urlencode(params)}"
|
|
52
|
+
with urllib.request.urlopen(url, timeout=60) as resp:
|
|
53
|
+
data = resp.read()
|
|
54
|
+
out_path.write_bytes(data)
|
|
55
|
+
return len(data)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> int:
|
|
59
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
60
|
+
p.add_argument("--dates", required=True, help="Comma-separated YYYY-MM-DD")
|
|
61
|
+
p.add_argument("--routes", required=True, help="Comma-separated DEP-ARR (e.g. IND-EWR,ORD-LGA)")
|
|
62
|
+
p.add_argument("--airlines", default=None,
|
|
63
|
+
help="Optional comma-separated IATA codes (e.g. UA,DL). Omit for any airline.")
|
|
64
|
+
p.add_argument("--stops", default="0",
|
|
65
|
+
help="0=any (default), 1=nonstop only, 2=one-stop-max")
|
|
66
|
+
p.add_argument("--output-dir", default=None, help="Override temp dir")
|
|
67
|
+
p.add_argument("--api-key", default=None, help="SerpAPI key (else uses $SERPAPI_KEY)")
|
|
68
|
+
args = p.parse_args()
|
|
69
|
+
|
|
70
|
+
api_key = args.api_key or os.environ.get("SERPAPI_KEY")
|
|
71
|
+
if not api_key:
|
|
72
|
+
print("ERROR: provide --api-key or set SERPAPI_KEY env var", file=sys.stderr)
|
|
73
|
+
return 2
|
|
74
|
+
|
|
75
|
+
out_dir = Path(args.output_dir) if args.output_dir else default_output_dir()
|
|
76
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
dates = [d.strip() for d in args.dates.split(",") if d.strip()]
|
|
79
|
+
routes = [tuple(r.strip().split("-")) for r in args.routes.split(",") if r.strip()]
|
|
80
|
+
|
|
81
|
+
total = 0
|
|
82
|
+
for dep, arr in routes:
|
|
83
|
+
for date in dates:
|
|
84
|
+
fname = out_dir / f"{dep}_{arr}_{date}.json"
|
|
85
|
+
try:
|
|
86
|
+
size = fetch_one(api_key, dep, arr, date, fname,
|
|
87
|
+
airlines=args.airlines, stops=args.stops)
|
|
88
|
+
print(f" {dep}->{arr} {date}: {size} bytes", file=sys.stderr)
|
|
89
|
+
total += 1
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(f" {dep}->{arr} {date}: FAILED ({e})", file=sys.stderr)
|
|
92
|
+
|
|
93
|
+
print(f"Fetched {total} files to:", file=sys.stderr)
|
|
94
|
+
print(out_dir) # stdout = the path, for piping
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
sys.exit(main())
|