@iinm/plain-agent 1.7.18 → 1.7.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 -83
- package/config/config.predefined.json +15 -15
- package/package.json +1 -3
- package/src/agentLoop.mjs +3 -1
- package/src/cliArgs.mjs +31 -1
- package/src/cliBatch.mjs +22 -0
- package/src/cliCost.mjs +309 -0
- package/src/cliFormatter.mjs +1 -1
- package/src/cliInteractive.mjs +29 -1
- package/src/config.d.ts +2 -2
- package/src/config.mjs +1 -1
- package/src/costTracker.mjs +58 -19
- package/src/env.mjs +9 -6
- package/src/main.mjs +17 -6
- package/src/model.d.ts +1 -1
- package/src/tools/patchFile.mjs +11 -12
- package/src/usageStore.mjs +167 -0
- package/src/utils/notify.mjs +3 -2
- package/src/voiceInput.mjs +24 -634
- package/src/voiceInputGemini.mjs +105 -0
- package/src/voiceInputOpenAI.mjs +104 -0
- package/src/voiceInputSession.mjs +543 -0
- package/src/voiceToggleKey.mjs +62 -0
- package/bin/plain-notify-terminal-bell +0 -3
package/README.md
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="https://pub-0bb49aa929f242d49c89ed8c297932b5.r2.dev/plain-agent/plain-agent-logo.png" alt="plain-agent logo" width="320">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
# Plain Agent
|
|
6
2
|
|
|
7
|
-
A lightweight CLI-based coding agent
|
|
3
|
+
A lightweight CLI-based coding agent.
|
|
8
4
|
|
|
9
5
|
## Why Plain Agent?
|
|
10
6
|
|
|
@@ -23,6 +19,7 @@ A lightweight CLI-based coding agent with zero framework dependencies.
|
|
|
23
19
|
|
|
24
20
|
## Limitations
|
|
25
21
|
|
|
22
|
+
- **CLI only** — Plain Agent does not provide a terminal UI.
|
|
26
23
|
- **Sequential subagent execution** — Subagents run one at a time rather than
|
|
27
24
|
in parallel. The trade-off is full visibility: every step is streamed to
|
|
28
25
|
your terminal so you can follow exactly what each subagent is doing.
|
|
@@ -31,7 +28,7 @@ A lightweight CLI-based coding agent with zero framework dependencies.
|
|
|
31
28
|
|
|
32
29
|
- Node.js 22 or later
|
|
33
30
|
- LLM provider credentials
|
|
34
|
-
-
|
|
31
|
+
- Bash / Docker for sandboxed execution
|
|
35
32
|
- [ripgrep](https://github.com/burntsushi/ripgrep)
|
|
36
33
|
- [fd](https://github.com/sharkdp/fd)
|
|
37
34
|
|
|
@@ -52,8 +49,8 @@ Create the configuration.
|
|
|
52
49
|
```js
|
|
53
50
|
// ~/.config/plain-agent/config.local.json
|
|
54
51
|
{
|
|
55
|
-
"model": "
|
|
56
|
-
// "model": "
|
|
52
|
+
"model": "claude-sonnet-4-6+thinking-high",
|
|
53
|
+
// "model": "gpt-5.5+thinking-high",
|
|
57
54
|
|
|
58
55
|
// Configure the providers you want to use
|
|
59
56
|
"platforms": [
|
|
@@ -83,15 +80,11 @@ Create the configuration.
|
|
|
83
80
|
"provider": "gemini",
|
|
84
81
|
"apiKey": "<GEMINI_API_KEY>",
|
|
85
82
|
"model": "gemini-3-flash-preview"
|
|
86
|
-
// Optional
|
|
87
|
-
// "baseURL": "<proxy_url>"
|
|
88
83
|
|
|
89
84
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
90
85
|
// "provider": "gemini-vertex-ai",
|
|
91
86
|
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
92
87
|
// "model": "gemini-3-flash-preview"
|
|
93
|
-
// Optional:
|
|
94
|
-
// "account": "<service_account_email>"
|
|
95
88
|
},
|
|
96
89
|
|
|
97
90
|
// askURL: Answers questions based on provided URL content.
|
|
@@ -100,15 +93,8 @@ Create the configuration.
|
|
|
100
93
|
"provider": "gemini",
|
|
101
94
|
"apiKey": "<GEMINI_API_KEY>"
|
|
102
95
|
"model": "gemini-3-flash-preview"
|
|
103
|
-
// Optional
|
|
104
|
-
// "baseURL": "<proxy_url>"
|
|
105
96
|
|
|
106
97
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
107
|
-
// "provider": "gemini-vertex-ai",
|
|
108
|
-
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
109
|
-
// "model": "gemini-3-flash-preview"
|
|
110
|
-
// Optional:
|
|
111
|
-
// "account": "<service_account_email>"
|
|
112
98
|
}
|
|
113
99
|
},
|
|
114
100
|
|
|
@@ -166,6 +152,7 @@ Create the configuration.
|
|
|
166
152
|
```
|
|
167
153
|
|
|
168
154
|
```js
|
|
155
|
+
// OpenAI-compatible provider (Ollama) example with a custom model
|
|
169
156
|
{
|
|
170
157
|
"platforms": [
|
|
171
158
|
{
|
|
@@ -279,6 +266,9 @@ plain
|
|
|
279
266
|
plain -m <model+variant>
|
|
280
267
|
```
|
|
281
268
|
|
|
269
|
+
Interrupt the agent while it's running:
|
|
270
|
+
Press **Ctrl-C** to pause auto-approve. The agent will finish the current tool call, then return to the prompt.
|
|
271
|
+
|
|
282
272
|
(Optional) Set up a sandbox for your project with the `sandbox-configurator` agent.
|
|
283
273
|
|
|
284
274
|
```
|
|
@@ -292,7 +282,7 @@ After the agent finishes, run the generated setup script once to build the sandb
|
|
|
292
282
|
```
|
|
293
283
|
|
|
294
284
|
Run in batch mode (non-interactive).
|
|
295
|
-
In batch mode, config files are not loaded automatically. Only the files specified with
|
|
285
|
+
In batch mode, config files are not loaded automatically. Only the files specified with `-c` are loaded.
|
|
296
286
|
|
|
297
287
|
```sh
|
|
298
288
|
plain batch \
|
|
@@ -307,9 +297,14 @@ Display the help message.
|
|
|
307
297
|
/help
|
|
308
298
|
```
|
|
309
299
|
|
|
310
|
-
|
|
300
|
+
Show daily token costs across sessions. `plain cost` reads
|
|
301
|
+
`~/.local/share/plain-agent/usage.jsonl`; use `--from` / `--to` to set the
|
|
302
|
+
period. Currencies are shown separately.
|
|
311
303
|
|
|
312
|
-
|
|
304
|
+
```sh
|
|
305
|
+
plain cost
|
|
306
|
+
plain cost --from 2026-04-01 --to 2026-04-30
|
|
307
|
+
```
|
|
313
308
|
|
|
314
309
|
## Available Tools
|
|
315
310
|
|
|
@@ -327,29 +322,24 @@ The agent can use the following tools to assist with tasks:
|
|
|
327
322
|
|
|
328
323
|
## Configuration
|
|
329
324
|
|
|
325
|
+
Files are loaded in the following order. Settings in later files override earlier ones.
|
|
326
|
+
|
|
330
327
|
```
|
|
331
328
|
~/.config/plain-agent/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
329
|
+
├── (1) config.json # User configuration
|
|
330
|
+
├── (2) config.local.json # User local configuration (including secrets)
|
|
331
|
+
├── prompts/ # Global/User-defined prompts
|
|
332
|
+
└── agents/ # Global/User-defined agent roles
|
|
336
333
|
|
|
337
334
|
<project-root>
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
335
|
+
└── .plain-agent/
|
|
336
|
+
├── (3) config.json # Project-specific configuration
|
|
337
|
+
├── (4) config.local.json # Project-specific local configuration (including secrets)
|
|
338
|
+
├── memory/ # Task-specific memory files
|
|
339
|
+
├── prompts/ # Project-specific prompts
|
|
340
|
+
└── agents/ # Project-specific agent roles
|
|
344
341
|
```
|
|
345
342
|
|
|
346
|
-
The agent loads configuration files in the following order. Settings in later files will override those in earlier files.
|
|
347
|
-
|
|
348
|
-
- `~/.config/plain-agent/config.json`
|
|
349
|
-
- `~/.config/plain-agent/config.local.json`
|
|
350
|
-
- `.plain-agent/config.json`
|
|
351
|
-
- `.plain-agent/config.local.json`
|
|
352
|
-
|
|
353
343
|
### Example
|
|
354
344
|
|
|
355
345
|
<details>
|
|
@@ -412,19 +402,19 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
412
402
|
"patterns": [
|
|
413
403
|
{
|
|
414
404
|
"toolName": { "$regex": "^(write_file|patch_file)$" },
|
|
415
|
-
"input": { "filePath": { "$regex": "
|
|
405
|
+
"input": { "filePath": { "$regex": "^\\.plain-agent/memory/.+\\.md$" } },
|
|
416
406
|
"action": "allow"
|
|
417
407
|
},
|
|
418
408
|
{
|
|
419
409
|
"toolName": { "$regex": "^(write_file|patch_file)$" },
|
|
420
|
-
"input": { "filePath": { "$regex": "^
|
|
410
|
+
"input": { "filePath": { "$regex": "^src/" } },
|
|
421
411
|
"action": "allow"
|
|
422
412
|
},
|
|
423
413
|
|
|
424
414
|
// ⚠️ Arbitrary code execution can access unauthorized files and networks. Always use a sandbox.
|
|
425
415
|
{
|
|
426
416
|
"toolName": "exec_command",
|
|
427
|
-
"input": { "command": "npm", "args": ["run", { "$regex": "^(
|
|
417
|
+
"input": { "command": "npm", "args": ["run", { "$regex": "^(lint|test)$" }] },
|
|
428
418
|
"action": "allow"
|
|
429
419
|
},
|
|
430
420
|
|
|
@@ -500,8 +490,8 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
500
490
|
}
|
|
501
491
|
},
|
|
502
492
|
|
|
503
|
-
// Override default notification command
|
|
504
|
-
// "notifyCmd": "
|
|
493
|
+
// Override default notification command (falls back to terminal bell)
|
|
494
|
+
// "notifyCmd": { "command": "plain-notify-desktop", "args": [] }
|
|
505
495
|
|
|
506
496
|
// (Optional) Voice input. See "Voice Input" below.
|
|
507
497
|
// "voiceInput": {
|
|
@@ -516,6 +506,17 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
516
506
|
|
|
517
507
|
You can define reusable prompts in Markdown files.
|
|
518
508
|
|
|
509
|
+
### Locations
|
|
510
|
+
|
|
511
|
+
The agent searches for prompts in the following directories:
|
|
512
|
+
|
|
513
|
+
- `~/.config/plain-agent/prompts/`
|
|
514
|
+
- `.plain-agent/prompts/`
|
|
515
|
+
- `.claude/commands/`
|
|
516
|
+
- `.claude/skills/`
|
|
517
|
+
|
|
518
|
+
The prompt ID is the relative path of the file without the `.md` extension. For example, `.plain-agent/prompts/commit.md` becomes `/prompts:commit`.
|
|
519
|
+
|
|
519
520
|
### Prompt File Format
|
|
520
521
|
|
|
521
522
|
```md
|
|
@@ -537,30 +538,8 @@ import: https://raw.githubusercontent.com/anthropics/claude-code/5cff78741f54a0d
|
|
|
537
538
|
- Parallel execution of subagents is not supported. Delegate to subagents sequentially.
|
|
538
539
|
```
|
|
539
540
|
|
|
540
|
-
```md
|
|
541
|
-
---
|
|
542
|
-
import: https://raw.githubusercontent.com/anthropics/claude-code/db8834ba1d72e9a26fba30ac85f3bc4316bb0689/plugins/code-review/commands/code-review.md
|
|
543
|
-
---
|
|
544
|
-
|
|
545
|
-
- Parallel execution of subagents is not supported. Delegate to subagents sequentially.
|
|
546
|
-
- If CLAUDE.md is not found, refer to AGENTS.md instead for project rules and conventions.
|
|
547
|
-
- If the PR branch is already checked out, review changes from local files instead of fetching from GitHub.
|
|
548
|
-
- After explaining the review results to the user, ask whether to post the comments to GitHub as well.
|
|
549
|
-
```
|
|
550
|
-
|
|
551
541
|
Remote prompts are fetched and cached locally. The local content will be appended to the imported content.
|
|
552
542
|
|
|
553
|
-
### Locations
|
|
554
|
-
|
|
555
|
-
The agent searches for prompts in the following directories:
|
|
556
|
-
|
|
557
|
-
- `~/.config/plain-agent/prompts/`
|
|
558
|
-
- `.plain-agent/prompts/`
|
|
559
|
-
- `.claude/commands/`
|
|
560
|
-
- `.claude/skills/`
|
|
561
|
-
|
|
562
|
-
The prompt ID is the relative path of the file without the `.md` extension. For example, `.plain-agent/prompts/commit.md` becomes `/prompts:commit`.
|
|
563
|
-
|
|
564
543
|
### Shortcuts
|
|
565
544
|
|
|
566
545
|
Prompts located in a `shortcuts/` subdirectory (e.g., `.plain-agent/prompts/shortcuts/commit.md`) can be invoked directly as a top-level command (e.g., `/commit`).
|
|
@@ -569,6 +548,14 @@ Prompts located in a `shortcuts/` subdirectory (e.g., `.plain-agent/prompts/shor
|
|
|
569
548
|
|
|
570
549
|
Subagents are specialized agents designed for specific tasks.
|
|
571
550
|
|
|
551
|
+
### Locations
|
|
552
|
+
|
|
553
|
+
The agent searches for subagent definitions in the following directories:
|
|
554
|
+
|
|
555
|
+
- `~/.config/plain-agent/agents/`
|
|
556
|
+
- `.plain-agent/agents/`
|
|
557
|
+
- `.claude/agents/`
|
|
558
|
+
|
|
572
559
|
### Subagent File Format
|
|
573
560
|
|
|
574
561
|
```md
|
|
@@ -591,14 +578,6 @@ Use AGENTS.md instead of CLAUDE.md in this project.
|
|
|
591
578
|
|
|
592
579
|
Remote subagents are fetched and cached locally. The local content will be appended to the imported content.
|
|
593
580
|
|
|
594
|
-
### Locations
|
|
595
|
-
|
|
596
|
-
The agent searches for subagent definitions in the following directories:
|
|
597
|
-
|
|
598
|
-
- `~/.config/plain-agent/agents/`
|
|
599
|
-
- `.plain-agent/agents/`
|
|
600
|
-
- `.claude/agents/`
|
|
601
|
-
|
|
602
581
|
## Claude Code Plugin Support
|
|
603
582
|
|
|
604
583
|
Example:
|
|
@@ -642,7 +621,7 @@ and send them like regular text.
|
|
|
642
621
|
|
|
643
622
|
### Providers
|
|
644
623
|
|
|
645
|
-
**OpenAI Realtime**
|
|
624
|
+
**OpenAI Realtime**
|
|
646
625
|
|
|
647
626
|
```js
|
|
648
627
|
{
|
|
@@ -655,7 +634,7 @@ and send them like regular text.
|
|
|
655
634
|
}
|
|
656
635
|
```
|
|
657
636
|
|
|
658
|
-
**Gemini Live**
|
|
637
|
+
**Gemini Live**
|
|
659
638
|
|
|
660
639
|
```js
|
|
661
640
|
{
|
|
@@ -672,8 +651,7 @@ and send them like regular text.
|
|
|
672
651
|
|
|
673
652
|
- `toggleKey` — Rebind the toggle. Accepts `"ctrl-<char>"` where `<char>`
|
|
674
653
|
is a letter (a-z) or one of `[ \ ] ^ _`. Defaults to `"ctrl-o"`.
|
|
675
|
-
- `recorder` — Override recorder auto-detection. Must write raw 16-bit
|
|
676
|
-
little-endian mono PCM to stdout at 24 kHz (OpenAI) or 16 kHz (Gemini).
|
|
654
|
+
- `recorder` — Override recorder auto-detection, e.g. `{ "command": "sox", "args": ["-q", "-d", "-b", "16", "-c", "1", "-r", "24000", "-e", "signed-integer", "-t", "raw", "-"] }`. Must write raw 16-bit little-endian mono PCM to stdout at 24 kHz (OpenAI) or 16 kHz (Gemini).
|
|
677
655
|
|
|
678
656
|
## Development
|
|
679
657
|
|
|
@@ -694,13 +672,9 @@ npx npm-check-updates -t minor -c 3 -u
|
|
|
694
672
|
## Release
|
|
695
673
|
|
|
696
674
|
```sh
|
|
697
|
-
npm run check
|
|
698
|
-
|
|
699
|
-
git commit -m "<message>"
|
|
700
|
-
|
|
701
675
|
npm version <major|minor|patch>
|
|
702
|
-
git push --follow-tags
|
|
703
676
|
|
|
677
|
+
git push --follow-tags
|
|
704
678
|
gh release create $(git describe --tags) --generate-notes
|
|
705
679
|
|
|
706
680
|
npm publish --access public
|
|
@@ -813,7 +813,7 @@
|
|
|
813
813
|
}
|
|
814
814
|
},
|
|
815
815
|
{
|
|
816
|
-
"name": "gpt-5.
|
|
816
|
+
"name": "gpt-5.5",
|
|
817
817
|
"variant": "thinking-medium",
|
|
818
818
|
"platform": {
|
|
819
819
|
"name": "openai",
|
|
@@ -823,7 +823,7 @@
|
|
|
823
823
|
"model": {
|
|
824
824
|
"format": "openai-responses",
|
|
825
825
|
"config": {
|
|
826
|
-
"model": "gpt-5.
|
|
826
|
+
"model": "gpt-5.5",
|
|
827
827
|
"reasoning": { "effort": "medium", "summary": "auto" },
|
|
828
828
|
"store": false,
|
|
829
829
|
"include": ["reasoning.encrypted_content"]
|
|
@@ -833,14 +833,14 @@
|
|
|
833
833
|
"currency": "USD",
|
|
834
834
|
"unit": "1M",
|
|
835
835
|
"costs": {
|
|
836
|
-
"input_tokens":
|
|
837
|
-
"input_tokens_details.cached_tokens": -
|
|
838
|
-
"output_tokens":
|
|
836
|
+
"input_tokens": 5,
|
|
837
|
+
"input_tokens_details.cached_tokens": -4.5,
|
|
838
|
+
"output_tokens": 30
|
|
839
839
|
}
|
|
840
840
|
}
|
|
841
841
|
},
|
|
842
842
|
{
|
|
843
|
-
"name": "gpt-5.
|
|
843
|
+
"name": "gpt-5.5",
|
|
844
844
|
"variant": "thinking-high",
|
|
845
845
|
"platform": {
|
|
846
846
|
"name": "openai",
|
|
@@ -850,7 +850,7 @@
|
|
|
850
850
|
"model": {
|
|
851
851
|
"format": "openai-responses",
|
|
852
852
|
"config": {
|
|
853
|
-
"model": "gpt-5.
|
|
853
|
+
"model": "gpt-5.5",
|
|
854
854
|
"reasoning": { "effort": "high", "summary": "auto" },
|
|
855
855
|
"store": false,
|
|
856
856
|
"include": ["reasoning.encrypted_content"]
|
|
@@ -860,14 +860,14 @@
|
|
|
860
860
|
"currency": "USD",
|
|
861
861
|
"unit": "1M",
|
|
862
862
|
"costs": {
|
|
863
|
-
"input_tokens":
|
|
864
|
-
"input_tokens_details.cached_tokens": -
|
|
865
|
-
"output_tokens":
|
|
863
|
+
"input_tokens": 5,
|
|
864
|
+
"input_tokens_details.cached_tokens": -4.5,
|
|
865
|
+
"output_tokens": 30
|
|
866
866
|
}
|
|
867
867
|
}
|
|
868
868
|
},
|
|
869
869
|
{
|
|
870
|
-
"name": "gpt-5.
|
|
870
|
+
"name": "gpt-5.5",
|
|
871
871
|
"variant": "thinking-xhigh",
|
|
872
872
|
"platform": {
|
|
873
873
|
"name": "openai",
|
|
@@ -877,7 +877,7 @@
|
|
|
877
877
|
"model": {
|
|
878
878
|
"format": "openai-responses",
|
|
879
879
|
"config": {
|
|
880
|
-
"model": "gpt-5.
|
|
880
|
+
"model": "gpt-5.5",
|
|
881
881
|
"reasoning": { "effort": "xhigh", "summary": "auto" },
|
|
882
882
|
"store": false,
|
|
883
883
|
"include": ["reasoning.encrypted_content"]
|
|
@@ -887,9 +887,9 @@
|
|
|
887
887
|
"currency": "USD",
|
|
888
888
|
"unit": "1M",
|
|
889
889
|
"costs": {
|
|
890
|
-
"input_tokens":
|
|
891
|
-
"input_tokens_details.cached_tokens": -
|
|
892
|
-
"output_tokens":
|
|
890
|
+
"input_tokens": 5,
|
|
891
|
+
"input_tokens_details.cached_tokens": -4.5,
|
|
892
|
+
"output_tokens": 30
|
|
893
893
|
}
|
|
894
894
|
}
|
|
895
895
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iinm/plain-agent",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.20",
|
|
4
4
|
"description": "A lightweight CLI-based coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,9 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
12
|
"plain": "./bin/plain",
|
|
13
|
-
"plain-interrupt": "./bin/plain-interrupt",
|
|
14
13
|
"plain-notify-desktop": "./bin/plain-notify-desktop",
|
|
15
|
-
"plain-notify-terminal-bell": "./bin/plain-notify-terminal-bell",
|
|
16
14
|
"plain-sandbox": "./sandbox/bin/plain-sandbox"
|
|
17
15
|
},
|
|
18
16
|
"files": [
|
package/src/agentLoop.mjs
CHANGED
|
@@ -128,7 +128,9 @@ export function createAgentLoop({
|
|
|
128
128
|
|
|
129
129
|
const { message: assistantMessage, providerTokenUsage } = modelOutput;
|
|
130
130
|
stateManager.appendMessages([assistantMessage]);
|
|
131
|
-
|
|
131
|
+
if (providerTokenUsage) {
|
|
132
|
+
agentEventEmitter.emit("providerTokenUsage", providerTokenUsage);
|
|
133
|
+
}
|
|
132
134
|
|
|
133
135
|
// Gemini may stop with "thinking" -> continue
|
|
134
136
|
const lastContent = assistantMessage.content.at(-1);
|
package/src/cliArgs.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {HelpSubcommand | InteractiveSubcommand | BatchSubcommand | ListModelsSubcommand | InstallClaudeCodePluginsSubcommand} Subcommand
|
|
2
|
+
* @typedef {HelpSubcommand | InteractiveSubcommand | BatchSubcommand | ListModelsSubcommand | InstallClaudeCodePluginsSubcommand | CostSubcommand} Subcommand
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
* @typedef {{ type: 'install-claude-code-plugins' }} InstallClaudeCodePluginsSubcommand
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{ type: 'cost', from: string | null, to: string | null }} CostSubcommand
|
|
27
|
+
*/
|
|
28
|
+
|
|
25
29
|
/**
|
|
26
30
|
* @typedef {Object} CliArgs
|
|
27
31
|
* @property {Subcommand} subcommand - The subcommand to execute
|
|
@@ -106,6 +110,28 @@ export function parseCliArgs(argv) {
|
|
|
106
110
|
};
|
|
107
111
|
}
|
|
108
112
|
|
|
113
|
+
if (subcommandName === "cost") {
|
|
114
|
+
const costArgs = args.slice(1);
|
|
115
|
+
let from = null;
|
|
116
|
+
let to = null;
|
|
117
|
+
for (let i = 0; i < costArgs.length; i++) {
|
|
118
|
+
if (costArgs[i] === "--from") {
|
|
119
|
+
if (costArgs[i + 1]) {
|
|
120
|
+
from = costArgs[i + 1];
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
} else if (costArgs[i] === "--to") {
|
|
124
|
+
if (costArgs[i + 1]) {
|
|
125
|
+
to = costArgs[i + 1];
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
subcommand: { type: "cost", from, to },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
109
135
|
return {
|
|
110
136
|
subcommand: { type: "help" },
|
|
111
137
|
};
|
|
@@ -119,6 +145,7 @@ export function printHelp(exitCode = 0) {
|
|
|
119
145
|
console.log(`
|
|
120
146
|
Usage: plain [options]
|
|
121
147
|
plain batch [options] <task>
|
|
148
|
+
plain cost [--from YYYY-MM-DD] [--to YYYY-MM-DD]
|
|
122
149
|
plain list-models
|
|
123
150
|
plain install-claude-code-plugins
|
|
124
151
|
|
|
@@ -131,6 +158,9 @@ Subcommands:
|
|
|
131
158
|
batch <task> Run in batch mode with the given task instruction.
|
|
132
159
|
Config files are NOT auto-loaded in batch mode;
|
|
133
160
|
use -c to specify config files explicitly.
|
|
161
|
+
cost Show aggregated token cost per day for a period.
|
|
162
|
+
Defaults to the first day of the current month
|
|
163
|
+
through today.
|
|
134
164
|
list-models List available models
|
|
135
165
|
install-claude-code-plugins Install Claude Code plugins
|
|
136
166
|
|
package/src/cliBatch.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { formatCostForBatch } from "./cliFormatter.mjs";
|
|
6
|
+
import { appendUsageRecord, buildUsageRecord } from "./usageStore.mjs";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @typedef {object} BatchSessionOptions
|
|
@@ -46,6 +47,27 @@ export async function startBatchSession({
|
|
|
46
47
|
timestamp: new Date().toISOString(),
|
|
47
48
|
cost: formatCostForBatch(costSummary),
|
|
48
49
|
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const record = buildUsageRecord({
|
|
53
|
+
sessionId,
|
|
54
|
+
mode: "batch",
|
|
55
|
+
modelName,
|
|
56
|
+
workingDir: process.cwd(),
|
|
57
|
+
costSummary,
|
|
58
|
+
});
|
|
59
|
+
if (record) {
|
|
60
|
+
await appendUsageRecord(record);
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
64
|
+
outputEvent({
|
|
65
|
+
type: "error",
|
|
66
|
+
error: { message: `failed to record usage: ${message}` },
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
await onStop();
|
|
50
72
|
resolve();
|
|
51
73
|
});
|