@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 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 two hooks to `./.claude/settings.json`:
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, including bare
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ariso-ai/ari-hooks",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Set up Claude Code hooks that share your requests and their outcomes with Ari",
5
5
  "type": "module",
6
6
  "bin": {
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 Log in (if needed) and set up hooks in the current folder
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 Add the hooks to ./.claude/settings.json
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
- // Bare invocation: make "npx/global install run once in a folder"
74
- // the whole setup story. URL-only invocations (e.g. `ari-hooks
75
- // --web-url ...`) still run the full setup with the new URLs.
76
- if (!loadConfig().token) {
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
  }