habi-agent 0.1.3 → 0.1.5

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/src/cli.ts CHANGED
@@ -1,27 +1,47 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * HABI Agent CLI
3
+ * HABI Agent CLI v0.1.5
4
+ *
4
5
  * Usage:
5
- * habi-agent start start the local identity daemon
6
- * habi-agent status — check if agent is running
7
- * habi-agent identity — print hardware identity
8
- * habi-agent init — generate a Project Context File
9
- * habi-agent verify — verify current session
6
+ * npx habi-agent@latest start --key <key> --name <name>
7
+ * Installs HABI as a persistent background service, registers the device,
8
+ * injects Claude Code MCP config, and exits. Terminal can be closed.
9
+ *
10
+ * habi-agent stop Stop the background service
11
+ * habi-agent restart Restart the background service
12
+ * habi-agent uninstall Remove the background service
13
+ * habi-agent status Show service + agent health
14
+ * habi-agent identity Print hardware fingerprint
15
+ * habi-agent logs Tail the agent log file
10
16
  */
11
17
 
12
- import { HabiLocalServer } from './server';
13
- import { readHardwareIdentity, generateSessionFingerprint, sha256 } from './hardware';
14
- import { findContextFile, loadContext } from './context';
15
- import { registerDevice, sendHeartbeat } from './cloud';
16
- import * as fs from 'fs';
17
- import * as path from 'path';
18
- import * as http from 'http';
19
- import * as os from 'os';
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import * as http from 'http';
21
+ import * as os from 'os';
22
+ import * as readline from 'readline';
23
+ import { execSync, spawnSync } from 'child_process';
20
24
 
21
- const AGENT_PORT = 7432;
22
- const command = process.argv[2] || 'start';
25
+ import { readHardwareIdentity } from './hardware';
26
+ import { registerDevice } from './cloud';
27
+ import {
28
+ saveConfig,
29
+ configExists,
30
+ installService,
31
+ uninstallService,
32
+ stopService,
33
+ restartService,
34
+ isServiceInstalled,
35
+ } from './service';
36
+
37
+ const AGENT_PORT = 7432;
38
+ const CONFIG_DIR = path.join(os.homedir(), '.habi');
39
+ const LOG_PATH = path.join(CONFIG_DIR, 'habi.log');
40
+ const HABI_VERSION = '0.1.5';
41
+
42
+ // ── Arg parsing ─────────────────────────────────────────────────────────────
43
+ const command = process.argv[2] || 'help';
23
44
 
24
- // Parse --key and --name arguments (works on all platforms)
25
45
  const keyArgIdx = process.argv.indexOf('--key');
