iron-edge 2.4.4 → 2.4.6

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
@@ -168,12 +168,18 @@ Instalada junto. Depois de `npx iron-edge install`, os dois arquivos abaixo est
168
168
  # Conferir a instalação: o que é seu, o que está diferente sem registro, o que sumiu
169
169
  npx iron-edge verify
170
170
 
171
- # Medir a máquina (Node, git, Claude Code, Iron Edge, chave, plugin, cofre). Com a chave do
171
+ # Medir a máquina (Node, git, Claude Code, Iron Edge, chave, plugin, cofre, atalho). Com a chave do
172
172
  # Iron Office aceita, o resultado acende o checklist da página de Setup do site.
173
173
  npx iron-edge doctor
174
174
 
175
175
  # Instalar já criando um cofre vazio em ~/Iron Cofre (pronto pro Obsidian, nunca sobrescreve)
176
176
  npx iron-edge install --cofre
177
+
178
+ # Ligar o Iron Office: depois do install (ou update), no Mac/Linux, o comando curto é
179
+ ironoffice # = npx iron-edge office (abra um terminal novo na primeira vez)
180
+
181
+ # Instalar sem criar o atalho ironoffice no ~/.zshrc (ou ~/.bashrc)
182
+ npx iron-edge install --sem-atalho
177
183
  ```
178
184
 
179
185
  **Atualizar é um comando. Remover é operação conduzida.**
package/bin/iron-edge.js CHANGED
@@ -18,10 +18,11 @@
18
18
  * Claude Code conversando com o chat do dashboard
19
19
  *
20
20
  * doctor [--json] mede a maquina (Node, git, Claude Code, Iron Edge, chave,
21
- * plugin, cofre) e, com a chave aceita, manda o resultado
21
+ * plugin, cofre, atalho) e, com a chave aceita, manda o resultado
22
22
  * pra pagina de Setup do Iron Office
23
23
  *
24
24
  * install --cofre alem do install, cria ~/Iron Cofre/ (cofre vazio pro Obsidian)
25
+ * install/update --sem-atalho nao cria o alias ironoffice (= office) no ~/.zshrc ou ~/.bashrc
25
26
  *
26
27
  * Nao existe 'uninstall' automatico: remover continua sendo operacao conduzida, com o
27
28
  * manifesto na mao (o README descreve o procedimento).
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * atalho.js — cria o comando curto `ironoffice` (= `npx iron-edge office`) no shell do aluno.
4
+ * Roda no fim do install e do update com sucesso; `--sem-atalho` pula.
5
+ *
6
+ * Regras:
7
+ * - Windows: nao faz nada (o PowerShell nao le .zshrc/.bashrc). O aluno usa `npx iron-edge office`.
8
+ * - Mac: sempre ~/.zshrc (o shell padrao do Mac le so ele; um alias no .bashrc nunca carregaria).
9
+ * Linux: ~/.zshrc se existir, senao ~/.bashrc se existir, senao cria ~/.zshrc.
10
+ * - se o arquivo ja tem `alias ironoffice=`, nao mexe: nunca duplica.
11
+ * - so' ACRESCENTA no fim. Nunca remove nem edita linha existente: o arquivo e' do aluno.
12
+ * - escrita atomica: grava ao lado e renomeia por cima. Se o .zshrc e' um link (dotfiles),
13
+ * escreve no arquivo de verdade e o link continua link. Link quebrado: nao mexe.
14
+ * - os bytes do aluno ficam intactos: le e grava em Buffer (um .zshrc em Latin-1 nao e' recodificado).
15
+ * - arquivo que existe e nao da pra ler (permissao, pasta no lugar): nao escreve nada. So' "nao existe"
16
+ * (ENOENT) vale como arquivo novo. Antes, qualquer erro de leitura virava "vazio" e o rename
17
+ * apagava o .zshrc do aluno (gate da onda 9).
18
+ * - dryRun so' diz o que faria.
19
+ *
20
+ * Saida so' em ASCII, como o resto do instalador.
21
+ */
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const os = require('os');
25
+
26
+ const LINHA = 'alias ironoffice="npx iron-edge office"';
27
+ const BLOCO = '\n# Iron Edge: atalho do canal do Iron Office\n' + LINHA + '\n';
28
+ const JA_TEM = /^\s*alias\s+ironoffice=/m;
29
+
30
+ function arquivoDoShell(home, platform) {
31
+ if (platform === 'darwin') return path.join(home, '.zshrc');
32
+ for (const nome of ['.zshrc', '.bashrc']) {
33
+ const p = path.join(home, nome);
34
+ if (fs.existsSync(p)) return p;
35
+ }
36
+ return path.join(home, '.zshrc');
37
+ }
38
+
39
+ // Devolve { criado: caminho } | { criaria: caminho }
40
+ // | { pulado: 'windows' | 'ja existe' | 'sem-atalho' | 'sem leitura' | 'link quebrado', arquivo? }
41
+ function criarAtalho(opts) {
42
+ const o = opts || {};
43
+ const platform = o.platform || process.platform;
44
+ if (o.semAtalho) return { pulado: 'sem-atalho' };
45
+ if (platform === 'win32') return { pulado: 'windows' };
46
+ const home = o.HOME || os.homedir();
47
+ const rc = arquivoDoShell(home, platform);
48
+ let link = false;
49
+ try { link = fs.lstatSync(rc).isSymbolicLink(); } catch (_) { /* nao existe */ }
50
+ let real = rc;
51
+ if (link) {
52
+ try { real = fs.realpathSync.native(rc); } catch (_) { return { pulado: 'link quebrado', arquivo: rc }; }
53
+ }
54
+ let atual = Buffer.alloc(0);
55
+ try { atual = fs.readFileSync(real); } catch (e) {
56
+ if (!e || e.code !== 'ENOENT') return { pulado: 'sem leitura', arquivo: rc };
57
+ }
58
+ // latin1 mapeia byte a byte: acha o alias (ASCII) em qualquer codificacao sem estourar
59
+ if (JA_TEM.test(atual.toString('latin1'))) return { pulado: 'ja existe', arquivo: rc };
60
+ if (o.dryRun) return { criaria: rc };
61
+ let modo = 0o644;
62
+ try { modo = fs.statSync(real).mode & 0o777; } catch (_) {}
63
+ const tmp = real + '.iron-tmp-' + process.pid;
64
+ fs.writeFileSync(tmp, Buffer.concat([atual, Buffer.from(BLOCO)]), { mode: modo });
65
+ try { fs.renameSync(tmp, real); } catch (e) { try { fs.unlinkSync(tmp); } catch (_) {} throw e; }
66
+ return { criado: rc };
67
+ }
68
+
69
+ // Linha do relatorio do install/update
70
+ function relatarAtalho(r) {
71
+ if (r.criado) return 'Atalho criado: ironoffice em ' + r.criado + ' (abra um terminal novo)';
72
+ if (r.criaria) return 'criaria o atalho ironoffice em ' + r.criaria;
73
+ if (r.pulado === 'ja existe') return 'Atalho ja existia: ironoffice';
74
+ if (r.pulado === 'windows') return 'Windows: use npx iron-edge office';
75
+ if (r.pulado === 'sem leitura') return 'Atalho nao criado: nao consegui ler ' + r.arquivo + ' (nada foi mexido). Use npx iron-edge office';
76
+ if (r.pulado === 'link quebrado') return 'Atalho nao criado: ' + r.arquivo + ' aponta pra um arquivo que nao existe (nada foi mexido). Use npx iron-edge office';
77
+ return 'Atalho pulado (--sem-atalho): use npx iron-edge office';
78
+ }
79
+
80
+ // Rodado no fim do install/update: nunca derruba a instalacao que ja deu certo.
81
+ function atalhoNoFim(opts) {
82
+ try {
83
+ return relatarAtalho(criarAtalho(opts));
84
+ } catch (e) {
85
+ return 'Atalho nao criado (' + (e.code || e.message) + '): use npx iron-edge office';
86
+ }
87
+ }
88
+
89
+ module.exports = { criarAtalho, relatarAtalho, atalhoNoFim, LINHA };
@@ -4,7 +4,8 @@
4
4
  * pra a pagina de Setup acender cada passo sozinha (o site nao enxerga a maquina).
5
5
  *
6
6
  * Mede: node (>= 20), git, Claude Code, Iron Edge instalado pelo npm (manifesto), chave do Iron
7
- * Office (formato + confere com o site), plugin iron-office, cofre (~/Iron Cofre, informativo).
7
+ * Office (formato + confere com o site), plugin iron-office, cofre (~/Iron Cofre, informativo),
8
+ * atalho ironoffice no .zshrc/.bashrc (informativo).
8
9
  *
9
10
  * Envio: SO' com a chave aceita pelo site (ping = ok) faz POST /api/chat/setup-status. Sem chave,
10
11
  * com chave recusada ou sem rede, o resultado fica so' no terminal. Falhar o envio e' aviso, nao erro.
@@ -90,6 +91,23 @@ function checkPlugin(claudeBin) {
90
91
  return out.includes(PLUGIN) ? item(true, PLUGIN) : item(false, 'falta o plugin: rode npx iron-edge office');
91
92
  }
92
93
 
94
+ // alias ironoffice (installer/atalho.js): no Windows nao existe, entao conta como ok
95
+ function checkAtalho(home) {
96
+ if (IS_WIN) return item(true, 'não se aplica (Windows: npx iron-edge office)');
97
+ // nao existe = segue procurando; existe e nao abre = diz isso (o update tambem nao conseguiria)
98
+ let semLeitura = '';
99
+ for (const nome of ['.zshrc', '.bashrc']) {
100
+ let txt = '';
101
+ try { txt = fs.readFileSync(path.join(home, nome), 'latin1'); } catch (e) {
102
+ if (!semLeitura && e && e.code !== 'ENOENT') semLeitura = nome;
103
+ continue;
104
+ }
105
+ if (/^\s*alias\s+ironoffice=/m.test(txt)) return item(true, 'ironoffice em ~/' + nome);
106
+ }
107
+ if (semLeitura) return item(false, 'não consegui ler ~/' + semLeitura);
108
+ return item(false, 'ausente: rode npx iron-edge update');
109
+ }
110
+
93
111
  function checkCofre(home) {
94
112
  const ok = fs.existsSync(path.join(home, 'Iron Cofre', 'CLAUDE.md'));
95
113
  return item(ok, ok ? '~/Iron Cofre' : 'sem cofre pronto (opcional: npx iron-edge install --cofre)');
@@ -111,6 +129,7 @@ async function medir(opts) {
111
129
  key: key.check,
112
130
  plugin: checkPlugin(claude.bin),
113
131
  cofre: checkCofre(home),
132
+ atalho: checkAtalho(home),
114
133
  },
115
134
  ran_at: new Date().toISOString(),
116
135
  };
@@ -138,7 +157,7 @@ async function enviar(result, key, baseUrl) {
138
157
  }
139
158
  }
140
159
 
141
- const ROTULO = { node: 'Node', git: 'git', claude: 'Claude Code', iron_edge: 'Iron Edge', key: 'chave', plugin: 'plugin', cofre: 'cofre' };
160
+ const ROTULO = { node: 'Node', git: 'git', claude: 'Claude Code', iron_edge: 'Iron Edge', key: 'chave', plugin: 'plugin', cofre: 'cofre', atalho: 'atalho' };
142
161
  const OBRIGATORIOS = ['node', 'git', 'claude', 'iron_edge'];
143
162
 
144
163
  async function main(argv, opts) {
@@ -153,7 +172,7 @@ async function main(argv, opts) {
153
172
  }
154
173
  console.log('Iron Edge doctor ' + result.doctor);
155
174
  for (const [k, c] of Object.entries(result.checks)) {
156
- console.log(' ' + (c.ok ? '[ok] ' : (k === 'cofre' ? '[info] ' : '[falta] ')) + ROTULO[k] + ': ' + ascii(c.detail));
175
+ console.log(' ' + (c.ok ? '[ok] ' : (k === 'cofre' || k === 'atalho' ? '[info] ' : '[falta] ')) + ROTULO[k] + ': ' + ascii(c.detail));
157
176
  }
158
177
  console.log('');
159
178
  if (!key && !readKey(o.home || homeDir())) {
@@ -16,7 +16,7 @@
16
16
  * - Manifesto com SHA-256 (gerados marcados generated:true).
17
17
  * - shopify.yaml nasce desativado. config em branco (sem PII).
18
18
  *
19
- * Uso: node install.js [--dry-run] [--source <dir>] [--cofre]
19
+ * Uso: node install.js [--dry-run] [--source <dir>] [--cofre] [--sem-atalho]
20
20
  */
21
21
 
22
22
  'use strict';
@@ -42,6 +42,7 @@ function abort(msg) {
42
42
  const args = process.argv.slice(2);
43
43
  const DRY_RUN = args.includes('--dry-run');
44
44
  const COFRE = args.includes('--cofre'); // cria ~/Iron Cofre/ no fim (installer/cofre.js)
45
+ const SEM_ATALHO = args.includes('--sem-atalho'); // nao cria o alias ironoffice (installer/atalho.js)
45
46
  const srcIdx = args.indexOf('--source');
46
47
  let SOURCE = srcIdx >= 0 && args[srcIdx + 1] ? path.resolve(args[srcIdx + 1]) : __dirname;
47
48
  // Opera sobre o caminho FISICO: ancestral symlink herdado da maquina (perfil
@@ -337,6 +338,8 @@ if (DRY_RUN) {
337
338
  log('\n--- arquivos que SERAO gerados ---');
338
339
  for (const g of GENERATED) log(' ' + g.dest);
339
340
  if (COFRE) require('./cofre').criarCofre({ home: HOME, dryRun: true });
341
+ log('\n--- atalho ---');
342
+ log(' ' + require('./atalho').atalhoNoFim({ HOME, dryRun: true, semAtalho: SEM_ATALHO }));
340
343
  log('\nDRY-RUN concluido. Nada foi copiado.');
341
344
  process.exit(0);
342
345
  }
@@ -552,6 +555,7 @@ log(`Agentes : ${AGENTS_DEST}`);
552
555
  log(`Skills : ${SKILLS_DEST}`);
553
556
  log(`Nucleo : ${IRON_DIR}`);
554
557
  log(`Manifesto: ${manifestPath}`);
558
+ log(require('./atalho').atalhoNoFim({ HOME, semAtalho: SEM_ATALHO }));
555
559
  log('\n--------------------------------');
556
560
  log(' PROXIMO PASSO');
557
561
  log('--------------------------------');
@@ -37,7 +37,7 @@ function isValidKey(key) {
37
37
  }
38
38
 
39
39
  // Le o arquivo da chave e devolve so' uma chave no formato certo; qualquer outra coisa vira ''.
40
- // O `echo` do passo 4 no Windows pode gravar UTF-16 com BOM: normaliza antes de decidir.
40
+ // O `echo` do passo 2 no Windows pode gravar UTF-16 com BOM: normaliza antes de decidir.
41
41
  function readKey(home) {
42
42
  try {
43
43
  const buf = fs.readFileSync(keyPath(home));
@@ -94,6 +94,32 @@ function resolveClaude() {
94
94
  return null;
95
95
  }
96
96
 
97
+ // Versao do plugin instalado, lida do `claude plugin list` ("Version: x.y.z" logo abaixo do nome).
98
+ // Timeout de 5 s: um `claude plugin list` travado nao pode segurar a abertura do canal (estourou = sem aviso).
99
+ function pluginVersion(claude) {
100
+ const r = run(claude, ['plugin', 'list'], { timeout: 5000 });
101
+ if (r.error) return '';
102
+ const out = (r.stdout || '') + (r.stderr || '');
103
+ const i = out.indexOf(PLUGIN);
104
+ if (i < 0) return '';
105
+ const m = out.slice(i).match(/Version:\s*([0-9][\w.-]*)/);
106
+ return m ? m[1] : '';
107
+ }
108
+
109
+ // Plugin mudou desde a ultima abertura do canal: sessoes do Claude abertas antes rodam o hook velho,
110
+ // que o painel nao aceita mais (onda 10). Avisa uma vez por versao; guarda a ultima em ~/.iron.
111
+ // Primeira abertura (sem registro anterior): grava calado, porque quem acabou de instalar nao tem sessao velha.
112
+ function avisoPluginNovo(claude, home) {
113
+ const atual = pluginVersion(claude);
114
+ if (!atual) return;
115
+ const arq = path.join(home || os.homedir(), '.iron', 'office-plugin-version');
116
+ let anterior = '';
117
+ try { anterior = fs.readFileSync(arq, 'utf8').trim(); } catch (_) { /* primeira vez */ }
118
+ if (anterior === atual) return;
119
+ if (anterior) console.log(' [aviso] Plugin atualizado: sessoes do Claude abertas antes disso nao aparecem mais no painel; abra o canal de novo.');
120
+ try { fs.mkdirSync(path.dirname(arq), { recursive: true }); fs.writeFileSync(arq, atual + '\n'); } catch (_) { /* aviso de novo na proxima */ }
121
+ }
122
+
97
123
  function step(claude, label, args, okPatterns) {
98
124
  const r = run(claude, args);
99
125
  const out = ((r.stdout || '') + (r.stderr || '')).trim();
@@ -108,9 +134,9 @@ async function main(argv) {
108
134
  if (!key) {
109
135
  if (keyFileExists()) {
110
136
  console.error('A chave em ' + keyPath() + ' esta invalida (nao tem o formato iok_...).');
111
- console.error('Refaca o passo 4 em ' + ONBOARDING);
137
+ console.error('Refaca o passo 2 (Ligar o Iron) em ' + ONBOARDING);
112
138
  } else {
113
- console.error('Falta a chave do Iron Office. Faca o passo 4 em ' + ONBOARDING);
139
+ console.error('Falta a chave do Iron Office. Faca o passo 2 (Ligar o Iron) em ' + ONBOARDING);
114
140
  }
115
141
  return 1;
116
142
  }
@@ -124,7 +150,7 @@ async function main(argv) {
124
150
  console.log('Iron Office - preparando o canal');
125
151
  const verdict = await ping(key);
126
152
  if (verdict === 'rejected') {
127
- console.error(' [falhou] chave recusada pelo Iron Office: ela foi trocada. Refaca o passo 4 em ' + ONBOARDING);
153
+ console.error(' [falhou] chave recusada pelo Iron Office: ela foi trocada. Refaca o passo 2 (Ligar o Iron) em ' + ONBOARDING);
128
154
  return 1;
129
155
  }
130
156
  if (verdict === 'ok') console.log(' [ok] chave aceita pelo Iron Office');
@@ -143,6 +169,7 @@ async function main(argv) {
143
169
  }
144
170
  // atualizar e' melhor-esforco: sem internet no meio, segue com a versao ja instalada
145
171
  step(claude, 'atualizacao do plugin', ['plugin', 'update', PLUGIN], [/already|up.to.date|latest/i]);
172
+ avisoPluginNovo(claude);
146
173
 
147
174
  const safe = argv.includes('--safe');
148
175
  console.log('');
@@ -163,7 +190,7 @@ async function main(argv) {
163
190
  return r.status ?? 1;
164
191
  }
165
192
 
166
- module.exports = { buildClaudeArgs, keyPath, readKey, keyFileExists, isValidKey, ping, resolveClaude, CHANNEL, PLUGIN, MARKETPLACE_REPO, KEY_RE };
193
+ module.exports = { avisoPluginNovo, pluginVersion, buildClaudeArgs, keyPath, readKey, keyFileExists, isValidKey, ping, resolveClaude, CHANNEL, PLUGIN, MARKETPLACE_REPO, KEY_RE };
167
194
 
168
195
  if (require.main === module) {
169
196
  main(process.argv.slice(2)).then(code => process.exit(code), err => {
@@ -28,6 +28,8 @@ const MANIFEST = path.join(IRON_DIR, 'install-manifest.json');
28
28
 
29
29
  const args = process.argv.slice(2);
30
30
  const DRY = args.includes('--dry-run');
31
+ const SEM_ATALHO = args.includes('--sem-atalho'); // nao cria o alias ironoffice (installer/atalho.js)
32
+ const atalho = (dryRun) => require('./atalho').atalhoNoFim({ HOME, dryRun, semAtalho: SEM_ATALHO });
31
33
  const srcIdx = args.indexOf('--source');
32
34
  const SOURCE = srcIdx >= 0 && args[srcIdx + 1] ? path.resolve(args[srcIdx + 1]) : path.join(__dirname, '..');
33
35
 
@@ -292,7 +294,7 @@ log('');
292
294
  relatarConfig();
293
295
 
294
296
 
295
- if (DRY) { log(''); log('--dry-run: nada foi escrito.'); process.exit(0); }
297
+ if (DRY) { log(''); log(atalho(true)); log('--dry-run: nada foi escrito.'); process.exit(0); }
296
298
 
297
299
  // Permissao da pasta de segredos, TAMBEM no update. O chmod entrou no install no ciclo 2 e o
298
300
  // gate mediu a lacuna: quem ja tinha o Iron instalado seguia com `secrets/` em 755, porque o
@@ -385,6 +387,7 @@ const versaoDoPacote = process.env.IRON_EDGE_VERSION ? 'iron-edge-' + process.en
385
387
  const versaoDesatualizada = Boolean(versaoDoPacote) && versaoRegistrada !== versaoDoPacote;
386
388
  if (!plano.trocar.length && !plano.adicionar.length && !plano.restaurar.length && !plano.remover.length && !plano.gerar.length && !plano.preservar.length && !versaoDesatualizada && !metadataReparado) {
387
389
  log(''); log('Nada a fazer: sua instalacao ja esta na versao deste pacote.');
390
+ log(atalho(false));
388
391
  process.exit(0);
389
392
  }
390
393
 
@@ -519,6 +522,7 @@ log('');
519
522
  log('================================');
520
523
  log(' Atualizado com sucesso');
521
524
  log('================================');
525
+ log(atalho(false));
522
526
  log('Confira quando quiser: npx iron-edge verify');
523
527
  log('Reabra o Claude Code para carregar o que mudou.');
524
528
  log('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iron-edge",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
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 o comando office (chat do Iron Office no dashboard; confere a chave com o site antes de abrir o Claude) e o doctor (mede a maquina e acende o Setup do site).",
5
5
  "keywords": [
6
6
  "claude",
@@ -32,7 +32,7 @@
32
32
  "node": ">=18.0.0"
33
33
  },
34
34
  "scripts": {
35
- "test": "node scripts/lint.js && node scripts/test-verify.js && node scripts/test-update.js && node scripts/test-office.js && node scripts/test-cofre.js",
35
+ "test": "node scripts/lint.js && node scripts/test-verify.js && node scripts/test-update.js && node scripts/test-office.js && node scripts/test-cofre.js && node scripts/test-atalho.js",
36
36
  "test:matriz": "node scripts/test-matriz-update.js"
37
37
  },
38
38
  "devDependencies": {