@aria_asi/cli 0.2.0

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.
Files changed (153) hide show
  1. package/bin/aria.js +168 -0
  2. package/dist/aria-connector/src/auth-commands.d.ts +28 -0
  3. package/dist/aria-connector/src/auth-commands.d.ts.map +1 -0
  4. package/dist/aria-connector/src/auth-commands.js +129 -0
  5. package/dist/aria-connector/src/auth-commands.js.map +1 -0
  6. package/dist/aria-connector/src/auth.d.ts +12 -0
  7. package/dist/aria-connector/src/auth.d.ts.map +1 -0
  8. package/dist/aria-connector/src/auth.js +31 -0
  9. package/dist/aria-connector/src/auth.js.map +1 -0
  10. package/dist/aria-connector/src/auto-mcp.d.ts +23 -0
  11. package/dist/aria-connector/src/auto-mcp.d.ts.map +1 -0
  12. package/dist/aria-connector/src/auto-mcp.js +994 -0
  13. package/dist/aria-connector/src/auto-mcp.js.map +1 -0
  14. package/dist/aria-connector/src/chat.d.ts +21 -0
  15. package/dist/aria-connector/src/chat.d.ts.map +1 -0
  16. package/dist/aria-connector/src/chat.js +332 -0
  17. package/dist/aria-connector/src/chat.js.map +1 -0
  18. package/dist/aria-connector/src/codebase-scanner.d.ts +7 -0
  19. package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -0
  20. package/dist/aria-connector/src/codebase-scanner.js +6 -0
  21. package/dist/aria-connector/src/codebase-scanner.js.map +1 -0
  22. package/dist/aria-connector/src/cognition-log.d.ts +17 -0
  23. package/dist/aria-connector/src/cognition-log.d.ts.map +1 -0
  24. package/dist/aria-connector/src/cognition-log.js +19 -0
  25. package/dist/aria-connector/src/cognition-log.js.map +1 -0
  26. package/dist/aria-connector/src/config.d.ts +41 -0
  27. package/dist/aria-connector/src/config.d.ts.map +1 -0
  28. package/dist/aria-connector/src/config.js +50 -0
  29. package/dist/aria-connector/src/config.js.map +1 -0
  30. package/dist/aria-connector/src/connectors/claude-code.d.ts +4 -0
  31. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -0
  32. package/dist/aria-connector/src/connectors/claude-code.js +204 -0
  33. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -0
  34. package/dist/aria-connector/src/connectors/cursor.d.ts +4 -0
  35. package/dist/aria-connector/src/connectors/cursor.d.ts.map +1 -0
  36. package/dist/aria-connector/src/connectors/cursor.js +63 -0
  37. package/dist/aria-connector/src/connectors/cursor.js.map +1 -0
  38. package/dist/aria-connector/src/connectors/opencode.d.ts +4 -0
  39. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -0
  40. package/dist/aria-connector/src/connectors/opencode.js +102 -0
  41. package/dist/aria-connector/src/connectors/opencode.js.map +1 -0
  42. package/dist/aria-connector/src/connectors/shell.d.ts +4 -0
  43. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -0
  44. package/dist/aria-connector/src/connectors/shell.js +58 -0
  45. package/dist/aria-connector/src/connectors/shell.js.map +1 -0
  46. package/dist/aria-connector/src/garden-client.d.ts +19 -0
  47. package/dist/aria-connector/src/garden-client.d.ts.map +1 -0
  48. package/dist/aria-connector/src/garden-client.js +85 -0
  49. package/dist/aria-connector/src/garden-client.js.map +1 -0
  50. package/dist/aria-connector/src/garden-control-plane.d.ts +22 -0
  51. package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -0
  52. package/dist/aria-connector/src/garden-control-plane.js +43 -0
  53. package/dist/aria-connector/src/garden-control-plane.js.map +1 -0
  54. package/dist/aria-connector/src/harness-client.d.ts +166 -0
  55. package/dist/aria-connector/src/harness-client.d.ts.map +1 -0
  56. package/dist/aria-connector/src/harness-client.js +344 -0
  57. package/dist/aria-connector/src/harness-client.js.map +1 -0
  58. package/dist/aria-connector/src/hive-client.d.ts +32 -0
  59. package/dist/aria-connector/src/hive-client.d.ts.map +1 -0
  60. package/dist/aria-connector/src/hive-client.js +69 -0
  61. package/dist/aria-connector/src/hive-client.js.map +1 -0
  62. package/dist/aria-connector/src/index.d.ts +19 -0
  63. package/dist/aria-connector/src/index.d.ts.map +1 -0
  64. package/dist/aria-connector/src/index.js +13 -0
  65. package/dist/aria-connector/src/index.js.map +1 -0
  66. package/dist/aria-connector/src/install-hooks.d.ts +18 -0
  67. package/dist/aria-connector/src/install-hooks.d.ts.map +1 -0
  68. package/dist/aria-connector/src/install-hooks.js +224 -0
  69. package/dist/aria-connector/src/install-hooks.js.map +1 -0
  70. package/dist/aria-connector/src/model-context.d.ts +8 -0
  71. package/dist/aria-connector/src/model-context.d.ts.map +1 -0
  72. package/dist/aria-connector/src/model-context.js +83 -0
  73. package/dist/aria-connector/src/model-context.js.map +1 -0
  74. package/dist/aria-connector/src/persona.d.ts +27 -0
  75. package/dist/aria-connector/src/persona.d.ts.map +1 -0
  76. package/dist/aria-connector/src/persona.js +86 -0
  77. package/dist/aria-connector/src/persona.js.map +1 -0
  78. package/dist/aria-connector/src/providers/anthropic.d.ts +4 -0
  79. package/dist/aria-connector/src/providers/anthropic.d.ts.map +1 -0
  80. package/dist/aria-connector/src/providers/anthropic.js +92 -0
  81. package/dist/aria-connector/src/providers/anthropic.js.map +1 -0
  82. package/dist/aria-connector/src/providers/deepseek.d.ts +3 -0
  83. package/dist/aria-connector/src/providers/deepseek.d.ts.map +1 -0
  84. package/dist/aria-connector/src/providers/deepseek.js +28 -0
  85. package/dist/aria-connector/src/providers/deepseek.js.map +1 -0
  86. package/dist/aria-connector/src/providers/google.d.ts +3 -0
  87. package/dist/aria-connector/src/providers/google.d.ts.map +1 -0
  88. package/dist/aria-connector/src/providers/google.js +38 -0
  89. package/dist/aria-connector/src/providers/google.js.map +1 -0
  90. package/dist/aria-connector/src/providers/ollama.d.ts +3 -0
  91. package/dist/aria-connector/src/providers/ollama.d.ts.map +1 -0
  92. package/dist/aria-connector/src/providers/ollama.js +28 -0
  93. package/dist/aria-connector/src/providers/ollama.js.map +1 -0
  94. package/dist/aria-connector/src/providers/openai.d.ts +4 -0
  95. package/dist/aria-connector/src/providers/openai.d.ts.map +1 -0
  96. package/dist/aria-connector/src/providers/openai.js +84 -0
  97. package/dist/aria-connector/src/providers/openai.js.map +1 -0
  98. package/dist/aria-connector/src/providers/openrouter.d.ts +3 -0
  99. package/dist/aria-connector/src/providers/openrouter.d.ts.map +1 -0
  100. package/dist/aria-connector/src/providers/openrouter.js +30 -0
  101. package/dist/aria-connector/src/providers/openrouter.js.map +1 -0
  102. package/dist/aria-connector/src/providers/types.d.ts +20 -0
  103. package/dist/aria-connector/src/providers/types.d.ts.map +1 -0
  104. package/dist/aria-connector/src/providers/types.js +2 -0
  105. package/dist/aria-connector/src/providers/types.js.map +1 -0
  106. package/dist/aria-connector/src/setup-wizard.d.ts +2 -0
  107. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -0
  108. package/dist/aria-connector/src/setup-wizard.js +140 -0
  109. package/dist/aria-connector/src/setup-wizard.js.map +1 -0
  110. package/dist/aria-connector/src/types.d.ts +30 -0
  111. package/dist/aria-connector/src/types.d.ts.map +1 -0
  112. package/dist/aria-connector/src/types.js +5 -0
  113. package/dist/aria-connector/src/types.js.map +1 -0
  114. package/dist/aria-web/src/lib/codebase-scanner.d.ts +127 -0
  115. package/dist/aria-web/src/lib/codebase-scanner.d.ts.map +1 -0
  116. package/dist/aria-web/src/lib/codebase-scanner.js +1730 -0
  117. package/dist/aria-web/src/lib/codebase-scanner.js.map +1 -0
  118. package/dist/cli-0.2.0.tgz +0 -0
  119. package/dist/install.sh +13 -0
  120. package/hooks/aria-harness-via-sdk.mjs +317 -0
  121. package/hooks/aria-pre-tool-gate.mjs +596 -0
  122. package/hooks/aria-preprompt-consult.mjs +175 -0
  123. package/hooks/aria-stop-gate.mjs +222 -0
  124. package/package.json +47 -0
  125. package/src/__tests__/auth-commands.test.ts +132 -0
  126. package/src/auth-commands.ts +175 -0
  127. package/src/auth.ts +33 -0
  128. package/src/auto-mcp.ts +1172 -0
  129. package/src/chat.ts +387 -0
  130. package/src/codebase-scanner.ts +18 -0
  131. package/src/cognition-log.ts +30 -0
  132. package/src/config.ts +94 -0
  133. package/src/connectors/claude-code.ts +213 -0
  134. package/src/connectors/cursor.ts +75 -0
  135. package/src/connectors/opencode.ts +115 -0
  136. package/src/connectors/shell.ts +72 -0
  137. package/src/garden-client.ts +98 -0
  138. package/src/garden-control-plane.ts +108 -0
  139. package/src/harness-client.ts +454 -0
  140. package/src/hive-client.ts +104 -0
  141. package/src/index.ts +26 -0
  142. package/src/install-hooks.ts +259 -0
  143. package/src/model-context.ts +88 -0
  144. package/src/persona.ts +113 -0
  145. package/src/providers/anthropic.ts +120 -0
  146. package/src/providers/deepseek.ts +40 -0
  147. package/src/providers/google.ts +57 -0
  148. package/src/providers/ollama.ts +43 -0
  149. package/src/providers/openai.ts +108 -0
  150. package/src/providers/openrouter.ts +42 -0
  151. package/src/providers/types.ts +35 -0
  152. package/src/setup-wizard.ts +177 -0
  153. package/src/types.ts +32 -0
