@awesomate/hosting-mcp 0.7.2 → 0.7.3
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/package.json
CHANGED
|
@@ -97,6 +97,33 @@ fallback** — that's deliberate.
|
|
|
97
97
|
`GET {apiBase}/api/hosting-access/context` (401 without a token still proves
|
|
98
98
|
reachability).
|
|
99
99
|
|
|
100
|
+
### When you're stuck: generate a support report
|
|
101
|
+
|
|
102
|
+
If a connect or tool failure survives the documented fixes above (wrong
|
|
103
|
+
account, proxy, resume, restart, legacy registrations), don't keep guessing —
|
|
104
|
+
hand Awesomate a diagnostic they can act on:
|
|
105
|
+
|
|
106
|
+
1. Run `node ~/.claude/skills/awesomate-hosting/scripts/support-report.mjs
|
|
107
|
+
--note "<one line: what the user was doing and what happened>"`.
|
|
108
|
+
It writes a **fully redacted** report to
|
|
109
|
+
`~/.awesomate/support-report-<timestamp>.md` — tokens reduced to
|
|
110
|
+
prefix+last4, no key material, proxy credentials stripped. It includes
|
|
111
|
+
versions, profiles/pin/registration state, live connectivity probes, and
|
|
112
|
+
the last bootstrap log automatically.
|
|
113
|
+
2. Show the user the file path and the headline findings (the ACTIVE line,
|
|
114
|
+
any ⚠ LEGACY registration flags, and the probe results).
|
|
115
|
+
3. **Ask the user before submitting.** With their OK, re-run with `--submit`
|
|
116
|
+
— it POSTs the report to Awesomate and returns a reference like
|
|
117
|
+
`ASR-XXXXXXXX`. Tell the user to quote that reference to
|
|
118
|
+
support@awesomate.ai or their Awesomate contact; the report is already
|
|
119
|
+
attached to it server-side. Submission works even when the token is
|
|
120
|
+
broken (that's usually why you're here).
|
|
121
|
+
4. If `--submit` fails too (fully offline), the user emails the file itself —
|
|
122
|
+
it's safe to send as-is.
|
|
123
|
+
|
|
124
|
+
Never edit the report to add raw tokens, codes, or keys, and never submit
|
|
125
|
+
without the user's explicit go-ahead.
|
|
126
|
+
|
|
100
127
|
If this skill is loaded but **no `awesomate_*` tools exist in the session at
|
|
101
128
|
all**, the MCP server was registered after Claude Code started (the bootstrap
|
|
102
129
|
just ran). Don't investigate settings files or reinstall anything — and don't
|
|
@@ -222,6 +249,12 @@ Git skills drive the Git workflow; this skill just deploys the result.
|
|
|
222
249
|
- `wp.sh` — run a WP-CLI command against a live site over that SSH.
|
|
223
250
|
- `deploy.sh` — snapshot-first Studio→live deploy (files, optional DB).
|
|
224
251
|
- `pull-live.sh` — clone a live site down to a local folder (read-only on live).
|
|
252
|
+
- `resolve-account.mjs` — shared account resolver (pin/env/profile precedence)
|
|
253
|
+
used by the shell scripts and the REST fallback; `--api` emits API/PAT/ACCT,
|
|
254
|
+
`--ssh` emits the ssh block.
|
|
255
|
+
- `support-report.mjs` — redacted diagnostic bundle for Awesomate support
|
|
256
|
+
(see "When you're stuck" above); `--submit` delivers it and returns a
|
|
257
|
+
reference ID.
|
|
225
258
|
|
|
226
259
|
All scripts read `~/.awesomate/credentials.json`; none take secrets on the
|
|
227
260
|
command line.
|
|
@@ -61,6 +61,37 @@ const credPath = join(dir, 'credentials.json');
|
|
|
61
61
|
const keyPath = join(keysDir, 'id_ed25519');
|
|
62
62
|
const PIN_FILENAME = '.awesomate.json';
|
|
63
63
|
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Connect log: every run's output (redacted) lands in ~/.awesomate/
|
|
66
|
+
// last-connect.log so a failure is diagnosable AFTER the fact — the
|
|
67
|
+
// support-report script attaches it automatically. Tokens/codes never reach
|
|
68
|
+
// the log: bootstrap output doesn't print them and the argv line is redacted.
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
const CONNECT_LOG = [`--- awesomate connect ${new Date().toISOString()} ---`];
|
|
71
|
+
function redactSecrets(s) {
|
|
72
|
+
return String(s)
|
|
73
|
+
.replace(/amt_pat_[A-Za-z0-9_-]{8,}/g, (t) => `${t.slice(0, 12)}…${t.slice(-4)}`)
|
|
74
|
+
.replace(/amt_bs_[A-Za-z0-9_-]{4,}/g, 'amt_bs_[redacted]');
|
|
75
|
+
}
|
|
76
|
+
CONNECT_LOG.push(`argv: ${process.argv.slice(2).map(redactSecrets).join(' ')}`);
|
|
77
|
+
for (const method of ['log', 'error']) {
|
|
78
|
+
const original = console[method].bind(console);
|
|
79
|
+
console[method] = (...args) => {
|
|
80
|
+
CONNECT_LOG.push(redactSecrets(args.join(' ')));
|
|
81
|
+
original(...args);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
process.on('exit', (code) => {
|
|
85
|
+
try {
|
|
86
|
+
CONNECT_LOG.push(`exit: ${code}`);
|
|
87
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
88
|
+
writeFileSync(join(dir, 'last-connect.log'), `${CONNECT_LOG.join('\n')}\n`, { mode: 0o600 });
|
|
89
|
+
} catch { /* logging must never block exit */ }
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const SUPPORT_HINT =
|
|
93
|
+
'Stuck? Generate a support report: node ~/.claude/skills/awesomate-hosting/scripts/support-report.mjs --note "<what happened>" (add --submit to send it to Awesomate and get a reference ID).';
|
|
94
|
+
|
|
64
95
|
// ---------------------------------------------------------------------------
|
|
65
96
|
// Proxy-aware fetch. Node's built-in fetch does NOT honor HTTP(S)_PROXY —
|
|
66
97
|
// in sandboxes that force egress through a proxy, requests bypass it and the
|
|
@@ -502,6 +533,7 @@ async function main() {
|
|
|
502
533
|
console.log('Setup complete. Next: run awesomate_whoami to confirm the account, then ask me to read your plan and list your sites.');
|
|
503
534
|
console.log('(The skill works in your current Claude session right away via its REST fallback — restart Claude Code when convenient to load the MCP tools.)');
|
|
504
535
|
if (issues.length) {
|
|
536
|
+
console.log(SUPPORT_HINT);
|
|
505
537
|
console.log(`AWESOMATE CONNECT: PARTIAL account=${slug} pin=${pinPath ?? 'none'} issues=${issues.join(',')}`);
|
|
506
538
|
process.exit(1);
|
|
507
539
|
}
|
|
@@ -513,6 +545,7 @@ main().catch((err) => {
|
|
|
513
545
|
if (err.isApiError && err.status === 401) {
|
|
514
546
|
console.error('Grab a fresh code from hub.awesomate.ai/sites — codes last 10 minutes (and stay re-runnable within that window).');
|
|
515
547
|
}
|
|
548
|
+
console.error(SUPPORT_HINT);
|
|
516
549
|
console.error('AWESOMATE CONNECT: FAILED reason=error');
|
|
517
550
|
process.exit(1);
|
|
518
551
|
});
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Awesomate support report — turn a broken setup into something support can
|
|
4
|
+
* actually act on.
|
|
5
|
+
*
|
|
6
|
+
* Collects a REDACTED diagnostic snapshot of this machine's Awesomate
|
|
7
|
+
* connection state (versions, profiles, pins, MCP registrations, proxy env,
|
|
8
|
+
* live connectivity probes, ssh tooling, the last bootstrap log) and writes
|
|
9
|
+
* it to ~/.awesomate/support-report-<timestamp>.md.
|
|
10
|
+
*
|
|
11
|
+
* node support-report.mjs [--note "what went wrong"] [--category connect|tools|ssh|deploy|n8n|other]
|
|
12
|
+
* node support-report.mjs --submit # also POST it to Awesomate (returns a reference ID)
|
|
13
|
+
*
|
|
14
|
+
* Redaction guarantees (safe to email or submit):
|
|
15
|
+
* - access tokens → first 12 chars + … + last 4 (identifies the row, useless as a credential)
|
|
16
|
+
* - setup codes → amt_bs_[redacted]
|
|
17
|
+
* - ssh → fingerprint + paths only, never key material
|
|
18
|
+
* - proxy URLs → user:pass@ stripped
|
|
19
|
+
*
|
|
20
|
+
* Node 18+ built-ins only.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { execFileSync } from 'node:child_process';
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'node:fs';
|
|
25
|
+
import { homedir, hostname, platform, release, arch } from 'node:os';
|
|
26
|
+
import { join, dirname, resolve } from 'node:path';
|
|
27
|
+
|
|
28
|
+
function arg(name, fallback) {
|
|
29
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
30
|
+
return i >= 0 && process.argv[i + 1] && !process.argv[i + 1].startsWith('--') ? process.argv[i + 1] : fallback;
|
|
31
|
+
}
|
|
32
|
+
const SUBMIT = process.argv.includes('--submit');
|
|
33
|
+
const NOTE = arg('note', null);
|
|
34
|
+
const CATEGORY = arg('category', 'connect');
|
|
35
|
+
|
|
36
|
+
const HOME = homedir();
|
|
37
|
+
const AWM_DIR = join(HOME, '.awesomate');
|
|
38
|
+
const CRED_PATH = join(AWM_DIR, 'credentials.json');
|
|
39
|
+
const PIN_FILENAME = '.awesomate.json';
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Redaction — applied to individual values AND as a final pass over the whole
|
|
43
|
+
// document, so nothing secret can leak through a path we forgot.
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function redact(s) {
|
|
46
|
+
return String(s)
|
|
47
|
+
.replace(/amt_pat_[A-Za-z0-9_-]{8,}/g, (t) => `${t.slice(0, 12)}…${t.slice(-4)}`)
|
|
48
|
+
.replace(/amt_bs_[A-Za-z0-9_-]{4,}/g, 'amt_bs_[redacted]')
|
|
49
|
+
.replace(/(https?:\/\/)[^/@\s]+@/g, '$1[credentials-redacted]@')
|
|
50
|
+
.replace(/-----BEGIN[\s\S]*?-----END [A-Z ]*KEY-----/g, '[key-material-redacted]');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readJson(path) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
56
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
57
|
+
} catch { return null; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tryExec(cmd, args) {
|
|
61
|
+
try {
|
|
62
|
+
return execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 8000 }).trim();
|
|
63
|
+
} catch { return null; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Account resolution — same precedence as the MCP server / resolve-account.mjs
|
|
68
|
+
// (duplicated minimally here so the report works even when those are broken).
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
function findPin() {
|
|
71
|
+
const home = resolve(HOME);
|
|
72
|
+
let dir = resolve(process.cwd());
|
|
73
|
+
for (;;) {
|
|
74
|
+
const candidate = join(dir, PIN_FILENAME);
|
|
75
|
+
if (existsSync(candidate)) return candidate;
|
|
76
|
+
if (dir === home) return null;
|
|
77
|
+
const parent = dirname(dir);
|
|
78
|
+
if (parent === dir) return null;
|
|
79
|
+
dir = parent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function profileSummary(key, p) {
|
|
84
|
+
return {
|
|
85
|
+
profile: key,
|
|
86
|
+
slug: p.slug ?? null,
|
|
87
|
+
contactId: p.contactId ?? null,
|
|
88
|
+
email: p.email ?? null,
|
|
89
|
+
plan: p.plan ?? null,
|
|
90
|
+
apiBase: p.apiBase ?? null,
|
|
91
|
+
tokenRedacted: p.pat ? redact(p.pat) : null,
|
|
92
|
+
tokenExpiresAt: p.expiresAt ?? null,
|
|
93
|
+
ssh: p.ssh ? { host: p.ssh.host, user: p.ssh.user, port: p.ssh.port ?? 22, fingerprint: p.ssh.fingerprint ?? null, keyPath: p.ssh.keyPath ?? null } : null,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function main() {
|
|
98
|
+
const lines = [];
|
|
99
|
+
const push = (s = '') => lines.push(s);
|
|
100
|
+
|
|
101
|
+
// ── Environment ──────────────────────────────────────────────────────────
|
|
102
|
+
push('# Awesomate support report');
|
|
103
|
+
push('');
|
|
104
|
+
push(`- generated: ${new Date().toISOString()}`);
|
|
105
|
+
push(`- host: ${hostname()} (${platform()} ${release()}, ${arch()})`);
|
|
106
|
+
push(`- node: ${process.version}`);
|
|
107
|
+
push(`- claude CLI: ${tryExec('claude', ['--version']) ?? 'not found on PATH'}`);
|
|
108
|
+
const installedVersion = (() => {
|
|
109
|
+
try { return readFileSync(join(HOME, '.claude', 'skills', 'awesomate-hosting', '.installed-version'), 'utf8').trim(); } catch { return null; }
|
|
110
|
+
})();
|
|
111
|
+
push(`- installed skill version: ${installedVersion ?? 'no marker (pre-0.6 install?)'}`);
|
|
112
|
+
push(`- cwd: ${process.cwd()}`);
|
|
113
|
+
if (NOTE) { push(''); push(`## What the user reports`); push(''); push(NOTE); }
|
|
114
|
+
|
|
115
|
+
// ── Proxy environment ────────────────────────────────────────────────────
|
|
116
|
+
push(''); push('## Proxy environment'); push('');
|
|
117
|
+
const proxyVars = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'NO_PROXY', 'no_proxy', 'NODE_USE_ENV_PROXY'];
|
|
118
|
+
const setVars = proxyVars.filter((v) => process.env[v]);
|
|
119
|
+
if (setVars.length === 0) push('- none set');
|
|
120
|
+
for (const v of setVars) push(`- ${v}=${redact(process.env[v])}`);
|
|
121
|
+
|
|
122
|
+
// ── Credentials + account resolution ─────────────────────────────────────
|
|
123
|
+
push(''); push('## Credentials & account resolution'); push('');
|
|
124
|
+
const creds = readJson(CRED_PATH);
|
|
125
|
+
let profiles = {};
|
|
126
|
+
let activeApiBase = 'https://hub.awesomate.ai';
|
|
127
|
+
let activePat = null;
|
|
128
|
+
let activeSlug = null;
|
|
129
|
+
if (!creds) {
|
|
130
|
+
push(`- ${CRED_PATH}: ${existsSync(CRED_PATH) ? 'EXISTS BUT UNPARSEABLE' : 'missing (never connected on this machine?)'}`);
|
|
131
|
+
} else {
|
|
132
|
+
if (creds.profiles && typeof creds.profiles === 'object') profiles = creds.profiles;
|
|
133
|
+
else if (creds.pat) profiles = { [creds.slug || 'default']: creds };
|
|
134
|
+
push(`- credentials file: v${creds.version ?? 1}, defaultProfile: ${creds.defaultProfile ?? '(none)'}`);
|
|
135
|
+
for (const [key, p] of Object.entries(profiles)) {
|
|
136
|
+
push(`- profile ${JSON.stringify(profileSummary(key, p))}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const pinPath = findPin();
|
|
140
|
+
const pin = pinPath ? readJson(pinPath) : null;
|
|
141
|
+
push(`- folder pin: ${pinPath ? `${pinPath} → ${JSON.stringify(pin)}` : 'none found (walk-up from cwd)'}`);
|
|
142
|
+
const names = Object.keys(profiles);
|
|
143
|
+
if (process.env.AWESOMATE_PAT) { activePat = process.env.AWESOMATE_PAT; activeSlug = '(env PAT)'; push('- ACTIVE: env AWESOMATE_PAT override'); }
|
|
144
|
+
else if (process.env.AWESOMATE_ACCOUNT && profiles[process.env.AWESOMATE_ACCOUNT]) { const p = profiles[process.env.AWESOMATE_ACCOUNT]; activePat = p.pat; activeSlug = p.slug; activeApiBase = p.apiBase ?? activeApiBase; push(`- ACTIVE: env AWESOMATE_ACCOUNT=${process.env.AWESOMATE_ACCOUNT}`); }
|
|
145
|
+
else if (pin?.account && profiles[pin.account]) { const p = profiles[pin.account]; activePat = p.pat; activeSlug = p.slug; activeApiBase = p.apiBase ?? activeApiBase; push(`- ACTIVE: pin → ${pin.account}`); }
|
|
146
|
+
else if (pin?.account && !profiles[pin.account]) { push(`- ACTIVE: NONE — pin names "${pin.account}" but no such profile exists (this is a hard error by design)`); }
|
|
147
|
+
else if (names.length === 1) { const p = profiles[names[0]]; activePat = p.pat; activeSlug = p.slug; activeApiBase = p.apiBase ?? activeApiBase; push(`- ACTIVE: sole profile ${names[0]}`); }
|
|
148
|
+
else if (creds?.defaultProfile && profiles[creds.defaultProfile]) { const p = profiles[creds.defaultProfile]; activePat = p.pat; activeSlug = p.slug; activeApiBase = p.apiBase ?? activeApiBase; push(`- ACTIVE: defaultProfile ${creds.defaultProfile}`); }
|
|
149
|
+
else { push(`- ACTIVE: NONE (${names.length} profiles, no pin/default)`); }
|
|
150
|
+
|
|
151
|
+
// ── MCP registrations ────────────────────────────────────────────────────
|
|
152
|
+
push(''); push('## MCP registrations'); push('');
|
|
153
|
+
const claudeJson = readJson(join(HOME, '.claude.json'));
|
|
154
|
+
const userEntry = claudeJson?.mcpServers?.['awesomate-hosting'];
|
|
155
|
+
push(`- user scope (~/.claude.json): ${userEntry ? JSON.stringify({ command: userEntry.command, args: userEntry.args, envKeys: Object.keys(userEntry.env ?? {}) }) : 'none'}`);
|
|
156
|
+
if (userEntry?.env?.AWESOMATE_PAT) push(' ⚠ LEGACY: token baked into env — this pins the registration to one stale account. Re-run Connect to fix.');
|
|
157
|
+
for (const [projectDir, project] of Object.entries(claudeJson?.projects ?? {})) {
|
|
158
|
+
const e = project?.mcpServers?.['awesomate-hosting'];
|
|
159
|
+
if (e) push(`- local scope (${projectDir}): envKeys=${JSON.stringify(Object.keys(e.env ?? {}))}${e.env?.AWESOMATE_PAT ? ' ⚠ LEGACY env token' : ''}`);
|
|
160
|
+
}
|
|
161
|
+
const mcpJson = readJson(join(process.cwd(), '.mcp.json'));
|
|
162
|
+
const projEntry = mcpJson?.mcpServers?.['awesomate-hosting'];
|
|
163
|
+
if (projEntry) push(`- project scope (cwd/.mcp.json): envKeys=${JSON.stringify(Object.keys(projEntry.env ?? {}))}${projEntry.env?.AWESOMATE_PAT ? ' ⚠ LEGACY env token' : ''}`);
|
|
164
|
+
|
|
165
|
+
// ── Connectivity probes (proxy-aware) ────────────────────────────────────
|
|
166
|
+
push(''); push('## Connectivity probes'); push('');
|
|
167
|
+
let doFetch = fetch;
|
|
168
|
+
if (process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy) {
|
|
169
|
+
try {
|
|
170
|
+
const undici = await import('undici');
|
|
171
|
+
const dispatcher = new undici.EnvHttpProxyAgent();
|
|
172
|
+
doFetch = (url, opts = {}) => undici.fetch(url, { ...opts, dispatcher });
|
|
173
|
+
push('- probe transport: undici EnvHttpProxyAgent (proxy honored)');
|
|
174
|
+
} catch {
|
|
175
|
+
push('- probe transport: built-in fetch (undici unavailable — proxy NOT honored unless NODE_USE_ENV_PROXY=1)');
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
push('- probe transport: built-in fetch (no proxy configured)');
|
|
179
|
+
}
|
|
180
|
+
async function probe(label, url, headers) {
|
|
181
|
+
try {
|
|
182
|
+
const res = await doFetch(url, { headers, signal: AbortSignal.timeout(10_000) });
|
|
183
|
+
const body = await res.text();
|
|
184
|
+
const hasApiBody = (() => { try { const j = JSON.parse(body); return j && typeof j === 'object'; } catch { return false; } })();
|
|
185
|
+
push(`- ${label}: HTTP ${res.status} (${hasApiBody ? 'API response body' : 'NON-API body — likely a gateway/proxy in the way'})`);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
push(`- ${label}: FAILED (${err?.cause?.code ?? err?.name ?? err?.message})`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await probe('GET /api/hosting-access/context (no auth — 401 proves reachability)', `${activeApiBase}/api/hosting-access/context`);
|
|
191
|
+
if (activePat) {
|
|
192
|
+
await probe(`GET /api/hosting-access/context (as ${activeSlug})`, `${activeApiBase}/api/hosting-access/context`, { Authorization: `Bearer ${activePat}` });
|
|
193
|
+
} else {
|
|
194
|
+
push('- authenticated probe skipped: no active profile resolved');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── SSH tooling ──────────────────────────────────────────────────────────
|
|
198
|
+
push(''); push('## SSH tooling'); push('');
|
|
199
|
+
push(`- ssh: ${tryExec('ssh', ['-V']) ?? tryExec('sh', ['-c', 'ssh -V 2>&1']) ?? 'not found'}`);
|
|
200
|
+
push(`- ssh-keygen: ${tryExec('sh', ['-c', 'command -v ssh-keygen']) ?? 'not found'}`);
|
|
201
|
+
const keyPath = join(AWM_DIR, 'keys', 'id_ed25519');
|
|
202
|
+
push(`- key file: ${existsSync(keyPath) ? `present — ${tryExec('ssh-keygen', ['-lf', keyPath]) ?? 'fingerprint unavailable'}` : 'absent'}`);
|
|
203
|
+
|
|
204
|
+
// ── Last bootstrap log ───────────────────────────────────────────────────
|
|
205
|
+
push(''); push('## Last connect attempt (~/.awesomate/last-connect.log)'); push('');
|
|
206
|
+
const logPath = join(AWM_DIR, 'last-connect.log');
|
|
207
|
+
if (existsSync(logPath)) {
|
|
208
|
+
push('```');
|
|
209
|
+
push(redact(readFileSync(logPath, 'utf8')).trim().split('\n').slice(-60).join('\n'));
|
|
210
|
+
push('```');
|
|
211
|
+
} else {
|
|
212
|
+
push('- no log (bootstrap predates 0.7.3, or never ran on this machine)');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Write + optionally submit ────────────────────────────────────────────
|
|
216
|
+
const report = redact(lines.join('\n')) + '\n';
|
|
217
|
+
mkdirSync(AWM_DIR, { recursive: true, mode: 0o700 });
|
|
218
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
219
|
+
const outPath = join(AWM_DIR, `support-report-${stamp}.md`);
|
|
220
|
+
writeFileSync(outPath, report);
|
|
221
|
+
chmodSync(outPath, 0o600);
|
|
222
|
+
console.log(`✓ Support report written to ${outPath} (all tokens redacted — safe to send).`);
|
|
223
|
+
|
|
224
|
+
if (!SUBMIT) {
|
|
225
|
+
console.log('Send it to support@awesomate.ai, or re-run with --submit to deliver it directly and get a reference ID.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const submitBody = {
|
|
230
|
+
report,
|
|
231
|
+
summary: NOTE ? NOTE.slice(0, 255) : `Diagnostic from ${activeSlug ?? 'unconnected machine'} (${hostname()})`,
|
|
232
|
+
slug: activeSlug && activeSlug !== '(env PAT)' ? activeSlug : null,
|
|
233
|
+
email: null,
|
|
234
|
+
category: CATEGORY,
|
|
235
|
+
clientVersion: installedVersion,
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
const res = await doFetch(`${activeApiBase}/api/hosting-access/support-report`, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
headers: { 'Content-Type': 'application/json', ...(activePat ? { Authorization: `Bearer ${activePat}` } : {}) },
|
|
241
|
+
body: JSON.stringify(submitBody),
|
|
242
|
+
signal: AbortSignal.timeout(15_000),
|
|
243
|
+
});
|
|
244
|
+
const json = await res.json().catch(() => null);
|
|
245
|
+
if (res.ok && json?.reference) {
|
|
246
|
+
console.log(`✓ Submitted to Awesomate support. Reference: ${json.reference}`);
|
|
247
|
+
console.log(` ${json.next}`);
|
|
248
|
+
} else {
|
|
249
|
+
console.error(`Submit failed (HTTP ${res.status}${json?.error ? `: ${json.error}` : ''}) — email the file above to support@awesomate.ai instead.`);
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.error(`Submit failed (${err?.cause?.code ?? err?.message}) — email the file above to support@awesomate.ai instead.`);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
main().catch((err) => {
|
|
259
|
+
console.error('support-report failed:', err?.stack ?? err?.message ?? String(err));
|
|
260
|
+
process.exit(1);
|
|
261
|
+
});
|