@dexh/shannon 0.0.1 → 0.0.2
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 +31 -64
- package/index.ts +23 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,72 +1,43 @@
|
|
|
1
1
|
# Shannon
|
|
2
2
|
|
|
3
|
-
Shannon is a CLI and SDK wrapper around the interactive Claude Code CLI. It
|
|
4
|
-
|
|
5
|
-
The first supported path is:
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
shannon -p "hi i'm tom" --output-format=stream-json --verbose
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Shannon does not use `claude -p` internally.
|
|
3
|
+
Shannon is a CLI and SDK wrapper around the interactive Claude Code CLI. It runs a real `claude` session inside tmux, sends a prompt, and emits stream JSON.
|
|
12
4
|
|
|
13
5
|
## Requirements
|
|
14
6
|
|
|
15
|
-
- Bun
|
|
7
|
+
- [Bun](https://bun.sh)
|
|
16
8
|
- `claude` on `PATH`
|
|
17
9
|
- `tmux` on `PATH`
|
|
18
|
-
- A working Claude Code login
|
|
10
|
+
- A working Claude Code login
|
|
19
11
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
bun install
|
|
24
|
-
bun link
|
|
25
|
-
```
|
|
12
|
+
## CLI
|
|
26
13
|
|
|
27
|
-
|
|
14
|
+
Run without installing:
|
|
28
15
|
|
|
29
16
|
```sh
|
|
30
|
-
|
|
17
|
+
npx @dexh/shannon -p "Reply with exactly: hello" --output-format=stream-json --verbose
|
|
31
18
|
```
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
Or install globally:
|
|
34
21
|
|
|
35
22
|
```sh
|
|
23
|
+
npm install -g @dexh/shannon
|
|
36
24
|
shannon -p "Reply with exactly: hello" --output-format=stream-json --verbose
|
|
37
25
|
```
|
|
38
26
|
|
|
39
|
-
|
|
27
|
+
Output formats: `stream-json` (JSONL), `json` (single array), `text` (final result text).
|
|
28
|
+
|
|
29
|
+
## SDK
|
|
40
30
|
|
|
41
31
|
```sh
|
|
42
|
-
|
|
43
|
-
| shannon --input-format=stream-json --output-format=stream-json --verbose --replay-user-messages
|
|
32
|
+
npm install @dexh/shannon
|
|
44
33
|
```
|
|
45
34
|
|
|
46
|
-
Current stream shape:
|
|
47
|
-
|
|
48
|
-
- `system` / `hook_response` when interactive transcript hook attachments are present
|
|
49
|
-
- `system` / `init`
|
|
50
|
-
- `assistant`
|
|
51
|
-
- synthesized `result` / `success`
|
|
52
|
-
- final `shannon_session` / `metadata`
|
|
53
|
-
|
|
54
|
-
The final metadata row includes the Claude session id, transcript path, project session folder, tmux session name, cwd, and cleanup status.
|
|
55
|
-
|
|
56
|
-
`--output-format=json` emits one JSON array containing Shannon's supported
|
|
57
|
-
message rows. `--output-format=text` emits the final result text.
|
|
58
|
-
|
|
59
|
-
## SDK
|
|
60
|
-
|
|
61
35
|
```ts
|
|
62
|
-
import { query } from "@
|
|
36
|
+
import { query } from "@dexh/shannon";
|
|
63
37
|
|
|
64
38
|
for await (const message of query({
|
|
65
39
|
prompt: "Reply with exactly: hello",
|
|
66
|
-
options: {
|
|
67
|
-
outputFormat: "stream-json",
|
|
68
|
-
verbose: true,
|
|
69
|
-
},
|
|
40
|
+
options: { outputFormat: "stream-json", verbose: true },
|
|
70
41
|
})) {
|
|
71
42
|
console.log(JSON.stringify(message));
|
|
72
43
|
}
|
|
@@ -75,7 +46,7 @@ for await (const message of query({
|
|
|
75
46
|
Async input is also accepted for finite user-message streams:
|
|
76
47
|
|
|
77
48
|
```ts
|
|
78
|
-
import { query, type ShannonUserMessage } from "@
|
|
49
|
+
import { query, type ShannonUserMessage } from "@dexh/shannon";
|
|
79
50
|
|
|
80
51
|
async function* messages(): AsyncIterable<ShannonUserMessage> {
|
|
81
52
|
yield {
|
|
@@ -94,36 +65,32 @@ for await (const message of query({ prompt: messages() })) {
|
|
|
94
65
|
}
|
|
95
66
|
```
|
|
96
67
|
|
|
97
|
-
|
|
98
|
-
underlying Shannon subprocess.
|
|
68
|
+
Pass an `AbortController` in options to terminate the underlying Shannon subprocess.
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
query parameter surface:
|
|
70
|
+
## Agent SDK facade
|
|
102
71
|
|
|
103
|
-
|
|
104
|
-
|
|
72
|
+
`@dexh/shannon-agent-sdk` is a Claude Agent SDK-compatible facade that re-exports Shannon's SDK surface. Full parity is a work in progress (see `GOAL_PROGRESS.md`).
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npm install @dexh/shannon-agent-sdk
|
|
105
76
|
```
|
|
106
77
|
|
|
107
|
-
|
|
78
|
+
```ts
|
|
79
|
+
import { query } from "@dexh/shannon-agent-sdk";
|
|
108
80
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
81
|
+
for await (const message of query({
|
|
82
|
+
prompt: "Reply with exactly: hello",
|
|
83
|
+
options: { outputFormat: "stream-json", verbose: true },
|
|
84
|
+
})) {
|
|
85
|
+
console.log(JSON.stringify(message));
|
|
86
|
+
}
|
|
87
|
+
```
|
|
112
88
|
|
|
113
89
|
## Development
|
|
114
90
|
|
|
115
91
|
```sh
|
|
92
|
+
bun install
|
|
116
93
|
bun test
|
|
117
94
|
bun run typecheck
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
CI runs the same non-live checks plus package dry-runs for both packages.
|
|
121
|
-
The publish workflow publishes `@humanlayer/shannon` and
|
|
122
|
-
`@humanlayer/shannon-agent-sdk` from a GitHub release or manual dispatch when
|
|
123
|
-
`NPM_TOKEN` is configured.
|
|
124
|
-
|
|
125
|
-
Run the CLI directly:
|
|
126
|
-
|
|
127
|
-
```sh
|
|
128
95
|
bun ./index.ts -p "hello" --output-format=stream-json --verbose
|
|
129
96
|
```
|
package/index.ts
CHANGED
|
@@ -629,10 +629,10 @@ export async function runShannon(options: CliOptions) {
|
|
|
629
629
|
const prompts = options.prompt
|
|
630
630
|
? asyncIterableFromArray([options.prompt])
|
|
631
631
|
: readPromptsFromStdin(options.inputFormat);
|
|
632
|
+
const promptIterator = prompts[Symbol.asyncIterator]();
|
|
632
633
|
let meta: SessionMetadata | undefined;
|
|
633
634
|
let transcriptRowCount = 0;
|
|
634
635
|
let cleanup: JsonRecord = { tmux_killed: false };
|
|
635
|
-
let promptReady = false;
|
|
636
636
|
let promptCount = 0;
|
|
637
637
|
let cleanupStarted = false;
|
|
638
638
|
let metadataEmitted = false;
|
|
@@ -657,6 +657,12 @@ export async function runShannon(options: CliOptions) {
|
|
|
657
657
|
});
|
|
658
658
|
|
|
659
659
|
try {
|
|
660
|
+
let prompt = await nextPrompt(promptIterator);
|
|
661
|
+
if (!prompt) {
|
|
662
|
+
throw new Error("Expected at least one user message on stdin for --input-format=stream-json");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const firstPromptSentAt = Date.now();
|
|
660
666
|
await runCommand([
|
|
661
667
|
"tmux",
|
|
662
668
|
"new-session",
|
|
@@ -667,27 +673,23 @@ export async function runShannon(options: CliOptions) {
|
|
|
667
673
|
options.cwd,
|
|
668
674
|
"claude",
|
|
669
675
|
...options.claudeArgs,
|
|
676
|
+
prompt,
|
|
670
677
|
]);
|
|
671
|
-
await waitForPrompt(tmuxSession);
|
|
672
|
-
promptReady = true;
|
|
673
678
|
|
|
674
|
-
|
|
675
|
-
|
|
679
|
+
let launchedWithPrompt = true;
|
|
680
|
+
while (prompt) {
|
|
676
681
|
promptCount += 1;
|
|
677
682
|
|
|
678
|
-
|
|
683
|
+
const promptSentAt = launchedWithPrompt ? firstPromptSentAt : Date.now();
|
|
684
|
+
if (!launchedWithPrompt) {
|
|
679
685
|
await waitForPrompt(tmuxSession);
|
|
680
|
-
|
|
686
|
+
await sendPrompt(tmuxSession, prompt);
|
|
681
687
|
}
|
|
682
688
|
|
|
683
689
|
if (options.replayUserMessages && options.outputFormat === "stream-json") {
|
|
684
690
|
emitJson(toUserReplay(prompt));
|
|
685
691
|
}
|
|
686
692
|
|
|
687
|
-
const promptSentAt = Date.now();
|
|
688
|
-
await sendPrompt(tmuxSession, prompt);
|
|
689
|
-
promptReady = false;
|
|
690
|
-
|
|
691
693
|
if (!meta) {
|
|
692
694
|
const discovery = await waitForSessionWithPrompt(
|
|
693
695
|
projectFolder,
|
|
@@ -741,10 +743,9 @@ export async function runShannon(options: CliOptions) {
|
|
|
741
743
|
} else {
|
|
742
744
|
emitOutput(options.outputFormat, turnMessages);
|
|
743
745
|
}
|
|
744
|
-
}
|
|
745
746
|
|
|
746
|
-
|
|
747
|
-
|
|
747
|
+
launchedWithPrompt = false;
|
|
748
|
+
prompt = await nextPrompt(promptIterator);
|
|
748
749
|
}
|
|
749
750
|
|
|
750
751
|
if (options.outputFormat === "json") {
|
|
@@ -925,10 +926,17 @@ async function waitForPrompt(tmuxSession: string) {
|
|
|
925
926
|
async function sendPrompt(tmuxSession: string, prompt: string) {
|
|
926
927
|
await runCommand(["tmux", "set-buffer", "-b", `shannon-${tmuxSession}`, prompt]);
|
|
927
928
|
await runCommand(["tmux", "paste-buffer", "-b", `shannon-${tmuxSession}`, "-t", tmuxSession]);
|
|
928
|
-
await runCommand(["tmux", "send-keys", "-t", tmuxSession, "Escape"]);
|
|
929
929
|
await runCommand(["tmux", "send-keys", "-t", tmuxSession, "C-m"]);
|
|
930
930
|
}
|
|
931
931
|
|
|
932
|
+
async function nextPrompt(iterator: AsyncIterator<string>): Promise<string | undefined> {
|
|
933
|
+
while (true) {
|
|
934
|
+
const next = await iterator.next();
|
|
935
|
+
if (next.done) return undefined;
|
|
936
|
+
if (next.value) return next.value;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
932
940
|
async function waitForAssistantReply(
|
|
933
941
|
transcriptPath: string,
|
|
934
942
|
prompt: string,
|
package/package.json
CHANGED