@crouton-kit/humanloop 0.1.0 → 0.1.3
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/dist/cli.js +82 -89
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/tui/app.d.ts +7 -2
- package/dist/tui/app.js +303 -105
- package/dist/tui/input.d.ts +2 -1
- package/dist/tui/input.js +129 -115
- package/dist/tui/render.d.ts +10 -5
- package/dist/tui/render.js +329 -157
- package/dist/tui/terminal.js +5 -0
- package/dist/tui/tmux.d.ts +6 -2
- package/dist/tui/tmux.js +20 -2
- package/dist/types.d.ts +57 -45
- package/dist/types.js +1 -1
- package/dist/visuals/generate.d.ts +9 -4
- package/dist/visuals/generate.js +30 -39
- package/package.json +14 -2
package/dist/cli.js
CHANGED
|
@@ -11,88 +11,75 @@ program
|
|
|
11
11
|
'\n' +
|
|
12
12
|
'Use this when you (the agent) need the human to validate decisions, choose\n' +
|
|
13
13
|
'between options, or provide freetext input before you continue. The tool\n' +
|
|
14
|
-
'blocks until the human finishes review and returns their
|
|
14
|
+
'blocks until the human finishes review and returns their responses as JSON.\n' +
|
|
15
15
|
'\n' +
|
|
16
16
|
'Workflow:\n' +
|
|
17
|
-
' 1. Write a
|
|
18
|
-
' 2. Run `hl create <file>`; it blocks and prints
|
|
19
|
-
' 3. Parse the JSON; each
|
|
17
|
+
' 1. Write a deck file matching `hl schema` (a JSON object with interactions[]).\n' +
|
|
18
|
+
' 2. Run `hl create <file>`; it blocks and prints output JSON to stdout.\n' +
|
|
19
|
+
' 3. Parse the JSON; each response has id, optional selectedOptionId, optional freetext.\n' +
|
|
20
20
|
'\n' +
|
|
21
|
-
'
|
|
22
|
-
'option or enter custom), freetext (open-ended response).')
|
|
21
|
+
'Interaction options: supply options[] for choices; set allowFreetext for comment/freetext.')
|
|
23
22
|
.version('0.1.0')
|
|
24
23
|
.addHelpText('after', '\nExamples:\n' +
|
|
25
24
|
' hl schema # print the input JSON schema\n' +
|
|
26
|
-
' hl create
|
|
27
|
-
' hl create
|
|
28
|
-
' hl create
|
|
25
|
+
' hl create deck.json # open TUI, block, print responses JSON\n' +
|
|
26
|
+
' hl create deck.json --output out.json # write result to file\n' +
|
|
27
|
+
' hl create deck.json --no-tmux # run in current pane even inside tmux\n');
|
|
29
28
|
program
|
|
30
29
|
.command('create')
|
|
31
30
|
.description('Open the decisions TUI on <file> and block until the human finishes review.\n' +
|
|
32
|
-
'Prints
|
|
33
|
-
.argument('<file>', 'Path to
|
|
34
|
-
.option('--session-id <id>', 'Claude session ID; enables per-
|
|
31
|
+
'Prints output JSON to stdout (or to --output / --write-to).')
|
|
32
|
+
.argument('<file>', 'Path to deck JSON file (see `hl schema` for format)')
|
|
33
|
+
.option('--session-id <id>', 'Claude session ID; enables per-interaction visual context from conversation history. Defaults to the most recent session in cwd.')
|
|
35
34
|
.option('--no-visuals', 'Skip visual context generation (faster, no haiku calls)')
|
|
36
35
|
.option('--output <path>', 'Write result JSON to <path> instead of stdout')
|
|
37
36
|
.option('--no-tmux', 'Do not auto-dispatch the TUI to a new tmux pane even when $TMUX is set')
|
|
38
37
|
.addOption(new Option('--write-to <path>', 'internal: tmux child mode').hideHelp())
|
|
39
38
|
.addHelpText('after', '\n' +
|
|
40
39
|
'INPUT FORMAT\n' +
|
|
41
|
-
' JSON file with
|
|
42
|
-
'
|
|
40
|
+
' JSON file with an `interactions` array. Each interaction has an `id`,\n' +
|
|
41
|
+
' `title`, `options[]`, and optional `allowFreetext`. Run `hl schema`\n' +
|
|
43
42
|
' for the full schema. Example:\n' +
|
|
44
43
|
' {\n' +
|
|
45
|
-
' "
|
|
46
|
-
' {"id":
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
' "rationale": "Affects reliability budget"}\n' +
|
|
44
|
+
' "interactions": [\n' +
|
|
45
|
+
' {"id":"i1","title":"Use Postgres","options":[\n' +
|
|
46
|
+
' {"id":"approve","label":"Approve"},\n' +
|
|
47
|
+
' {"id":"reject","label":"Reject"}\n' +
|
|
48
|
+
' ],"allowFreetext":true},\n' +
|
|
49
|
+
' {"id":"i2","title":"Migration tool","options":[\n' +
|
|
50
|
+
' {"id":"prisma","label":"Prisma"},\n' +
|
|
51
|
+
' {"id":"drizzle","label":"Drizzle"}\n' +
|
|
52
|
+
' ]},\n' +
|
|
53
|
+
' {"id":"i3","title":"Rate limit policy","options":[],"allowFreetext":true}\n' +
|
|
56
54
|
' ]\n' +
|
|
57
55
|
' }\n' +
|
|
58
56
|
'\n' +
|
|
59
57
|
'OUTPUT FORMAT (stdout on success, JSON)\n' +
|
|
60
58
|
' {\n' +
|
|
61
|
-
' "
|
|
59
|
+
' "responses": [ ... ],\n' +
|
|
62
60
|
' "completedAt": "2026-04-20T15:23:00.000Z"\n' +
|
|
63
61
|
' }\n' +
|
|
64
62
|
'\n' +
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
' choice: { id, type: "choice", selected: string, isCustom: boolean, comment?: string }\n' +
|
|
68
|
-
' freetext: { id, type: "freetext", response: string }\n' +
|
|
63
|
+
' Response shape:\n' +
|
|
64
|
+
' { id: string, selectedOptionId?: string, freetext?: string }\n' +
|
|
69
65
|
'\n' +
|
|
70
|
-
' The human can skip
|
|
71
|
-
'
|
|
66
|
+
' The human can skip interactions. `responses` may have FEWER entries than\n' +
|
|
67
|
+
' input interactions — look up by `id`, do not assume index alignment.\n' +
|
|
72
68
|
'\n' +
|
|
73
69
|
'BEHAVIOR\n' +
|
|
74
70
|
' tmux When $TMUX is set, the TUI auto-splits into a new pane to the\n' +
|
|
75
71
|
' right (-d keeps focus on the caller). Disable with --no-tmux.\n' +
|
|
76
|
-
' progress
|
|
72
|
+
' progress Responses are persisted atomically to <file>.progress.json after\n' +
|
|
77
73
|
' every change. If the process is killed, the next run resumes\n' +
|
|
78
74
|
' from where the human left off. The file is removed on full\n' +
|
|
79
|
-
' completion; partial-
|
|
75
|
+
' completion; partial-response files are preserved.\n' +
|
|
80
76
|
' visuals With --session-id (or auto-detected) haiku generates a short\n' +
|
|
81
|
-
' ANSI context block per
|
|
77
|
+
' ANSI context block per interaction from recent conversation turns.\n' +
|
|
82
78
|
'\n' +
|
|
83
79
|
'EXIT CODES\n' +
|
|
84
80
|
' 0 success — result JSON emitted\n' +
|
|
85
81
|
' 1 error — message on stderr (file missing, invalid JSON, empty\n' +
|
|
86
|
-
'
|
|
87
|
-
'\n' +
|
|
88
|
-
'EXAMPLES\n' +
|
|
89
|
-
' # Typical agent flow:\n' +
|
|
90
|
-
' cat > /tmp/d.json <<EOF\n' +
|
|
91
|
-
' {"questions":[{"id":"x","type":"validation",\n' +
|
|
92
|
-
' "statement":"...","rationale":"..."}]}\n' +
|
|
93
|
-
' EOF\n' +
|
|
94
|
-
' hl create /tmp/d.json > /tmp/answers.json\n' +
|
|
95
|
-
' jq \'.answers[] | select(.id=="x")\' /tmp/answers.json\n')
|
|
82
|
+
' interactions, no TTY, etc.)\n')
|
|
96
83
|
.action(async (file, opts) => {
|
|
97
84
|
const sessionId = opts.visuals
|
|
98
85
|
? (opts.sessionId || findRecentSessionId(process.cwd()) || findRecentSessionId() || undefined)
|
|
@@ -128,83 +115,89 @@ program
|
|
|
128
115
|
const msg = err instanceof Error ? err.message : String(err);
|
|
129
116
|
process.stderr.write(`ERROR: ${msg}\n`);
|
|
130
117
|
if (msg.includes('not found')) {
|
|
131
|
-
process.stderr.write('\nFix: pass a path to an existing
|
|
118
|
+
process.stderr.write('\nFix: pass a path to an existing deck JSON file.\nSee format: hl schema\n');
|
|
132
119
|
}
|
|
133
|
-
else if (msg.includes('No
|
|
134
|
-
process.stderr.write('\nFix: the file must contain a non-empty `
|
|
120
|
+
else if (msg.includes('No interactions')) {
|
|
121
|
+
process.stderr.write('\nFix: the file must contain a non-empty `interactions` array.\nSee format: hl schema\n');
|
|
135
122
|
}
|
|
136
123
|
else if (msg.includes('TTY')) {
|
|
137
124
|
process.stderr.write('\nFix: hl needs an interactive terminal. If the caller captures stdin,\nrun inside tmux so hl can auto-dispatch the TUI to a new pane, or pipe\nstdin from /dev/tty.\n');
|
|
138
125
|
}
|
|
139
126
|
else if (msg.includes('JSON')) {
|
|
140
|
-
process.stderr.write('\nFix: the
|
|
127
|
+
process.stderr.write('\nFix: the deck file must be valid JSON matching `hl schema`.\n');
|
|
128
|
+
}
|
|
129
|
+
else if (msg.startsWith('interactions[') || msg.includes('Duplicate interaction id') || msg.includes('must be')) {
|
|
130
|
+
process.stderr.write('\nFix: the deck file must match `hl schema`. Run `hl schema` to see the required shape.\n');
|
|
141
131
|
}
|
|
142
132
|
process.exit(1);
|
|
143
133
|
}
|
|
144
134
|
});
|
|
145
135
|
program
|
|
146
136
|
.command('schema')
|
|
147
|
-
.description('Print the
|
|
137
|
+
.description('Print the v2 Interaction[] deck schema to stdout')
|
|
148
138
|
.addHelpText('after', '\n' +
|
|
149
139
|
'Use this to learn the exact input format for `hl create`.\n' +
|
|
150
|
-
'The schema documents the three question types and their required fields.\n' +
|
|
151
140
|
'\n' +
|
|
152
141
|
'Example:\n' +
|
|
153
|
-
' hl schema >
|
|
154
|
-
' hl schema | jq
|
|
142
|
+
' hl schema > deck.schema.json # save for reference\n' +
|
|
143
|
+
' hl schema | jq # pretty-print\n')
|
|
155
144
|
.action(() => {
|
|
156
145
|
const schema = {
|
|
157
146
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
158
|
-
description: 'Input schema for hl create',
|
|
147
|
+
description: 'Input schema for hl create (v2)',
|
|
159
148
|
type: 'object',
|
|
149
|
+
required: ['interactions'],
|
|
160
150
|
properties: {
|
|
161
151
|
title: {
|
|
162
152
|
type: 'string',
|
|
163
|
-
description: 'Optional title shown in the TUI header',
|
|
153
|
+
description: 'Optional deck title shown in the TUI header',
|
|
164
154
|
},
|
|
165
|
-
|
|
155
|
+
interactions: {
|
|
166
156
|
type: 'array',
|
|
157
|
+
minItems: 1,
|
|
167
158
|
items: {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
159
|
+
type: 'object',
|
|
160
|
+
required: ['id', 'title', 'options'],
|
|
161
|
+
properties: {
|
|
162
|
+
id: { type: 'string', description: 'Unique identifier' },
|
|
163
|
+
title: { type: 'string', description: 'Short display label (≤4 words). Required.' },
|
|
164
|
+
subtitle: { type: 'string', description: 'One-line "why this matters"' },
|
|
165
|
+
body: { type: 'string', description: 'Markdown body shown in item-review' },
|
|
166
|
+
bodyPath: { type: 'string', description: 'Path to body file; sisyphus inlines before mount' },
|
|
167
|
+
options: {
|
|
168
|
+
type: 'array',
|
|
169
|
+
description: 'Selectable choices. Empty for freetext-only interactions.',
|
|
170
|
+
items: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
required: ['id', 'label'],
|
|
173
|
+
properties: {
|
|
174
|
+
id: { type: 'string' },
|
|
175
|
+
label: { type: 'string' },
|
|
176
|
+
description: { type: 'string' },
|
|
177
|
+
shortcut: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'Single char shortcut. Auto-assigned if absent. Avoid: c r n p q j k space',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
178
182
|
},
|
|
179
183
|
},
|
|
180
|
-
{
|
|
181
|
-
type: '
|
|
182
|
-
description: '
|
|
183
|
-
required: ['id', 'type', 'question', 'rationale', 'options'],
|
|
184
|
-
properties: {
|
|
185
|
-
id: { type: 'string' },
|
|
186
|
-
type: { const: 'choice' },
|
|
187
|
-
question: { type: 'string' },
|
|
188
|
-
rationale: { type: 'string' },
|
|
189
|
-
options: { type: 'array', items: { type: 'string' }, minItems: 2 },
|
|
190
|
-
},
|
|
184
|
+
allowFreetext: {
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
description: 'If true, user can add a freetext comment (or respond freely if options is empty)',
|
|
191
187
|
},
|
|
192
|
-
{
|
|
193
|
-
type: '
|
|
194
|
-
description: '
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
rationale: { type: 'string' },
|
|
201
|
-
},
|
|
188
|
+
freetextLabel: {
|
|
189
|
+
type: 'string',
|
|
190
|
+
description: 'Prompt shown above the freetext input. Default: "Comment" or "Response"',
|
|
191
|
+
},
|
|
192
|
+
kind: {
|
|
193
|
+
type: 'string',
|
|
194
|
+
enum: ['notify', 'validation', 'decision', 'context', 'error'],
|
|
195
|
+
description: 'Display hint — opaque to humanloop, used by sisyphus for inbox icons',
|
|
202
196
|
},
|
|
203
|
-
|
|
197
|
+
},
|
|
204
198
|
},
|
|
205
199
|
},
|
|
206
200
|
},
|
|
207
|
-
required: ['questions'],
|
|
208
201
|
};
|
|
209
202
|
process.stdout.write(JSON.stringify(schema, null, 2) + '\n');
|
|
210
203
|
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { mountPanel, validateInput, launchTui } from './tui/app.js';
|
|
2
|
+
export { defaultGenerateVisual } from './visuals/generate.js';
|
|
3
|
+
export type { Interaction, InteractionOption, InteractionResponse, InteractionKind, Deck, DeckSource, MountedPanel, MountedPanelOpts, GenerateVisual, VisualBlock, } from './types.js';
|
|
4
|
+
export type { Key } from './tui/terminal.js';
|
|
5
|
+
export type { ConversationMessage } from './conversation/reader.js';
|
package/dist/index.js
ADDED
package/dist/tui/app.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function
|
|
1
|
+
import type { Deck, InteractionResponse, MountedPanel, MountedPanelOpts } from '../types.js';
|
|
2
|
+
export declare function validateInput(parsed: unknown): Deck;
|
|
3
|
+
export declare function mountPanel(opts: MountedPanelOpts): MountedPanel;
|
|
4
|
+
export declare function launchTui(decisionsPath: string, sessionId?: string): Promise<{
|
|
5
|
+
responses: InteractionResponse[];
|
|
6
|
+
completedAt: string;
|
|
7
|
+
}>;
|