habi-agent 0.1.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.
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +295 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud.d.ts +50 -0
- package/dist/cloud.d.ts.map +1 -0
- package/dist/cloud.js +147 -0
- package/dist/cloud.js.map +1 -0
- package/dist/context.d.ts +62 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +172 -0
- package/dist/context.js.map +1 -0
- package/dist/hardware.d.ts +38 -0
- package/dist/hardware.d.ts.map +1 -0
- package/dist/hardware.js +130 -0
- package/dist/hardware.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +276 -0
- package/dist/server.js.map +1 -0
- package/package.json +24 -0
- package/src/cli.ts +264 -0
- package/src/cloud.ts +165 -0
- package/src/context.ts +186 -0
- package/src/hardware.ts +114 -0
- package/src/index.ts +5 -0
- package/src/server.ts +255 -0
- package/tsconfig.json +10 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HABI Local Identity Server
|
|
4
|
+
* Runs on localhost:7432 exclusively — never binds to 0.0.0.0
|
|
5
|
+
*
|
|
6
|
+
* Pure Node.js http — zero external dependencies.
|
|
7
|
+
* Security: localhost-only binding is the enforced hardware boundary.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.HabiLocalServer = void 0;
|
|
44
|
+
const http = __importStar(require("http"));
|
|
45
|
+
const hardware_1 = require("./hardware");
|
|
46
|
+
const context_1 = require("./context");
|
|
47
|
+
const PORT = 7432;
|
|
48
|
+
const BIND_HOST = '127.0.0.1'; // NEVER 0.0.0.0
|
|
49
|
+
function json(res, status, body) {
|
|
50
|
+
const payload = JSON.stringify(body);
|
|
51
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
52
|
+
res.end(payload);
|
|
53
|
+
}
|
|
54
|
+
function readBody(req) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let raw = '';
|
|
57
|
+
req.on('data', (chunk) => raw += chunk.toString());
|
|
58
|
+
req.on('end', () => {
|
|
59
|
+
try {
|
|
60
|
+
resolve(raw ? JSON.parse(raw) : {});
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
reject(new Error('Invalid JSON body'));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
req.on('error', reject);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
class HabiLocalServer {
|
|
70
|
+
hardware = null;
|
|
71
|
+
context = null;
|
|
72
|
+
session = null;
|
|
73
|
+
server = null;
|
|
74
|
+
// ── Security: reject anything not from localhost ────────────────────────
|
|
75
|
+
isLocalhost(req) {
|
|
76
|
+
const ip = req.socket.remoteAddress;
|
|
77
|
+
return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
78
|
+
}
|
|
79
|
+
// ── Route handlers ──────────────────────────────────────────────────────
|
|
80
|
+
handleHealth(res) {
|
|
81
|
+
json(res, 200, { ok: true, agent: 'habi-agent', version: '0.1.0', port: PORT });
|
|
82
|
+
}
|
|
83
|
+
handleIdentity(res) {
|
|
84
|
+
try {
|
|
85
|
+
const hw = this.getHardware();
|
|
86
|
+
const sess = this.getOrCreateSession();
|
|
87
|
+
json(res, 200, {
|
|
88
|
+
ok: true,
|
|
89
|
+
hardware: {
|
|
90
|
+
fingerprint: hw.fingerprint,
|
|
91
|
+
type: hw.type,
|
|
92
|
+
iface: hw.iface,
|
|
93
|
+
platform: hw.platform,
|
|
94
|
+
hostname: hw.hostname,
|
|
95
|
+
},
|
|
96
|
+
session: {
|
|
97
|
+
fingerprint: sess.fingerprint,
|
|
98
|
+
boundAt: sess.boundAt,
|
|
99
|
+
expiresAt: sess.expiresAt,
|
|
100
|
+
active: Date.now() < sess.expiresAt,
|
|
101
|
+
},
|
|
102
|
+
agent: { version: '0.1.0', port: PORT },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
json(res, 500, { ok: false, error: String(err) });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
handleVerify(res) {
|
|
110
|
+
try {
|
|
111
|
+
const hw = this.getHardware();
|
|
112
|
+
const sess = this.getOrCreateSession();
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
if (now >= sess.expiresAt) {
|
|
115
|
+
this.session = null;
|
|
116
|
+
json(res, 200, { ok: false, verified: false, reason: 'Session expired — rebind required' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
json(res, 200, {
|
|
120
|
+
ok: true,
|
|
121
|
+
verified: true,
|
|
122
|
+
fingerprint: sess.fingerprint,
|
|
123
|
+
hardwareId: hw.fingerprint,
|
|
124
|
+
hardwareType: hw.type,
|
|
125
|
+
expiresAt: sess.expiresAt,
|
|
126
|
+
expiresIn: Math.floor((sess.expiresAt - now) / 1000),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
json(res, 500, { ok: false, verified: false, error: String(err) });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async handleAssertContext(req, res) {
|
|
134
|
+
try {
|
|
135
|
+
const body = await readBody(req);
|
|
136
|
+
const operation = body.operation;
|
|
137
|
+
if (!operation) {
|
|
138
|
+
json(res, 400, { ok: false, error: 'operation field required' });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const ctx = this.getContext();
|
|
142
|
+
if (!ctx) {
|
|
143
|
+
json(res, 200, { ok: true, allowed: true, warning: 'No Project Context File — permissive mode. Run: habi-agent init' });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const hw = this.getHardware();
|
|
147
|
+
const machineCheck = (0, context_1.assertMachineAuthorized)(hw.fingerprint, ctx.context);
|
|
148
|
+
if (!machineCheck.allowed) {
|
|
149
|
+
json(res, 200, { ok: true, allowed: false, reason: machineCheck.reason, hardwareId: hw.fingerprint });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const opCheck = (0, context_1.assertOperation)(operation, ctx.context);
|
|
153
|
+
json(res, 200, {
|
|
154
|
+
ok: true,
|
|
155
|
+
allowed: opCheck.allowed,
|
|
156
|
+
reason: opCheck.reason,
|
|
157
|
+
hardwareId: hw.fingerprint,
|
|
158
|
+
contextHash: ctx.contextHash,
|
|
159
|
+
project: ctx.context.project,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
json(res, 500, { ok: false, error: String(err) });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async handleAudit(req, res) {
|
|
167
|
+
try {
|
|
168
|
+
const body = await readBody(req);
|
|
169
|
+
const hw = this.getHardware();
|
|
170
|
+
const entry = {
|
|
171
|
+
ts: Date.now(),
|
|
172
|
+
hardwareId: hw.fingerprint,
|
|
173
|
+
sessionFingerprint: this.session?.fingerprint ?? 'UNBOUND',
|
|
174
|
+
...body,
|
|
175
|
+
};
|
|
176
|
+
const entryHash = (0, hardware_1.sha256)(JSON.stringify(entry));
|
|
177
|
+
console.log(`[HABI AUDIT] ${new Date().toISOString()} ${body.eventType ?? 'EVENT'} ${entryHash.slice(0, 12)}`);
|
|
178
|
+
json(res, 200, { ok: true, entryHash });
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
json(res, 500, { ok: false, error: String(err) });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ── Internal helpers ────────────────────────────────────────────────────
|
|
185
|
+
getHardware() {
|
|
186
|
+
if (!this.hardware) {
|
|
187
|
+
this.hardware = (0, hardware_1.readHardwareIdentity)();
|
|
188
|
+
console.log(`[HABI] Hardware: ${this.hardware.fingerprint} (${this.hardware.type}) on ${this.hardware.iface}`);
|
|
189
|
+
}
|
|
190
|
+
return this.hardware;
|
|
191
|
+
}
|
|
192
|
+
getContext() {
|
|
193
|
+
if (!this.context) {
|
|
194
|
+
const p = (0, context_1.findContextFile)();
|
|
195
|
+
if (p) {
|
|
196
|
+
this.context = (0, context_1.loadContext)(p);
|
|
197
|
+
console.log(`[HABI] Context: ${this.context.context.project} (${this.context.contextHash.slice(0, 12)}...)`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return this.context;
|
|
201
|
+
}
|
|
202
|
+
getOrCreateSession() {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
if (this.session && now < this.session.expiresAt)
|
|
205
|
+
return this.session;
|
|
206
|
+
const hw = this.getHardware();
|
|
207
|
+
const ctx = this.getContext();
|
|
208
|
+
const contextHash = ctx?.contextHash ?? (0, hardware_1.sha256)('no-context');
|
|
209
|
+
const fingerprint = (0, hardware_1.generateSessionFingerprint)(hw.fingerprint, contextHash, now);
|
|
210
|
+
this.session = {
|
|
211
|
+
fingerprint,
|
|
212
|
+
hardwareId: hw.fingerprint,
|
|
213
|
+
contextHash,
|
|
214
|
+
boundAt: now,
|
|
215
|
+
expiresAt: now + 15 * 60 * 1000,
|
|
216
|
+
};
|
|
217
|
+
console.log(`[HABI] Session: ${fingerprint.slice(0, 16)}... expires ${new Date(this.session.expiresAt).toISOString()}`);
|
|
218
|
+
return this.session;
|
|
219
|
+
}
|
|
220
|
+
// ── Start / stop ────────────────────────────────────────────────────────
|
|
221
|
+
start() {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
try {
|
|
224
|
+
this.getHardware();
|
|
225
|
+
this.getContext();
|
|
226
|
+
this.getOrCreateSession();
|
|
227
|
+
this.server = http.createServer(async (req, res) => {
|
|
228
|
+
// Localhost enforcement
|
|
229
|
+
if (!this.isLocalhost(req)) {
|
|
230
|
+
json(res, 403, { error: 'HABI agent is localhost-only' });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const url = req.url ?? '/';
|
|
234
|
+
const method = req.method ?? 'GET';
|
|
235
|
+
if (url === '/health' && method === 'GET')
|
|
236
|
+
return this.handleHealth(res);
|
|
237
|
+
if (url === '/identity' && method === 'GET')
|
|
238
|
+
return this.handleIdentity(res);
|
|
239
|
+
if (url === '/verify' && method === 'POST')
|
|
240
|
+
return this.handleVerify(res);
|
|
241
|
+
if (url === '/assert-context' && method === 'POST')
|
|
242
|
+
return this.handleAssertContext(req, res);
|
|
243
|
+
if (url === '/audit' && method === 'POST')
|
|
244
|
+
return this.handleAudit(req, res);
|
|
245
|
+
json(res, 404, { error: `Unknown route: ${method} ${url}` });
|
|
246
|
+
});
|
|
247
|
+
this.server.listen(PORT, BIND_HOST, () => {
|
|
248
|
+
const hw = this.hardware;
|
|
249
|
+
console.log(`\n╔══════════════════════════════════════════════════╗`);
|
|
250
|
+
console.log(`║ HABI Agent running on ${BIND_HOST}:${PORT} ║`);
|
|
251
|
+
console.log(`║ Hardware: ${hw.fingerprint.padEnd(34)} ║`);
|
|
252
|
+
console.log(`║ Type: ${hw.type.padEnd(34)} ║`);
|
|
253
|
+
console.log(`╚══════════════════════════════════════════════════╝\n`);
|
|
254
|
+
resolve();
|
|
255
|
+
});
|
|
256
|
+
this.server.on('error', (err) => {
|
|
257
|
+
if (err.code === 'EADDRINUSE') {
|
|
258
|
+
reject(new Error(`HABI: Port ${PORT} already in use. Another instance may be running.`));
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
reject(err);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (err) {
|
|
266
|
+
reject(err);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
stop() {
|
|
271
|
+
this.server?.close();
|
|
272
|
+
console.log('[HABI] Agent stopped.');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.HabiLocalServer = HabiLocalServer;
|
|
276
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "habi-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HABI Local Identity Agent — hardware fingerprint daemon on localhost:7432",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"habi-agent": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "ts-node src/cli.ts",
|
|
13
|
+
"start": "node dist/cli.js",
|
|
14
|
+
"test": "jest"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"dotenv": "^16.4.5"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.12.0",
|
|
21
|
+
"typescript": "^5.4.0",
|
|
22
|
+
"ts-node": "^10.9.2"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* HABI Agent CLI
|
|
4
|
+
* 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
|
|
10
|
+
*/
|
|
11
|
+
|
|
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';
|
|
20
|
+
|
|
21
|
+
const AGENT_PORT = 7432;
|
|
22
|
+
const command = process.argv[2] || 'start';
|
|
23
|
+
|
|
24
|
+
// ── Utility: ping the running agent ────────────────────────────────────────
|
|
25
|
+
function pingAgent(p: string = '/health'): Promise<Record<string, unknown>> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const req = http.get(
|
|
28
|
+
{ host: '127.0.0.1', port: AGENT_PORT, path: p },
|
|
29
|
+
(res) => {
|
|
30
|
+
let body = '';
|
|
31
|
+
res.on('data', (chunk: Buffer) => body += chunk.toString());
|
|
32
|
+
res.on('end', () => {
|
|
33
|
+
try { resolve(JSON.parse(body)); }
|
|
34
|
+
catch { reject(new Error('Invalid JSON from agent')); }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
req.on('error', reject);
|
|
39
|
+
req.setTimeout(2000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function cmdStart() {
|
|
44
|
+
console.log('\n┌─────────────────────────────────────────────┐');
|
|
45
|
+
console.log('│ HABI Local Identity Agent v0.1.0 │');
|
|
46
|
+
console.log('│ On Spot Solutions LLC — hardware-first AI │');
|
|
47
|
+
console.log('└─────────────────────────────────────────────┘\n');
|
|
48
|
+
|
|
49
|
+
// Check if already running
|
|
50
|
+
try {
|
|
51
|
+
await pingAgent();
|
|
52
|
+
console.log('⚠ HABI agent is already running on port 7432.');
|
|
53
|
+
console.log(' Run: habi-agent status to check it.');
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} catch {
|
|
56
|
+
// Not running — proceed
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const server = new HabiLocalServer();
|
|
60
|
+
|
|
61
|
+
// Graceful shutdown
|
|
62
|
+
const shutdown = () => {
|
|
63
|
+
console.log('\n[HABI] Shutting down agent...');
|
|
64
|
+
server.stop();
|
|
65
|
+
process.exit(0);
|
|
66
|
+
};
|
|
67
|
+
process.on('SIGINT', shutdown);
|
|
68
|
+
process.on('SIGTERM', shutdown);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await server.start();
|
|
72
|
+
|
|
73
|
+
// ── Cloud registration ──────────────────────────────────────────────
|
|
74
|
+
const hw = readHardwareIdentity();
|
|
75
|
+
const habiKey = process.env.HABI_KEY || process.env.HABI_OWNER_EMAIL || "";
|
|
76
|
+
const friendlyName = process.env.HABI_DEVICE_NAME || os.hostname();
|
|
77
|
+
|
|
78
|
+
if (!habiKey) {
|
|
79
|
+
console.log('[HABI Cloud] HABI_OWNER_EMAIL not set — running in local-only mode.');
|
|
80
|
+
console.log('[HABI Cloud] Set export HABI_OWNER_EMAIL=you@example.com to enable cloud registration.\n');
|
|
81
|
+
} else {
|
|
82
|
+
console.log(`[HABI Cloud] Registering "${friendlyName}" via HABI key...`);
|
|
83
|
+
const cloudState = await registerDevice({
|
|
84
|
+
friendlyName,
|
|
85
|
+
hardwareFingerprint: hw.fingerprint,
|
|
86
|
+
fingerprintType: hw.type,
|
|
87
|
+
agentVersion: '0.1.0',
|
|
88
|
+
osPlatform: os.platform(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (cloudState) {
|
|
92
|
+
console.log(`[HABI Cloud] ✓ Device registered — ID: ${cloudState.deviceId.slice(0, 8)}...`);
|
|
93
|
+
console.log(`[HABI Cloud] ✓ Session token active until ${new Date(cloudState.sessionTokenExpiresAt).toLocaleTimeString()}\n`);
|
|
94
|
+
|
|
95
|
+
// ── Heartbeat loop every 30s ──────────────────────────────────
|
|
96
|
+
let currentToken = cloudState.sessionToken;
|
|
97
|
+
setInterval(async () => {
|
|
98
|
+
const hb = await sendHeartbeat({
|
|
99
|
+
deviceId: cloudState.deviceId,
|
|
100
|
+
sessionToken: currentToken,
|
|
101
|
+
hardwareFingerprint: hw.fingerprint,
|
|
102
|
+
});
|
|
103
|
+
if (hb) {
|
|
104
|
+
currentToken = hb.sessionToken;
|
|
105
|
+
if (hb.pendingJobs && hb.pendingJobs.length > 0) {
|
|
106
|
+
console.log(`[HABI Cloud] ${hb.pendingJobs.length} pending job(s) received from dispatch queue.`);
|
|
107
|
+
// Job execution handled in Phase 2 — log for now
|
|
108
|
+
for (const job of hb.pendingJobs) {
|
|
109
|
+
console.log(`[HABI Cloud] Job ${job.id.slice(0, 8)}: ${JSON.stringify(job.payload).slice(0, 80)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}, 30000);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Keep alive
|
|
118
|
+
await new Promise(() => {});
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(`\n✗ HABI agent failed to start:\n ${err}\n`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function cmdStatus() {
|
|
126
|
+
try {
|
|
127
|
+
const health = await pingAgent('/health');
|
|
128
|
+
console.log('\n✓ HABI agent is RUNNING');
|
|
129
|
+
console.log(` Port: ${AGENT_PORT}`);
|
|
130
|
+
console.log(` Version: ${health.version || 'unknown'}`);
|
|
131
|
+
|
|
132
|
+
const identity = await pingAgent('/identity') as Record<string, unknown>;
|
|
133
|
+
const hw = identity.hardware as Record<string, unknown>;
|
|
134
|
+
const sess = identity.session as Record<string, unknown>;
|
|
135
|
+
if (hw) {
|
|
136
|
+
console.log(` Hardware: ${hw.fingerprint} (${hw.type})`);
|
|
137
|
+
console.log(` Platform: ${hw.platform} / ${hw.hostname}`);
|
|
138
|
+
}
|
|
139
|
+
if (sess) {
|
|
140
|
+
const expiresAt = new Date(sess.expiresAt as number);
|
|
141
|
+
const active = sess.active ? '✓ ACTIVE' : '✗ EXPIRED';
|
|
142
|
+
console.log(` Session: ${(sess.fingerprint as string)?.slice(0, 16)}...`);
|
|
143
|
+
console.log(` Status: ${active} (expires ${expiresAt.toLocaleTimeString()})`);
|
|
144
|
+
}
|
|
145
|
+
console.log();
|
|
146
|
+
} catch {
|
|
147
|
+
console.log('\n✗ HABI agent is NOT RUNNING');
|
|
148
|
+
console.log(' Start it with: npx ts-node src/cli.ts start\n');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function cmdIdentity() {
|
|
154
|
+
try {
|
|
155
|
+
const hw = readHardwareIdentity();
|
|
156
|
+
const ctx = (() => {
|
|
157
|
+
try {
|
|
158
|
+
const p = findContextFile();
|
|
159
|
+
return p ? loadContext(p) : null;
|
|
160
|
+
} catch { return null; }
|
|
161
|
+
})();
|
|
162
|
+
|
|
163
|
+
const contextHash = ctx?.contextHash ?? sha256('no-context');
|
|
164
|
+
const fingerprint = generateSessionFingerprint(hw.fingerprint, contextHash, Date.now());
|
|
165
|
+
|
|
166
|
+
console.log('\n┌─ HABI Hardware Identity ──────────────────────────────────┐');
|
|
167
|
+
console.log(`│ Hardware ID: ${hw.fingerprint}`);
|
|
168
|
+
console.log(`│ Type: ${hw.type}`);
|
|
169
|
+
console.log(`│ Interface: ${hw.iface}`);
|
|
170
|
+
console.log(`│ Platform: ${hw.platform}`);
|
|
171
|
+
console.log(`│ Hostname: ${hw.hostname}`);
|
|
172
|
+
console.log(`│ Context: ${ctx ? ctx.context.project : 'none loaded'}`);
|
|
173
|
+
console.log(`│ Context Hash: ${ctx ? ctx.contextHash.slice(0, 24) + '...' : 'n/a'}`);
|
|
174
|
+
console.log(`│ Session FP: ${fingerprint.slice(0, 24)}...`);
|
|
175
|
+
console.log('└────────────────────────────────────────────────────────────┘\n');
|
|
176
|
+
} catch (err) {
|
|
177
|
+
console.error(`\n✗ Could not read hardware identity:\n ${err}\n`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function cmdInit() {
|
|
183
|
+
const hw = readHardwareIdentity();
|
|
184
|
+
const projectName = process.argv[3] || path.basename(process.cwd());
|
|
185
|
+
|
|
186
|
+
const contextContent = {
|
|
187
|
+
project: projectName,
|
|
188
|
+
version: '1.0.0',
|
|
189
|
+
owner: process.env.HABI_OWNER_EMAIL || process.env.USER || process.env.USERNAME || 'owner',
|
|
190
|
+
allowedMachines: [hw.fingerprint],
|
|
191
|
+
allowedOperations: {
|
|
192
|
+
paths: [`${process.cwd()}/**`],
|
|
193
|
+
cloudAccounts: [],
|
|
194
|
+
databases: [],
|
|
195
|
+
repos: [],
|
|
196
|
+
},
|
|
197
|
+
deniedOperations: [
|
|
198
|
+
'DROP TABLE',
|
|
199
|
+
'DROP DATABASE',
|
|
200
|
+
'rm -rf /',
|
|
201
|
+
'DELETE FROM audit_log',
|
|
202
|
+
'truncate audit_log',
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const contextHash = sha256(JSON.stringify(contextContent, null, 2));
|
|
207
|
+
const fullContext = { ...contextContent, contextHash };
|
|
208
|
+
const outPath = path.join(process.cwd(), 'habi-context.json');
|
|
209
|
+
|
|
210
|
+
if (fs.existsSync(outPath)) {
|
|
211
|
+
console.log(`\n⚠ Context file already exists: ${outPath}`);
|
|
212
|
+
console.log(' Delete it first if you want to regenerate.\n');
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fs.writeFileSync(outPath, JSON.stringify(fullContext, null, 2), 'utf8');
|
|
217
|
+
|
|
218
|
+
console.log('\n✓ HABI Project Context File created');
|
|
219
|
+
console.log(` File: ${outPath}`);
|
|
220
|
+
console.log(` Project: ${projectName}`);
|
|
221
|
+
console.log(` Machine: ${hw.fingerprint} (${hw.type})`);
|
|
222
|
+
console.log(` Hash: ${contextHash.slice(0, 24)}...`);
|
|
223
|
+
console.log('\n⚠ IMPORTANT: Add habi-context.json to your .gitignore');
|
|
224
|
+
console.log(' This file contains your machine identity — do not commit it.\n');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function cmdVerify() {
|
|
228
|
+
try {
|
|
229
|
+
const data = await pingAgent('/verify') as Record<string, unknown>;
|
|
230
|
+
if (data.verified) {
|
|
231
|
+
console.log('\n✓ Session VERIFIED');
|
|
232
|
+
console.log(` Hardware ID: ${data.hardwareId}`);
|
|
233
|
+
console.log(` Fingerprint: ${(data.fingerprint as string)?.slice(0, 24)}...`);
|
|
234
|
+
console.log(` Expires in: ${data.expiresIn}s\n`);
|
|
235
|
+
} else {
|
|
236
|
+
console.log('\n✗ Session NOT VERIFIED');
|
|
237
|
+
console.log(` Reason: ${data.reason || data.error}\n`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
} catch {
|
|
241
|
+
console.log('\n✗ HABI agent not running. Start with: npx ts-node src/cli.ts start\n');
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ── Dispatch ────────────────────────────────────────────────────────────────
|
|
247
|
+
(async () => {
|
|
248
|
+
switch (command) {
|
|
249
|
+
case 'start': await cmdStart(); break;
|
|
250
|
+
case 'status': await cmdStatus(); break;
|
|
251
|
+
case 'identity': await cmdIdentity(); break;
|
|
252
|
+
case 'init': await cmdInit(); break;
|
|
253
|
+
case 'verify': await cmdVerify(); break;
|
|
254
|
+
default:
|
|
255
|
+
console.log(`\nHABI Agent v0.1.0 — On Spot Solutions LLC`);
|
|
256
|
+
console.log(`Usage: npx ts-node src/cli.ts <command>`);
|
|
257
|
+
console.log(`\nCommands:`);
|
|
258
|
+
console.log(` start Start the local identity daemon on port 7432`);
|
|
259
|
+
console.log(` status Show agent status and hardware identity`);
|
|
260
|
+
console.log(` identity Print hardware fingerprint`);
|
|
261
|
+
console.log(` init Generate a Project Context File for this directory`);
|
|
262
|
+
console.log(` verify Verify the current session\n`);
|
|
263
|
+
}
|
|
264
|
+
})();
|