puxadinho 0.0.0-stage → 0.1.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
@@ -1,3 +1,69 @@
1
- # Temporary Holding Version
1
+ # puxadinho (CLI)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Boxes Linux na nuvem para você e seus agentes.
4
+
5
+ ```sh
6
+ npx puxadinho@latest setup claude # ou: setup codex
7
+ npm i -g puxadinho # instala o comando "puxadinho"
8
+ puxadinho login
9
+ puxadinho create render-ep12
10
+ ```
11
+
12
+ Este pacote é só um lançador: no primeiro uso baixa o binário Go da sua plataforma
13
+ (`puxadinho_<os>_<arch>`, macOS e Linux, arm64 e amd64) da release `v<versão>`, confere o
14
+ sha256 contra o `checksums.txt` que vem dentro deste pacote, guarda em
15
+ `~/.cache/puxadinho/<versão>/` e executa com os mesmos argumentos e código de saída.
16
+ Sinais: Ctrl-C (SIGINT) chega direto ao binário pelo terminal e o lançador não o repete
17
+ (um segundo SIGINT faria a CLI sair sem limpar); SIGTERM e SIGHUP são repassados.
18
+ Requer Node 24 ou mais novo.
19
+
20
+ ## Variáveis
21
+
22
+ - `PUXADINHO_DOWNLOAD_BASE`: base das releases (padrão
23
+ `https://github.com/hightech-company/puxadinho/releases/download`; aceita `file://`).
24
+ O binário é buscado em `<base>/v<versão>/`.
25
+
26
+ > TODO(release): o repositório acima é o `origin` atual do git; confirme o destino das
27
+ > releases antes de publicar (constante `DEFAULT_BASE` em `bin/puxadinho.js`).
28
+
29
+ ## Códigos de saída
30
+
31
+ | Código | Significado |
32
+ | --- | --- |
33
+ | 0 | deu certo |
34
+ | 1 | falha geral (inclui `operation_failed`) |
35
+ | 2 | uso errado (flag, argumento; `invalid_argument`, `unknown_field`) |
36
+ | 3 | autenticação (`unauthorized`, `permission_denied`, `missing_scope`) |
37
+ | 4 | não encontrado (`not_found`) |
38
+ | 5 | limite ou plano (`spend_limit_reached`, `quota_exceeded`, `spec_not_allowed`, `box_cooldown`) |
39
+ | 6 | a box não ficou pronta a tempo (`create_timeout`) |
40
+ | 75 | falha passageira, tente de novo (qualquer erro `retryable`, inclui `timeout`) |
41
+ | 125 | falha do `puxadinho run` (infraestrutura, não o seu comando) |
42
+
43
+ `exec` e `run` saem com o código do próprio comando (sinal = 128+n, estouro de tempo = 124).
44
+
45
+ Dois códigos existem só na CLI (não vêm da API e não estão no contrato): `timeout` — a espera
46
+ local (`wait`, `jobs wait`, `--timeout`) acabou, mas a operação ou o job continua no servidor —
47
+ e `operation_failed` — uma operação terminou `failed` sem código de erro. Com `--json`, eles
48
+ aparecem no campo `code` como os demais.
49
+
50
+ ## Segurança
51
+
52
+ - `puxadinho setup claude` usa `claude mcp add --header "Authorization: Bearer <chave>"`, que expõe
53
+ a chave por instantes na lista de processos da máquina. O Codex guarda a chave em
54
+ `~/.codex/config.toml` (modo 0600).
55
+ - O `checksums.txt` vem no pacote npm, não da origem do download: um binário trocado na release
56
+ (ou no meio do caminho) não confere e é descartado.
57
+ - A API padrão é `https://www.usepuxadinho.com.br` (troque com `PUXADINHO_API_URL` ou `--api-url`).
58
+
59
+ ## Publicar uma versão
60
+
61
+ 1. Ajuste `version` em `package.json`.
62
+ 2. `pnpm --filter puxadinho build:binaries` gera `dist/v<versão>/puxadinho_*` e o `checksums.txt`
63
+ (na raiz do pacote, que vai para o npm, e uma cópia em `dist/v<versão>/`)
64
+ (`go build` com `-ldflags "-X puxadinho.app/go/internal/cli/api.Version=<versão>"`).
65
+ 3. Crie a release `v<versão>` com os binários de `dist/v<versão>/`.
66
+ 4. `pnpm --filter puxadinho publish` (o pacote leva o `checksums.txt` desses mesmos binários:
67
+ publique sempre logo depois do build da release).
68
+
69
+ `dist/` e o `checksums.txt` gerado não vão para o git; o `checksums.txt` vai para o npm.
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ // Executa o binário da CLI desta máquina. Normalmente ele vem no pacote da plataforma
3
+ // (@puxadinho/cli-<os>-<cpu>, dependência opcional que o npm instala só na plataforma certa).
4
+ // Sem ele (ex.: npm com --no-optional), baixa uma vez, confere o sha256 contra o checksums.txt
5
+ // que vem DENTRO deste pacote (nunca o da origem do download) e o executa.
6
+ "use strict";
7
+ const { spawn } = require("node:child_process");
8
+ const { createHash } = require("node:crypto");
9
+ const fs = require("node:fs");
10
+ const os = require("node:os");
11
+ const path = require("node:path");
12
+
13
+ const pkg = require("../package.json");
14
+ const version = pkg.version;
15
+ const zlib = require("node:zlib");
16
+ // Reserva: o mesmo pacote da plataforma, baixado direto do registro do npm (.tgz).
17
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
18
+
19
+ const OS = { darwin: "darwin", linux: "linux" };
20
+ const ARCH = { arm64: "arm64", x64: "amd64" };
21
+
22
+ function fail(msg, code = 1) {
23
+ process.stderr.write(`puxadinho: ${msg}\n`);
24
+ process.exit(code);
25
+ }
26
+
27
+ async function fetchBuffer(url) {
28
+ if (url.startsWith("file://")) {
29
+ return fs.readFileSync(new URL(url));
30
+ }
31
+ const res = await fetch(url, { redirect: "follow" });
32
+ if (!res.ok) throw new Error(`${url} respondeu ${res.status}`);
33
+ return Buffer.from(await res.arrayBuffer());
34
+ }
35
+
36
+ /** Binário do pacote da plataforma (@puxadinho/cli-<os>-<cpu>), instalado pelo npm como dependência opcional. */
37
+ function platformBinary() {
38
+ try {
39
+ return require.resolve(`@puxadinho/cli-${process.platform}-${process.arch}/bin/puxadinho`);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /** Extrai um arquivo de um tar (ustar) já descompactado. */
46
+ function fileFromTar(buf, want) {
47
+ for (let off = 0; off + 512 <= buf.length; ) {
48
+ const h = buf.subarray(off, off + 512);
49
+ if (h.every((b) => b === 0)) break;
50
+ const str = (a, b) => h.subarray(a, b).toString("utf8").replace(/\0.*$/s, "");
51
+ const prefix = str(345, 500);
52
+ const name = prefix ? `${prefix}/${str(0, 100)}` : str(0, 100);
53
+ const size = parseInt(str(124, 136).trim() || "0", 8);
54
+ const start = off + 512;
55
+ if (name === want) return buf.subarray(start, start + size);
56
+ off = start + Math.ceil(size / 512) * 512;
57
+ }
58
+ return null;
59
+ }
60
+
61
+ async function ensureBinary() {
62
+ const local = platformBinary();
63
+ if (local) return local;
64
+ const goos = OS[process.platform];
65
+ const goarch = ARCH[process.arch];
66
+ if (!goos || !goarch) {
67
+ fail(`plataforma sem suporte: ${process.platform}/${process.arch} (há binários para macOS e Linux, arm64 e x64).`);
68
+ }
69
+ const name = `puxadinho_${goos}_${goarch}`;
70
+ const cacheDir = path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache"), "puxadinho", version);
71
+ const target = path.join(cacheDir, name);
72
+ if (fs.existsSync(target)) return target;
73
+
74
+ // O sha256 esperado vem do pacote npm (gerado no build:binaries da mesma versão):
75
+ // uma origem de download comprometida não consegue trocar binário e soma juntos.
76
+ let sums;
77
+ try {
78
+ sums = fs.readFileSync(path.join(__dirname, "..", "checksums.txt"), "utf8");
79
+ } catch {
80
+ fail("este pacote veio sem checksums.txt; reinstale o puxadinho (npm i -g puxadinho@latest).");
81
+ }
82
+ process.stderr.write(`puxadinho: baixando ${name} v${version}…\n`);
83
+ let bin;
84
+ try {
85
+ if (process.env.PUXADINHO_DOWNLOAD_BASE) {
86
+ // Espelho com o layout de release: <base>/v<versão>/<binário>.
87
+ const base = process.env.PUXADINHO_DOWNLOAD_BASE.replace(/\/+$/, "");
88
+ bin = await fetchBuffer(`${base}/v${version}/${name}`);
89
+ } else {
90
+ const reg = (process.env.npm_config_registry || DEFAULT_REGISTRY).replace(/\/+$/, "");
91
+ const plat = `cli-${process.platform}-${process.arch}`;
92
+ const tgz = await fetchBuffer(`${reg}/@puxadinho/${plat}/-/${plat}-${version}.tgz`);
93
+ bin = fileFromTar(zlib.gunzipSync(tgz), "package/bin/puxadinho");
94
+ if (!bin) throw new Error(`o pacote @puxadinho/${plat} veio sem bin/puxadinho`);
95
+ }
96
+ } catch (err) {
97
+ fail(`não consegui baixar o binário (${err.message}). Verifique a rede ou o registro do npm.`, 75);
98
+ }
99
+ const line = sums.split("\n").map((l) => l.trim().split(/\s+/)).find((p) => p[1] === name || p[1] === `*${name}`);
100
+ if (!line) fail(`o checksums.txt do pacote não lista ${name}.`);
101
+ const got = createHash("sha256").update(bin).digest("hex");
102
+ if (got !== line[0].toLowerCase()) fail(`sha256 de ${name} não confere; o download foi descartado.`);
103
+
104
+ fs.mkdirSync(cacheDir, { recursive: true });
105
+ const tmp = `${target}.${process.pid}.tmp`;
106
+ fs.writeFileSync(tmp, bin, { mode: 0o755 });
107
+ fs.renameSync(tmp, target);
108
+ return target;
109
+ }
110
+
111
+ async function main() {
112
+ const bin = await ensureBinary();
113
+ const child = spawn(bin, process.argv.slice(2), { stdio: "inherit" });
114
+ // Ctrl-C: o terminal já manda SIGINT ao grupo de processos inteiro (o filho
115
+ // recebe direto). Repassar daria um SEGUNDO SIGINT, que a CLI trata como
116
+ // "sair já" (o `run` deixaria a box para trás). Aqui só ignoramos o SIGINT e
117
+ // esperamos o filho sair. SIGTERM/SIGHUP (vindos de `kill`, ou do fim da
118
+ // sessão) chegam só a nós: esses são repassados.
119
+ process.on("SIGINT", () => {});
120
+ for (const sig of ["SIGTERM", "SIGHUP"]) {
121
+ process.on(sig, () => child.kill(sig));
122
+ }
123
+ child.on("error", (err) => fail(`não consegui executar ${bin}: ${err.message}`));
124
+ child.on("exit", (code, signal) => {
125
+ if (signal) {
126
+ // Morreu por sinal: repete o sinal em nós mesmos para o código de saída ser o mesmo.
127
+ process.removeAllListeners(signal);
128
+ process.kill(process.pid, signal);
129
+ return;
130
+ }
131
+ process.exit(code ?? 1);
132
+ });
133
+ }
134
+
135
+ main().catch((err) => fail(err.message));
package/checksums.txt ADDED
@@ -0,0 +1,4 @@
1
+ e2e6bd0da352c021c1440f18533c099abb90e5f6e9b75b109f1a4e62896526b9 puxadinho_darwin_arm64
2
+ a9a468933c3649e6b62f68723df7faa1fc81e68c3bc548f0b5d9252d48a416d3 puxadinho_darwin_amd64
3
+ bbefcb248a2b64f8061278f8445ddbf20dce09e00eb647b3cda680f40b592a52 puxadinho_linux_amd64
4
+ 53f93111a199c0e97c2e943cbc85a2869d0710848f9dd89d626bd4c671482072 puxadinho_linux_arm64
package/package.json CHANGED
@@ -1,6 +1,30 @@
1
1
  {
2
2
  "name": "puxadinho",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "CLI do puxadinho: boxes Linux na nuvem para você e seus agentes.",
5
+ "license": "SEE LICENSE IN README.md",
6
+ "type": "commonjs",
7
+ "bin": {
8
+ "puxadinho": "bin/puxadinho.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "checksums.txt",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "engines": {
19
+ "node": ">=24"
20
+ },
21
+ "scripts": {
22
+ "build:binaries": "node scripts/build-binaries.js"
23
+ },
24
+ "optionalDependencies": {
25
+ "@puxadinho/cli-darwin-arm64": "0.1.1",
26
+ "@puxadinho/cli-darwin-x64": "0.1.1",
27
+ "@puxadinho/cli-linux-arm64": "0.1.1",
28
+ "@puxadinho/cli-linux-x64": "0.1.1"
29
+ }
30
+ }