26
46
  if (keyArgIdx !== -1 && process.argv[keyArgIdx + 1]) {
27
47
  process.env.HABI_KEY = process.argv[keyArgIdx + 1];
@@ -31,8 +51,8 @@ if (nameArgIdx !== -1 && process.argv[nameArgIdx + 1]) {
31
51
  process.env.HABI_DEVICE_NAME = process.argv[nameArgIdx + 1];
32
52
  }
33
53
 
34
- // ── Utility: ping the running agent ────────────────────────────────────────
35
- function pingAgent(p: string = '/health'): Promise<Record<string, unknown>> {
54
+ // ── Utility ──────────────────────────────────────────────────────────────────
55
+ function pingAgent(p = '/health'): Promise<Record<string, unknown>> {
36
56
  return new Promise((resolve, reject) => {
37
57
  const req = http.get(
38
58
  { host: '127.0.0.1', port: AGENT_PORT, path: p },
@@ -41,7 +61,7 @@ function pingAgent(p: string = '/health'): Promise<Record<string, unknown>> {
41
61
  res.on('data', (chunk: Buffer) => body += chunk.toString());
42
62
  res.on('end', () => {
43
63
  try { resolve(JSON.parse(body)); }
44
- catch { reject(new Error('Invalid JSON from agent')); }
64
+ catch { reject(new Error('Invalid JSON')); }
45
65
  });
46
66
  }
47
67
  );
@@ -50,225 +70,310 @@ function pingAgent(p: string = '/health'): Promise<Record<string, unknown>> {
50
70
  });
51
71
  }
52
72
 
53
- async function cmdStart() {
54
- console.log('\n┌─────────────────────────────────────────────┐');
55
- console.log('│ HABI Local Identity Agent v0.1.0 │');
56
- console.log('│ On Spot Solutions LLC hardware-first AI │');
57
- console.log('└─────────────────────────────────────────────┘\n');
73
+ function ask(question: string): Promise<string> {
74
+ return new Promise((resolve) => {
75
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
76
+ rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); });
77
+ });
78
+ }
79
+
80
+ function banner() {
81
+ console.log(`
82
+ ┌─────────────────────────────────────────────────┐
83
+ │ HABI Local Identity Agent v${HABI_VERSION} │
84
+ │ On Spot Solutions LLC — hardware-first AI │
85
+ │ imhabi.com │
86
+ └─────────────────────────────────────────────────┘`);
87
+ }
88
+
89
+ // ── Claude Code detection & MCP injection ───────────────────────────────────
90
+ function claudeCodePaths(): string[] {
91
+ const home = os.homedir();
92
+ return [
93
+ path.join(home, '.claude', 'settings.json'),
94
+ path.join(home, 'AppData', 'Roaming', 'Claude', 'settings.json'), // Windows
95
+ path.join(home, 'Library', 'Application Support', 'Claude', 'settings.json'), // macOS alt
96
+ ];
97
+ }
98
+
99
+ function findClaudeSettings(): string | null {
100
+ return claudeCodePaths().find(p => fs.existsSync(p)) || null;
101
+ }
58
102
 
59
- // Check if already running
103
+ function isClaudeCodeInstalled(): boolean {
60
104
  try {
61
- await pingAgent();
62
- console.log('⚠ HABI agent is already running on port 7432.');
63
- console.log(' Run: habi-agent status to check it.');
64
- process.exit(0);
65
- } catch {
66
- // Not running proceed
105
+ const r = spawnSync(os.platform() === 'win32' ? 'where' : 'which', ['claude'], { encoding: 'utf8' });
106
+ return r.status === 0 && r.stdout.trim().length > 0;
107
+ } catch { return false; }
108
+ }
109
+
110
+ function injectMcpConfig(habiKey: string): { injected: boolean; path: string } {
111
+ const settingsPath = findClaudeSettings() || claudeCodePaths()[0];
112
+ const settingsDir = path.dirname(settingsPath);
113
+
114
+ if (!fs.existsSync(settingsDir)) fs.mkdirSync(settingsDir, { recursive: true });
115
+
116
+ let existing: Record<string, unknown> = {};
117
+ if (fs.existsSync(settingsPath)) {
118
+ try { existing = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); }
119
+ catch { existing = {}; }
67
120
  }
68
121
 
69
- const server = new HabiLocalServer();
122
+ const mcpServers = (existing.mcpServers || {}) as Record<string, unknown>;
70
123
 
71
- // Graceful shutdown
72
- const shutdown = () => {
73
- console.log('\n[HABI] Shutting down agent...');
74
- server.stop();
75
- process.exit(0);
124
+ // Inject or update HABI entry
125
+ mcpServers['habi'] = {
126
+ type: 'url',
127
+ url: 'https://dokcixjitwihtbdlkbdb.supabase.co/functions/v1/habi-mcp',
128
+ headers: { 'x-habi-key': habiKey },
76
129
  };
77
- process.on('SIGINT', shutdown);
78
- process.on('SIGTERM', shutdown);
79
130
 
131
+ existing.mcpServers = mcpServers;
132
+ fs.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
133
+ return { injected: true, path: settingsPath };
134
+ }
135
+
136
+ // ── COMMANDS ─────────────────────────────────────────────────────────────────
137
+
138
+ async function cmdStart() {
139
+ banner();
140
+
141
+ const habiKey = process.env.HABI_KEY || '';
142
+ const deviceName = process.env.HABI_DEVICE_NAME || os.hostname();
143
+
144
+ if (!habiKey) {
145
+ console.error('\n✗ --key is required. Get your key from imhabi.com\n');
146
+ process.exit(1);
147
+ }
148
+
149
+ console.log('\n[HABI] Reading hardware identity...');
150
+ const hw = readHardwareIdentity();
151
+ console.log(`[HABI] Hardware: ${hw.fingerprint} (${hw.type}) on ${hw.iface}`);
152
+
153
+ // ── 1. Register device ───────────────────────────────────────────────────
154
+ console.log(`[HABI] Registering "${deviceName}" with imhabi.com...`);
155
+ process.env.HABI_KEY = habiKey;
156
+ process.env.HABI_DEVICE_NAME = deviceName;
157
+
158
+ const cloudState = await registerDevice({
159
+ friendlyName: deviceName,
160
+ hardwareFingerprint: hw.fingerprint,
161
+ fingerprintType: hw.type,
162
+ agentVersion: HABI_VERSION,
163
+ osPlatform: os.platform(),
164
+ });
165
+
166
+ if (cloudState) {
167
+ console.log(`[HABI] ✓ "${deviceName}" registered — visible on imhabi.com`);
168
+ } else {
169
+ console.error('[HABI] ✗ Registration failed — check your key and internet connection');
170
+ process.exit(1);
171
+ }
172
+
173
+ // ── 2. Save config for daemon ────────────────────────────────────────────
174
+ saveConfig(habiKey, deviceName);
175
+ console.log(`[HABI] ✓ Config saved to ~/.habi/config.json`);
176
+
177
+ // ── 3. Install background service ───────────────────────────────────────
178
+ console.log(`[HABI] Installing background service...`);
80
179
  try {
81
- await server.start();
180
+ await installService();
181
+ console.log(`[HABI] ✓ Background service installed — runs at every login`);
182
+ console.log(`[HABI] ✓ You can close this terminal. HABI keeps running.`);
183
+ } catch (err) {
184
+ console.error(`[HABI] ✗ Service install failed: ${err}`);
185
+ console.log(`[HABI] You can still run manually: npx habi-agent@latest daemon`);
186
+ }
82
187
 
83
- // ── Cloud registration ──────────────────────────────────────────────
84
- const hw = readHardwareIdentity();
85
- const habiKey = process.env.HABI_KEY || process.env.HABI_OWNER_EMAIL || "";
86
- const friendlyName = process.env.HABI_DEVICE_NAME || os.hostname();
188
+ // ── 4. Claude Code detection ─────────────────────────────────────────────
189
+ console.log('\n[HABI] Checking for Claude Code...');
190
+ const claudeInstalled = isClaudeCodeInstalled();
87
191
 
88
- if (!habiKey) {
89
- console.log('[HABI Cloud] HABI_OWNER_EMAIL not set — running in local-only mode.');
90
- console.log('[HABI Cloud] Set export HABI_OWNER_EMAIL=you@example.com to enable cloud registration.\n');
91
- } else {
92
- console.log(`[HABI Cloud] Registering "${friendlyName}" via HABI key...`);
93
- const cloudState = await registerDevice({
94
- friendlyName,
95
- hardwareFingerprint: hw.fingerprint,
96
- fingerprintType: hw.type,
97
- agentVersion: '0.1.0',
98
- osPlatform: os.platform(),
99
- });
100
-
101
- if (cloudState) {
102
- console.log(`[HABI Cloud] ✓ Device registered — ID: ${cloudState.deviceId.slice(0, 8)}...`);
103
- console.log(`[HABI Cloud] ✓ Session token active until ${new Date(cloudState.sessionTokenExpiresAt).toLocaleTimeString()}\n`);
104
-
105
- // ── Heartbeat loop every 30s ──────────────────────────────────
106
- let currentToken = cloudState.sessionToken;
107
- setInterval(async () => {
108
- const hb = await sendHeartbeat({
109
- deviceId: cloudState.deviceId,
110
- sessionToken: currentToken,
111
- hardwareFingerprint: hw.fingerprint,
112
- });
113
- if (hb) {
114
- currentToken = hb.sessionToken;
115
- if (hb.pendingJobs && hb.pendingJobs.length > 0) {
116
- console.log(`[HABI Cloud] ${hb.pendingJobs.length} pending job(s) received from dispatch queue.`);
117
- // Job execution handled in Phase 2 — log for now
118
- for (const job of hb.pendingJobs) {
119
- console.log(`[HABI Cloud] Job ${job.id.slice(0, 8)}: ${JSON.stringify(job.payload).slice(0, 80)}`);
120
- }
121
- }
122
- }
123
- }, 30000);
192
+ if (!claudeInstalled) {
193
+ console.log('[HABI] Claude Code not detected.');
194
+ const answer = await ask('[HABI] Install Claude Code now to enable MCP enforcement? (Y/n) ');
195
+ if (answer.toLowerCase() !== 'n') {
196
+ console.log('[HABI] Installing Claude Code (npm install -g @anthropic-ai/claude-code)...');
197
+ try {
198
+ execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'inherit' });
199
+ console.log('[HABI] ✓ Claude Code installed');
200
+ const mcp = injectMcpConfig(habiKey);
201
+ console.log(`[HABI] ✓ HABI MCP server configured → ${mcp.path}`);
202
+ } catch (err) {
203
+ console.error(`[HABI] ✗ Claude Code install failed: ${err}`);
204
+ console.log('[HABI] Install manually: npm install -g @anthropic-ai/claude-code');
124
205
  }
206
+ } else {
207
+ console.log('[HABI] Skipping Claude Code install. MCP enforcement disabled.');
125
208
  }
126
-
127
- // Keep alive
128
- await new Promise(() => {});
129
- } catch (err) {
130
- console.error(`\n✗ HABI agent failed to start:\n ${err}\n`);
131
- process.exit(1);
209
+ } else {
210
+ console.log('[HABI] Claude Code detected');
211
+ const mcp = injectMcpConfig(habiKey);
212
+ console.log(`[HABI] HABI MCP server configured → ${mcp.path}`);
132
213
  }
214
+
215
+ // ── 5. Done ──────────────────────────────────────────────────────────────
216
+ console.log(`
217
+ ┌─────────────────────────────────────────────────────────────┐
218
+ │ ✓ HABI is running as a background service │
219
+ │ │
220
+ │ Machine: ${(deviceName + ' ').padEnd(44)} │
221
+ │ Hardware: ${(hw.fingerprint + ' ').padEnd(44)} │
222
+ │ Dashboard: https://imhabi.com │
223
+ │ Logs: ~/.habi/habi.log │
224
+ │ │
225
+ │ Commands: │
226
+ │ habi-agent status — check service health │
227
+ │ habi-agent stop — stop the service │
228
+ │ habi-agent restart — restart the service │
229
+ │ habi-agent uninstall — remove the service │
230
+ └─────────────────────────────────────────────────────────────┘
231
+ `);
232
+ process.exit(0);
133
233
  }
134
234
 
135
235
  async function cmdStatus() {
136
- try {
137
- const health = await pingAgent('/health');
138
- console.log('\n✓ HABI agent is RUNNING');
139
- console.log(` Port: ${AGENT_PORT}`);
140
- console.log(` Version: ${health.version || 'unknown'}`);
236
+ banner();
237
+ console.log('');
141
238
 
239
+ const installed = isServiceInstalled();
240
+ console.log(` Service: ${installed ? '✓ INSTALLED (runs at login)' : '✗ Not installed'}`);
241
+
242
+ // Check if agent is responding
243
+ try {
244
+ const health = await pingAgent('/health');
142
245
  const identity = await pingAgent('/identity') as Record<string, unknown>;
143
246
  const hw = identity.hardware as Record<string, unknown>;
144
- const sess = identity.session as Record<string, unknown>;
247
+ console.log(` Agent: ✓ RUNNING on port ${AGENT_PORT}`);
248
+ console.log(` Version: ${health.version || HABI_VERSION}`);
145
249
  if (hw) {
146
- console.log(` Hardware: ${hw.fingerprint} (${hw.type})`);
147
- console.log(` Platform: ${hw.platform} / ${hw.hostname}`);
148
- }
149
- if (sess) {
150
- const expiresAt = new Date(sess.expiresAt as number);
151
- const active = sess.active ? '✓ ACTIVE' : '✗ EXPIRED';
152
- console.log(` Session: ${(sess.fingerprint as string)?.slice(0, 16)}...`);
153
- console.log(` Status: ${active} (expires ${expiresAt.toLocaleTimeString()})`);
250
+ console.log(` Hardware: ${hw.fingerprint} (${hw.type})`);
251
+ console.log(` Platform: ${hw.platform} / ${hw.hostname}`);
154
252
  }
155
- console.log();
156
253
  } catch {
157
- console.log('\nHABI agent is NOT RUNNING');
158
- console.log(' Start it with: npx ts-node src/cli.ts start\n');
159
- process.exit(1);
254
+ console.log(` Agent: NOT RESPONDING on port ${AGENT_PORT}`);
255
+ if (installed) console.log(` (may still be starting check ~/.habi/habi.log)`);
160
256
  }
257
+
258
+ // Claude Code MCP check
259
+ const claudeSettings = findClaudeSettings();
260
+ if (claudeSettings) {
261
+ try {
262
+ const s = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
263
+ const habiMcp = s.mcpServers?.habi;
264
+ console.log(` Claude MCP: ${habiMcp ? '✓ CONFIGURED' : '✗ Not configured'}`);
265
+ } catch {
266
+ console.log(` Claude MCP: ✗ Could not read settings`);
267
+ }
268
+ } else {
269
+ console.log(` Claude MCP: — Claude Code not detected`);
270
+ }
271
+
272
+ if (fs.existsSync(LOG_PATH)) {
273
+ console.log(`\n Log file: ${LOG_PATH}`);
274
+ const lines = fs.readFileSync(LOG_PATH, 'utf8').split('\n').filter(Boolean).slice(-5);
275
+ console.log(' Last 5 log entries:');
276
+ lines.forEach(l => console.log(` ${l}`));
277
+ }
278
+ console.log('');
161
279
  }
162
280
 
163
- async function cmdIdentity() {
164
- try {
165
- const hw = readHardwareIdentity();
166
- const ctx = (() => {
167
- try {
168
- const p = findContextFile();
169
- return p ? loadContext(p) : null;
170
- } catch { return null; }
171
- })();
281
+ function cmdStop() {
282
+ console.log('[HABI] Stopping service...');
283
+ stopService();
284
+ console.log('[HABI] Service stopped. Run: habi-agent restart to start again.');
285
+ }
286
+
287
+ async function cmdRestart() {
288
+ console.log('[HABI] Restarting service...');
289
+ restartService();
290
+ console.log('[HABI] ✓ Service restarted.');
291
+ }
292
+
293
+ async function cmdUninstall() {
294
+ console.log('[HABI] Uninstalling HABI background service...');
295
+ const answer = await ask('[HABI] This will stop HABI and remove it from startup. Continue? (y/N) ');
296
+ if (answer.toLowerCase() !== 'y') {
297
+ console.log('[HABI] Cancelled.');
298
+ return;
299
+ }
300
+ await uninstallService();
301
+
302
+ // Also remove MCP config from Claude Code if present
303
+ const claudeSettings = findClaudeSettings();
304
+ if (claudeSettings && fs.existsSync(claudeSettings)) {
305
+ try {
306
+ const s = JSON.parse(fs.readFileSync(claudeSettings, 'utf8'));
307
+ if (s.mcpServers?.habi) {
308
+ delete s.mcpServers.habi;
309
+ fs.writeFileSync(claudeSettings, JSON.stringify(s, null, 2));
310
+ console.log('[HABI] ✓ Removed HABI from Claude Code MCP config');
311
+ }
312
+ } catch { /* ignore */ }
313
+ }
314
+
315
+ console.log('[HABI] ✓ Service uninstalled');
316
+ console.log('[HABI] Config and logs remain at ~/.habi/ — delete manually if desired');
317
+ }
172
318
 
173
- const contextHash = ctx?.contextHash ?? sha256('no-context');
174
- const fingerprint = generateSessionFingerprint(hw.fingerprint, contextHash, Date.now());
319
+ function cmdLogs() {
320
+ if (!fs.existsSync(LOG_PATH)) {
321
+ console.log('[HABI] No log file found at ' + LOG_PATH);
322
+ return;
323
+ }
324
+ const lines = fs.readFileSync(LOG_PATH, 'utf8').split('\n').filter(Boolean);
325
+ const last50 = lines.slice(-50);
326
+ console.log(`\n── HABI Log (last ${last50.length} lines) ──────────────────────\n`);
327
+ last50.forEach(l => console.log(l));
328
+ console.log('\n── End of log ──────────────────────────────────────────────\n');
329
+ }
175
330
 
176
- console.log('\n┌─ HABI Hardware Identity ──────────────────────────────────┐');
331
+ function cmdIdentity() {
332
+ try {
333
+ const hw = readHardwareIdentity();
334
+ console.log('\n┌─ HABI Hardware Identity ───────────────────────────────────┐');
177
335
  console.log(`│ Hardware ID: ${hw.fingerprint}`);
178
336
  console.log(`│ Type: ${hw.type}`);
179
337
  console.log(`│ Interface: ${hw.iface}`);
180
338
  console.log(`│ Platform: ${hw.platform}`);
181
339
  console.log(`│ Hostname: ${hw.hostname}`);
182
- console.log(`│ Context: ${ctx ? ctx.context.project : 'none loaded'}`);
183
- console.log(`│ Context Hash: ${ctx ? ctx.contextHash.slice(0, 24) + '...' : 'n/a'}`);
184
- console.log(`│ Session FP: ${fingerprint.slice(0, 24)}...`);
185
340
  console.log('└────────────────────────────────────────────────────────────┘\n');
186
341
  } catch (err) {
187
- console.error(`\n✗ Could not read hardware identity:\n ${err}\n`);
342
+ console.error(`\n✗ Could not read hardware identity: ${err}\n`);
188
343
  process.exit(1);
189
344
  }
190
345
  }
191
346
 
192
- async function cmdInit() {
193
- const hw = readHardwareIdentity();
194
- const projectName = process.argv[3] || path.basename(process.cwd());
195
-
196
- const contextContent = {
197
- project: projectName,
198
- version: '1.0.0',
199
- owner: process.env.HABI_OWNER_EMAIL || process.env.USER || process.env.USERNAME || 'owner',
200
- allowedMachines: [hw.fingerprint],
201
- allowedOperations: {
202
- paths: [`${process.cwd()}/**`],
203
- cloudAccounts: [],
204
- databases: [],
205
- repos: [],
206
- },
207
- deniedOperations: [
208
- 'DROP TABLE',
209
- 'DROP DATABASE',
210
- 'rm -rf /',
211
- 'DELETE FROM audit_log',
212
- 'truncate audit_log',
213
- ],
214
- };
347
+ function cmdHelp() {
348
+ console.log(`
349
+ HABI Agent v${HABI_VERSION} On Spot Solutions LLC
350
+ Hardware-Anchored Binding Infrastructure
215
351
 
216
- const contextHash = sha256(JSON.stringify(contextContent, null, 2));
217
- const fullContext = { ...contextContent, contextHash };
218
- const outPath = path.join(process.cwd(), 'habi-context.json');
352
+ Usage:
353
+ npx habi-agent@latest start --key <key> --name <name>
354
+ Register device, install background service, configure Claude Code
219
355
 
220
- if (fs.existsSync(outPath)) {
221
- console.log(`\n⚠ Context file already exists: ${outPath}`);
222
- console.log(' Delete it first if you want to regenerate.\n');
223
- process.exit(1);
224
- }
225
-
226
- fs.writeFileSync(outPath, JSON.stringify(fullContext, null, 2), 'utf8');
356
+ habi-agent status Show service status and agent health
357
+ habi-agent stop Stop the background service
358
+ habi-agent restart Restart the background service
359
+ habi-agent uninstall Remove the background service and MCP config
360
+ habi-agent logs Show the last 50 log entries
361
+ habi-agent identity Print hardware fingerprint
227
362
 
228
- console.log('\n✓ HABI Project Context File created');
229
- console.log(` File: ${outPath}`);
230
- console.log(` Project: ${projectName}`);
231
- console.log(` Machine: ${hw.fingerprint} (${hw.type})`);
232
- console.log(` Hash: ${contextHash.slice(0, 24)}...`);
233
- console.log('\n⚠ IMPORTANT: Add habi-context.json to your .gitignore');
234
- console.log(' This file contains your machine identity — do not commit it.\n');
235
- }
236
-
237
- async function cmdVerify() {
238
- try {
239
- const data = await pingAgent('/verify') as Record<string, unknown>;
240
- if (data.verified) {
241
- console.log('\n✓ Session VERIFIED');
242
- console.log(` Hardware ID: ${data.hardwareId}`);
243
- console.log(` Fingerprint: ${(data.fingerprint as string)?.slice(0, 24)}...`);
244
- console.log(` Expires in: ${data.expiresIn}s\n`);
245
- } else {
246
- console.log('\n✗ Session NOT VERIFIED');
247
- console.log(` Reason: ${data.reason || data.error}\n`);
248
- process.exit(1);
249
- }
250
- } catch {
251
- console.log('\n✗ HABI agent not running. Start with: npx ts-node src/cli.ts start\n');
252
- process.exit(1);
253
- }
363
+ Dashboard: https://imhabi.com
364
+ `);
254
365
  }
255
366
 
256
- // ── Dispatch ────────────────────────────────────────────────────────────────
367
+ // ── Dispatch ──────────────────────────────────────────────────────────────────
257
368
  (async () => {
258
369
  switch (command) {
259
- case 'start': await cmdStart(); break;
260
- case 'status': await cmdStatus(); break;
261
- case 'identity': await cmdIdentity(); break;
262
- case 'init': await cmdInit(); break;
263
- case 'verify': await cmdVerify(); break;
264
- default:
265
- console.log(`\nHABI Agent v0.1.0 — On Spot Solutions LLC`);
266
- console.log(`Usage: npx ts-node src/cli.ts <command>`);
267
- console.log(`\nCommands:`);
268
- console.log(` start Start the local identity daemon on port 7432`);
269
- console.log(` status Show agent status and hardware identity`);
270
- console.log(` identity Print hardware fingerprint`);
271
- console.log(` init Generate a Project Context File for this directory`);
272
- console.log(` verify Verify the current session\n`);
370
+ case 'start': await cmdStart(); break;
371
+ case 'stop': cmdStop(); break;
372
+ case 'restart': await cmdRestart(); break;
373
+ case 'uninstall': await cmdUninstall(); break;
374
+ case 'status': await cmdStatus(); break;
375
+ case 'logs': cmdLogs(); break;
376
+ case 'identity': cmdIdentity(); break;
377
+ default: cmdHelp(); break;
273
378
  }
274
379
  })();