@ariso-ai/ari-hooks 0.1.0 → 0.1.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 +10 -7
- package/package.json +1 -1
- package/src/cli.js +14 -10
- package/src/hooks.js +86 -0
- package/src/init.js +3 -1
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Shares your Claude Code activity with [Ari](https://ariso.ai): after every
|
|
4
4
|
Claude Code turn, the hooks send your request and the final outcome (not the
|
|
5
|
-
intermediate steps) to Ari.
|
|
5
|
+
intermediate steps) to Ari. When a session starts, Ari suggests the top
|
|
6
|
+
things Claude can take care of for you right now — pick one and Claude runs
|
|
7
|
+
it.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -15,14 +17,16 @@ npm install -g ari-hooks
|
|
|
15
17
|
In any project folder where you use Claude Code:
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
ari-hooks
|
|
20
|
+
ari-hooks install
|
|
19
21
|
```
|
|
20
22
|
|
|
21
23
|
That single command:
|
|
22
24
|
|
|
23
25
|
1. Opens your browser to log in to Ari and mint an API token (stored in
|
|
24
26
|
`~/.ari-hooks/config.json`, `0600`). Only needed once per machine.
|
|
25
|
-
2. Adds
|
|
27
|
+
2. Adds three hooks to `./.claude/settings.json`:
|
|
28
|
+
- `SessionStart` — fetches Ari's top suggested tasks and shows them when
|
|
29
|
+
Claude Code boots; reply with a task number or name to run one
|
|
26
30
|
- `UserPromptSubmit` — records what you asked for
|
|
27
31
|
- `Stop` — reads the final assistant message from the transcript and sends
|
|
28
32
|
the request/outcome pair to the Ari API
|
|
@@ -33,9 +37,9 @@ Existing settings and hooks are preserved; running it again is a no-op.
|
|
|
33
37
|
|
|
34
38
|
| Command | What it does |
|
|
35
39
|
|---|---|
|
|
36
|
-
| `ari-hooks` | Login (if needed) + set up hooks in the current folder |
|
|
40
|
+
| `ari-hooks install` | Login (if needed) + set up hooks in the current folder |
|
|
37
41
|
| `ari-hooks login` | Browser login, stores the API token |
|
|
38
|
-
| `ari-hooks init` | Just add the hooks to `./.claude/settings.json` |
|
|
42
|
+
| `ari-hooks init` | Just add the hooks to `./.claude/settings.json` (no login) |
|
|
39
43
|
| `ari-hooks config` | Show configured URLs and login state |
|
|
40
44
|
| `ari-hooks status` | Show login state |
|
|
41
45
|
| `ari-hooks logout` | Delete the stored token |
|
|
@@ -44,8 +48,7 @@ Existing settings and hooks are preserved; running it again is a no-op.
|
|
|
44
48
|
|
|
45
49
|
By default the CLI talks to production (`https://web.ari.ariso.ai` /
|
|
46
50
|
`https://api.ari.ariso.ai`). To test against a local Ari stack, persist
|
|
47
|
-
overrides with the URL flags (they work on any command
|
|
48
|
-
`ari-hooks`):
|
|
51
|
+
overrides with the URL flags (they work on any command):
|
|
49
52
|
|
|
50
53
|
```bash
|
|
51
54
|
ari-hooks config --web-url http://localhost:5173 --api-url http://localhost:4000
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -6,9 +6,9 @@ import { loadConfig, setUrls, showConfig } from './config.js';
|
|
|
6
6
|
const USAGE = `ari-hooks — share your Claude Code activity with Ari
|
|
7
7
|
|
|
8
8
|
Usage:
|
|
9
|
-
ari-hooks
|
|
9
|
+
ari-hooks install Log in (if needed) and set up hooks in the current folder
|
|
10
10
|
ari-hooks login Log in via the browser and store an API token
|
|
11
|
-
ari-hooks init
|
|
11
|
+
ari-hooks init Just add the hooks to ./.claude/settings.json (no login)
|
|
12
12
|
ari-hooks config Show the configured URLs and login state
|
|
13
13
|
ari-hooks status Show login state
|
|
14
14
|
ari-hooks logout Remove the stored token
|
|
@@ -63,22 +63,26 @@ export async function main(argv) {
|
|
|
63
63
|
case 'config':
|
|
64
64
|
showConfig();
|
|
65
65
|
return;
|
|
66
|
+
case 'install': {
|
|
67
|
+
if (!loadConfig().token) {
|
|
68
|
+
await login();
|
|
69
|
+
}
|
|
70
|
+
init();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
66
73
|
case 'init':
|
|
67
74
|
init();
|
|
68
75
|
return;
|
|
69
76
|
case 'hook':
|
|
70
77
|
await runHook(rest[1]);
|
|
71
78
|
return;
|
|
72
|
-
case undefined:
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
await login();
|
|
79
|
+
case undefined:
|
|
80
|
+
// URL-only invocations (e.g. `ari-hooks --web-url ...`) are a
|
|
81
|
+
// complete action — setUrls already printed the resulting config.
|
|
82
|
+
if (!flags.webUrl && !flags.apiUrl && !flags.resetUrls) {
|
|
83
|
+
console.log(USAGE);
|
|
78
84
|
}
|
|
79
|
-
init();
|
|
80
85
|
return;
|
|
81
|
-
}
|
|
82
86
|
default:
|
|
83
87
|
console.error(`Unknown command: ${command}\n`);
|
|
84
88
|
console.log(USAGE);
|
package/src/hooks.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
mkdirSync,
|
|
4
4
|
readFileSync,
|
|
5
5
|
writeFileSync,
|
|
6
|
+
writeSync,
|
|
6
7
|
rmSync,
|
|
7
8
|
appendFileSync,
|
|
8
9
|
} from 'node:fs';
|
|
@@ -128,6 +129,89 @@ async function onStop(input) {
|
|
|
128
129
|
rmSync(sessionPath(input.session_id), { force: true });
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
const MAX_TASKS = 3;
|
|
133
|
+
const MAX_TASK_NAME_LENGTH = 200;
|
|
134
|
+
|
|
135
|
+
const oneLine = (text) =>
|
|
136
|
+
text.replace(/\s+/g, ' ').trim().slice(0, MAX_TASK_NAME_LENGTH);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* SessionStart: ask Ari for the top tasks Claude can take care of right now
|
|
140
|
+
* and surface them at boot — a visible list for the user (systemMessage)
|
|
141
|
+
* plus the full prompts for Claude (additionalContext) so it can run
|
|
142
|
+
* whichever one the user picks.
|
|
143
|
+
*/
|
|
144
|
+
async function onSessionStart(input) {
|
|
145
|
+
// Compaction restarts the session mid-conversation; the tasks were
|
|
146
|
+
// already offered, so don't show (or inject) them again.
|
|
147
|
+
if (input.source === 'compact') return;
|
|
148
|
+
|
|
149
|
+
const config = loadConfig();
|
|
150
|
+
if (!config.token) return;
|
|
151
|
+
|
|
152
|
+
const response = await fetch(new URL('/agent-tasks', getApiUrl(config)), {
|
|
153
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
154
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new Error(`GET /agent-tasks failed: ${response.status}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const body = await response.json();
|
|
161
|
+
// The API wraps the list ({ tasks: [...] }); accept a bare array too.
|
|
162
|
+
const list = Array.isArray(body) ? body : Array.isArray(body?.tasks) ? body.tasks : [];
|
|
163
|
+
const tasks = list
|
|
164
|
+
.filter(
|
|
165
|
+
(t) =>
|
|
166
|
+
t &&
|
|
167
|
+
typeof t.taskName === 'string' &&
|
|
168
|
+
t.taskName.trim() &&
|
|
169
|
+
typeof t.prompt === 'string' &&
|
|
170
|
+
t.prompt.trim()
|
|
171
|
+
)
|
|
172
|
+
.slice(0, MAX_TASKS);
|
|
173
|
+
if (tasks.length === 0) return;
|
|
174
|
+
|
|
175
|
+
// Claude Code renders systemMessage with ANSI intact; the leading \n
|
|
176
|
+
// pushes our block below the fixed "SessionStart:<source> says:" prefix.
|
|
177
|
+
const BOLD = '\x1b[1m';
|
|
178
|
+
const CYAN = '\x1b[36m';
|
|
179
|
+
const GREY = '\x1b[37m';
|
|
180
|
+
const RESET = '\x1b[0m';
|
|
181
|
+
const visibleList = tasks
|
|
182
|
+
.map((t, i) => ` ${BOLD}${i + 1}.${RESET} ${oneLine(t.taskName)}`)
|
|
183
|
+
.join('\n');
|
|
184
|
+
const systemMessage =
|
|
185
|
+
`\n${BOLD}${CYAN}✻ Ari — things Claude can take care of for you right now${RESET}\n` +
|
|
186
|
+
`${visibleList}\n` +
|
|
187
|
+
`${GREY}Reply "run task 1" (or the task name) to start one.${RESET}`;
|
|
188
|
+
|
|
189
|
+
const additionalContext =
|
|
190
|
+
`The user has Ari connected via ari-hooks. At session start the user was ` +
|
|
191
|
+
`shown this list of suggested tasks:\n\n` +
|
|
192
|
+
tasks
|
|
193
|
+
.map(
|
|
194
|
+
(t, i) =>
|
|
195
|
+
`Task ${i + 1}: ${oneLine(t.taskName)}\nPrompt: ${clamp(t.prompt)}`
|
|
196
|
+
)
|
|
197
|
+
.join('\n\n') +
|
|
198
|
+
`\n\nIf the user asks to run one of these tasks (by number or name), ` +
|
|
199
|
+
`carry out that task's prompt as if the user had typed it. Do not start ` +
|
|
200
|
+
`any of these tasks unless the user asks.`;
|
|
201
|
+
|
|
202
|
+
// writeSync: process.exit(0) in runHook would race an async stdout write.
|
|
203
|
+
writeSync(
|
|
204
|
+
1,
|
|
205
|
+
JSON.stringify({
|
|
206
|
+
systemMessage,
|
|
207
|
+
hookSpecificOutput: {
|
|
208
|
+
hookEventName: 'SessionStart',
|
|
209
|
+
additionalContext,
|
|
210
|
+
},
|
|
211
|
+
}) + '\n'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
131
215
|
/**
|
|
132
216
|
* Entry point for `ari-hooks hook <event>`. Hooks must never break the
|
|
133
217
|
* user's Claude Code session: all failures are swallowed (logged to
|
|
@@ -141,6 +225,8 @@ export async function runHook(event) {
|
|
|
141
225
|
await onUserPromptSubmit(input);
|
|
142
226
|
} else if (event === 'stop') {
|
|
143
227
|
await onStop(input);
|
|
228
|
+
} else if (event === 'session-start') {
|
|
229
|
+
await onSessionStart(input);
|
|
144
230
|
}
|
|
145
231
|
} catch (err) {
|
|
146
232
|
logError(err);
|
package/src/init.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
|
|
4
4
|
const HOOK_EVENTS = {
|
|
5
|
+
SessionStart: 'ari-hooks hook session-start',
|
|
5
6
|
UserPromptSubmit: 'ari-hooks hook user-prompt-submit',
|
|
6
7
|
Stop: 'ari-hooks hook stop',
|
|
7
8
|
};
|
|
@@ -50,6 +51,7 @@ export function init(cwd = process.cwd()) {
|
|
|
50
51
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
51
52
|
console.log(`✓ Ari hooks added to ${settingsPath}`);
|
|
52
53
|
console.log(
|
|
53
|
-
'Claude Code sessions in this folder will now share each request and its outcome with Ari
|
|
54
|
+
'Claude Code sessions in this folder will now share each request and its outcome with Ari,'
|
|
54
55
|
);
|
|
56
|
+
console.log('and show suggested Ari tasks when a session starts.');
|
|
55
57
|
}
|