duckhunt-runner 0.2.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.
package/dist/repos.js ADDED
@@ -0,0 +1,190 @@
1
+ // subcomandos `duckhunt-runner repos ...`: gestionan el mapa repo→checkout local de la config sin
2
+ // editar json a mano. el mapa es local A PROPÓSITO (el server nunca ve paths, contrato 42); al
3
+ // server solo viajan las KEYS via el claim (presencia, sin paths).
4
+ import { execFileSync } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { configPath, loadConfig, saveConfig } from './config.js';
8
+ // mismo formato que valida el server para target_repo.repo: workspace/slug u owner/repo.
9
+ const REPO_KEY_RE = /^[^/\s]+\/[^/\s]+$/;
10
+ const USAGE = `uso:
11
+ duckhunt-runner repos list
12
+ duckhunt-runner repos add <workspace/slug> <path> [--dangerously-skip-permissions] [--no-worktree]
13
+ duckhunt-runner repos remove <workspace/slug>
14
+ duckhunt-runner repos discover [dir] [--dry-run] escanea checkouts git y los mapea por su remote
15
+ `;
16
+ // deriva la key workspace/slug del url del remote origin. cubre las formas habituales:
17
+ // git@host:ws/slug.git · https://host/ws/slug.git · ssh://git@host/ws/slug.git
18
+ // (los dos últimos segmentos del path, sin .git — coincide con target_repo.repo tanto
19
+ // para bitbucket como para github).
20
+ export function repoKeyFromRemote(url) {
21
+ const cleaned = url.trim().replace(/\.git$/, '');
22
+ const scpLike = cleaned.match(/^[^@\s]+@[^:/\s]+:(.+)$/);
23
+ const pathPart = scpLike
24
+ ? scpLike[1]
25
+ : (() => {
26
+ try {
27
+ return new URL(cleaned).pathname.replace(/^\/+/, '');
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ })();
33
+ if (!pathPart)
34
+ return null;
35
+ const segments = pathPart.split('/').filter(Boolean);
36
+ if (segments.length < 2)
37
+ return null;
38
+ const key = segments.slice(-2).join('/');
39
+ return REPO_KEY_RE.test(key) ? key : null;
40
+ }
41
+ // url del remote origin de un checkout, o null si no es repo git / no tiene origin.
42
+ function originUrl(dir) {
43
+ try {
44
+ return execFileSync('git', ['-C', dir, 'remote', 'get-url', 'origin'], {
45
+ encoding: 'utf-8',
46
+ stdio: ['ignore', 'pipe', 'ignore'],
47
+ }).trim() || null;
48
+ }
49
+ catch {
50
+ // sin repo git o sin remote origin: candidato descartado, no es un error.
51
+ return null;
52
+ }
53
+ }
54
+ // ficheros de settings de claude code que un run headless en ese repo cargará (hooks incluidos).
55
+ const CLAUDE_SETTINGS_FILES = ['.claude/settings.json', '.claude/settings.local.json'];
56
+ // aviso informativo: un hook Stop del repo se ejecuta al terminar cada run (p.ej. un rebuild de
57
+ // docker). el usuario asume sus hooks (decisión del contrato 42), pero conviene que lo sepa.
58
+ function warnStopHooks(repoPath) {
59
+ CLAUDE_SETTINGS_FILES.forEach((rel) => {
60
+ const file = path.join(repoPath, rel);
61
+ if (!fs.existsSync(file))
62
+ return;
63
+ try {
64
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
65
+ if (Array.isArray(parsed.hooks?.Stop) && parsed.hooks.Stop.length > 0) {
66
+ console.warn(`aviso: ${rel} define hooks Stop — se ejecutarán al terminar cada run en este repo`);
67
+ }
68
+ }
69
+ catch {
70
+ // settings ilegibles: no es asunto del daemon.
71
+ }
72
+ });
73
+ }
74
+ export function reposCommand(args) {
75
+ const cfg = loadConfig();
76
+ if (!cfg) {
77
+ console.error('sin config: ejecuta `duckhunt-runner login <base-url>` primero');
78
+ process.exitCode = 1;
79
+ return;
80
+ }
81
+ const [sub, ...rest] = args;
82
+ if (sub === 'list') {
83
+ const entries = Object.entries(cfg.repos);
84
+ if (entries.length === 0) {
85
+ console.log('sin repos mapeados. añade con `duckhunt-runner repos add <workspace/slug> <path>` o `repos discover <dir>`');
86
+ return;
87
+ }
88
+ entries.forEach(([repo, rc]) => {
89
+ const flags = [
90
+ ...(rc.dangerouslySkipPermissions ? ['skip-permissions'] : []),
91
+ ...(rc.worktree === false ? ['sin worktree'] : []),
92
+ ];
93
+ console.log(` ${repo} → ${rc.path}${flags.length ? ` (${flags.join(', ')})` : ''}`);
94
+ });
95
+ return;
96
+ }
97
+ if (sub === 'add') {
98
+ const positional = rest.filter((a) => !a.startsWith('--'));
99
+ const [repo, repoPath] = positional;
100
+ if (!repo || !repoPath) {
101
+ console.error(USAGE);
102
+ process.exitCode = 1;
103
+ return;
104
+ }
105
+ if (!REPO_KEY_RE.test(repo)) {
106
+ console.error(`repo inválido: "${repo}" (formato workspace/slug, como aparece en las branches del target)`);
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ const abs = path.resolve(repoPath);
111
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
112
+ console.error(`path no existe o no es un directorio: ${abs}`);
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ // aviso no bloqueante: el aislamiento por run necesita un checkout git.
117
+ if (!fs.existsSync(path.join(abs, '.git'))) {
118
+ console.warn(`aviso: ${abs} no parece un checkout git (.git ausente)`);
119
+ }
120
+ warnStopHooks(abs);
121
+ cfg.repos[repo] = {
122
+ path: abs,
123
+ ...(rest.includes('--dangerously-skip-permissions') ? { dangerouslySkipPermissions: true } : {}),
124
+ ...(rest.includes('--no-worktree') ? { worktree: false } : {}),
125
+ };
126
+ saveConfig(cfg);
127
+ console.log(`mapeado ${repo} → ${abs} en ${configPath()}`);
128
+ return;
129
+ }
130
+ if (sub === 'discover') {
131
+ const dryRun = rest.includes('--dry-run');
132
+ const base = path.resolve(rest.find((a) => !a.startsWith('--')) ?? '.');
133
+ if (!fs.existsSync(base) || !fs.statSync(base).isDirectory()) {
134
+ console.error(`directorio no existe: ${base}`);
135
+ process.exitCode = 1;
136
+ return;
137
+ }
138
+ // candidatos: el propio dir + sus hijos directos (los checkouts suelen vivir planos
139
+ // bajo ~/dev; profundidad 1 evita recorrer node_modules y árboles enormes).
140
+ const candidates = [
141
+ base,
142
+ ...fs
143
+ .readdirSync(base, { withFileTypes: true })
144
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
145
+ .map((e) => path.join(base, e.name)),
146
+ ].filter((dir) => fs.existsSync(path.join(dir, '.git')));
147
+ const found = candidates.flatMap((dir) => {
148
+ const url = originUrl(dir);
149
+ const key = url ? repoKeyFromRemote(url) : null;
150
+ return key ? [{ key, dir }] : [];
151
+ });
152
+ if (found.length === 0) {
153
+ console.log(`sin checkouts git con remote origin bajo ${base}`);
154
+ return;
155
+ }
156
+ const skipped = found.filter(({ key }) => key in cfg.repos);
157
+ const fresh = found.filter(({ key }) => !(key in cfg.repos));
158
+ skipped.forEach(({ key }) => console.log(` = ${key} ya mapeado (${cfg.repos[key].path})`));
159
+ fresh.forEach(({ key, dir }) => console.log(` + ${key} → ${dir}`));
160
+ if (fresh.length === 0) {
161
+ console.log('nada nuevo que mapear');
162
+ return;
163
+ }
164
+ if (dryRun) {
165
+ console.log(`dry-run: ${fresh.length} repo(s) sin aplicar`);
166
+ return;
167
+ }
168
+ // los flags sensibles (skip-permissions, no-worktree) NUNCA se autodescubren:
169
+ // son opt-in explícito por repo via `repos add` o editando la config.
170
+ fresh.forEach(({ key, dir }) => (cfg.repos[key] = { path: dir }));
171
+ saveConfig(cfg);
172
+ console.log(`mapeados ${fresh.length} repo(s) en ${configPath()}`);
173
+ return;
174
+ }
175
+ if (sub === 'remove') {
176
+ const [repo] = rest;
177
+ if (!repo || !(repo in cfg.repos)) {
178
+ console.error(repo ? `repo no mapeado: ${repo}` : USAGE);
179
+ process.exitCode = 1;
180
+ return;
181
+ }
182
+ delete cfg.repos[repo];
183
+ saveConfig(cfg);
184
+ console.log(`eliminado ${repo}`);
185
+ return;
186
+ }
187
+ console.error(USAGE);
188
+ process.exitCode = 1;
189
+ }
190
+ //# sourceMappingURL=repos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repos.js","sourceRoot":"","sources":["../src/repos.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function statusCommand(): Promise<void>;
2
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AA4BA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA2BnD"}
package/dist/status.js ADDED
@@ -0,0 +1,59 @@
1
+ // `duckhunt-runner status`: qué kit tiene esta máquina (claude, credencial detectada, aws cli) y
2
+ // qué hay mapeado (repos, cuentas aws). diagnóstico local, sin tocar el server.
3
+ import { execFile } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { promisify } from 'node:util';
8
+ import { configPath, loadConfig, scratchDir } from './config.js';
9
+ import { detectClaude } from './claude.js';
10
+ const execFileP = promisify(execFile);
11
+ // credencial que usará claude en esta máquina. ANTHROPIC_API_KEY en el env shadowea el login
12
+ // de suscripción (footgun documentado en el contrato 42): se avisa explícitamente.
13
+ async function credentialSummary() {
14
+ if (process.env.ANTHROPIC_API_KEY)
15
+ return 'api key (ANTHROPIC_API_KEY en el env — shadowea el login de suscripción)';
16
+ if (process.env.CLAUDE_CODE_OAUTH_TOKEN)
17
+ return 'oauth token (CLAUDE_CODE_OAUTH_TOKEN en el env)';
18
+ try {
19
+ const out = (await execFileP('claude', ['auth', 'status'], { encoding: 'utf-8' })).stdout.trim();
20
+ if (out)
21
+ return out.split('\n')[0];
22
+ }
23
+ catch {
24
+ // versiones sin `claude auth status`: inferir del fichero de credenciales.
25
+ }
26
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), '.claude');
27
+ return fs.existsSync(path.join(configDir, '.credentials.json')) ? 'login de claude code (credenciales locales)' : 'SIN credencial detectada: ejecuta `claude` y haz login';
28
+ }
29
+ export async function statusCommand() {
30
+ const cfg = loadConfig();
31
+ if (!cfg) {
32
+ console.log(`sin config (${configPath()}): ejecuta \`duckhunt-runner login <base-url>\``);
33
+ }
34
+ else {
35
+ console.log(`servidor: ${cfg.baseUrl}`);
36
+ console.log(`runner: ${cfg.label ?? os.hostname()} (client ${cfg.clientId})`);
37
+ console.log(`modelo: ${cfg.defaults.model ?? 'default del cli'}`);
38
+ }
39
+ const claude = await detectClaude();
40
+ console.log(`claude: ${claude.version ?? 'NO encontrado en el PATH'}`);
41
+ if (claude.version) {
42
+ console.log(`credencial: ${await credentialSummary()}`);
43
+ console.log(`flags: ${[...claude.supported].join(' ') || '(ninguno opcional)'}`);
44
+ }
45
+ const aws = await execFileP('aws', ['--version'], { encoding: 'utf-8' })
46
+ .then((r) => (r.stdout || r.stderr).trim().split('\n')[0])
47
+ .catch(() => null);
48
+ console.log(`aws cli: ${aws ?? 'no instalada (los runs no podrán consultar aws)'}`);
49
+ console.log(`scratch: ${scratchDir()}`);
50
+ if (!cfg)
51
+ return;
52
+ const repos = Object.entries(cfg.repos);
53
+ console.log(`repos: ${repos.length === 0 ? 'ninguno mapeado (`repos discover <dir>`)' : ''}`);
54
+ repos.forEach(([k, r]) => console.log(` ${k} → ${r.path}`));
55
+ const accounts = Object.entries(cfg.aws);
56
+ console.log(`cuentas aws: ${accounts.length === 0 ? 'ninguna mapeada (`aws add <account-id> <profile>`)' : ''}`);
57
+ accounts.forEach(([k, a]) => console.log(` ${k} → perfil ${a.profile}${a.region ? ` (${a.region})` : ''}`));
58
+ }
59
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "duckhunt-runner",
3
+ "version": "0.2.0",
4
+ "description": "Local daemon for duckhunt agent runs: claims runs and launches Claude Code headless on your own machine, with your login, your repos and your AWS CLI.",
5
+ "type": "module",
6
+ "license": "PolyForm-Noncommercial-1.0.0",
7
+ "author": "miermontoto",
8
+ "homepage": "https://github.com/miermontoto/duckhunt-runner#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/miermontoto/duckhunt-runner.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/miermontoto/duckhunt-runner/issues"
15
+ },
16
+ "keywords": [
17
+ "duckhunt",
18
+ "claude-code",
19
+ "agent",
20
+ "daemon",
21
+ "cli"
22
+ ],
23
+ "bin": {
24
+ "duckhunt-runner": "./dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "prepublishOnly": "npm run build",
35
+ "start": "node dist/index.js"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.7.0"
40
+ }
41
+ }