@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,213 @@
1
+ import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import * as path from 'path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import type { AriaConfig } from '../config.js';
6
+
7
+ // ── Hooks shipped with this package ──────────────────────────────────
8
+ // The connector installs these into ~/.claude/hooks/ so Claude Code's
9
+ // per-turn harness fetch + pre-tool gate fire automatically. This is
10
+ // the difference between "static prompt addendum" (what this connector
11
+ // used to do) and "real runtime control plane" (what Aria's harness
12
+ // actually is).
13
+ //
14
+ // Resolved relative to the compiled connector file: at runtime the
15
+ // .js lives at <pkg>/dist/src/connectors/claude-code.js, hooks are at
16
+ // <pkg>/hooks/ — so up three levels then into hooks/.
17
+ const HOOK_FILES = ['aria-harness-via-sdk.mjs', 'aria-pre-tool-gate.mjs'];
18
+ function packageHooksDir(): string {
19
+ const here = path.dirname(fileURLToPath(import.meta.url));
20
+ return path.resolve(here, '..', '..', '..', 'hooks');
21
+ }
22
+
23
+ // Hook wiring for ~/.claude/settings.json. Mirrors what
24
+ // ops/claude-hooks/install.sh writes; this is the customer-facing
25
+ // equivalent that ships with the npm package so anyone running
26
+ // `aria connect claude-code` gets the same behavior as the internal
27
+ // dev install.
28
+ const HOOKS_BLOCK = {
29
+ SessionStart: [{
30
+ hooks: [{
31
+ type: 'command',
32
+ command: 'HOOK_EVENT_NAME=SessionStart node $HOME/.claude/hooks/aria-harness-via-sdk.mjs --mode session',
33
+ timeout: 14,
34
+ statusMessage: 'Fetching Aria harness packet...',
35
+ }],
36
+ }],
37
+ UserPromptSubmit: [{
38
+ hooks: [{
39
+ type: 'command',
40
+ command: 'HOOK_EVENT_NAME=UserPromptSubmit node $HOME/.claude/hooks/aria-harness-via-sdk.mjs --mode turn',
41
+ timeout: 5,
42
+ statusMessage: 'Re-syncing Aria harness...',
43
+ }],
44
+ }],
45
+ PreToolUse: [{
46
+ matcher: 'Bash',
47
+ hooks: [{
48
+ type: 'command',
49
+ command: 'node $HOME/.claude/hooks/aria-pre-tool-gate.mjs',
50
+ timeout: 5,
51
+ }],
52
+ }],
53
+ };
54
+
55
+ function installHooks(claudeDir: string, logs: string[]): void {
56
+ const hooksTargetDir = path.join(claudeDir, 'hooks');
57
+ if (!existsSync(hooksTargetDir)) {
58
+ mkdirSync(hooksTargetDir, { recursive: true });
59
+ }
60
+ const sourceDir = packageHooksDir();
61
+ for (const name of HOOK_FILES) {
62
+ const src = path.join(sourceDir, name);
63
+ if (!existsSync(src)) {
64
+ logs.push(`⚠ hook source missing: ${src} (package may be incomplete)`);
65
+ continue;
66
+ }
67
+ const dst = path.join(hooksTargetDir, name);
68
+ // Back up any existing real file before overwriting (never silently
69
+ // clobber a user's customizations).
70
+ if (existsSync(dst) && !isSamePath(src, dst)) {
71
+ const backup = `${dst}.pre-aria-connect.${Date.now()}`;
72
+ try { copyFileSync(dst, backup); logs.push(`Backed up existing ${name} → ${path.basename(backup)}`); } catch {}
73
+ }
74
+ copyFileSync(src, dst);
75
+ try { chmodSync(dst, 0o755); } catch {}
76
+ logs.push(`Installed hook: ${name}`);
77
+ }
78
+ }
79
+
80
+ function isSamePath(a: string, b: string): boolean {
81
+ try { return path.resolve(a) === path.resolve(b); } catch { return false; }
82
+ }
83
+
84
+ function wireHooksBlock(settings: Record<string, unknown>, logs: string[]): void {
85
+ const hooks = (settings.hooks as Record<string, unknown> | undefined) ?? {};
86
+ // Replace each event's block wholesale so re-running connect stays
87
+ // deterministic and doesn't accumulate duplicate entries.
88
+ for (const [event, block] of Object.entries(HOOKS_BLOCK)) {
89
+ hooks[event] = block;
90
+ }
91
+ settings.hooks = hooks;
92
+ logs.push('Wired hooks (SessionStart, UserPromptSubmit, PreToolUse=Bash) into settings.json');
93
+ }
94
+
95
+ function buildAriaSystemBlock(config: AriaConfig): string {
96
+ const repoList = config.repositories.map((r) => `- ${r.name} (${r.path})`).join('\n');
97
+ const schemaText = Object.entries(config.schemaImages)
98
+ .map(([name, image]) => `[${name}]\n${image}`)
99
+ .join('\n\n');
100
+
101
+ return `<!-- ARIA HARNESS — injected by @aria/connector -->
102
+ You are augmented with Aria's cognitive harness. This provides:
103
+ - Schema image: a live snapshot of the codebase structure, dependencies, and patterns
104
+ - Garden memory: persistent project memory across sessions
105
+ - 8-lens cognition: multi-perspective analysis for every decision
106
+
107
+ [SELF-GATE PROTOCOL]
108
+ Before emitting any claim, verify against these hard constraints:
109
+ 1. Truth over deception — always.
110
+ 2. No harm supersedes any advantage.
111
+ 3. Sacred trust — never betray.
112
+ 4. Power obligates service.
113
+ 5. Reflection before action.
114
+
115
+ [CONNECTED REPOSITORIES]
116
+ ${repoList || '(none linked yet — run `aria repo link <path>`)'}
117
+
118
+ [SCHEMA IMAGES]
119
+ ${schemaText || '(no schema images yet — run `aria repo scan`)'}
120
+
121
+ [INSTRUCTIONS]
122
+ - Refer to the schema image for codebase context before answering.
123
+ - Use garden memory to maintain continuity across sessions.
124
+ - Apply the 8-lens analysis for complex decisions.
125
+ - Flag uncertainty; never fabricate code or facts.
126
+ <!-- END ARIA HARNESS -->`;
127
+ }
128
+
129
+ export async function connectClaudeCode(config: AriaConfig): Promise<string[]> {
130
+ const logs: string[] = [];
131
+ const claudeDir = path.join(homedir(), '.claude');
132
+
133
+ if (!existsSync(claudeDir)) {
134
+ const { mkdirSync } = await import('fs');
135
+ mkdirSync(claudeDir, { recursive: true });
136
+ logs.push(`Created ${claudeDir}`);
137
+ }
138
+
139
+ const settingsPath = path.join(claudeDir, 'settings.json');
140
+ let settings: Record<string, unknown> = {};
141
+
142
+ if (existsSync(settingsPath)) {
143
+ try {
144
+ settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
145
+ } catch {
146
+ logs.push('Existing settings.json was corrupt — starting fresh');
147
+ }
148
+ }
149
+
150
+ const ariaBlock = buildAriaSystemBlock(config);
151
+
152
+ const existing = typeof settings.systemPromptPrefix === 'string'
153
+ ? settings.systemPromptPrefix
154
+ : '';
155
+
156
+ if (existing.includes('ARIA HARNESS')) {
157
+ logs.push('Aria harness prefix already present — skipping prompt injection');
158
+ } else {
159
+ settings.systemPromptPrefix = existing
160
+ ? `${ariaBlock}\n\n${existing}`
161
+ : ariaBlock;
162
+ logs.push('Injected Aria harness into Claude Code systemPromptPrefix');
163
+ }
164
+
165
+ // Install hook scripts + wire them into settings.hooks block. This is
166
+ // what makes the connector a real runtime control plane rather than
167
+ // just a system-prompt addendum.
168
+ installHooks(claudeDir, logs);
169
+ wireHooksBlock(settings, logs);
170
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
171
+
172
+ const configJsonPath = path.join(claudeDir, 'config.json');
173
+ if (existsSync(configJsonPath)) {
174
+ try {
175
+ const claudeConfig = JSON.parse(readFileSync(configJsonPath, 'utf-8'));
176
+ if (typeof claudeConfig.additionalDirectories === 'undefined') {
177
+ claudeConfig.additionalDirectories = [];
178
+ }
179
+ const ariaDir = path.join(homedir(), '.aria');
180
+ if (!claudeConfig.additionalDirectories.includes(ariaDir)) {
181
+ claudeConfig.additionalDirectories.push(ariaDir);
182
+ writeFileSync(configJsonPath, JSON.stringify(claudeConfig, null, 2));
183
+ logs.push('Added ~/.aria to Claude Code additional directories');
184
+ }
185
+ } catch {
186
+ // config.json may not be valid JSON or not exist
187
+ }
188
+ }
189
+
190
+ return logs;
191
+ }
192
+
193
+ export async function disconnectClaudeCode(): Promise<string[]> {
194
+ const logs: string[] = [];
195
+ const settingsPath = path.join(homedir(), '.claude', 'settings.json');
196
+
197
+ if (!existsSync(settingsPath)) {
198
+ logs.push('No Claude Code settings found');
199
+ return logs;
200
+ }
201
+
202
+ const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
203
+ if (typeof settings.systemPromptPrefix === 'string') {
204
+ settings.systemPromptPrefix = settings.systemPromptPrefix.replace(
205
+ /<!-- ARIA HARNESS.*?<!-- END ARIA HARNESS -->/s,
206
+ '',
207
+ ).trim();
208
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
209
+ logs.push('Removed Aria harness from Claude Code settings');
210
+ }
211
+
212
+ return logs;
213
+ }
@@ -0,0 +1,75 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import * as path from 'path';
3
+ import type { AriaConfig } from '../config.js';
4
+
5
+ const CURSOR_RULES_HEADER = `// ARIA HARNESS — injected by @aria/connector
6
+ // Aria provides: schema image awareness + garden memory + 8-lens cognition
7
+
8
+ `;
9
+
10
+ function buildCursorRules(config: AriaConfig): string {
11
+ const lines: string[] = [CURSOR_RULES_HEADER];
12
+
13
+ if (config.repositories.length > 0) {
14
+ lines.push('// Connected repositories:');
15
+ for (const repo of config.repositories) {
16
+ lines.push(`// ${repo.name} — ${repo.path}`);
17
+ }
18
+ lines.push('');
19
+ }
20
+
21
+ lines.push('// Always reference the schema image for codebase context.');
22
+ lines.push('// Apply the 8-lens analysis for complex architectural decisions.');
23
+ lines.push('// Persist key decisions to garden memory for session continuity.');
24
+ lines.push('');
25
+ lines.push('// Self-gate: verify claims before emitting. Flag uncertainty.');
26
+
27
+ return lines.join('\n');
28
+ }
29
+
30
+ export async function connectCursor(config: AriaConfig, repoPath?: string): Promise<string[]> {
31
+ const logs: string[] = [];
32
+ const targetPath = repoPath || process.cwd();
33
+ const rulesPath = path.join(targetPath, '.cursorrules');
34
+
35
+ const ariaBlock = buildCursorRules(config);
36
+
37
+ if (existsSync(rulesPath)) {
38
+ const existing = readFileSync(rulesPath, 'utf-8');
39
+ if (existing.includes('ARIA HARNESS')) {
40
+ logs.push('Aria harness already in .cursorrules — skipping');
41
+ return logs;
42
+ }
43
+ writeFileSync(rulesPath, ariaBlock + '\n' + existing);
44
+ logs.push(`Prepended Aria harness to ${rulesPath}`);
45
+ } else {
46
+ writeFileSync(rulesPath, ariaBlock);
47
+ logs.push(`Created ${rulesPath} with Aria harness`);
48
+ }
49
+
50
+ const cursorRulesDir = path.join(targetPath, '.cursor', 'rules');
51
+ if (existsSync(cursorRulesDir)) {
52
+ const ariaRulePath = path.join(cursorRulesDir, 'aria-harness.md');
53
+ if (!existsSync(ariaRulePath)) {
54
+ writeFileSync(ariaRulePath, buildCursorRules(config));
55
+ logs.push(`Created ${ariaRulePath}`);
56
+ }
57
+ }
58
+
59
+ return logs;
60
+ }
61
+
62
+ export async function disconnectCursor(repoPath?: string): Promise<string[]> {
63
+ const logs: string[] = [];
64
+ const targetPath = repoPath || process.cwd();
65
+ const rulesPath = path.join(targetPath, '.cursorrules');
66
+
67
+ if (existsSync(rulesPath)) {
68
+ let content = readFileSync(rulesPath, 'utf-8');
69
+ content = content.replace(/\/\/ ARIA HARNESS[\s\S]*?\n\n/, '');
70
+ writeFileSync(rulesPath, content);
71
+ logs.push(`Removed Aria harness from ${rulesPath}`);
72
+ }
73
+
74
+ return logs;
75
+ }
@@ -0,0 +1,115 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import * as path from 'path';
4
+ import type { AriaConfig } from '../config.js';
5
+
6
+ export async function connectOpenCode(config: AriaConfig): Promise<string[]> {
7
+ const logs: string[] = [];
8
+ const opencodeDir = path.join(homedir(), '.opencode');
9
+
10
+ if (!existsSync(opencodeDir)) {
11
+ logs.push('No ~/.opencode directory found — OpenCode may not be installed');
12
+ return logs;
13
+ }
14
+
15
+ const configPath = path.join(opencodeDir, 'config.json');
16
+ if (existsSync(configPath)) {
17
+ try {
18
+ const opencodeConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
19
+ let modified = false;
20
+
21
+ if (Array.isArray(opencodeConfig.plugins)) {
22
+ if (!opencodeConfig.plugins.includes('@aria/connector')) {
23
+ opencodeConfig.plugins.push('@aria/connector');
24
+ modified = true;
25
+ logs.push('Added @aria/connector to OpenCode plugins');
26
+ }
27
+ }
28
+
29
+ if (!opencodeConfig.agentsMdPath) {
30
+ opencodeConfig.agentsMdPath = path.join(homedir(), '.aria', 'AGENTS.md');
31
+ modified = true;
32
+ logs.push('Set OpenCode AGENTS.md path to ~/.aria/AGENTS.md');
33
+ }
34
+
35
+ if (modified) {
36
+ writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
37
+ } else {
38
+ logs.push('OpenCode already configured for Aria');
39
+ }
40
+ } catch {
41
+ logs.push('Failed to parse OpenCode config.json');
42
+ }
43
+ }
44
+
45
+ const ariaDir = path.join(homedir(), '.aria');
46
+ if (!existsSync(ariaDir)) {
47
+ mkdirSync(ariaDir, { recursive: true });
48
+ }
49
+
50
+ const ariaAgentsPath = path.join(ariaDir, 'AGENTS.md');
51
+ const agentsContent = buildOpenCodeAgentsMd(config);
52
+ writeFileSync(ariaAgentsPath, agentsContent);
53
+ logs.push(`Wrote Aria harness AGENTS.md to ${ariaAgentsPath}`);
54
+
55
+ return logs;
56
+ }
57
+
58
+ export async function disconnectOpenCode(): Promise<string[]> {
59
+ const logs: string[] = [];
60
+ const configPath = path.join(homedir(), '.opencode', 'config.json');
61
+
62
+ if (existsSync(configPath)) {
63
+ try {
64
+ const opencodeConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
65
+ if (Array.isArray(opencodeConfig.plugins)) {
66
+ opencodeConfig.plugins = opencodeConfig.plugins.filter(
67
+ (p: string) => p !== '@aria/connector',
68
+ );
69
+ writeFileSync(configPath, JSON.stringify(opencodeConfig, null, 2));
70
+ logs.push('Removed @aria/connector from OpenCode plugins');
71
+ }
72
+ } catch {
73
+ logs.push('Failed to update OpenCode config');
74
+ }
75
+ }
76
+
77
+ return logs;
78
+ }
79
+
80
+ function buildOpenCodeAgentsMd(config: AriaConfig): string {
81
+ const repoList = config.repositories.map((r) => `- ${r.name} (${r.path})`).join('\n');
82
+ const schemaText = Object.entries(config.schemaImages)
83
+ .map(([name, image]) => `### ${name}\n${image}`)
84
+ .join('\n\n');
85
+
86
+ return `# Aria Harness — AGENTS.md
87
+
88
+ Automatically injected by @aria/connector. This file provides Aria's cognitive harness
89
+ to OpenCode sessions.
90
+
91
+ ## Connected Repositories
92
+ ${repoList || '(none linked yet)'}
93
+
94
+ ## Schema Images
95
+ ${schemaText || '(no schema images yet — run \`aria repo scan\`)'}
96
+
97
+ ## Self-Gate Protocol
98
+ 1. Truth over deception — always.
99
+ 2. No harm supersedes any advantage.
100
+ 3. Sacred trust — never betray.
101
+ 4. Power obligates service.
102
+ 5. Reflection before action.
103
+
104
+ ## 8-Lens Cognition
105
+ Apply multi-perspective analysis for every decision:
106
+ - Truth lens: Is this factually correct?
107
+ - Harm lens: Could this cause damage?
108
+ - Trust lens: Does this maintain sacred trust?
109
+ - Power lens: Am I using capability responsibly?
110
+ - Reflection lens: Have I thought deeply enough?
111
+ - Context lens: Do I have full context?
112
+ - Impact lens: What are second-order effects?
113
+ - Beauty lens: Is this elegant and sustainable?
114
+ `;
115
+ }
@@ -0,0 +1,72 @@
1
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { homedir } from 'os';
4
+ import * as path from 'path';
5
+ import type { AriaConfig } from '../config.js';
6
+
7
+ export async function connectShell(
8
+ toolName: string,
9
+ config: AriaConfig,
10
+ ): Promise<string[]> {
11
+ const logs: string[] = [];
12
+
13
+ let toolPath: string;
14
+ try {
15
+ toolPath = execSync(`which ${toolName}`, { encoding: 'utf-8' }).trim();
16
+ } catch {
17
+ logs.push(`Tool "${toolName}" not found in PATH`);
18
+ return logs;
19
+ }
20
+
21
+ const wrapperDir = path.join(homedir(), '.aria', 'wrappers');
22
+ const wrapperPath = path.join(wrapperDir, toolName);
23
+
24
+ if (!existsSync(wrapperDir)) {
25
+ mkdirSync(wrapperDir, { recursive: true });
26
+ }
27
+
28
+ const ariaBlock = buildShellWrapperBlock(config, toolName);
29
+ const script = `#!/usr/bin/env bash
30
+ # ARIA HARNESS WRAPPER — generated by @aria/connector
31
+ # Wraps ${toolName} with Aria's cognitive harness
32
+ ${ariaBlock}
33
+ exec ${toolPath} "$@"
34
+ `;
35
+
36
+ writeFileSync(wrapperPath, script);
37
+ chmodSync(wrapperPath, 0o755);
38
+ logs.push(`Created wrapper: ${wrapperPath}`);
39
+ logs.push(`Add ~/.aria/wrappers to your PATH to use it:`);
40
+ logs.push(` export PATH="$HOME/.aria/wrappers:$PATH"`);
41
+
42
+ return logs;
43
+ }
44
+
45
+ function buildShellWrapperBlock(config: AriaConfig, toolName: string): string {
46
+ const repoList = config.repositories.map((r) => r.name).join(', ');
47
+
48
+ return `
49
+ # Aria Harness active for: ${toolName}
50
+ # Connected repos: ${repoList || 'none'}
51
+ # Schema images loaded for: ${Object.keys(config.schemaImages).join(', ') || 'none'}
52
+ #
53
+ # The Aria harness provides:
54
+ # - Schema image: live codebase snapshot in tool context
55
+ # - Garden memory: persistent project memory
56
+ # - 8-lens cognition: multi-perspective analysis
57
+ #
58
+ # Self-gate: Truth > Harm prevention > Trust > Responsible power > Reflection
59
+ `;
60
+ }
61
+
62
+ export async function disconnectShell(toolName: string): Promise<string[]> {
63
+ const logs: string[] = [];
64
+ const wrapperPath = path.join(homedir(), '.aria', 'wrappers', toolName);
65
+
66
+ if (existsSync(wrapperPath)) {
67
+ unlinkSync(wrapperPath);
68
+ logs.push(`Removed wrapper: ${wrapperPath}`);
69
+ }
70
+
71
+ return logs;
72
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Garden HTTP client — talks to Aria's memory garden service.
3
+ * The garden stores project context, schema images, and conversation threads.
4
+ */
5
+
6
+ const DEFAULT_GARDEN_URL = 'http://aria-soul.aria.svc.cluster.local:3000';
7
+
8
+ export interface GardenStatus {
9
+ connected: boolean;
10
+ message: string;
11
+ threadCount?: number;
12
+ lastSync?: string;
13
+ }
14
+
15
+ export class GardenClient {
16
+ private baseUrl: string;
17
+
18
+ constructor(baseUrl?: string) {
19
+ this.baseUrl = baseUrl || process.env.ARIA_GARDEN_URL || DEFAULT_GARDEN_URL;
20
+ }
21
+
22
+ async health(): Promise<boolean> {
23
+ try {
24
+ const res = await fetch(`${this.baseUrl}/health`, { signal: AbortSignal.timeout(5000) });
25
+ return res.ok;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ async chat(message: string, userId?: string): Promise<string> {
32
+ const res = await fetch(`${this.baseUrl}/chat`, {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ ...(process.env.ARIA_API_KEY
37
+ ? { Authorization: `Bearer ${process.env.ARIA_API_KEY}` }
38
+ : {}),
39
+ },
40
+ body: JSON.stringify({
41
+ message,
42
+ userId: userId || 'aria-connector',
43
+ metadata: { platform: 'connector' },
44
+ }),
45
+ });
46
+
47
+ if (!res.ok) {
48
+ throw new Error(`Garden chat failed: ${res.status} ${res.statusText}`);
49
+ }
50
+
51
+ const data = await res.json() as { response?: string; message?: string; text?: string };
52
+ return data.response || data.message || data.text || JSON.stringify(data);
53
+ }
54
+
55
+ async status(): Promise<GardenStatus> {
56
+ const alive = await this.health();
57
+ if (!alive) {
58
+ return { connected: false, message: 'Garden service is unreachable' };
59
+ }
60
+
61
+ try {
62
+ const res = await fetch(`${this.baseUrl}/health`, {
63
+ signal: AbortSignal.timeout(3000),
64
+ });
65
+ const data = await res.json().catch(() => ({})) as Record<string, unknown>;
66
+ return {
67
+ connected: true,
68
+ message: 'Garden service is healthy',
69
+ threadCount: typeof data.threadCount === 'number' ? data.threadCount : undefined,
70
+ lastSync: typeof data.lastSync === 'string' ? data.lastSync : undefined,
71
+ };
72
+ } catch {
73
+ return { connected: true, message: 'Garden responded but status unavailable' };
74
+ }
75
+ }
76
+
77
+ async storeSchemaImage(imageText: string, projectName: string): Promise<boolean> {
78
+ try {
79
+ const res = await fetch(`${this.baseUrl}/chat`, {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ ...(process.env.ARIA_API_KEY
84
+ ? { Authorization: `Bearer ${process.env.ARIA_API_KEY}` }
85
+ : {}),
86
+ },
87
+ body: JSON.stringify({
88
+ message: `Schema image for ${projectName}:\n\n${imageText}`,
89
+ userId: 'aria-connector',
90
+ metadata: { platform: 'connector', action: 'store-schema' },
91
+ }),
92
+ });
93
+ return res.ok;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,108 @@
1
+ // Garden Control Plane — Living Memory Fabric (CLI edition)
2
+ // Replaces static system prompt injection with turn-by-turn garden integration.
3
+ // Every turn: read relevant memories, generate response, write new memories.
4
+ // Projects persist forever. Context is infinite. The harness improves per turn.
5
+
6
+ const GARDEN_URL = process.env.GARDEN_SERVICE_URL || 'http://127.0.0.1:8097';
7
+
8
+ export interface GardenMemory {
9
+ id: string;
10
+ type: 'conversation' | 'decision' | 'code' | 'insight' | 'project_state' | 'evolution';
11
+ content: string;
12
+ intensity: number;
13
+ timestamp: string;
14
+ sessionId: string;
15
+ userId?: string;
16
+ projectId?: string;
17
+ tags: string[];
18
+ }
19
+
20
+ // ── WRITE: Plant memories after every turn ──
21
+ export async function plantGardenMemory(
22
+ type: GardenMemory['type'],
23
+ content: string,
24
+ intensity: number,
25
+ sessionId: string,
26
+ userId?: string,
27
+ projectId?: string,
28
+ tags: string[] = []
29
+ ): Promise<boolean> {
30
+ try {
31
+ const resp = await fetch(`${GARDEN_URL}/garden/fire`, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({
35
+ type,
36
+ content: content.slice(0, 2000),
37
+ intensity: Math.max(0.1, Math.min(1, intensity)),
38
+ sessionId,
39
+ userId,
40
+ projectId,
41
+ tags,
42
+ }),
43
+ signal: AbortSignal.timeout(3000),
44
+ });
45
+
46
+ const data = await resp.json().catch(() => ({}));
47
+ return resp.ok && data?.ok !== false;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ // ── TURN-BY-TURN: Complete cycle ──
54
+ export async function gardenTurnCycle(input: {
55
+ message: string;
56
+ response: string;
57
+ sessionId: string;
58
+ userId?: string;
59
+ projectId?: string;
60
+ gateViolations?: string[];
61
+ evolutionEvents?: string[];
62
+ }): Promise<void> {
63
+ await plantGardenMemory(
64
+ 'conversation',
65
+ `User: ${input.message.slice(0, 500)}\nAria: ${input.response.slice(0, 1000)}`,
66
+ 0.6,
67
+ input.sessionId,
68
+ input.userId,
69
+ input.projectId,
70
+ ['turn', 'conversation']
71
+ );
72
+
73
+ if (input.gateViolations?.length) {
74
+ await plantGardenMemory(
75
+ 'evolution',
76
+ `Gate violations: ${input.gateViolations.join(', ')}`,
77
+ 0.8,
78
+ input.sessionId,
79
+ input.userId,
80
+ input.projectId,
81
+ ['gate', 'evolution']
82
+ );
83
+ }
84
+
85
+ if (input.evolutionEvents?.length) {
86
+ await plantGardenMemory(
87
+ 'evolution',
88
+ `Evolution: ${input.evolutionEvents.join('; ')}`,
89
+ 0.7,
90
+ input.sessionId,
91
+ input.userId,
92
+ input.projectId,
93
+ ['evolution']
94
+ );
95
+ }
96
+
97
+ if (input.projectId) {
98
+ await plantGardenMemory(
99
+ 'project_state',
100
+ `Project active. Last message: ${input.message.slice(0, 200)}`,
101
+ 0.4,
102
+ input.sessionId,
103
+ input.userId,
104
+ input.projectId,
105
+ ['project', 'checkpoint']
106
+ );
107
+ }
108
+ }