terminal-smart-cli 0.98.0 → 0.99.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.
@@ -0,0 +1,67 @@
1
+ #!/bin/sh
2
+ # EXEMPLO — não aplicado. Instalar em /usr/local/sbin/ts-hub-parar-consumir (root:root, 0755).
3
+ # Chamado pelo ts-hub-parar.service (root) quando o hub grava um pedido. Ver README.md.
4
+ #
5
+ # Regras:
6
+ # - nunca lê o CONTEÚDO do pedido (o arquivo vem do usuário do hub): só o nome, validado
7
+ # contra ^parar-<13 dígitos>-<16 hex>\.pedido$;
8
+ # - tudo o que estiver em pedidos/ sai de lá por rename (mv no mesmo sistema de arquivos não
9
+ # segue link): válidos para consumidos/<nome>, o resto para consumidos/invalido-...;
10
+ # o diretório sempre fica vazio, então o .path não dispara em laço;
11
+ # - roda o ts-parar-tudo UMA vez por lote, mesmo que haja vários pedidos;
12
+ # - grava confirmacoes/<base>.ok (ou .falhou) de forma atômica, legível pelo hub (0644).
13
+ #
14
+ # Variáveis (só para teste; em produção valem os padrões):
15
+ # TS_HUB_PARAR_BASE (padrão /var/lib/ts-hub-parar)
16
+ # TS_HUB_PARAR_CMD (padrão /usr/local/sbin/ts-parar-tudo)
17
+
18
+ BASE="${TS_HUB_PARAR_BASE:-/var/lib/ts-hub-parar}"
19
+ CMD="${TS_HUB_PARAR_CMD:-/usr/local/sbin/ts-parar-tudo}"
20
+ PED="$BASE/pedidos"
21
+ CONF="$BASE/confirmacoes"
22
+ CONS="$BASE/consumidos"
23
+ umask 022
24
+
25
+ [ -d "$PED" ] && [ ! -L "$PED" ] || { echo "ts-hub-parar: $PED ausente" >&2; exit 1; }
26
+ [ -d "$CONF" ] && [ ! -L "$CONF" ] || { echo "ts-hub-parar: $CONF ausente" >&2; exit 1; }
27
+ [ -d "$CONS" ] && [ ! -L "$CONS" ] || { echo "ts-hub-parar: $CONS ausente" >&2; exit 1; }
28
+
29
+ # Nome válido: casado caractere a caractere (sem grep: nome com quebra de linha, espaço ou
30
+ # curinga nunca passa).
31
+ D='[0-9]'
32
+ X='[0-9a-f]'
33
+ D13="$D$D$D$D$D$D$D$D$D$D$D$D$D"
34
+ X16="$X$X$X$X$X$X$X$X$X$X$X$X$X$X$X$X"
35
+
36
+ validos=""
37
+ n=0
38
+ for f in "$PED"/* "$PED"/.[!.]* "$PED"/..?*; do
39
+ [ -e "$f" ] || [ -L "$f" ] || continue # padrão que não casou com nada
40
+ nome=${f##*/}
41
+ n=$((n + 1))
42
+ valido=0
43
+ case "$nome" in
44
+ parar-$D13-$X16.pedido) [ -f "$f" ] && [ ! -L "$f" ] && valido=1 ;;
45
+ esac
46
+ if [ "$valido" = 1 ]; then
47
+ if mv -fT -- "$f" "$CONS/$nome"; then
48
+ validos="$validos ${nome%.pedido}"
49
+ fi
50
+ else
51
+ mv -fT -- "$f" "$CONS/invalido-$(date -u +%Y%m%d%H%M%S)-$$-$n" || echo "ts-hub-parar: não consegui tirar uma entrada de $PED" >&2
52
+ fi
53
+ done
54
+
55
+ [ -n "$validos" ] || exit 0
56
+ set -f # daqui em diante, nada de curinga na lista (os nomes já são só [a-z0-9-])
57
+
58
+ if "$CMD" "celular"; then resultado=ok; else resultado=falhou; fi
59
+ echo "ts-hub-parar: ts-parar-tudo terminou ($resultado)"
60
+
61
+ for base in $validos; do
62
+ tmp=$(mktemp "$CONF/.tmp.XXXXXX") || continue
63
+ printf '{"resultado":"%s","em":"%s"}\n' "$resultado" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$tmp"
64
+ chmod 0644 "$tmp"
65
+ mv -fT -- "$tmp" "$CONF/$base.$resultado"
66
+ done
67
+ exit 0
@@ -0,0 +1,16 @@
1
+ # EXEMPLO — não aplicado. Instalação e motivos: README.md nesta pasta.
2
+ # Copiar para /etc/systemd/system/ts-hub-parar.path (dono root, 0644).
3
+ #
4
+ # Observa o diretório de pedidos do hub. O serviço sempre esvazia o diretório (renomeia tudo
5
+ # para consumidos/), então o .path não dispara em laço; o limite abaixo é só uma segunda trava.
6
+ [Unit]
7
+ Description=Terminal Smart: pedido de "Parar tudo" vindo do hub (celular)
8
+
9
+ [Path]
10
+ DirectoryNotEmpty=/var/lib/ts-hub-parar/pedidos
11
+ Unit=ts-hub-parar.service
12
+ TriggerLimitIntervalSec=60
13
+ TriggerLimitBurst=20
14
+
15
+ [Install]
16
+ WantedBy=paths.target
@@ -0,0 +1,23 @@
1
+ # EXEMPLO — não aplicado. Instalação e motivos: README.md nesta pasta.
2
+ # Copiar para /etc/systemd/system/ts-hub-parar.service (dono root, 0644).
3
+ #
4
+ # Roda como root (o ts-parar-tudo precisa parar os agentes). Não lê o conteúdo de nenhum
5
+ # pedido: só valida o NOME, renomeia o pedido para consumidos/, roda
6
+ # /usr/local/sbin/ts-parar-tudo "celular" uma vez e grava a confirmação que o hub lê.
7
+ [Unit]
8
+ Description=Terminal Smart: executa "Parar tudo" e confirma ao hub
9
+ StartLimitIntervalSec=60
10
+ StartLimitBurst=10
11
+
12
+ [Service]
13
+ Type=oneshot
14
+ User=root
15
+ Group=root
16
+ UMask=0022
17
+ ExecStart=/usr/local/sbin/ts-hub-parar-consumir
18
+ TimeoutStartSec=90
19
+ PrivateTmp=yes
20
+ # Endurecimento extra, se o ts-parar-tudo continuar funcionando com ele (testar antes):
21
+ #ProtectHome=read-only
22
+ #ProtectKernelTunables=yes
23
+ #ProtectControlGroups=no
@@ -0,0 +1,10 @@
1
+ # EXEMPLO — não aplicado. Copiar para /etc/tmpfiles.d/ts-hub-parar.conf e rodar
2
+ # systemd-tmpfiles --create /etc/tmpfiles.d/ts-hub-parar.conf
3
+ #
4
+ # pedidos/ só o hub (tshub) escreve; root lê o nome e renomeia
5
+ # confirmacoes/ só root escreve; o hub lê (0755, arquivos 0644)
6
+ # consumidos/ só root; histórico dos pedidos, apagado depois de 30 dias
7
+ d /var/lib/ts-hub-parar 0755 root root -
8
+ d /var/lib/ts-hub-parar/pedidos 0700 tshub tshub -
9
+ d /var/lib/ts-hub-parar/confirmacoes 0755 root root -
10
+ d /var/lib/ts-hub-parar/consumidos 0700 root root 30d
@@ -0,0 +1,39 @@
1
+ # EXEMPLO — não aplicado. Copiar para /etc/systemd/system/ts-hub.service.
2
+ # O hub roda como tshub (sem sudo, sem docker, sem tmux): só abre conexão de SAÍDA até o
3
+ # relay, lê as telas exportadas (grupo ts-telas) e pede o Parar tudo por arquivo-sinal.
4
+ [Unit]
5
+ Description=Terminal Smart: hub (acesso pelo celular, só leitura + Parar tudo)
6
+ Wants=network-online.target
7
+ After=network-online.target ts-hub-parar.path
8
+
9
+ [Service]
10
+ User=tshub
11
+ Group=tshub
12
+ SupplementaryGroups=ts-telas
13
+ UMask=0077
14
+ Environment=TS_HUB_DIR=/var/lib/tshub/hub
15
+ Environment="TS_HUB_NOME=VPS dos agentes"
16
+ Environment=TS_HUB_PARAR_DIR=/var/lib/ts-hub-parar
17
+ Environment=TS_HUB_TELAS_DIR=/var/lib/ts-hub-telas
18
+ ExecStart=/usr/bin/node /opt/ts-hub/hub/principal.js iniciar
19
+ Restart=on-failure
20
+ RestartSec=10
21
+ NoNewPrivileges=yes
22
+ PrivateTmp=yes
23
+ PrivateDevices=yes
24
+ ProtectSystem=strict
25
+ ProtectHome=yes
26
+ ProtectKernelTunables=yes
27
+ ProtectKernelModules=yes
28
+ ProtectControlGroups=yes
29
+ RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
30
+ RestrictSUIDSGID=yes
31
+ LockPersonality=yes
32
+ CapabilityBoundingSet=
33
+ ReadWritePaths=/var/lib/tshub /var/lib/ts-hub-parar/pedidos
34
+ ReadOnlyPaths=/var/lib/ts-hub-telas /var/lib/ts-hub-parar/confirmacoes
35
+ # Sem caminho para a produção, nem para a própria VPS/rede privada (o relay fica na Cloudflare).
36
+ IPAddressDeny=82.25.68.244 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16
37
+
38
+ [Install]
39
+ WantedBy=multi-user.target
@@ -0,0 +1,19 @@
1
+ # Instalado na VPS em /etc/systemd/user/ts-tmux.service (root, 0644); habilitado para o
2
+ # tsagentes com `systemctl --user enable --now ts-tmux` (como tsagentes).
3
+ # tmux dos agentes (servidor -L ts). Roda dentro do user@997: o Parar tudo derruba junto.
4
+ [Unit]
5
+ Description=TS: tmux dos agentes (-L ts)
6
+ ConditionUser=tsagentes
7
+
8
+ [Service]
9
+ Type=simple
10
+ WorkingDirectory=%h
11
+ ExecStart=/usr/bin/tmux -L ts -f /usr/local/lib/ts/tmux-agentes.conf -D
12
+ # janela de teste do hub (entrega 1); sai quando os agentes reais tiverem as janelas deles
13
+ ExecStartPost=/bin/sh -c 'sleep 1; /usr/bin/tmux -L ts has-session -t piloto 2>/dev/null || /usr/bin/tmux -L ts new-session -d -s piloto -n docker -c %h /usr/local/lib/ts/tela-piloto.sh'
14
+ ExecStop=/usr/bin/tmux -L ts kill-server
15
+ Restart=on-failure
16
+ RestartSec=5
17
+
18
+ [Install]
19
+ WantedBy=default.target
package/lib/acp.js CHANGED
@@ -146,11 +146,17 @@ function acpServer({ token, lang = 'pt', plan = 'free' }) {
146
146
  const o = res && res.outcome;
147
147
  return !!(o && o.outcome === 'selected' && o.optionId === 'allow');
148
148
  },
149
- onRemote: () => {},
150
149
  });
151
150
  } catch (e) {
152
151
  if (e && (e.code === 'no_credits' || e.status === 402)) {
153
- notify('session/update', { sessionId: sid, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: lang === 'en' ? 'AI limit reached on this plan — top up/upgrade to continue.' : 'Limite de IA do plano atingido — recarregue/upgrade pra continuar.' } } });
152
+ // Com chave própria ativa (a execução rodou nela), 402/teto é saldo do PROVEDOR; sem ela,
153
+ // é a IA do Terminal Smart sem créditos legados — orienta a usar a IA própria (sua
154
+ // chave, Ollama local ou sua assinatura). Mesmos textos do terminal.
155
+ const T = require('./i18n').t(lang);
156
+ let own = null;
157
+ try { const a = require('./keyring').ativo(); if (a && (a.key || (require('./providers').info(a.id) || {}).semChave)) own = a; } catch (_) {}
158
+ const text = own ? T.byok_no_balance(own.provedor || own.id) : T.no_credits;
159
+ notify('session/update', { sessionId: sid, update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } } });
154
160
  return { stopReason: 'refusal' };
155
161
  }
156
162
  log('erro no prompt:', String((e && e.message) || e));