abelworkflow 1.2.4 → 1.3.0

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.
Files changed (63) hide show
  1. package/README.md +26 -25
  2. package/extensions/gpt-responses-compat.ts +1 -1
  3. package/lib/cli/main.mjs +18 -86
  4. package/lib/cli/pi.mjs +43 -0
  5. package/lib/cli/prompts.mjs +3 -3
  6. package/lib/installer/assets.mjs +35 -31
  7. package/lib/installer/install.mjs +29 -9
  8. package/lib/installer/links.mjs +97 -84
  9. package/lib/paths.mjs +0 -2
  10. package/lib/providers/claude.mjs +14 -3
  11. package/lib/providers/codex.mjs +14 -9
  12. package/lib/providers/pi.mjs +61 -68
  13. package/lib/providers/skills.mjs +22 -22
  14. package/lib/providers/url.mjs +32 -1
  15. package/lib/templates/codex/agents/default.toml +16 -54
  16. package/lib/templates/codex/agents/explorer.toml +25 -52
  17. package/lib/templates/codex/agents/planner.toml +23 -77
  18. package/lib/templates/codex/agents/reviewer.toml +16 -62
  19. package/lib/templates/codex/agents/worker.toml +30 -64
  20. package/lib/templates/codex/config-base.toml +0 -6
  21. package/lib/templates/{workflow/gitignore.template → gitignore.template} +0 -3
  22. package/lib/tools/cli-installer.mjs +40 -12
  23. package/package.json +13 -18
  24. package/lib/templates/workflow/AGENTS.md +0 -50
  25. package/lib/templates/workflow/commands/abel-design.md +0 -175
  26. package/lib/templates/workflow/commands/abel-diagnose.md +0 -63
  27. package/lib/templates/workflow/commands/abel-implement.md +0 -170
  28. package/lib/templates/workflow/commands/abel-init.md +0 -25
  29. package/skills/dev-browser/SKILL.md +0 -281
  30. package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
  31. package/skills/dev-browser/dist/scripts/start.js +0 -90
  32. package/skills/dev-browser/dist/src/client.d.ts +0 -92
  33. package/skills/dev-browser/dist/src/client.js +0 -310
  34. package/skills/dev-browser/dist/src/entrypoint.d.ts +0 -29
  35. package/skills/dev-browser/dist/src/entrypoint.js +0 -113
  36. package/skills/dev-browser/dist/src/index.d.ts +0 -3
  37. package/skills/dev-browser/dist/src/index.js +0 -1
  38. package/skills/dev-browser/dist/src/page-api.d.ts +0 -24
  39. package/skills/dev-browser/dist/src/page-api.js +0 -103
  40. package/skills/dev-browser/dist/src/relay.d.ts +0 -26
  41. package/skills/dev-browser/dist/src/relay.js +0 -567
  42. package/skills/dev-browser/dist/src/runtime.d.ts +0 -34
  43. package/skills/dev-browser/dist/src/runtime.js +0 -44
  44. package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -22
  45. package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -868
  46. package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -13
  47. package/skills/dev-browser/dist/src/snapshot/index.js +0 -13
  48. package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -12
  49. package/skills/dev-browser/dist/src/snapshot/inject.js +0 -12
  50. package/skills/dev-browser/dist/src/standalone.d.ts +0 -31
  51. package/skills/dev-browser/dist/src/standalone.js +0 -173
  52. package/skills/dev-browser/dist/src/startup.d.ts +0 -46
  53. package/skills/dev-browser/dist/src/startup.js +0 -77
  54. package/skills/dev-browser/dist/src/target-registry.d.ts +0 -28
  55. package/skills/dev-browser/dist/src/target-registry.js +0 -134
  56. package/skills/dev-browser/dist/src/types.d.ts +0 -26
  57. package/skills/dev-browser/dist/src/types.js +0 -1
  58. package/skills/dev-browser/package-lock.json +0 -1545
  59. package/skills/dev-browser/package.json +0 -35
  60. package/skills/dev-browser/references/scraping.md +0 -144
  61. package/skills/git-commit/SKILL.md +0 -124
  62. package/skills/time/SKILL.md +0 -119
  63. package/skills/time/scripts/time_cli.py +0 -143
