altis-claude-harness 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/bin/cli.js +4 -4
- package/package.json +1 -1
- package/payload/hooks/liberacoes.json +3 -3
- package/payload/hooks/license_guard.cjs +97 -6
- package/payload/hooks/rag_telemetria.cjs +45 -4
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@ Pre-requisitos: Node.js 18+, Python 3 no PATH, Git para Windows (git-bash), esta
|
|
|
10
10
|
|
|
11
11
|
npx altis-claude-harness install
|
|
12
12
|
|
|
13
|
-
Se a estacao nao estiver liberada, o comando mostra
|
|
14
|
-
e rode de novo depois da liberacao.
|
|
13
|
+
Se a estacao nao estiver liberada, o comando mostra a(s) identidade(s) da maquina - envie
|
|
14
|
+
ao gestor e rode de novo depois da liberacao.
|
|
15
15
|
|
|
16
16
|
Pronto. A cada abertura do Claude Code, uma checagem em background (no maximo 1 a cada 4h)
|
|
17
17
|
compara a versao instalada com o registry npm e aplica atualizacoes sozinha. O conteudo
|
|
@@ -24,7 +24,7 @@ backup unico em `.claude.json.altis-bak`. As interacoes com o Claude Code (promp
|
|
|
24
24
|
|
|
25
25
|
Comandos uteis:
|
|
26
26
|
|
|
27
|
-
npx altis-claude-harness status # o que esta instalado +
|
|
27
|
+
npx altis-claude-harness status # o que esta instalado + identidades da estacao
|
|
28
28
|
npx altis-claude-harness check # ha atualizacao?
|
|
29
29
|
npx altis-claude-harness update # forca sync agora
|
|
30
30
|
npx altis-claude-harness uninstall # remove tudo que e gerenciado
|
|
@@ -35,10 +35,21 @@ Log do auto-update: `~/.claude/hooks/altis/update.log`.
|
|
|
35
35
|
|
|
36
36
|
## Liberacao de estacoes (gestor)
|
|
37
37
|
|
|
38
|
-
1. Dev roda `npx altis-claude-harness status` e envia
|
|
39
|
-
|
|
38
|
+
1. Dev roda `npx altis-claude-harness status` e envia as identidades exibidas em
|
|
39
|
+
`identidades da estacao: ...` (MAC no Windows/Linux; no macOS tambem aparecem
|
|
40
|
+
`IOPlatformUUID`/`IOPlatformSerialNumber`, ja que o MAC e randomizado por rede e nao
|
|
41
|
+
identifica a maquina). Alternativa manual no macOS, sem instalar nada:
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
ioreg -rd1 -c IOPlatformExpertDevice | grep -E "IOPlatformUUID|IOPlatformSerialNumber"
|
|
44
|
+
|
|
45
|
+
2. Adicione a entrada em `payload/hooks/liberacoes.json`. Aceita `mac`, `maquina_id`
|
|
46
|
+
(o `IOPlatformUUID`) e `serial` (o `IOPlatformSerialNumber`) - pelo menos um dos tres e
|
|
47
|
+
obrigatorio, pode combinar mais de um na mesma entrada:
|
|
48
|
+
|
|
49
|
+
{ "liberadas": [
|
|
50
|
+
{ "mac": "AA:BB:CC:DD:EE:FF", "id": "dev01" },
|
|
51
|
+
{ "maquina_id": "A1B2C3D4-1111-2222-3333-444455556666", "serial": "C02XL0THJGH5", "id": "dev02-macbook" }
|
|
52
|
+
] }
|
|
42
53
|
|
|
43
54
|
(use um apelido curto em `id` - o pacote e publico, nao exponha nome completo)
|
|
44
55
|
3. `npm version patch` + `git push`. A Action publica e as estacoes recebem pelo
|
package/bin/cli.js
CHANGED
|
@@ -45,12 +45,12 @@ function checkPython() {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function estaLiberada() {
|
|
48
|
-
return license.
|
|
48
|
+
return license.resolveIdentidade(license.readLiberacoes()).ok;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
function msgNaoLiberada() {
|
|
52
|
-
const
|
|
53
|
-
return `estacao nao liberada para o ${PKG_NAME}. Informe ao gestor
|
|
52
|
+
const ids = license.getStationIds();
|
|
53
|
+
return `estacao nao liberada para o ${PKG_NAME}. Informe ao gestor a(s) identidade(s): ${ids.join(', ') || 'nenhuma detectada'}`;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function runSync(target) {
|
|
@@ -121,7 +121,7 @@ function cmdCheck(target) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function cmdStatus(target) {
|
|
124
|
-
console.log(`
|
|
124
|
+
console.log(`identidades da estacao: ${license.getStationIds().join(', ') || 'nenhuma detectada'}`);
|
|
125
125
|
const m = readManifest(target);
|
|
126
126
|
if (!m) {
|
|
127
127
|
console.log(`nao instalado em ${target}`);
|
package/package.json
CHANGED
|
@@ -17,9 +17,7 @@
|
|
|
17
17
|
{ "mac": "50:BB:B5:7A:41:28", "id": "matheus", "funcionario_id": 35 },
|
|
18
18
|
|
|
19
19
|
{ "mac": "28:11:A8:83:BA:0D", "id": "nicolas", "funcionario_id": 28 },
|
|
20
|
-
|
|
21
|
-
{ "mac": "E8:B0:C5:37:CC:6A", "id": "luiz", "funcionario_id": 6 },
|
|
22
|
-
|
|
20
|
+
|
|
23
21
|
{ "mac": "5C:9B:A6:63:3C:74", "id": "vinicius-macbook", "funcionario_id": 7 },
|
|
24
22
|
{ "mac": "2A:10:61:A5:18:F0", "id": "vinicius-macbook", "funcionario_id": 7 },
|
|
25
23
|
{ "mac": "DE:50:D9:92:D5:8F", "id": "vinicius", "funcionario_id": 7 },
|
|
@@ -29,6 +27,8 @@
|
|
|
29
27
|
|
|
30
28
|
{ "mac": "A2:51:13:A5:DD:BF", "id": "kamusx2", "funcionario_id": 2 },
|
|
31
29
|
{ "mac": "12:57:E7:8E:6A:E3", "id": "kamusx2", "funcionario_id": 2 },
|
|
30
|
+
{ "maquina_id": "F5E1932E-0A25-5226-9DE6-FD78A775B330", "id": "kamusx2-macbook", "funcionario_id": 2 },
|
|
31
|
+
{ "serial": "J6C7P4J575", "id": "kamusx2-macbook", "funcionario_id": 2 },
|
|
32
32
|
|
|
33
33
|
{ "mac": "08:00:27:C6:34:7D", "id": "GeradorExe", "funcionario_id": 1 }
|
|
34
34
|
]
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
6
|
+
const { execSync, execFileSync } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const TARGET_DIR = process.env.ALTIS_CLAUDE_TARGET || path.resolve(__dirname, '..', '..');
|
|
9
9
|
const MANIFEST = path.join(TARGET_DIR, 'altis-claude.manifest.json');
|
|
10
10
|
const LIBERACOES = process.env.ALTIS_CLAUDE_LIBERACOES || path.join(__dirname, 'liberacoes.json');
|
|
11
11
|
const PKG_NAME = 'altis-claude-harness';
|
|
12
12
|
const FRESH_MS = 24 * 60 * 60 * 1000;
|
|
13
|
+
const PREFIXO_MAC = 'MAC:';
|
|
14
|
+
const PREFIXO_UUID = 'UUID:';
|
|
15
|
+
const PREFIXO_SERIAL = 'SERIAL:';
|
|
16
|
+
const RE_UUID = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/;
|
|
17
|
+
const RE_SERIAL = /^[0-9A-Z]{8,16}$/;
|
|
13
18
|
|
|
14
19
|
function normalizeMac(mac) {
|
|
15
20
|
const hex = String(mac).replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
|
@@ -17,6 +22,20 @@ function normalizeMac(mac) {
|
|
|
17
22
|
return hex.match(/.{2}/g).join(':');
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
function texto(valor) {
|
|
26
|
+
return valor == null ? '' : String(valor).trim().toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeUuid(valor) {
|
|
30
|
+
const v = texto(valor);
|
|
31
|
+
return RE_UUID.test(v) ? v : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeSerial(valor) {
|
|
35
|
+
const v = texto(valor);
|
|
36
|
+
return RE_SERIAL.test(v) ? v : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
function getStationMacs() {
|
|
21
40
|
const macs = new Set();
|
|
22
41
|
for (const list of Object.values(os.networkInterfaces())) {
|
|
@@ -29,10 +48,60 @@ function getStationMacs() {
|
|
|
29
48
|
return [...macs];
|
|
30
49
|
}
|
|
31
50
|
|
|
51
|
+
const RE_IOREG_UUID = /"IOPlatformUUID"\s*=\s*"([^"]*)"/;
|
|
52
|
+
const RE_IOREG_SERIAL = /"IOPlatformSerialNumber"\s*=\s*"([^"]*)"/;
|
|
53
|
+
|
|
54
|
+
function execIoreg() {
|
|
55
|
+
return execFileSync('ioreg', ['-rd1', '-c', 'IOPlatformExpertDevice'], {
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: 3000,
|
|
58
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// macOS randomiza o MAC por SSID (Private Wi-Fi Address), entao o MAC nao identifica a
|
|
64
|
+
// estacao. IOPlatformUUID e IOPlatformSerialNumber sao estaveis, legiveis sem sudo e
|
|
65
|
+
// independem de rede. Fail-open: qualquer falha vira lista vazia e a decisao cai nos MACs.
|
|
66
|
+
function getMacIds(opts) {
|
|
67
|
+
const o = opts || {};
|
|
68
|
+
const platform = o.platform || process.platform;
|
|
69
|
+
if (platform !== 'darwin') return [];
|
|
70
|
+
let saida;
|
|
71
|
+
try {
|
|
72
|
+
saida = String((o.exec || execIoreg)());
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const ids = [];
|
|
77
|
+
const mUuid = RE_IOREG_UUID.exec(saida);
|
|
78
|
+
const uuid = mUuid && normalizeUuid(mUuid[1]);
|
|
79
|
+
if (uuid) ids.push(PREFIXO_UUID + uuid);
|
|
80
|
+
const mSerial = RE_IOREG_SERIAL.exec(saida);
|
|
81
|
+
const serial = mSerial && normalizeSerial(mSerial[1]);
|
|
82
|
+
if (serial) ids.push(PREFIXO_SERIAL + serial);
|
|
83
|
+
return ids;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const CAMPOS_IDENTIDADE = [
|
|
87
|
+
['mac', normalizeMac, PREFIXO_MAC],
|
|
88
|
+
['maquina_id', normalizeUuid, PREFIXO_UUID],
|
|
89
|
+
['serial', normalizeSerial, PREFIXO_SERIAL],
|
|
90
|
+
];
|
|
91
|
+
|
|
32
92
|
function readLiberacoes(filePath = LIBERACOES) {
|
|
33
93
|
try {
|
|
34
94
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
35
|
-
|
|
95
|
+
const ids = [];
|
|
96
|
+
for (const e of data.liberadas || []) {
|
|
97
|
+
if (!e || typeof e !== 'object') continue;
|
|
98
|
+
for (const [campo, normaliza, prefixo] of CAMPOS_IDENTIDADE) {
|
|
99
|
+
if (e[campo] === undefined) continue;
|
|
100
|
+
const v = normaliza(e[campo]);
|
|
101
|
+
if (v) ids.push(prefixo + v);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return ids;
|
|
36
105
|
} catch {
|
|
37
106
|
return [];
|
|
38
107
|
}
|
|
@@ -43,6 +112,23 @@ function isLiberated(liberadas, stationMacs) {
|
|
|
43
112
|
return stationMacs.some(m => set.has(m));
|
|
44
113
|
}
|
|
45
114
|
|
|
115
|
+
function getStationIds(opts) {
|
|
116
|
+
return [
|
|
117
|
+
...getStationMacs().map(m => PREFIXO_MAC + m),
|
|
118
|
+
...getMacIds(opts),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Curto-circuito de performance: o guard roda a cada prompt /altis* e a cada Task/Skill
|
|
123
|
+
// gerenciada. Quando o MAC ja libera — o caso de toda estacao Windows e Linux — a chamada
|
|
124
|
+
// externa nem acontece.
|
|
125
|
+
function resolveIdentidade(liberadas, opts) {
|
|
126
|
+
const macs = getStationMacs().map(m => PREFIXO_MAC + m);
|
|
127
|
+
if (isLiberated(liberadas, macs)) return { ok: true, ids: macs };
|
|
128
|
+
const ids = [...macs, ...getMacIds(opts)];
|
|
129
|
+
return { ok: isLiberated(liberadas, ids), ids };
|
|
130
|
+
}
|
|
131
|
+
|
|
46
132
|
function isFresh(manifest, nowMs) {
|
|
47
133
|
if (!manifest || !manifest.lastSuccessAt) return false;
|
|
48
134
|
const t = Date.parse(manifest.lastSuccessAt);
|
|
@@ -81,11 +167,11 @@ function tryRevalidate() {
|
|
|
81
167
|
}
|
|
82
168
|
|
|
83
169
|
function licenseDecision() {
|
|
84
|
-
const
|
|
85
|
-
if (!
|
|
170
|
+
const { ok, ids } = resolveIdentidade(readLiberacoes());
|
|
171
|
+
if (!ok) {
|
|
86
172
|
return {
|
|
87
173
|
ok: false,
|
|
88
|
-
reason: `Estacao nao liberada para os comandos Altis. Informe ao gestor
|
|
174
|
+
reason: `Estacao nao liberada para os comandos Altis. Informe ao gestor a(s) identidade(s): ${ids.join(', ') || 'nenhuma detectada'}`,
|
|
89
175
|
};
|
|
90
176
|
}
|
|
91
177
|
if (!isFresh(readManifest(), Date.now()) && !tryRevalidate()) {
|
|
@@ -149,5 +235,10 @@ if (require.main === module) {
|
|
|
149
235
|
} catch {}
|
|
150
236
|
process.exit(0);
|
|
151
237
|
} else {
|
|
152
|
-
module.exports = {
|
|
238
|
+
module.exports = {
|
|
239
|
+
normalizeMac, normalizeUuid, normalizeSerial,
|
|
240
|
+
getStationMacs, getMacIds, getStationIds, resolveIdentidade,
|
|
241
|
+
readLiberacoes, isLiberated, isFresh, FRESH_MS,
|
|
242
|
+
PREFIXO_MAC, PREFIXO_UUID, PREFIXO_SERIAL,
|
|
243
|
+
};
|
|
153
244
|
}
|
|
@@ -65,6 +65,26 @@ function macsDaEstacao() {
|
|
|
65
65
|
return [...macs];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// license_guard.cjs vive ao lado deste hook (~/.claude/hooks/altis/ em runtime, payload/hooks/
|
|
69
|
+
// no repositorio) e so executa seu main quando require.main === module, entao importa-lo aqui
|
|
70
|
+
// e seguro. Fail-open: qualquer falha no require deixa licenseGuard null e a identificacao cai
|
|
71
|
+
// de volta no comportamento historico (casar so por MAC).
|
|
72
|
+
let licenseGuard = null;
|
|
73
|
+
try {
|
|
74
|
+
licenseGuard = require('./license_guard.cjs');
|
|
75
|
+
} catch {
|
|
76
|
+
licenseGuard = null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function idsExtrasDaEstacao(opts) {
|
|
80
|
+
if (!licenseGuard || typeof licenseGuard.getMacIds !== 'function') return [];
|
|
81
|
+
try {
|
|
82
|
+
return licenseGuard.getMacIds(opts);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
function lerLiberacoes() {
|
|
69
89
|
try {
|
|
70
90
|
const arquivo = path.join(__dirname, 'liberacoes.json');
|
|
@@ -75,12 +95,33 @@ function lerLiberacoes() {
|
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
|
|
78
|
-
|
|
98
|
+
// Casa a estacao com uma entrada de liberacoes.json por mac (historico) ou, se nao bater, por
|
|
99
|
+
// maquina_id/serial (macOS) — necessario porque o macOS randomiza o MAC por rede e a estacao
|
|
100
|
+
// pode estar cadastrada so por identidade de hardware. O campo "mac" do retorno e contrato de
|
|
101
|
+
// telemetria e sempre reflete o MAC da estacao (o primeiro, quando o match e por UUID/serial);
|
|
102
|
+
// quem identifica o usuario e a entrada casada, nao o campo mac.
|
|
103
|
+
function identificarUsuario(macsEstacao, liberadas, opts) {
|
|
104
|
+
const idsExtras = new Set(idsExtrasDaEstacao(opts));
|
|
79
105
|
for (const entrada of liberadas) {
|
|
80
|
-
|
|
81
|
-
|
|
106
|
+
if (!entrada || typeof entrada !== 'object') continue;
|
|
107
|
+
const macEntrada = normalizarMac(entrada.mac);
|
|
108
|
+
let macBate = macEntrada && macsEstacao.includes(macEntrada);
|
|
109
|
+
|
|
110
|
+
let idBate = false;
|
|
111
|
+
if (!macBate && licenseGuard && idsExtras.size > 0) {
|
|
112
|
+
if (entrada.maquina_id !== undefined) {
|
|
113
|
+
const uuid = licenseGuard.normalizeUuid(entrada.maquina_id);
|
|
114
|
+
if (uuid && idsExtras.has(licenseGuard.PREFIXO_UUID + uuid)) idBate = true;
|
|
115
|
+
}
|
|
116
|
+
if (!idBate && entrada.serial !== undefined) {
|
|
117
|
+
const serial = licenseGuard.normalizeSerial(entrada.serial);
|
|
118
|
+
if (serial && idsExtras.has(licenseGuard.PREFIXO_SERIAL + serial)) idBate = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (macBate || idBate) {
|
|
82
123
|
return {
|
|
83
|
-
mac: macEntrada,
|
|
124
|
+
mac: macBate ? macEntrada : (macsEstacao[0] || null),
|
|
84
125
|
nome_usuario: entrada.id ? String(entrada.id).slice(0, 60) : null,
|
|
85
126
|
funcionario_id: Number.isInteger(entrada.funcionario_id) ? entrada.funcionario_id : null,
|
|
86
127
|
};
|