@marcopeg/hal 1.0.17 → 1.0.20
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/README.md +57 -28
- package/dist/bot/commands/git/callback.d.ts +8 -0
- package/dist/bot/commands/git/callback.d.ts.map +1 -0
- package/dist/bot/commands/git/callback.js +72 -0
- package/dist/bot/commands/git/callback.js.map +1 -0
- package/dist/bot/commands/git/clean.d.ts +4 -0
- package/dist/bot/commands/git/clean.d.ts.map +1 -0
- package/dist/bot/commands/git/clean.js +50 -0
- package/dist/bot/commands/git/clean.js.map +1 -0
- package/dist/bot/commands/git/commit.d.ts +4 -0
- package/dist/bot/commands/git/commit.d.ts.map +1 -0
- package/dist/bot/commands/git/commit.js +72 -0
- package/dist/bot/commands/git/commit.js.map +1 -0
- package/dist/bot/commands/git/exec.d.ts +6 -0
- package/dist/bot/commands/git/exec.d.ts.map +1 -0
- package/dist/bot/commands/git/exec.js +16 -0
- package/dist/bot/commands/git/exec.js.map +1 -0
- package/dist/bot/commands/git/index.d.ts +6 -0
- package/dist/bot/commands/git/index.d.ts.map +1 -0
- package/dist/bot/commands/git/index.js +6 -0
- package/dist/bot/commands/git/index.js.map +1 -0
- package/dist/bot/commands/git/init.d.ts +4 -0
- package/dist/bot/commands/git/init.d.ts.map +1 -0
- package/dist/bot/commands/git/init.js +22 -0
- package/dist/bot/commands/git/init.js.map +1 -0
- package/dist/bot/commands/git/status.d.ts +4 -0
- package/dist/bot/commands/git/status.d.ts.map +1 -0
- package/dist/bot/commands/git/status.js +18 -0
- package/dist/bot/commands/git/status.js.map +1 -0
- package/dist/bot/commands/help.js +1 -1
- package/dist/bot/commands/help.js.map +1 -1
- package/dist/bot/commands/loader.d.ts +12 -5
- package/dist/bot/commands/loader.d.ts.map +1 -1
- package/dist/bot/commands/loader.js +73 -15
- package/dist/bot/commands/loader.js.map +1 -1
- package/dist/bot/commands/message.d.ts.map +1 -1
- package/dist/bot/commands/message.js +49 -24
- package/dist/bot/commands/message.js.map +1 -1
- package/dist/bot/commands/reset.js +1 -1
- package/dist/bot/commands/reset.js.map +1 -1
- package/dist/bot/commands/session.d.ts +2 -2
- package/dist/bot/commands/session.d.ts.map +1 -1
- package/dist/bot/commands/session.js +7 -4
- package/dist/bot/commands/session.js.map +1 -1
- package/dist/bot/commands/start.js +2 -2
- package/dist/bot/commands/start.js.map +1 -1
- package/dist/bot/commands/watcher.d.ts +2 -1
- package/dist/bot/commands/watcher.d.ts.map +1 -1
- package/dist/bot/commands/watcher.js +2 -2
- package/dist/bot/commands/watcher.js.map +1 -1
- package/dist/bot/handlers/text.d.ts.map +1 -1
- package/dist/bot/handlers/text.js +7 -0
- package/dist/bot/handlers/text.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +30 -9
- package/dist/bot.js.map +1 -1
- package/dist/cli.js +49 -19
- package/dist/cli.js.map +1 -1
- package/dist/config-watcher.d.ts +10 -0
- package/dist/config-watcher.d.ts.map +1 -0
- package/dist/config-watcher.js +49 -0
- package/dist/config-watcher.js.map +1 -0
- package/dist/config.d.ts +118 -41
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +124 -58
- package/dist/config.js.map +1 -1
- package/dist/context/resolver.d.ts +4 -0
- package/dist/context/resolver.d.ts.map +1 -1
- package/dist/context/resolver.js +8 -2
- package/dist/context/resolver.js.map +1 -1
- package/dist/default-models.d.ts +3 -0
- package/dist/default-models.d.ts.map +1 -0
- package/dist/default-models.js +16 -0
- package/dist/default-models.js.map +1 -0
- package/dist/engine/adapters/codex.d.ts.map +1 -1
- package/dist/engine/adapters/codex.js +25 -5
- package/dist/engine/adapters/codex.js.map +1 -1
- package/dist/engine/adapters/cursor.d.ts +3 -0
- package/dist/engine/adapters/cursor.d.ts.map +1 -0
- package/dist/engine/adapters/cursor.js +103 -0
- package/dist/engine/adapters/cursor.js.map +1 -0
- package/dist/engine/adapters/opencode.d.ts +2 -2
- package/dist/engine/adapters/opencode.d.ts.map +1 -1
- package/dist/engine/adapters/opencode.js +27 -11
- package/dist/engine/adapters/opencode.js.map +1 -1
- package/dist/engine/prompt.d.ts.map +1 -1
- package/dist/engine/prompt.js +8 -0
- package/dist/engine/prompt.js.map +1 -1
- package/dist/engine/registry.d.ts.map +1 -1
- package/dist/engine/registry.js +2 -0
- package/dist/engine/registry.js.map +1 -1
- package/dist/engine/types.d.ts +1 -1
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/engine/types.js +1 -0
- package/dist/engine/types.js.map +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/marcopeg/hal/main/images/hal.jpg" alt="HAL 9000" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">HAL</h1>
|
|
2
6
|
|
|
3
7
|
A Telegram bot that provides access to AI coding agents as a personal assistant. Run multiple engines (Claude Code, GitHub Copilot, and more) across multiple projects simultaneously, each with its own dedicated Telegram bot.
|
|
4
8
|
|
|
@@ -132,10 +136,10 @@ Every field is optional. Project entries are matched to base projects by `name`
|
|
|
132
136
|
|
|
133
137
|
Any string value in `hal.config.json` or `hal.config.local.json` (except inside `context` blocks — see [Context Injection](#context-injection)) can reference an environment variable with `${VAR_NAME}` syntax. Variables are resolved at boot time from the following sources in priority order (first match wins):
|
|
134
138
|
|
|
135
|
-
1.
|
|
136
|
-
2.
|
|
137
|
-
3.
|
|
138
|
-
4.
|
|
139
|
+
1. `{config-dir}/.env.local` _(gitignored)_
|
|
140
|
+
2. `{config-dir}/.env`
|
|
141
|
+
3. `{project-cwd}/.env.local` _(gitignored)_
|
|
142
|
+
4. `{project-cwd}/.env`
|
|
139
143
|
5. Shell environment (`process.env`)
|
|
140
144
|
|
|
141
145
|
```bash
|
|
@@ -178,6 +182,10 @@ These keys are injected for every message, even without any `context` configurat
|
|
|
178
182
|
| `sys.time` | Current time, `HH:MM:SS` |
|
|
179
183
|
| `sys.ts` | Current Unix timestamp (seconds) |
|
|
180
184
|
| `sys.tz` | Timezone name (e.g. `Europe/Berlin`) |
|
|
185
|
+
| `engine.name` | Engine identifier (e.g. `claude`, `copilot`) |
|
|
186
|
+
| `engine.command` | CLI command used to invoke the engine |
|
|
187
|
+
| `engine.model` | AI model from config (only present when explicitly set) |
|
|
188
|
+
| `engine.defaultModel` | HAL default model applied (only present when `engine.model` is omitted; see [Model defaults](#model-defaults)) |
|
|
181
189
|
|
|
182
190
|
#### Custom context via config
|
|
183
191
|
|
|
@@ -237,8 +245,8 @@ export default async (context) => ({
|
|
|
237
245
|
})
|
|
238
246
|
```
|
|
239
247
|
|
|
240
|
-
- **Input**: fully-resolved `Record
|
|
241
|
-
- **Output**: a `Record
|
|
248
|
+
- **Input**: fully-resolved `Record\<string, string\>` context
|
|
249
|
+
- **Output**: a `Record\<string, string\>` — the final context passed to the engine
|
|
242
250
|
- If a hook throws, the bot logs the error and falls back to the pre-hook context
|
|
243
251
|
|
|
244
252
|
#### Prompt format
|
|
@@ -263,9 +271,10 @@ Default settings applied to all projects. Any setting defined in a project overr
|
|
|
263
271
|
|-----|-------------|---------|
|
|
264
272
|
| `globals.engine.name` | Engine: `claude`, `copilot`, `codex`, `opencode` | `"claude"` |
|
|
265
273
|
| `globals.engine.command` | Override the CLI command path | _(engine name)_ |
|
|
266
|
-
| `globals.engine.model` | Override the AI model | _(engine
|
|
274
|
+
| `globals.engine.model` | Override the AI model (see [Model defaults](#model-defaults)) | _(per engine)_ |
|
|
267
275
|
| `globals.engine.session` | Use persistent sessions (`--resume` / `--continue`) | `true` |
|
|
268
276
|
| `globals.engine.sessionMsg` | Message sent when renewing session (e.g. `/clean`) | `"hi!"` |
|
|
277
|
+
| `globals.engine.codex.*` | Codex permission flags (see [Engine Configuration](#engine-configuration)) | all `false` |
|
|
269
278
|
| `globals.logging.level` | Log level: `debug`, `info`, `warn`, `error` | `"info"` |
|
|
270
279
|
| `globals.logging.flow` | Write logs to terminal | `true` |
|
|
271
280
|
| `globals.logging.persist` | Write logs to file | `false` |
|
|
@@ -290,9 +299,10 @@ Each project entry creates one Telegram bot connected to one directory.
|
|
|
290
299
|
| `access.allowedUserIds` | No | Override the global user whitelist for this bot |
|
|
291
300
|
| `engine.name` | No | Override the engine for this project |
|
|
292
301
|
| `engine.command` | No | Override the CLI command path |
|
|
293
|
-
| `engine.model` | No | Override the AI model |
|
|
302
|
+
| `engine.model` | No | Override the AI model (see [Model defaults](#model-defaults)) |
|
|
294
303
|
| `engine.session` | No | Use persistent sessions for this project |
|
|
295
304
|
| `engine.sessionMsg` | No | Message used when renewing session |
|
|
305
|
+
| `engine.codex.*` | No | Codex permission flags (see [Engine Configuration](#engine-configuration)) |
|
|
296
306
|
| `transcription.showTranscription` | No | Override transcription display |
|
|
297
307
|
| `dataDir` | No | Override user data directory (see below) |
|
|
298
308
|
| `context` | No | Per-project context overrides (see [Context Injection](#context-injection)) |
|
|
@@ -395,16 +405,16 @@ The slug is used as a folder name for log and data paths. It is derived from:
|
|
|
395
405
|
|
|
396
406
|
| Value | Resolved Path |
|
|
397
407
|
|-------|---------------|
|
|
398
|
-
| _(empty)_ |
|
|
399
|
-
| `~` |
|
|
400
|
-
| Relative path (e.g. `.mydata`) |
|
|
408
|
+
| _(empty)_ | `{project-cwd}/.hal/users` |
|
|
409
|
+
| `~` | `{config-dir}/.hal/{slug}/data` |
|
|
410
|
+
| Relative path (e.g. `.mydata`) | `{project-cwd}/{value}` |
|
|
401
411
|
| Absolute path | Used as-is |
|
|
402
412
|
|
|
403
413
|
### Log Files
|
|
404
414
|
|
|
405
415
|
When `logging.persist: true`, logs are written to:
|
|
406
416
|
```
|
|
407
|
-
|
|
417
|
+
{config-dir}/.hal/logs/{project-slug}/YYYY-MM-DD.txt
|
|
408
418
|
```
|
|
409
419
|
|
|
410
420
|
### Engine Configuration
|
|
@@ -443,22 +453,30 @@ In this example:
|
|
|
443
453
|
- **frontend** uses GitHub Copilot with the `gpt-5-mini` model
|
|
444
454
|
- **legacy** is inactive and will be skipped at boot
|
|
445
455
|
|
|
446
|
-
The `engine` object supports five fields
|
|
456
|
+
The `engine` object supports five fields. Engine-specific sub-objects (e.g. `codex`) can be used to control permissions and behavior per engine.
|
|
447
457
|
|
|
448
458
|
| Field | Description | Default |
|
|
449
459
|
|-------|-------------|---------|
|
|
450
460
|
| `name` | Engine identifier: `claude`, `copilot`, `codex`, `opencode` | `"claude"` |
|
|
451
461
|
| `command` | Custom path to the CLI binary | _(engine name)_ |
|
|
452
|
-
| `model` | AI model override (omit
|
|
462
|
+
| `model` | AI model override (omit for engine or HAL default; see [Model defaults](#model-defaults)) | _(per engine)_ |
|
|
453
463
|
| `session` | Use persistent sessions (`--resume` / `--continue`) | `true` |
|
|
454
464
|
| `sessionMsg` | Message sent when renewing session (e.g. `/clean`) | `"hi!"` |
|
|
455
465
|
|
|
466
|
+
When using the Codex engine, the `engine` object also accepts a `codex` block:
|
|
467
|
+
|
|
468
|
+
| Field | Description | Default |
|
|
469
|
+
|-------|-------------|---------|
|
|
470
|
+
| `codex.networkAccess` | Allow outbound network in shell commands | `false` |
|
|
471
|
+
| `codex.fullDiskAccess` | Unrestricted filesystem access (implies network) | `false` |
|
|
472
|
+
| `codex.dangerouslyEnableYolo` | Disable all sandboxing and approvals | `false` |
|
|
473
|
+
|
|
456
474
|
#### Claude Code
|
|
457
475
|
|
|
458
476
|
- **CLI:** `claude` — install and authenticate via [Claude Code CLI](https://github.com/anthropics/claude-code) (see [Prerequisites](#prerequisites)).
|
|
459
477
|
- **Project files:** `CLAUDE.md`, `.claude/settings.json` (see [How It Works](#how-it-works)).
|
|
460
478
|
- **Config:** `engine.name: "claude"`. Optional: `engine.command`, `engine.model` (passed as `--model`), `engine.session`, `engine.sessionMsg`.
|
|
461
|
-
- **Sessions:** When `engine.session` is `true`, the CLI is invoked with `--resume
|
|
479
|
+
- **Sessions:** When `engine.session` is `true`, the CLI is invoked with `--resume {sessionId}`. `/clean` clears the stored session and replies with a static message (no engine call).
|
|
462
480
|
|
|
463
481
|
#### GitHub Copilot
|
|
464
482
|
|
|
@@ -471,8 +489,9 @@ The `engine` object supports five fields:
|
|
|
471
489
|
|
|
472
490
|
- **CLI:** `codex` — install and authenticate via [Codex CLI](https://github.com/openai/codex-cli) (see [Prerequisites](#prerequisites)).
|
|
473
491
|
- **Project file:** `AGENTS.md`.
|
|
474
|
-
- **Config:** `engine.name: "codex"`. Optional: `engine.command`, `engine.model` (e.g. `gpt-5.1-codex-mini`), `engine.session`, `engine.sessionMsg
|
|
492
|
+
- **Config:** `engine.name: "codex"`. Optional: `engine.command`, `engine.model` (e.g. `gpt-5.1-codex-mini`), `engine.session`, `engine.sessionMsg`, and the permission flags under `engine.codex` (see table above).
|
|
475
493
|
- **Sessions:** When `engine.session` is `true`, the CLI is invoked with `codex exec resume --last` to continue the most recent session; otherwise `codex exec` starts a fresh run. `/clean` sends `engine.sessionMsg` without resuming, so the engine starts a new session; the engine's reply is sent to the user (same behaviour as Copilot).
|
|
494
|
+
- **Permission flags:** HAL always passes `--skip-git-repo-check` so Codex runs without the trusted-directory check. You can escalate via `engine.codex`: `networkAccess` (allow outbound HTTP etc.), `fullDiskAccess` (unrestricted filesystem; implies network), or `dangerouslyEnableYolo` (disable all sandboxing and approvals). Higher tiers supersede lower ones. **Warning:** Use `dangerouslyEnableYolo` only in hardened environments (e.g. Docker, VMs). The example config in `examples/hal.config.json` enables all three codex flags for the Obsidian project so you can try full permissions locally.
|
|
476
495
|
|
|
477
496
|
#### GitHub Copilot Models
|
|
478
497
|
|
|
@@ -498,7 +517,16 @@ When using the `copilot` engine, the following models are available via `engine.
|
|
|
498
517
|
| `gpt-5-mini` | OpenAI GPT-5 Mini |
|
|
499
518
|
| `gpt-4.1` | OpenAI GPT-4.1 |
|
|
500
519
|
|
|
501
|
-
|
|
520
|
+
#### Model defaults
|
|
521
|
+
|
|
522
|
+
When `engine.model` is omitted (neither in globals nor project config), behavior depends on the engine:
|
|
523
|
+
|
|
524
|
+
- **Engine default** — Codex, Copilot, and Cursor: HAL does not pass a model flag, so the CLI picks its own default (Cursor passes `--model auto`).
|
|
525
|
+
- **HAL default** — Claude Code and OpenCode: HAL passes a built-in default so the engine always receives a model. Defaults are defined in `src/default-models.ts`:
|
|
526
|
+
- Claude Code: `default` (account-recommended model)
|
|
527
|
+
- OpenCode: `opencode/gpt-5-nano` (free Zen model)
|
|
528
|
+
|
|
529
|
+
To change HAL defaults, edit `src/default-models.ts`.
|
|
502
530
|
|
|
503
531
|
## Directory Structure
|
|
504
532
|
|
|
@@ -575,8 +603,8 @@ You can add your own slash commands as `.mjs` files. When a user sends `/mycomma
|
|
|
575
603
|
|
|
576
604
|
| Location | Scope |
|
|
577
605
|
|----------|-------|
|
|
578
|
-
| `{project.cwd}/.hal/commands
|
|
579
|
-
| `{configDir}/.hal/commands
|
|
606
|
+
| `{project.cwd}/.hal/commands/{name}.mjs` | Project-specific |
|
|
607
|
+
| `{configDir}/.hal/commands/{name}.mjs` | Global — available to all projects |
|
|
580
608
|
|
|
581
609
|
Project-specific commands take precedence over global ones on name collision.
|
|
582
610
|
|
|
@@ -605,7 +633,7 @@ Tokens following the command name, split on whitespace.
|
|
|
605
633
|
/status → args = []
|
|
606
634
|
```
|
|
607
635
|
|
|
608
|
-
#### `ctx: Record
|
|
636
|
+
#### `ctx: Record\<string, string\>`
|
|
609
637
|
|
|
610
638
|
The fully-resolved context that would be sent to the AI for this message — identical to what the engine sees in its `# Context` header. Includes all implicit keys plus any config vars and hook results:
|
|
611
639
|
|
|
@@ -614,6 +642,7 @@ The fully-resolved context that would be sent to the AI for this message — ide
|
|
|
614
642
|
| `bot.*` | `bot.userId`, `bot.username`, `bot.firstName`, `bot.chatId`, `bot.messageId`, `bot.timestamp`, `bot.datetime`, `bot.messageType` |
|
|
615
643
|
| `sys.*` | `sys.date`, `sys.time`, `sys.datetime`, `sys.ts`, `sys.tz` |
|
|
616
644
|
| `project.*` | `project.name`, `project.cwd`, `project.slug` |
|
|
645
|
+
| `engine.*` | `engine.name`, `engine.command`, `engine.model` (if set), `engine.defaultModel` (if HAL default applied) |
|
|
617
646
|
| custom | Any keys defined in `context` config blocks, after `${}` / `#{}` / `@{}` substitution and context hook transforms |
|
|
618
647
|
|
|
619
648
|
Use `/context` (the built-in global command) to inspect the exact keys available at runtime.
|
|
@@ -658,7 +687,7 @@ interface Agent {
|
|
|
658
687
|
call(
|
|
659
688
|
prompt: string,
|
|
660
689
|
options?: { onProgress?: (message: string) => void }
|
|
661
|
-
): Promise
|
|
690
|
+
): Promise\<string\>;
|
|
662
691
|
}
|
|
663
692
|
```
|
|
664
693
|
|
|
@@ -668,7 +697,7 @@ Unlike regular user messages, agent calls have no session history and no context
|
|
|
668
697
|
|--------|------|-------------|
|
|
669
698
|
| `onProgress` | `(message: string) => void` | Called during execution with activity updates (e.g. `"Reading: /path/to/file"`). Use it to keep the user informed while the agent is working. |
|
|
670
699
|
|
|
671
|
-
Returns the agent's final text output as a string. Throws on failure — the bot's command error handler will catch it and reply with `Command failed:
|
|
700
|
+
Returns the agent's final text output as a string. Throws on failure — the bot's command error handler will catch it and reply with `Command failed: {message}`.
|
|
672
701
|
|
|
673
702
|
```js
|
|
674
703
|
export default async function({ args, gram, agent }) {
|
|
@@ -700,7 +729,7 @@ The project-level context object. Useful fields:
|
|
|
700
729
|
| `projectCtx.config.cwd` | `string` | Absolute path to the project directory |
|
|
701
730
|
| `projectCtx.config.configDir` | `string` | Absolute path to the directory containing `hal.config.json` |
|
|
702
731
|
| `projectCtx.config.dataDir` | `string` | Absolute path to user data storage root |
|
|
703
|
-
| `projectCtx.config.context` | `Record
|
|
732
|
+
| `projectCtx.config.context` | `Record\<string, string\> \| undefined` | Raw config-level context values (pre-hook) |
|
|
704
733
|
| `projectCtx.logger` | Pino logger | Structured logger — use for debug output that ends up in log files |
|
|
705
734
|
|
|
706
735
|
### Examples
|
|
@@ -714,7 +743,7 @@ The project-level context object. Useful fields:
|
|
|
714
743
|
[Claude Code skills](https://docs.anthropic.com/en/docs/claude-code/skills) live in `.claude/skills/` inside the project directory (shared across all engines). Each skill is a folder containing a `SKILL.md` file with a YAML frontmatter block and a prompt body:
|
|
715
744
|
|
|
716
745
|
```
|
|
717
|
-
|
|
746
|
+
{project-cwd}/
|
|
718
747
|
└── .claude/
|
|
719
748
|
└── skills/
|
|
720
749
|
└── chuck/
|
|
@@ -734,16 +763,16 @@ At boot time (and whenever `SKILL.md` files change) the bot reads every skill fo
|
|
|
734
763
|
|
|
735
764
|
When a user invokes a skill command (e.g. `/chuck`) the bot:
|
|
736
765
|
1. Reads the `SKILL.md` prompt body
|
|
737
|
-
2. Appends any user arguments as `User input:
|
|
766
|
+
2. Appends any user arguments as `User input: {args}` if present
|
|
738
767
|
3. Calls the AI engine with that prompt via the engine-agnostic `agent.call()` interface
|
|
739
768
|
4. Sends the response back to the user
|
|
740
769
|
|
|
741
|
-
Skills can be **overridden per-project**: create a `.hal/commands
|
|
770
|
+
Skills can be **overridden per-project**: create a `.hal/commands/{name}.mjs` file with the same name as the skill and the `.mjs` handler takes full precedence.
|
|
742
771
|
|
|
743
772
|
**Command precedence** (highest wins):
|
|
744
773
|
|
|
745
774
|
```
|
|
746
|
-
project .hal/commands
|
|
775
|
+
project .hal/commands/{name}.mjs > global .hal/commands/{name}.mjs > .claude/skills/{name}/
|
|
747
776
|
```
|
|
748
777
|
|
|
749
778
|
See [`examples/obsidian/.claude/skills/chuck/`](examples/obsidian/.claude/skills/chuck/SKILL.md) and [`examples/obsidian/.claude/skills/weather/`](examples/obsidian/.claude/skills/weather/SKILL.md) for example skills.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Context, NextFunction } from "grammy";
|
|
2
|
+
import type { ProjectContext } from "../../../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Callback query handler for `/git_clean` inline keyboard interactions.
|
|
5
|
+
* Recognizes data prefixed with `gc:` and ignores all other callbacks.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createGitCallbackHandler(ctx: ProjectContext): (gramCtx: Context, next: NextFunction) => Promise<void>;
|
|
8
|
+
//# sourceMappingURL=callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/callback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,cAAc,IAC5C,SAAS,OAAO,EAAE,MAAM,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CA8EnE"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { gitExec } from "./exec.js";
|
|
3
|
+
/**
|
|
4
|
+
* Callback query handler for `/git_clean` inline keyboard interactions.
|
|
5
|
+
* Recognizes data prefixed with `gc:` and ignores all other callbacks.
|
|
6
|
+
*/
|
|
7
|
+
export function createGitCallbackHandler(ctx) {
|
|
8
|
+
return async (gramCtx, next) => {
|
|
9
|
+
const data = gramCtx.callbackQuery?.data;
|
|
10
|
+
if (!data?.startsWith("gc:")) {
|
|
11
|
+
return next();
|
|
12
|
+
}
|
|
13
|
+
const { config, logger } = ctx;
|
|
14
|
+
const cwd = config.cwd;
|
|
15
|
+
try {
|
|
16
|
+
if (data.startsWith("gc:select:")) {
|
|
17
|
+
const file = data.slice("gc:select:".length);
|
|
18
|
+
const keyboard = new InlineKeyboard()
|
|
19
|
+
.text("Confirm", `gc:confirm:${file}`)
|
|
20
|
+
.text("Cancel", "gc:cancel");
|
|
21
|
+
await gramCtx.editMessageText(`Revert \`${file}\` and lose the changes?`, { parse_mode: "Markdown", reply_markup: keyboard });
|
|
22
|
+
await gramCtx.answerCallbackQuery();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (data === "gc:all") {
|
|
26
|
+
const keyboard = new InlineKeyboard()
|
|
27
|
+
.text("Confirm", "gc:confirm:__all__")
|
|
28
|
+
.text("Cancel", "gc:cancel");
|
|
29
|
+
await gramCtx.editMessageText("Revert *all* uncommitted changes and lose them?", { parse_mode: "Markdown", reply_markup: keyboard });
|
|
30
|
+
await gramCtx.answerCallbackQuery();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (data.startsWith("gc:confirm:")) {
|
|
34
|
+
const target = data.slice("gc:confirm:".length);
|
|
35
|
+
if (target === "__all__") {
|
|
36
|
+
await gitExec(cwd, ["restore", "."]);
|
|
37
|
+
// Also clean untracked files
|
|
38
|
+
await gitExec(cwd, ["clean", "-fd"]).catch(() => { });
|
|
39
|
+
await gramCtx.editMessageText("All uncommitted changes reverted.", {
|
|
40
|
+
reply_markup: undefined,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await gitExec(cwd, ["restore", target]);
|
|
45
|
+
await gramCtx.editMessageText(`Restored \`${target}\`.`, {
|
|
46
|
+
parse_mode: "Markdown",
|
|
47
|
+
reply_markup: undefined,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
await gramCtx.answerCallbackQuery();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (data === "gc:cancel") {
|
|
54
|
+
await gramCtx.editMessageText("Cancelled.", {
|
|
55
|
+
reply_markup: undefined,
|
|
56
|
+
});
|
|
57
|
+
await gramCtx.answerCallbackQuery();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error({ data, error: err instanceof Error ? err.message : String(err) }, "git_clean callback failed");
|
|
63
|
+
try {
|
|
64
|
+
await gramCtx.answerCallbackQuery({ text: "Operation failed." });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/callback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/clean.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAYxD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,IACzC,SAAS,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAkD/C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { gitExec } from "./exec.js";
|
|
3
|
+
/** Parse `git status --short` into a list of file paths. */
|
|
4
|
+
function parseChangedFiles(statusOutput) {
|
|
5
|
+
return statusOutput
|
|
6
|
+
.split("\n")
|
|
7
|
+
.map((line) => line.trim())
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.map((line) => line.slice(3)); // strip status flags + space (e.g. " M src/file.ts")
|
|
10
|
+
}
|
|
11
|
+
export function createGitCleanHandler(ctx) {
|
|
12
|
+
return async (gramCtx) => {
|
|
13
|
+
const { config, logger } = ctx;
|
|
14
|
+
const cwd = config.cwd;
|
|
15
|
+
const messageText = gramCtx.message?.text ?? "";
|
|
16
|
+
const fileArg = messageText.replace(/^\/git_clean\s*/, "").trim();
|
|
17
|
+
try {
|
|
18
|
+
const { stdout } = await gitExec(cwd, ["status", "--short"]);
|
|
19
|
+
const files = parseChangedFiles(stdout);
|
|
20
|
+
if (files.length === 0) {
|
|
21
|
+
await gramCtx.reply("Working tree is clean.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (fileArg) {
|
|
25
|
+
if (!files.includes(fileArg)) {
|
|
26
|
+
await gramCtx.reply(`File \`${fileArg}\` has no uncommitted changes.`, { parse_mode: "Markdown" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await gitExec(cwd, ["restore", fileArg]);
|
|
30
|
+
await gramCtx.reply(`Restored \`${fileArg}\`.`, {
|
|
31
|
+
parse_mode: "Markdown",
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const keyboard = new InlineKeyboard();
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
keyboard.text(file, `gc:select:${file}`).row();
|
|
38
|
+
}
|
|
39
|
+
keyboard.text("Reset all", "gc:all").row();
|
|
40
|
+
await gramCtx.reply("Select file(s) to revert:", {
|
|
41
|
+
reply_markup: keyboard,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.error({ error: err instanceof Error ? err.message : String(err) }, "/git_clean failed");
|
|
46
|
+
await gramCtx.reply(`Failed to check changes: ${err instanceof Error ? err.message : String(err)}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/clean.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,4DAA4D;AAC5D,SAAS,iBAAiB,CAAC,YAAoB;IAC7C,OAAO,YAAY;SAChB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qDAAqD;AACxF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAmB;IACvD,OAAO,KAAK,EAAE,OAAgB,EAAiB,EAAE;QAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QACvB,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAElE,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;YAExC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;gBAC9C,OAAO;YACT,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC7B,MAAM,OAAO,CAAC,KAAK,CACjB,UAAU,OAAO,gCAAgC,EACjD,EAAE,UAAU,EAAE,UAAU,EAAE,CAC3B,CAAC;oBACF,OAAO;gBACT,CAAC;gBAED,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;gBACzC,MAAM,OAAO,CAAC,KAAK,CAAC,cAAc,OAAO,KAAK,EAAE;oBAC9C,UAAU,EAAE,UAAU;iBACvB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;YACtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;YACjD,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;YAE3C,MAAM,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE;gBAC/C,YAAY,EAAE,QAAQ;aACvB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAC3D,mBAAmB,CACpB,CAAC;YACF,MAAM,OAAO,CAAC,KAAK,CACjB,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/commit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOxD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,IAC1C,SAAS,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAyF/C"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createAgent } from "../../../agent/index.js";
|
|
2
|
+
import { gitExec } from "./exec.js";
|
|
3
|
+
const COMMIT_MSG_PROMPT = `Generate a concise git commit message (one line, max 72 chars) for the following changes. Return ONLY the commit message, no quotes or explanation.
|
|
4
|
+
|
|
5
|
+
`;
|
|
6
|
+
export function createGitCommitHandler(ctx) {
|
|
7
|
+
return async (gramCtx) => {
|
|
8
|
+
const { config, logger } = ctx;
|
|
9
|
+
const cwd = config.cwd;
|
|
10
|
+
const messageText = gramCtx.message?.text ?? "";
|
|
11
|
+
const userMessage = messageText.replace(/^\/git_commit\s*/, "").trim();
|
|
12
|
+
try {
|
|
13
|
+
await gitExec(cwd, ["add", "."]);
|
|
14
|
+
const { stdout: statusOut } = await gitExec(cwd, [
|
|
15
|
+
"status",
|
|
16
|
+
"--porcelain",
|
|
17
|
+
]);
|
|
18
|
+
if (!statusOut.trim()) {
|
|
19
|
+
await gramCtx.reply("Nothing to commit — working tree is clean.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let commitMessage;
|
|
23
|
+
if (userMessage) {
|
|
24
|
+
commitMessage = userMessage;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const statusMsg = await gramCtx.reply("_Generating commit message..._", { parse_mode: "Markdown" });
|
|
28
|
+
try {
|
|
29
|
+
const { stdout: diffOut } = await gitExec(cwd, [
|
|
30
|
+
"diff",
|
|
31
|
+
"--cached",
|
|
32
|
+
"--stat",
|
|
33
|
+
]);
|
|
34
|
+
const agent = createAgent(ctx);
|
|
35
|
+
const generated = await agent.call(`${COMMIT_MSG_PROMPT}${diffOut || statusOut}`);
|
|
36
|
+
commitMessage = generated.trim().replace(/^["']|["']$/g, "");
|
|
37
|
+
try {
|
|
38
|
+
await gramCtx.api.deleteMessage(gramCtx.chat.id, statusMsg.message_id);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// ignore delete errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
try {
|
|
46
|
+
await gramCtx.api.deleteMessage(gramCtx.chat.id, statusMsg.message_id);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// ignore delete errors
|
|
50
|
+
}
|
|
51
|
+
logger.error({ error: err instanceof Error ? err.message : String(err) }, "AI commit message generation failed");
|
|
52
|
+
await gramCtx.reply("Failed to generate commit message. Please provide one:\n`/git_commit your message here`", { parse_mode: "Markdown" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const { stdout: commitOut } = await gitExec(cwd, [
|
|
57
|
+
"commit",
|
|
58
|
+
"-m",
|
|
59
|
+
commitMessage,
|
|
60
|
+
]);
|
|
61
|
+
const summary = commitOut.trim().split("\n")[0] ?? "Committed.";
|
|
62
|
+
await gramCtx.reply(`\`\`\`\n${summary}\n\`\`\``, {
|
|
63
|
+
parse_mode: "Markdown",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
logger.error({ error: err instanceof Error ? err.message : String(err) }, "/git_commit failed");
|
|
68
|
+
await gramCtx.reply(`Commit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/commit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/exec.ts"],"names":[],"mappings":"AAKA,wBAAsB,OAAO,CAC3B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7C;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export async function gitExec(cwd, args) {
|
|
5
|
+
return execFileAsync("git", args, { cwd, timeout: 30_000 });
|
|
6
|
+
}
|
|
7
|
+
export async function isGitRepo(cwd) {
|
|
8
|
+
try {
|
|
9
|
+
await gitExec(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAW,EACX,IAAc;IAEd,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createGitCallbackHandler } from "./callback.js";
|
|
2
|
+
export { createGitCleanHandler } from "./clean.js";
|
|
3
|
+
export { createGitCommitHandler } from "./commit.js";
|
|
4
|
+
export { createGitInitHandler } from "./init.js";
|
|
5
|
+
export { createGitStatusHandler } from "./status.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createGitCallbackHandler } from "./callback.js";
|
|
2
|
+
export { createGitCleanHandler } from "./clean.js";
|
|
3
|
+
export { createGitCommitHandler } from "./commit.js";
|
|
4
|
+
export { createGitInitHandler } from "./init.js";
|
|
5
|
+
export { createGitStatusHandler } from "./status.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,IACxC,SAAS,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAyB/C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { gitExec, isGitRepo } from "./exec.js";
|
|
2
|
+
export function createGitInitHandler(ctx) {
|
|
3
|
+
return async (gramCtx) => {
|
|
4
|
+
const { config, logger } = ctx;
|
|
5
|
+
const cwd = config.cwd;
|
|
6
|
+
try {
|
|
7
|
+
if (await isGitRepo(cwd)) {
|
|
8
|
+
await gramCtx.reply("Already a git repository.");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
await gitExec(cwd, ["init"]);
|
|
12
|
+
await gitExec(cwd, ["add", "."]);
|
|
13
|
+
await gitExec(cwd, ["commit", "-m", "Initial commit"]);
|
|
14
|
+
await gramCtx.reply("Git repository initialized with initial commit.");
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
logger.error({ error: err instanceof Error ? err.message : String(err) }, "/git_init failed");
|
|
18
|
+
await gramCtx.reply(`Failed to initialize git repository: ${err instanceof Error ? err.message : String(err)}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/init.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE/C,MAAM,UAAU,oBAAoB,CAAC,GAAmB;IACtD,OAAO,KAAK,EAAE,OAAgB,EAAiB,EAAE;QAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QAEvB,IAAI,CAAC;YACH,IAAI,MAAM,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;gBACjD,OAAO;YACT,CAAC;YAED,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7B,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;YACjC,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAEvD,MAAM,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAC3D,kBAAkB,CACnB,CAAC;YACF,MAAM,OAAO,CAAC,KAAK,CACjB,wCAAwC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC3F,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../../src/bot/commands/git/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,IAC1C,SAAS,OAAO,KAAG,OAAO,CAAC,IAAI,CAAC,CAmB/C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { gitExec } from "./exec.js";
|
|
2
|
+
export function createGitStatusHandler(ctx) {
|
|
3
|
+
return async (gramCtx) => {
|
|
4
|
+
const { config, logger } = ctx;
|
|
5
|
+
try {
|
|
6
|
+
const { stdout } = await gitExec(config.cwd, ["status"]);
|
|
7
|
+
const output = stdout.trim() || "Nothing to report.";
|
|
8
|
+
await gramCtx.reply(`\`\`\`\n${output}\n\`\`\``, {
|
|
9
|
+
parse_mode: "Markdown",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
logger.error({ error: err instanceof Error ? err.message : String(err) }, "/git_status failed");
|
|
14
|
+
await gramCtx.reply(`Failed to get git status: ${err instanceof Error ? err.message : String(err)}`);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../src/bot/commands/git/status.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,sBAAsB,CAAC,GAAmB;IACxD,OAAO,KAAK,EAAE,OAAgB,EAAiB,EAAE;QAC/C,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;QAE/B,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,oBAAoB,CAAC;YACrD,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,MAAM,UAAU,EAAE;gBAC/C,UAAU,EAAE,UAAU;aACvB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAC3D,oBAAoB,CACrB,CAAC;YACF,MAAM,OAAO,CAAC,KAAK,CACjB,6BAA6B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAChF,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -4,7 +4,7 @@ const DEFAULT_TEMPLATE = "${HAL_COMMANDS}";
|
|
|
4
4
|
export function createHelpHandler(ctx) {
|
|
5
5
|
return async (gramCtx) => {
|
|
6
6
|
const helpCfg = ctx.config.commands.help;
|
|
7
|
-
const template = helpCfg
|
|
7
|
+
const template = helpCfg.message ?? DEFAULT_TEMPLATE;
|
|
8
8
|
const message = await resolveCommandMessage(template, ctx, gramCtx);
|
|
9
9
|
await gramCtx.reply(message, { parse_mode: "Markdown" });
|
|
10
10
|
};
|