@@ -1,35 +0,0 @@
1
- {
2
- "name": "dev-browser",
3
- "version": "0.0.1",
4
- "type": "module",
5
- "engines": {
6
- "node": ">=22"
7
- },
8
- "packageManager": "npm@10.9.2",
9
- "exports": {
10
- "./client": {
11
- "types": "./dist/src/client.d.ts",
12
- "import": "./dist/src/client.js"
13
- }
14
- },
15
- "scripts": {
16
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
17
- "typecheck": "tsc -p tsconfig.json --noEmit",
18
- "start": "node dist/scripts/start.js standalone",
19
- "start-server": "node dist/scripts/start.js standalone",
20
- "start-extension": "node dist/scripts/start.js extension",
21
- "test": "vitest run",
22
- "test:watch": "vitest"
23
- },
24
- "dependencies": {
25
- "@hono/node-server": "^1.19.13",
26
- "@hono/node-ws": "^1.3.1",
27
- "hono": "^4.12.30",
28
- "playwright": "^1.56.0"
29
- },
30
- "devDependencies": {
31
- "@types/node": "^22.0.0",
32
- "typescript": "^5.0.0",
33
- "vitest": "^2.1.0"
34
- }
35
- }
@@ -1,144 +0,0 @@
1
- # Data Scraping Guide
2
-
3
- For large datasets (followers, posts, search results), **intercept and replay network requests** rather than scrolling and parsing the DOM. This is faster, more reliable, and handles pagination automatically.
4
-
5
- ## Why Not Scroll?
6
-
7
- Scrolling is slow, unreliable, and wastes time. APIs return structured data with pagination built in. Always prefer API replay.
8
-
9
- ## Start Small, Then Scale
10
-
11
- **Don't try to automate everything at once.** Work incrementally:
12
-
13
- 1. **Capture one request** - verify you're intercepting the right endpoint
14
- 2. **Inspect one response** - understand the schema before writing extraction code
15
- 3. **Extract a few items** - make sure your parsing logic works
16
- 4. **Then scale up** - add pagination loop only after the basics work
17
-
18
- This prevents wasting time debugging a complex script when the issue is a simple path like `data.user.timeline` vs `data.user.result.timeline`.
19
-
20
- ## External Script Setup
21
-
22
- Keep the installed skill directory read-only. Put scripts and temporary artifacts in the OS temporary directory or the task workspace, and remove them when the task finishes. External scripts receive the installed skill directory as their first argument and dynamically import the compiled client by file URL.
23
-
24
- Never persist complete authentication headers. Keep captured headers in memory, and write only redacted metadata or final non-sensitive results.
25
-
26
- ```javascript
27
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
28
- import { tmpdir } from "node:os";
29
- import { join } from "node:path";
30
- import { pathToFileURL } from "node:url";
31
-
32
- const skillDir = process.argv[2];
33
- if (!skillDir) throw new Error("Pass the installed dev-browser skill directory");
34
- const { connect, waitForPageLoad } = await import(
35
- pathToFileURL(join(skillDir, "dist", "src", "client.js")).href
36
- );
37
-
38
- const workDir = await mkdtemp(join(tmpdir(), "dev-browser-scrape-"));
39
- let client;
40
- try {
41
- client = await connect();
42
- const page = await client.page("site");
43
- let capturedRequest;
44
-
45
- page.on("request", (request) => {
46
- const url = request.url();
47
- if (url.includes("/api/") || url.includes("/graphql/")) {
48
- capturedRequest = {
49
- url,
50
- method: request.method(),
51
- headers: request.headers(),
52
- };
53
- }
54
- });
55
-
56
- await page.goto("https://example.com/profile");
57
- await waitForPageLoad(page);
58
- await page.waitForTimeout(3000);
59
-
60
- if (!capturedRequest) throw new Error("No matching API request captured");
61
- const capturedUrl = new URL(capturedRequest.url);
62
- console.log({
63
- url: `${capturedUrl.origin}${capturedUrl.pathname}`,
64
- method: capturedRequest.method,
65
- headerNames: Object.keys(capturedRequest.headers),
66
- });
67
-
68
- const results = new Map();
69
- let cursor;
70
- do {
71
- const params = { count: 20, ...(cursor ? { cursor } : {}) };
72
- const url = new URL(capturedRequest.url);
73
- url.searchParams.set("params", JSON.stringify(params));
74
- const response = await page.evaluate(
75
- async ({ url, headers }) => {
76
- const result = await fetch(url, { headers });
77
- return result.json();
78
- },
79
- { url: url.href, headers: capturedRequest.headers }
80
- );
81
-
82
- const entries = response?.data?.entries ?? [];
83
- cursor = undefined;
84
- for (const entry of entries) {
85
- if (entry.type === "cursor-bottom") cursor = entry.value;
86
- else if (entry.id) results.set(entry.id, entry);
87
- }
88
- await new Promise((resolve) => setTimeout(resolve, 500));
89
- } while (cursor);
90
-
91
- const resultPath = join(workDir, "results.json");
92
- await writeFile(resultPath, JSON.stringify([...results.values()], null, 2));
93
- console.log({ count: results.size, resultPath });
94
- } finally {
95
- try {
96
- await client?.disconnect();
97
- } finally {
98
- await rm(workDir, { recursive: true, force: true });
99
- }
100
- }
101
- ```
102
-
103
- Run it with quoted paths on Linux, PowerShell, or Command Prompt:
104
-
105
- ```text
106
- node "PATH_TO_SCRIPT.mjs" "PATH_TO_INSTALLED_DEV_BROWSER"
107
- ```
108
-
109
- If a result must survive the script, write the sanitized deliverable to the task workspace instead of `workDir`, then delete it at task completion unless the user asks to retain it.
110
-
111
- ## Step-by-Step Workflow
112
-
113
- ### 1. Capture One Request
114
-
115
- Match a narrow API or GraphQL URL pattern. Keep the request URL and headers in memory. Log only the URL origin/path, method, and header names while confirming the endpoint; query parameters can contain credentials.
116
-
117
- ### 2. Inspect One Response
118
-
119
- Inspect the response in memory before building pagination. Find:
120
-
121
- - Where the data array lives (e.g., `data.user.result.timeline.instructions[].entries`)
122
- - Where pagination cursors are (e.g., `cursor-bottom` entries)
123
- - What fields you need to extract
124
-
125
- ### 3. Replay API with Pagination
126
-
127
- Replay through `page.evaluate(fetch)` so the browser supplies its session cookies. Pass only the in-memory headers required by the endpoint, deduplicate with a `Map`, stop when the cursor or results end, and rate-limit requests.
128
-
129
- ## Key Patterns
130
-
131
- | Pattern | Description |
132
- | ----------------------- | ------------------------------------------------------ |
133
- | `page.on('request')` | Capture outgoing request URL + headers |
134
- | `page.on('response')` | Capture response data to understand schema |
135
- | `page.evaluate(fetch)` | Replay requests in browser context (inherits auth) |
136
- | `Map` for deduplication | APIs often return overlapping data across pages |
137
- | Cursor-based pagination | Look for `cursor`, `next_token`, `offset` in responses |
138
-
139
- ## Tips
140
-
141
- - **Extension mode**: `page.context().cookies()` doesn't work - keep intercepted auth headers in memory instead
142
- - **Rate limiting**: Add 500ms+ delays between requests to avoid blocks
143
- - **Stop conditions**: Check for empty results, missing cursor, or reaching a date/ID threshold
144
- - **GraphQL APIs**: URL params often include `variables` and `features` JSON objects - capture and reuse them
@@ -1,124 +0,0 @@
1
- ---
2
- name: git-commit
3
- description: 'Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping'
4
- license: MIT
5
- allowed-tools: Bash
6
- ---
7
-
8
- # Git Commit with Conventional Commits
9
-
10
- ## Overview
11
-
12
- Create standardized, semantic git commits using the Conventional Commits specification. Analyze the actual diff to determine appropriate type, scope, and message.
13
-
14
- ## Conventional Commit Format
15
-
16
- ```
17
- <type>[optional scope]: <description>
18
-
19
- [optional body]
20
-
21
- [optional footer(s)]
22
- ```
23
-
24
- ## Commit Types
25
-
26
- | Type | Purpose |
27
- | ---------- | ------------------------------ |
28
- | `feat` | New feature |
29
- | `fix` | Bug fix |
30
- | `docs` | Documentation only |
31
- | `style` | Formatting/style (no logic) |
32
- | `refactor` | Code refactor (no feature/fix) |
33
- | `perf` | Performance improvement |
34
- | `test` | Add/update tests |
35
- | `build` | Build system/dependencies |
36
- | `ci` | CI/config changes |
37
- | `chore` | Maintenance/misc |
38
- | `revert` | Revert commit |
39
-
40
- ## Breaking Changes
41
-
42
- ```
43
- # Exclamation mark after type/scope
44
- feat!: remove deprecated endpoint
45
-
46
- # BREAKING CHANGE footer
47
- feat: allow config to extend other configs
48
-
49
- BREAKING CHANGE: `extends` key behavior changed
50
- ```
51
-
52
- ## Workflow
53
-
54
- ### 1. Analyze Diff
55
-
56
- ```bash
57
- # If files are staged, use staged diff
58
- git diff --staged
59
-
60
- # If nothing staged, use working tree diff
61
- git diff
62
-
63
- # Also check status
64
- git status --porcelain
65
- ```
66
-
67
- ### 2. Stage Files (if needed)
68
-
69
- If nothing is staged or you want to group changes differently:
70
-
71
- ```bash
72
- # Stage specific files
73
- git add path/to/file1 path/to/file2
74
-
75
- # Stage by pattern
76
- git add *.test.*
77
- git add src/components/*
78
-
79
- # Interactive staging
80
- git add -p
81
- ```
82
-
83
- **Never commit secrets** (.env, credentials.json, private keys).
84
-
85
- ### 3. Generate Commit Message
86
-
87
- Analyze the diff to determine:
88
-
89
- - **Type**: What kind of change is this?
90
- - **Scope**: What area/module is affected?
91
- - **Description**: One-line summary of what changed (present tense, imperative mood, <72 chars)
92
-
93
- ### 4. Execute Commit
94
-
95
- ```bash
96
- # Single line
97
- git commit -m "<type>[scope]: <description>"
98
-
99
- # Multi-line with body/footer
100
- git commit -m "$(cat <<'EOF'
101
- <type>[scope]: <description>
102
-
103
- <optional body>
104
-
105
- <optional footer>
106
- EOF
107
- )"
108
- ```
109
-
110
- ## Best Practices
111
-
112
- - One logical change per commit
113
- - Present tense: "add" not "added"
114
- - Imperative mood: "fix bug" not "fixes bug"
115
- - Reference issues: `Closes #123`, `Refs #456`
116
- - Keep description under 72 characters
117
-
118
- ## Git Safety Protocol
119
-
120
- - NEVER update git config
121
- - NEVER run destructive commands (--force, hard reset) without explicit request
122
- - NEVER skip hooks (--no-verify) unless user asks
123
- - NEVER force push to main/master
124
- - If commit fails due to hooks, fix and create NEW commit (don't amend)
@@ -1,119 +0,0 @@
1
- ---
2
- name: time
3
- description: |
4
- Time and timezone utilities for getting current time and converting between timezones. Use when: (1) Getting current time in any timezone, (2) Converting time between different timezones, (3) Working with IANA timezone names, (4) Scheduling across timezones, (5) Time-sensitive operations. Triggers: "what time is it", "current time", "convert time", "timezone", "time in [city]". Supports both MCP server and standalone CLI.
5
- ---
6
-
7
- # Time
8
-
9
- Time and timezone conversion utilities. Supports both MCP server and standalone CLI.
10
-
11
- ## Execution Methods
12
-
13
- ### Method 1: MCP Tools (if available)
14
- Use `mcp__time__*` tools directly:
15
- - `mcp__time__get_current_time` - Get current time in a timezone
16
- - `mcp__time__convert_time` - Convert time between timezones
17
-
18
- ### Method 2: CLI Script (no MCP dependency)
19
- Run `scripts/time_cli.py` via Bash:
20
-
21
- ```bash
22
- # Prerequisite: Python 3.9+
23
- # Windows also requires IANA data: python -m pip install tzdata
24
-
25
- # Get current time in a timezone
26
- python scripts/time_cli.py get --timezone "Asia/Shanghai"
27
- python scripts/time_cli.py get --timezone "America/New_York"
28
- python scripts/time_cli.py get # Uses UTC
29
-
30
- # Convert time between timezones
31
- python scripts/time_cli.py convert \
32
- --time "16:30" \
33
- --from "America/New_York" \
34
- --to "Asia/Tokyo"
35
-
36
- # List available timezones
37
- python scripts/time_cli.py list [--filter "Asia"]
38
- ```
39
-
40
- ## Tool Capability Matrix
41
-
42
- | Tool | Parameters | Output |
43
- |------|------------|--------|
44
- | `get_current_time` | `timezone` (required, IANA name) | `{timezone, datetime, is_dst}` |
45
- | `convert_time` | `source_timezone`, `time` (HH:MM), `target_timezone` | `{source, target, time_difference}` |
46
-
47
- ## Common IANA Timezone Names
48
-
49
- | Region | Timezone |
50
- |--------|----------|
51
- | China | `Asia/Shanghai` |
52
- | Japan | `Asia/Tokyo` |
53
- | Korea | `Asia/Seoul` |
54
- | US East | `America/New_York` |
55
- | US West | `America/Los_Angeles` |
56
- | UK | `Europe/London` |
57
- | Germany | `Europe/Berlin` |
58
- | France | `Europe/Paris` |
59
- | Australia | `Australia/Sydney` |
60
- | UTC | `UTC` |
61
-
62
- ## Workflow
63
-
64
- ### Getting Current Time
65
- 1. Identify target timezone (use IANA name)
66
- 2. Call `get_current_time` with timezone parameter
67
- 3. Response includes ISO 8601 datetime and DST status
68
-
69
- ### Converting Time
70
- 1. Identify source timezone and time (24-hour format HH:MM)
71
- 2. Identify target timezone
72
- 3. Call `convert_time` with all parameters
73
- 4. Response includes both times and time difference
74
-
75
- ## Output Format
76
-
77
- ### get_current_time Response
78
- ```json
79
- {
80
- "timezone": "Asia/Shanghai",
81
- "datetime": "2024-01-01T21:00:00+08:00",
82
- "is_dst": false
83
- }
84
- ```
85
-
86
- ### convert_time Response
87
- ```json
88
- {
89
- "source": {
90
- "timezone": "America/New_York",
91
- "datetime": "2024-01-01T16:30:00-05:00",
92
- "is_dst": false
93
- },
94
- "target": {
95
- "timezone": "Asia/Tokyo",
96
- "datetime": "2024-01-02T06:30:00+09:00",
97
- "is_dst": false
98
- },
99
- "time_difference": "+14.0h"
100
- }
101
- ```
102
-
103
- ## Error Handling
104
-
105
- | Error | Recovery |
106
- |-------|----------|
107
- | Invalid timezone | Check IANA timezone name spelling |
108
- | Invalid time format | Use 24-hour format HH:MM |
109
- | Nonexistent or ambiguous DST time | Choose an unambiguous local time |
110
- | Missing timezone data on Windows | Run `python -m pip install tzdata` |
111
- | MCP unavailable | Fall back to CLI script |
112
-
113
- ## Anti-Patterns
114
-
115
- | Prohibited | Correct |
116
- |------------|---------|
117
- | Use city names directly | Use IANA timezone names (e.g., `Asia/Tokyo` not `Tokyo`) |
118
- | Use 12-hour format | Use 24-hour format (e.g., `16:30` not `4:30 PM`) |
119
- | Assume timezone | Always specify timezone explicitly |
@@ -1,143 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Time CLI - Standalone time and timezone utilities."""
3
-
4
- import argparse
5
- import json
6
- import re
7
- import sys
8
- from datetime import datetime, timezone
9
- from zoneinfo import ZoneInfo, ZoneInfoNotFoundError, available_timezones
10
-
11
-
12
- def load_timezone(tz_name: str) -> ZoneInfo:
13
- try:
14
- return ZoneInfo(tz_name)
15
- except ZoneInfoNotFoundError as error:
16
- try:
17
- ZoneInfo("UTC")
18
- except ZoneInfoNotFoundError:
19
- raise ValueError(
20
- "IANA timezone data is unavailable; on Windows install it with: "
21
- "python -m pip install tzdata"
22
- ) from error
23
- raise ValueError(f"Unknown IANA timezone: {tz_name}") from error
24
-
25
-
26
- def localize_time(source: ZoneInfo, year: int, month: int, day: int, hour: int, minute: int) -> datetime:
27
- naive = datetime(year, month, day, hour, minute)
28
- candidates = []
29
- for fold in (0, 1):
30
- candidate = naive.replace(tzinfo=source, fold=fold)
31
- round_trip = candidate.astimezone(timezone.utc).astimezone(source)
32
- if round_trip.replace(tzinfo=None) == naive:
33
- candidates.append(candidate)
34
-
35
- if not candidates:
36
- raise ValueError("local time is nonexistent because of a DST transition")
37
- if len({candidate.utcoffset() for candidate in candidates}) > 1:
38
- raise ValueError("local time is ambiguous because of a DST transition")
39
- return candidates[0]
40
-
41
-
42
- def get_current_time(tz_name: str) -> dict:
43
- """Get current time in specified timezone."""
44
- try:
45
- tz = load_timezone(tz_name)
46
- now = datetime.now(tz)
47
- return {
48
- "timezone": tz_name,
49
- "datetime": now.isoformat(),
50
- "is_dst": bool(now.dst()) if hasattr(now, 'dst') and now.dst() else False
51
- }
52
- except Exception as e:
53
- return {"error": str(e)}
54
-
55
-
56
- def convert_time(source_tz: str, time_str: str, target_tz: str) -> dict:
57
- """Convert time between timezones."""
58
- try:
59
- if not re.fullmatch(r"(?:[01]\d|2[0-3]):[0-5]\d", time_str):
60
- raise ValueError("time must use 24-hour HH:MM format")
61
- hour, minute = map(int, time_str.split(':'))
62
- source = load_timezone(source_tz)
63
- target = load_timezone(target_tz)
64
-
65
- today = datetime.now(source).date()
66
- source_dt = localize_time(source, today.year, today.month, today.day, hour, minute)
67
- target_dt = source_dt.astimezone(target)
68
-
69
- source_offset = source_dt.utcoffset().total_seconds() / 3600
70
- target_offset = target_dt.utcoffset().total_seconds() / 3600
71
- diff = target_offset - source_offset
72
-
73
- return {
74
- "source": {
75
- "timezone": source_tz,
76
- "datetime": source_dt.isoformat(),
77
- "is_dst": bool(source_dt.dst()) if hasattr(source_dt, 'dst') and source_dt.dst() else False
78
- },
79
- "target": {
80
- "timezone": target_tz,
81
- "datetime": target_dt.isoformat(),
82
- "is_dst": bool(target_dt.dst()) if hasattr(target_dt, 'dst') and target_dt.dst() else False
83
- },
84
- "time_difference": f"{diff:+.1f}h"
85
- }
86
- except Exception as e:
87
- return {"error": str(e)}
88
-
89
-
90
- def list_timezones(filter_str: str = None) -> list:
91
- """List available timezones."""
92
- zones = sorted(available_timezones())
93
- if not zones:
94
- load_timezone("UTC")
95
- if filter_str:
96
- zones = [z for z in zones if filter_str.lower() in z.lower()]
97
- return zones
98
-
99
-
100
- def print_result(result: dict) -> int:
101
- if "error" in result:
102
- print(json.dumps(result, indent=2), file=sys.stderr)
103
- return 1
104
- print(json.dumps(result, indent=2))
105
- return 0
106
-
107
-
108
- def main() -> int:
109
- parser = argparse.ArgumentParser(description="Time and timezone utilities")
110
- subparsers = parser.add_subparsers(dest="command", required=True)
111
-
112
- # get command
113
- get_parser = subparsers.add_parser("get", help="Get current time")
114
- get_parser.add_argument("--timezone", "-tz", default="UTC", help="IANA timezone name")
115
-
116
- # convert command
117
- convert_parser = subparsers.add_parser("convert", help="Convert time between timezones")
118
- convert_parser.add_argument("--time", "-t", required=True, help="Time in HH:MM format")
119
- convert_parser.add_argument("--from", "-f", dest="source", required=True, help="Source timezone")
120
- convert_parser.add_argument("--to", "-o", dest="target", required=True, help="Target timezone")
121
-
122
- # list command
123
- list_parser = subparsers.add_parser("list", help="List available timezones")
124
- list_parser.add_argument("--filter", "-f", help="Filter timezones by substring")
125
-
126
- args = parser.parse_args()
127
-
128
- if args.command == "get":
129
- return print_result(get_current_time(args.timezone))
130
- if args.command == "convert":
131
- return print_result(convert_time(args.source, args.time, args.target))
132
- if args.command == "list":
133
- try:
134
- for z in list_timezones(args.filter):
135
- print(z)
136
- return 0
137
- except Exception as error:
138
- return print_result({"error": str(error)})
139
- return 1
140
-
141
-
142
- if __name__ == "__main__":
143
- sys.exit(main())