iron-edge 2.4.0 → 2.4.1

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 CHANGED
@@ -225,10 +225,11 @@ Antes, faça o **passo 4** do onboarding do Iron Office (ele salva a sua chave e
225
225
 
226
226
  O que esperar:
227
227
 
228
- 1. O comando instala (ou atualiza) o plugin de canal `iron-office` e abre o Claude Code com ele ligado.
229
- 2. O Claude Code mostra um **aviso de tela cheia**: canais ainda são recurso em teste. Aceite.
230
- 3. Abra a página **Iron** do dashboard. Em até 1 minuto aparece "Iron online". Escreva ali e o Iron responde ali.
231
- 4. **Deixe essa aba do terminal aberta.** É ela que faz o Iron responder no site.
228
+ 1. O comando confere a sua chave (formato `iok_…` e aceita pelo site) **antes** de abrir o Claude Code. Chave trocada ou fora do formato: ele para e manda refazer o passo 4, em vez de abrir uma sessão que nunca fica online.
229
+ 2. Instala (ou atualiza) o plugin de canal `iron-office` e abre o Claude Code com ele ligado.
230
+ 3. O Claude Code mostra um **aviso de tela cheia**: canais ainda são recurso em teste. Aceite.
231
+ 4. Abra a página **Iron** do dashboard. Em até 1 minuto aparece "Iron online". Escreva ali e o Iron responde ali.
232
+ 5. **Deixe essa aba do terminal aberta.** É ela que faz o Iron responder no site.
232
233
 
233
234
  Por padrão a sessão do `office` roda **sem pedir licença** a cada ferramenta (é o que deixa o Iron trabalhar enquanto você está só no site). `npx iron-edge office --safe` abre pedindo licença a cada ferramenta. Qualquer outro argumento vai direto pro `claude`.
234
235
 
@@ -2,9 +2,10 @@
2
2
  /**
3
3
  * office.js — `npx iron-edge office`: liga o Iron Office (chat do dashboard) no Claude Code.
4
4
  *
5
- * Passos: (1) confere a chave do Iron Office, (2) confere o Claude Code, (3) instala ou
6
- * atualiza o plugin de canal `iron-office` pelo marketplace do GitHub, (4) abre o Claude Code
7
- * com o canal ligado. O aluno deixa essa aba aberta: e' ela que faz o Iron responder no site.
5
+ * Passos: (1) confere a chave do Iron Office (formato + confere com o site), (2) confere o
6
+ * Claude Code, (3) instala ou atualiza o plugin de canal `iron-office` pelo marketplace do
7
+ * GitHub, (4) abre o Claude Code com o canal ligado. O aluno deixa essa aba aberta: e' ela que
8
+ * faz o Iron responder no site.
8
9
  *
9
10
  * Flags: --safe tira o `--permission-mode bypassPermissions` (o Claude pede licenca a cada
10
11
  * ferramenta). Qualquer outro argumento vai direto pro `claude`.
@@ -21,22 +22,54 @@ const { spawnSync } = require('child_process');
21
22
  const MARKETPLACE_REPO = 'filipecoff21-code/iron-office-channel';
22
23
  const PLUGIN = 'iron-office@iron-office-channel';
23
24
  const CHANNEL = 'plugin:' + PLUGIN;
24
- const ONBOARDING = 'https://iron-office.vercel.app/onboarding';
25
+ const OFFICE_URL = (process.env.IRON_OFFICE_URL || 'https://iron-office.vercel.app').replace(/\/+$/, '');
26
+ const ONBOARDING = OFFICE_URL + '/onboarding';
25
27
  const IS_WIN = process.platform === 'win32';
28
+ const KEY_RE = /^iok_[0-9a-f]{48}$/;
29
+ const PING_TIMEOUT_MS = 5000;
26
30
 
27
31
  function keyPath(home) {
28
32
  return path.join(home || os.homedir(), '.claude', 'iron-office-api-key');
29
33
  }
30
34
 
35
+ function isValidKey(key) {
36
+ return typeof key === 'string' && KEY_RE.test(key);
37
+ }
38
+
39
+ // Le o arquivo da chave e devolve so' uma chave no formato certo; qualquer outra coisa vira ''.
31
40
  // O `echo` do passo 4 no Windows pode gravar UTF-16 com BOM: normaliza antes de decidir.
32
41
  function readKey(home) {
33
42
  try {
34
43
  const buf = fs.readFileSync(keyPath(home));
35
44
  const utf16 = buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe;
36
- return buf.toString(utf16 ? 'utf16le' : 'utf8').replace(/[\uFEFF\u0000]/g, '').trim();
45
+ const key = buf.toString(utf16 ? 'utf16le' : 'utf8').replace(/[\u0000]/g, '').trim();
46
+ return isValidKey(key) ? key : '';
37
47
  } catch (_) { return ''; }
38
48
  }
39
49
 
50
+ function keyFileExists(home) {
51
+ try { return fs.statSync(keyPath(home)).isFile(); } catch (_) { return false; }
52
+ }
53
+
54
+ // Confere a chave com o site antes de abrir o Claude.
55
+ // Devolve 'ok' (aceita), 'rejected' (401: a chave foi trocada) ou 'offline' (sem rede / erro).
56
+ async function ping(key, baseUrl) {
57
+ const url = (baseUrl || OFFICE_URL).replace(/\/+$/, '') + '/api/api-key/ping';
58
+ if (typeof fetch !== 'function') return 'offline';
59
+ const ctl = new AbortController();
60
+ const timer = setTimeout(() => ctl.abort(), PING_TIMEOUT_MS);
61
+ try {
62
+ const r = await fetch(url, { headers: { 'x-api-key': key }, signal: ctl.signal });
63
+ if (r.status === 200) return 'ok';
64
+ if (r.status === 401) return 'rejected';
65
+ return 'offline';
66
+ } catch (_) {
67
+ return 'offline';
68
+ } finally {
69
+ clearTimeout(timer);
70
+ }
71
+ }
72
+
40
73
  // Argumentos do `claude` que abre a sessao com o canal. Separado pra ser testado sem rede.
41
74
  function buildClaudeArgs(argv) {
42
75
  const safe = argv.includes('--safe');
@@ -70,9 +103,15 @@ function step(claude, label, args, okPatterns) {
70
103
  return false;
71
104
  }
72
105
 
73
- function main(argv) {
74
- if (!readKey()) {
75
- console.error('Falta a chave do Iron Office. Faca o passo 4 em ' + ONBOARDING);
106
+ async function main(argv) {
107
+ const key = readKey();
108
+ if (!key) {
109
+ if (keyFileExists()) {
110
+ console.error('A chave em ' + keyPath() + ' esta invalida (nao tem o formato iok_...).');
111
+ console.error('Refaca o passo 4 em ' + ONBOARDING);
112
+ } else {
113
+ console.error('Falta a chave do Iron Office. Faca o passo 4 em ' + ONBOARDING);
114
+ }
76
115
  return 1;
77
116
  }
78
117
 
@@ -83,6 +122,14 @@ function main(argv) {
83
122
  }
84
123
 
85
124
  console.log('Iron Office - preparando o canal');
125
+ const verdict = await ping(key);
126
+ if (verdict === 'rejected') {
127
+ console.error(' [falhou] chave recusada pelo Iron Office: ela foi trocada. Refaca o passo 4 em ' + ONBOARDING);
128
+ return 1;
129
+ }
130
+ if (verdict === 'ok') console.log(' [ok] chave aceita pelo Iron Office');
131
+ else console.log(' [aviso] nao deu pra conferir a chave com o site (sem rede?). Seguindo com a chave local.');
132
+
86
133
  const okMarket = step(claude, 'marketplace ' + MARKETPLACE_REPO, ['plugin', 'marketplace', 'add', MARKETPLACE_REPO], [/already/i]);
87
134
  const okPlugin = okMarket && step(claude, 'plugin ' + PLUGIN, ['plugin', 'install', PLUGIN, '--scope', 'user'], [/already/i]);
88
135
  if (!okMarket || !okPlugin) {
@@ -116,8 +163,11 @@ function main(argv) {
116
163
  return r.status ?? 1;
117
164
  }
118
165
 
119
- module.exports = { buildClaudeArgs, keyPath, readKey, CHANNEL, PLUGIN, MARKETPLACE_REPO };
166
+ module.exports = { buildClaudeArgs, keyPath, readKey, isValidKey, ping, CHANNEL, PLUGIN, MARKETPLACE_REPO, KEY_RE };
120
167
 
121
168
  if (require.main === module) {
122
- process.exit(main(process.argv.slice(2)));
169
+ main(process.argv.slice(2)).then(code => process.exit(code), err => {
170
+ console.error('Erro inesperado: ' + (err && err.message ? err.message : err));
171
+ process.exit(1);
172
+ });
123
173
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "iron-edge",
3
- "version": "2.4.0",
4
- "description": "Ecossistema de agentes para Claude Code: 44 agentes de topo, 18 squads e 14 skills, com quality gate, memoria, instalador que nunca sobrescreve e o comando office (chat do Iron Office no dashboard).",
3
+ "version": "2.4.1",
4
+ "description": "Ecossistema de agentes para Claude Code: 44 agentes de topo, 18 squads e 14 skills, com quality gate, memoria, instalador que nunca sobrescreve e o comando office (chat do Iron Office no dashboard; confere a chave com o site antes de abrir o Claude).",
5
5
  "keywords": [
6
6
  "claude",
7
7
  "claude-code",