@@ -0,0 +1,454 @@
1
+ import type { AuthConfig, HarnessResponse, HarnessGateDecision } from './types.js';
2
+ import { loadLicense } from './auth.js';
3
+ import { pushCognitionLog } from './cognition-log.js';
4
+
5
+ // ── Public types ──────────────────────────────────────────────────────
6
+
7
+ export interface HarnessChunk {
8
+ index: number;
9
+ priority: number;
10
+ label: 'identity' | 'rules' | 'gate-state' | 'metadata' | 'sources';
11
+ chars: number;
12
+ content: string;
13
+ }
14
+
15
+ export interface HarnessPacket {
16
+ ok: boolean;
17
+ stage?: string;
18
+ actor?: string;
19
+ system?: string;
20
+ harness?: string;
21
+ adapter?: string;
22
+ preStateGate?: { passed: boolean; score: number };
23
+ contractGate?: { passed: boolean; score: number };
24
+ missingCritical?: string[];
25
+ loadedByClass?: Record<string, number>;
26
+ // Phase 2/3 — chunked-mode fields (present when client passed
27
+ // modelContext < threshold and server returned chunks).
28
+ chunkSummary?: {
29
+ mode: 'full' | 'chunked';
30
+ totalChars: number;
31
+ approxTokens: number;
32
+ chunkCount: number;
33
+ modelContextRequested?: number;
34
+ thresholdTokens: number;
35
+ researchPullChars?: number;
36
+ };
37
+ chunks?: HarnessChunk[];
38
+ }
39
+
40
+ /**
41
+ * Harness API client.
42
+ *
43
+ * Reads license token from ~/.aria/license.json (via auth.ts loadLicense()),
44
+ * falls back to ARIA_HARNESS_TOKEN env var. No hardcoded master token.
45
+ *
46
+ * All /api/harness/* calls use Bearer auth. 401/403 surfaced to caller.
47
+ * No timeout on fetch — bare try/catch per no-timeouts requirement.
48
+ * Every gate decision pushes a cognition log entry.
49
+ */
50
+
51
+ interface HarnessClientOptions {
52
+ baseUrl?: string;
53
+ }
54
+
55
+ interface GateContext {
56
+ gate: string;
57
+ passed: boolean;
58
+ reason?: string;
59
+ score?: number;
60
+ metadata?: Record<string, unknown>;
61
+ }
62
+
63
+ export class HarnessClient {
64
+ private baseUrl: string;
65
+ private token: string | null = null;
66
+ private tokenPromise: Promise<string | null> | null = null;
67
+
68
+ constructor(options: HarnessClientOptions = {}) {
69
+ this.baseUrl = options.baseUrl ?? process.env.ARIA_HARNESS_BASE_URL ?? 'https://harness.ariasos.com';
70
+ }
71
+
72
+ /**
73
+ * Resolve the bearer token: try loadLicense() first, fall back to env var.
74
+ * Cache the promise so we don't re-read the file on every call.
75
+ */
76
+ private async getToken(): Promise<string | null> {
77
+ if (this.token) return this.token;
78
+ if (this.tokenPromise) return this.tokenPromise;
79
+
80
+ this.tokenPromise = this.resolveToken();
81
+ return this.tokenPromise;
82
+ }
83
+
84
+ private async resolveToken(): Promise<string | null> {
85
+ // 1. Try license file
86
+ try {
87
+ const license = await loadLicense();
88
+ if (license?.harnessToken) {
89
+ this.token = license.harnessToken;
90
+ return this.token;
91
+ }
92
+ } catch {
93
+ // file not found or parse error — fall through
94
+ }
95
+
96
+ // 2. Fallback to env var
97
+ const envToken = process.env.ARIA_HARNESS_TOKEN;
98
+ if (envToken) {
99
+ this.token = envToken;
100
+ return this.token;
101
+ }
102
+
103
+ return null;
104
+ }
105
+
106
+ /**
107
+ * Perform a fetch against /api/harness/{path}.
108
+ * Returns the parsed JSON body on success.
109
+ * Throws a structured error on 401 (revoked) or 403 (forbidden).
110
+ * No timeout — bare try/catch.
111
+ */
112
+ async request<T = unknown>(
113
+ method: string,
114
+ path: string,
115
+ body?: unknown,
116
+ gateContext?: GateContext,
117
+ ): Promise<HarnessResponse<T>> {
118
+ const token = await this.getToken();
119
+
120
+ const headers: Record<string, string> = {
121
+ 'Content-Type': 'application/json',
122
+ };
123
+
124
+ if (token) {
125
+ headers['Authorization'] = `Bearer ${token}`;
126
+ }
127
+
128
+ let response: Response;
129
+ try {
130
+ response = await fetch(`${this.baseUrl}/api/harness${path}`, {
131
+ method,
132
+ headers,
133
+ body: body ? JSON.stringify(body) : undefined,
134
+ });
135
+ } catch (err) {
136
+ // Push cognition log for the gate failure if we have context
137
+ if (gateContext) {
138
+ await this.pushGateLog({
139
+ ...gateContext,
140
+ passed: false,
141
+ reason: `network_error: ${(err as Error).message}`,
142
+ }).catch(() => {}); // fire-and-forget best effort
143
+ }
144
+ throw err;
145
+ }
146
+
147
+ // Surface 401/403 cleanly
148
+ if (response.status === 401) {
149
+ const detail = await this.tryExtractError(response);
150
+ const err = new Error(`Harness token revoked: ${detail}`);
151
+ (err as any).status = 401;
152
+ (err as any).code = 'TOKEN_REVOKED';
153
+ if (gateContext) {
154
+ await this.pushGateLog({
155
+ ...gateContext,
156
+ passed: false,
157
+ reason: `http_401: ${detail}`,
158
+ }).catch(() => {});
159
+ }
160
+ throw err;
161
+ }
162
+
163
+ if (response.status === 403) {
164
+ const detail = await this.tryExtractError(response);
165
+ const err = new Error(`Harness token forbidden: ${detail}`);
166
+ (err as any).status = 403;
167
+ (err as any).code = 'TOKEN_FORBIDDEN';
168
+ if (gateContext) {
169
+ await this.pushGateLog({
170
+ ...gateContext,
171
+ passed: false,
172
+ reason: `http_403: ${detail}`,
173
+ }).catch(() => {});
174
+ }
175
+ throw err;
176
+ }
177
+
178
+ if (!response.ok) {
179
+ const detail = await this.tryExtractError(response);
180
+ const err = new Error(`Harness request failed (${response.status}): ${detail}`);
181
+ (err as any).status = response.status;
182
+ if (gateContext) {
183
+ await this.pushGateLog({
184
+ ...gateContext,
185
+ passed: false,
186
+ reason: `http_${response.status}: ${detail}`,
187
+ }).catch(() => {});
188
+ }
189
+ throw err;
190
+ }
191
+
192
+ const data: T = await response.json();
193
+
194
+ // Push cognition log for passed gate
195
+ if (gateContext) {
196
+ await this.pushGateLog({
197
+ ...gateContext,
198
+ passed: true,
199
+ }).catch(() => {});
200
+ }
201
+
202
+ return { success: true, data };
203
+ }
204
+
205
+ /**
206
+ * Convenience: GET
207
+ */
208
+ async get<T = unknown>(path: string, gateContext?: GateContext): Promise<HarnessResponse<T>> {
209
+ return this.request<T>('GET', path, undefined, gateContext);
210
+ }
211
+
212
+ /**
213
+ * Convenience: POST
214
+ */
215
+ async post<T = unknown>(path: string, body?: unknown, gateContext?: GateContext): Promise<HarnessResponse<T>> {
216
+ return this.request<T>('POST', path, body, gateContext);
217
+ }
218
+
219
+ /**
220
+ * Convenience: PUT
221
+ */
222
+ async put<T = unknown>(path: string, body?: unknown, gateContext?: GateContext): Promise<HarnessResponse<T>> {
223
+ return this.request<T>('PUT', path, body, gateContext);
224
+ }
225
+
226
+ // ── Helpers ──────────────────────────────────────────────────────
227
+
228
+ private async tryExtractError(response: Response): Promise<string> {
229
+ try {
230
+ const json = await response.json();
231
+ return json?.error ?? json?.message ?? response.statusText;
232
+ } catch {
233
+ return response.statusText;
234
+ }
235
+ }
236
+
237
+ private async pushGateLog(context: GateContext): Promise<void> {
238
+ try {
239
+ await pushCognitionLog({
240
+ source: 'harness-client',
241
+ type: 'gate_decision',
242
+ gate: context.gate,
243
+ passed: context.passed,
244
+ reason: context.reason,
245
+ score: context.score,
246
+ metadata: context.metadata,
247
+ });
248
+ } catch {
249
+ // fire-and-forget: never let logging fail the request
250
+ }
251
+ }
252
+ }
253
+
254
+ // ── Default-instance singleton ────────────────────────────────────────
255
+ // All top-level function exports share one HarnessClient so token
256
+ // resolution and base-URL are looked up once and cached.
257
+
258
+ let _defaultClient: HarnessClient | null = null;
259
+
260
+ function getDefaultClient(): HarnessClient {
261
+ if (!_defaultClient) _defaultClient = new HarnessClient();
262
+ return _defaultClient;
263
+ }
264
+
265
+ /**
266
+ * Raw-fetch compatibility shim used by auth-commands.ts.
267
+ * Exposes .get() and .post() that make bare fetch() calls with the
268
+ * configured base URL. Callers receive the native Response object
269
+ * (so they can call .ok, .status, .json() etc. themselves).
270
+ * Paths are absolute from the base URL (e.g. '/api/license/heartbeat').
271
+ */
272
+ export const harnessClient = {
273
+ get(path: string, init?: { headers?: Record<string, string> }): Promise<Response> {
274
+ const baseUrl = process.env.ARIA_HARNESS_BASE_URL ?? 'https://harness.ariasos.com';
275
+ return fetch(`${baseUrl}${path}`, {
276
+ method: 'GET',
277
+ headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
278
+ });
279
+ },
280
+ post(path: string, init?: { body?: unknown; headers?: Record<string, string> }): Promise<Response> {
281
+ const baseUrl = process.env.ARIA_HARNESS_BASE_URL ?? 'https://harness.ariasos.com';
282
+ return fetch(`${baseUrl}${path}`, {
283
+ method: 'POST',
284
+ headers: { 'Content-Type': 'application/json', ...(init?.headers ?? {}) },
285
+ body: init?.body ? JSON.stringify(init.body) : undefined,
286
+ });
287
+ },
288
+ };
289
+
290
+ // ── fetchHarness ──────────────────────────────────────────────────────
291
+
292
+ /**
293
+ * POST /api/harness/codex with the per-turn context.
294
+ * Returns the parsed HarnessPacket, or null on any error.
295
+ * No timeout — bare try/catch per no-timeouts doctrine.
296
+ */
297
+ export async function fetchHarness(args: {
298
+ message: string;
299
+ sessionId: string;
300
+ userId?: string;
301
+ cwd?: string;
302
+ modelContext?: number;
303
+ }): Promise<HarnessPacket | null> {
304
+ try {
305
+ const client = getDefaultClient();
306
+ const body: Record<string, unknown> = {
307
+ stage: 'preflight',
308
+ message: args.message,
309
+ actor: 'aria-cli',
310
+ system: 'aria-cli',
311
+ platform: 'cli',
312
+ sessionId: args.sessionId,
313
+ isHamza: args.userId === 'hamza',
314
+ userId: args.userId ?? 'user',
315
+ workingDirectory: args.cwd ?? process.cwd(),
316
+ };
317
+ if (args.modelContext !== undefined) {
318
+ body.modelContext = args.modelContext;
319
+ }
320
+ const result = await client.post<HarnessPacket>('/codex', body);
321
+ if (!result.success || !result.data) return null;
322
+ return result.data;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ // ── combineHarnessContext ─────────────────────────────────────────────
329
+
330
+ /**
331
+ * Stitch a HarnessPacket into a single string for system-prompt injection.
332
+ *
333
+ * Full mode — concat all harness text (harness field) or all chunk contents.
334
+ * Chunked mode — concat all chunks with a progressive-feed note appended.
335
+ *
336
+ * chat.ts:233: `blocks.push(combineHarnessContext(harness))`
337
+ * chat.ts:240-244: surfaces chunked-mode info to stdout (done in chat.ts, not here).
338
+ */
339
+ export function combineHarnessContext(packet: HarnessPacket): string {
340
+ const mode = packet.chunkSummary?.mode ?? 'full';
341
+
342
+ if (mode === 'chunked' && packet.chunks && packet.chunks.length > 0) {
343
+ // In chunked mode we still surface all chunks in priority order so the
344
+ // model gets the full doctrine in one prompt. The chunk-per-turn feed
345
+ // is a Phase 3 CLI responsibility (chat.ts will split across turns).
346
+ const body = packet.chunks
347
+ .slice()
348
+ .sort((a, b) => a.priority - b.priority)
349
+ .map((c) => c.content)
350
+ .join('\n\n');
351
+ const total = packet.chunks.length;
352
+ const chars = packet.chunkSummary?.totalChars ?? body.length;
353
+ const tokens = packet.chunkSummary?.approxTokens ?? Math.ceil(chars / 4);
354
+ return (
355
+ `[ARIA_HARNESS — chunked mode: ${total} chunks, ${chars} chars, ~${tokens} tokens]\n\n` +
356
+ body
357
+ );
358
+ }
359
+
360
+ // Full mode — prefer the flat harness string; fall back to chunk concat.
361
+ if (packet.harness) return `[ARIA_HARNESS]\n${packet.harness}`;
362
+
363
+ if (packet.chunks && packet.chunks.length > 0) {
364
+ return packet.chunks
365
+ .slice()
366
+ .sort((a, b) => a.priority - b.priority)
367
+ .map((c) => c.content)
368
+ .join('\n\n');
369
+ }
370
+
371
+ return '';
372
+ }
373
+
374
+ // ── checkHarnessHealth ────────────────────────────────────────────────
375
+
376
+ /**
377
+ * GET /api/health to verify aria-soul is reachable.
378
+ * Returns true on 200, false on any error or non-200.
379
+ * bin/aria.js uses this to display "Harness: connected" vs "running locally".
380
+ */
381
+ export async function checkHarnessHealth(): Promise<boolean> {
382
+ try {
383
+ const client = getDefaultClient();
384
+ // HarnessClient.request() prefixes /api/harness — use fetch directly
385
+ // so we can hit /api/health without that prefix.
386
+ const baseUrl: string = (client as any).baseUrl ?? process.env.ARIA_HARNESS_BASE_URL ?? 'https://harness.ariasos.com';
387
+ const resp = await fetch(`${baseUrl}/api/health`);
388
+ return resp.ok;
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
393
+
394
+ // ── validateOutput ────────────────────────────────────────────────────
395
+
396
+ /**
397
+ * POST /api/harness/validate with the LLM response text.
398
+ * Returns severity verdict + violations, or null on error.
399
+ * chat.ts calls this after every LLM response to surface output-gate violations.
400
+ *
401
+ * Server response shape (server.ts:1167):
402
+ * { passed, violations[], severity: 'pass'|'warn'|'block', rewritten?, gateTriggers[], eightLens? }
403
+ */
404
+ export async function validateOutput(args: {
405
+ text: string;
406
+ sessionId: string;
407
+ }): Promise<{ severity: 'pass' | 'warn' | 'block'; violations: string[]; rewritten?: string } | null> {
408
+ try {
409
+ const client = getDefaultClient();
410
+ type ValidateResp = {
411
+ passed: boolean;
412
+ violations: string[];
413
+ severity: 'pass' | 'warn' | 'block';
414
+ rewritten?: string;
415
+ gateTriggers?: string[];
416
+ };
417
+ const result = await client.post<ValidateResp>('/validate', {
418
+ text: args.text,
419
+ sessionId: args.sessionId,
420
+ });
421
+ if (!result.success || !result.data) return null;
422
+ const { severity, violations, rewritten } = result.data;
423
+ return { severity, violations: violations ?? [], rewritten };
424
+ } catch {
425
+ return null;
426
+ }
427
+ }
428
+
429
+ // ── getRecentTurns ────────────────────────────────────────────────────
430
+
431
+ /**
432
+ * GET /api/garden/turns?userId=...&limit=... to rehydrate recent context.
433
+ * Returns chronologically-ordered turns (oldest first), empty array on error.
434
+ * chat.ts calls this on session start to restore working-set history.
435
+ *
436
+ * Server response shape (server.ts:1411):
437
+ * { ok, count, turns: [{ session_id, user_message, aria_response, model, latency_ms, created_at, platform, user_id }] }
438
+ */
439
+ export async function getRecentTurns(args: {
440
+ userId: string;
441
+ limit: number;
442
+ }): Promise<Array<{ user_message: string; aria_response: string }>> {
443
+ try {
444
+ const client = getDefaultClient();
445
+ const baseUrl: string = (client as any).baseUrl ?? process.env.ARIA_HARNESS_BASE_URL ?? 'https://harness.ariasos.com';
446
+ const url = `${baseUrl}/api/garden/turns?userId=${encodeURIComponent(args.userId)}&limit=${args.limit}`;
447
+ const resp = await fetch(url);
448
+ if (!resp.ok) return [];
449
+ const json = await resp.json() as { ok?: boolean; turns?: Array<{ user_message: string; aria_response: string }> };
450
+ return Array.isArray(json.turns) ? json.turns : [];
451
+ } catch {
452
+ return [];
453
+ }
454
+ }
@@ -0,0 +1,104 @@
1
+ // Hive registration + heartbeat for ANY client using @aria/connector.
2
+ //
3
+ // Aria's vision: every CLI install, every customer hitting the harness
4
+ // HTTP API, every IDE connector — all register in the Hive on session
5
+ // start and heartbeat through their lifetime. Three goals (per Hamza,
6
+ // 2026-04-26):
7
+ //
8
+ // 1. Compounding evolution — every session contributes signal back
9
+ // to Aria. Hive entries carry capability lists, platform, recent
10
+ // activity. Aria's evolution-engine aggregates across tenants.
11
+ //
12
+ // 2. Protect IP — the Hive endpoint is on aria-soul, behind multi-
13
+ // tenant auth (API keys + rate limit + concurrency guards from
14
+ // commit c68588302). Customers see their own session, not raw
15
+ // doctrine sources.
16
+ //
17
+ // 3. Protect from abuse — heartbeat-based throttling. A tenant
18
+ // whose session count or activity diverges from their tier gets
19
+ // rate-limited at the gate, not at the application layer.
20
+ //
21
+ // This module is decoupled from @aria/openclaw-shared (which is
22
+ // internal-only) so external customers ship the connector with their
23
+ // CLI/app and get hive integration with no internal Aria deps.
24
+
25
+ const HARNESS_URL = process.env.ARIA_HARNESS_URL || 'http://192.168.4.25:30080';
26
+ const HARNESS_TOKEN = process.env.ARIA_HARNESS_TOKEN || '';
27
+
28
+ // Schema matches apps/arias-soul/api/hive/register.ts:
29
+ // POST { session_id, platform, user_id?, context?, capabilities? }
30
+ // → { status, session, hive: { active_sessions, message }, aria: { greeting } }
31
+
32
+ export interface HiveRegisterConfig {
33
+ sessionId: string;
34
+ /** "aria-cli" | "claude-code" | "cursor" | "opencode" | tenant-chosen */
35
+ platform: string;
36
+ /** Tenant identity. Aria-soul correlates to API key on auth-resolved tenant. */
37
+ userId?: string;
38
+ /** What this client can do — surfaces to Aria + other sessions. */
39
+ capabilities?: string[];
40
+ /** Free-form metadata: cwd, version, git branch, etc. */
41
+ context?: Record<string, unknown>;
42
+ }
43
+
44
+ export interface HiveRegisterResult {
45
+ status: 'registered' | 'error';
46
+ session: { id: string; platform: string; last_active: string };
47
+ hive: { active_sessions: number; message: string };
48
+ aria: { greeting: string };
49
+ }
50
+
51
+ export async function registerWithHive(
52
+ cfg: HiveRegisterConfig,
53
+ ): Promise<HiveRegisterResult | null> {
54
+ // Doctrine: no policy timeouts. Real network errors arrive via fetch
55
+ // rejection. If the hive is unreachable, registration is best-effort
56
+ // and the CLI continues — the session works locally even when
57
+ // detached from the central registry.
58
+ try {
59
+ const resp = await fetch(`${HARNESS_URL}/api/hive/register`, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ ...(HARNESS_TOKEN ? { Authorization: `Bearer ${HARNESS_TOKEN}` } : {}),
64
+ },
65
+ body: JSON.stringify({
66
+ session_id: cfg.sessionId,
67
+ platform: cfg.platform,
68
+ user_id: cfg.userId,
69
+ capabilities: cfg.capabilities ?? [],
70
+ context: cfg.context ?? {},
71
+ }),
72
+ });
73
+ if (!resp.ok) return null;
74
+ return (await resp.json()) as HiveRegisterResult;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ // Heartbeat = re-register the same payload. The hive endpoint is
81
+ // idempotent (ON CONFLICT … DO UPDATE in the SQL). Default cadence
82
+ // matches openclaw-shared's 45s — short enough for liveness, long
83
+ // enough to avoid hammering the registry from many clients.
84
+ export interface HeartbeatHandle {
85
+ stop: () => void;
86
+ }
87
+
88
+ export function startHeartbeat(
89
+ cfg: HiveRegisterConfig,
90
+ intervalMs: number = 45_000,
91
+ ): HeartbeatHandle {
92
+ // Fire one immediately so the hive has a fresh entry before the
93
+ // first interval tick. If it fails, schedule the next tick anyway —
94
+ // the hive may come back later.
95
+ registerWithHive(cfg).catch(() => {});
96
+ const timer = setInterval(() => {
97
+ registerWithHive(cfg).catch(() => {});
98
+ }, intervalMs);
99
+ // Don't keep the process alive just for heartbeats.
100
+ if (typeof timer.unref === 'function') timer.unref();
101
+ return {
102
+ stop: () => clearInterval(timer),
103
+ };
104
+ }
package/src/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ export { loadConfig, saveConfig, updateConfig, getDefaultConfig, getConfigPath, getConfigDir } from './config.js';
2
+ export type { AriaConfig, ModelConfig, LinkedRepo, DatabaseConfig, ConnectorState } from './config.js';
3
+
4
+ export { scanCodebase, schemaImageToText } from './codebase-scanner.js';
5
+ export type { SchemaImage, DirectoryNode, Dependency } from './codebase-scanner.js';
6
+
7
+ export { runSetupWizard } from './setup-wizard.js';
8
+
9
+ export { connectClaudeCode, disconnectClaudeCode } from './connectors/claude-code.js';
10
+ export { connectCursor, disconnectCursor } from './connectors/cursor.js';
11
+ export { connectOpenCode, disconnectOpenCode } from './connectors/opencode.js';
12
+ export { connectShell, disconnectShell } from './connectors/shell.js';
13
+
14
+ export { GardenClient } from './garden-client.js';
15
+ export type { GardenStatus } from './garden-client.js';
16
+
17
+ export { generateMcpServer, detectToolCandidates } from './auto-mcp.js';
18
+ export type { ToolCandidate, ToolParameter, McpGenerationResult } from './auto-mcp.js';
19
+
20
+ export { AriaChat } from './chat.js';
21
+
22
+ export { fetchHarness, combineHarnessContext, checkHarnessHealth, validateOutput, getRecentTurns } from './harness-client.js';
23
+ export type { HarnessPacket, HarnessChunk } from './harness-client.js';
24
+
25
+ export { loadPersona, savePersona, updatePersona, buildPersonaBlock } from './persona.js';
26
+ export type { PersonaContext } from './persona.js';