@zonease/happy 0.6.37 → 0.6.39

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.
Files changed (111) hide show
  1. package/dist/{types-DYCJXXdA.mjs → api-CIZKyfSn.mjs} +136 -1725
  2. package/dist/{types-BrTorNAy.cjs → api-CwOCQeLH.cjs} +317 -1961
  3. package/dist/auth-0VnlXT9f.mjs +181 -0
  4. package/dist/auth-BE8dD20T.mjs +270 -0
  5. package/dist/auth-BIleVCw0.cjs +274 -0
  6. package/dist/auth-Cas9DR3b.cjs +183 -0
  7. package/dist/browser-Cc08m7of.cjs +22 -0
  8. package/dist/browser-Dll5hxOV.mjs +20 -0
  9. package/dist/claudeLocal-CPT3zcIU.mjs +344 -0
  10. package/dist/claudeLocal-WkgLbl1a.cjs +352 -0
  11. package/dist/codex/happyMcpStdioBridge.cjs +4 -1
  12. package/dist/codex/happyMcpStdioBridge.mjs +4 -1
  13. package/dist/common-BdNgghUJ.mjs +75 -0
  14. package/dist/common-CZvSRU4p.cjs +83 -0
  15. package/dist/{config-D_g6ohaV.cjs → config-Draa-5XC.cjs} +21 -34
  16. package/dist/{config-DPn-cbpz.mjs → config-rTT4qTU5.mjs} +7 -20
  17. package/dist/connect-BfC1tYP_.mjs +679 -0
  18. package/dist/connect-CJZ73syK.cjs +681 -0
  19. package/dist/{constants-B4x1K7by.cjs → constants-E_UWYTo3.cjs} +2 -2
  20. package/dist/{constants-BO9OAFOT.mjs → constants-pmXZGibX.mjs} +1 -1
  21. package/dist/controlClient-CM_QcJnB.mjs +159 -0
  22. package/dist/controlClient-DZMIsKsT.cjs +168 -0
  23. package/dist/deviceExec-CU1R9CRb.cjs +84 -0
  24. package/dist/deviceExec-D93AOVV4.mjs +81 -0
  25. package/dist/diagnose-B3D1a_v0.cjs +60 -0
  26. package/dist/diagnose-CuRKXxt1.mjs +58 -0
  27. package/dist/doctor-C08m5Z9f.cjs +78 -0
  28. package/dist/doctor-Cya9YSA0.cjs +214 -0
  29. package/dist/doctor-DZYB6IVc.mjs +211 -0
  30. package/dist/doctor-ON7pN5G5.mjs +74 -0
  31. package/dist/downloadImage-DfSFLb-h.mjs +887 -0
  32. package/dist/downloadImage-MQMrUUxS.cjs +897 -0
  33. package/dist/enroll-BxlbVftP.cjs +115 -0
  34. package/dist/enroll-C52x8oly.mjs +113 -0
  35. package/dist/evidence-Ccd9iYcj.mjs +127 -0
  36. package/dist/evidence-CjWBG5rX.cjs +129 -0
  37. package/dist/formatImageMessage-B4lSxlk2.mjs +43 -0
  38. package/dist/formatImageMessage-DbyG8c2j.cjs +46 -0
  39. package/dist/github-DKcyF1zp.mjs +57 -0
  40. package/dist/github-h9GhJJ_C.cjs +59 -0
  41. package/dist/index-CDTNgfUf.cjs +578 -0
  42. package/dist/index-CPKdvi7J.mjs +554 -0
  43. package/dist/index.cjs +812 -40
  44. package/dist/index.mjs +813 -40
  45. package/dist/install-BokHRgFW.cjs +98 -0
  46. package/dist/install-C_cp8b2-.cjs +96 -0
  47. package/dist/install-DAeCn8ae.mjs +94 -0
  48. package/dist/install-oRAjVLjP.mjs +95 -0
  49. package/dist/lib.cjs +18 -12
  50. package/dist/lib.d.cts +16 -16
  51. package/dist/lib.d.mts +16 -16
  52. package/dist/lib.mjs +13 -7
  53. package/dist/{loadableToolOutput-lQH8Kb_w.mjs → loadableToolOutput-Cuygmd4K.mjs} +4 -2
  54. package/dist/{loadableToolOutput-CasonFIt.cjs → loadableToolOutput-DDCJjywt.cjs} +63 -61
  55. package/dist/mcpToolSchemas-CjeTfrAs.mjs +62 -0
  56. package/dist/mcpToolSchemas-DK42yoZ_.cjs +64 -0
  57. package/dist/persistence-BFjvtUad.mjs +944 -0
  58. package/dist/persistence-JLUn_Nou.cjs +996 -0
  59. package/dist/projectPath-BTBIMB57.mjs +10 -0
  60. package/dist/projectPath-ChLhdOyt.cjs +13 -0
  61. package/dist/prompt-BTWDZZO0.cjs +25 -0
  62. package/dist/prompt-BsAZsPbo.mjs +22 -0
  63. package/dist/release-BMEzNONE.mjs +168 -0
  64. package/dist/release-EhOJeAZe.cjs +171 -0
  65. package/dist/run-BDn2aE3Q.cjs +2152 -0
  66. package/dist/run-DBZqN6Kn.mjs +2128 -0
  67. package/dist/runClaude-DNHvERX0.mjs +3763 -0
  68. package/dist/runClaude-uaKxPZPm.cjs +3766 -0
  69. package/dist/{runCodex-BTt3fxG4.cjs → runCodex-82UrZLme.cjs} +169 -153
  70. package/dist/{runCodex-BXCB5trB.mjs → runCodex-C-RTM0wA.mjs} +36 -20
  71. package/dist/{runGemini-37HHbp9n.mjs → runGemini-Bm6-4Xm2.mjs} +34 -17
  72. package/dist/{runGemini-C-0nSFBv.cjs → runGemini-CXMr4Jwb.cjs} +212 -195
  73. package/dist/{runOneShot-CXhbFJ2j.cjs → runOneShot-1B6RMYT7.cjs} +31 -55
  74. package/dist/{runOneShot-USX8QPfE.mjs → runOneShot-ioARRHrx.mjs} +14 -38
  75. package/dist/{serve-DZAq-_1F.cjs → serve-BtdAhkIE.cjs} +35 -58
  76. package/dist/{serve-BRedJPzL.mjs → serve-CwibwU6N.mjs} +12 -35
  77. package/dist/session-2sU4Bu1R.mjs +108 -0
  78. package/dist/session-D--F87zL.cjs +110 -0
  79. package/dist/sessionKeyCache-4Tp8pJ3N.cjs +36 -0
  80. package/dist/sessionKeyCache-D2BwO91C.mjs +33 -0
  81. package/dist/sessionList-BCSw3qYt.mjs +51 -0
  82. package/dist/sessionList-ICJsjNtJ.cjs +54 -0
  83. package/dist/sessionSummary-Bkz52BPh.cjs +393 -0
  84. package/dist/sessionSummary-lHGBWQSb.mjs +387 -0
  85. package/dist/spawnHappyCLI-CoORX0YW.mjs +66 -0
  86. package/dist/spawnHappyCLI-D_V-AAoP.cjs +70 -0
  87. package/dist/ssh-BTOlhhxi.cjs +333 -0
  88. package/dist/ssh-DL_AkRAD.mjs +328 -0
  89. package/dist/systemdScope-BeOtAkhk.mjs +25 -0
  90. package/dist/systemdScope-DW3-UoKB.cjs +29 -0
  91. package/dist/task-CQzrHRZe.mjs +165 -0
  92. package/dist/task-CSpaC9HX.cjs +167 -0
  93. package/dist/time-Bj2HRkKH.cjs +33 -0
  94. package/dist/time-DgsBLJA_.mjs +29 -0
  95. package/dist/trimIdent-4gvk9Wk6.cjs +22 -0
  96. package/dist/trimIdent-BAjmv_TG.mjs +20 -0
  97. package/dist/types-BoJh4Qfv.mjs +57 -0
  98. package/dist/types-DWeB7aqh.cjs +59 -0
  99. package/dist/uninstall-BiKyRAb1.cjs +77 -0
  100. package/dist/uninstall-nFMFGUVM.mjs +75 -0
  101. package/dist/update-BcqRHXf2.mjs +109 -0
  102. package/dist/update-Dw8pgsAT.cjs +111 -0
  103. package/dist/workflowCommon-B0d01Y_a.mjs +56 -0
  104. package/dist/workflowCommon-Bl568-rt.cjs +62 -0
  105. package/dist/workflowReports-BEWuQesA.cjs +113 -0
  106. package/dist/workflowReports-Dd8-4-8f.mjs +108 -0
  107. package/package.json +1 -1
  108. package/dist/index-BlkHOkB3.cjs +0 -11312
  109. package/dist/index-DJ2VpGiR.mjs +0 -11272
  110. package/dist/mcpToolSchemas-CdPXJQmb.mjs +0 -175
  111. package/dist/mcpToolSchemas-f-kttjFw.cjs +0 -186
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ var axios = require('axios');
4
+ var tweetnacl = require('tweetnacl');
5
+ var node_crypto = require('node:crypto');
6
+ var logger = require('./persistence-JLUn_Nou.cjs');
7
+ var auth = require('./auth-BIleVCw0.cjs');
8
+ require('node:fs/promises');
9
+ require('node:fs');
10
+ require('node:os');
11
+ require('node:path');
12
+ require('zod');
13
+ require('chalk');
14
+ require('fs');
15
+ require('qrcode-terminal');
16
+ require('./time-Bj2HRkKH.cjs');
17
+ require('./browser-Cc08m7of.cjs');
18
+ require('open');
19
+ require('react');
20
+ require('ink');
21
+
22
+ async function hmac_sha512(key, data) {
23
+ const hmac = node_crypto.createHmac("sha512", key);
24
+ hmac.update(data);
25
+ return new Uint8Array(hmac.digest());
26
+ }
27
+
28
+ async function deriveSecretKeyTreeRoot(seed, usage) {
29
+ const I = await hmac_sha512(new TextEncoder().encode(usage + " Master Seed"), seed);
30
+ return {
31
+ key: I.slice(0, 32),
32
+ chainCode: I.slice(32)
33
+ };
34
+ }
35
+ async function deriveSecretKeyTreeChild(chainCode, index) {
36
+ const data = new Uint8Array([0, ...new TextEncoder().encode(index)]);
37
+ const I = await hmac_sha512(chainCode, data);
38
+ return {
39
+ key: I.subarray(0, 32),
40
+ chainCode: I.subarray(32)
41
+ };
42
+ }
43
+ async function deriveKey(master, usage, path) {
44
+ let state = await deriveSecretKeyTreeRoot(master, usage);
45
+ let remaining = [...path];
46
+ while (remaining.length > 0) {
47
+ let index = remaining[0];
48
+ remaining = remaining.slice(1);
49
+ state = await deriveSecretKeyTreeChild(state.chainCode, index);
50
+ }
51
+ return state.key;
52
+ }
53
+
54
+ function parseEnrollToken(raw) {
55
+ const trimmed = raw.trim();
56
+ const separator = trimmed.indexOf(".");
57
+ if (separator <= 0 || separator === trimmed.length - 1) {
58
+ throw new Error("Invalid enrollment token format");
59
+ }
60
+ const lookupId = trimmed.slice(0, separator);
61
+ const secret = logger.decodeBase64(trimmed.slice(separator + 1), "base64url");
62
+ if (secret.length !== 32) {
63
+ throw new Error("Invalid enrollment token secret");
64
+ }
65
+ return { lookupId, secret };
66
+ }
67
+ async function enrollDevice(rawToken, options = {}) {
68
+ const existing = await logger.readCredentials();
69
+ if (existing && !options.force) {
70
+ return { alreadyEnrolled: true };
71
+ }
72
+ const { lookupId, secret } = parseEnrollToken(rawToken);
73
+ let response;
74
+ try {
75
+ const result = await axios.post(
76
+ `${logger.configuration.serverUrl}/v1/devices/enroll`,
77
+ { lookupId },
78
+ { timeout: 3e4 }
79
+ );
80
+ response = result.data;
81
+ } catch (error) {
82
+ if (axios.isAxiosError(error) && error.response) {
83
+ const message = error.response.data?.error ?? `HTTP ${error.response.status}`;
84
+ throw new Error(`Enrollment failed: ${message}`);
85
+ }
86
+ throw error;
87
+ }
88
+ const sealed = logger.decodeBase64(response.response);
89
+ const boxSecretKey = tweetnacl.hash(secret).slice(0, 32);
90
+ const opened = auth.decryptWithEphemeralKey(sealed, boxSecretKey);
91
+ if (!opened) {
92
+ throw new Error("Enrollment failed: could not open the account key with this token");
93
+ }
94
+ await logger.updateSettings((settings) => ({ ...settings, deviceMode: true }));
95
+ if (opened.length === 32) {
96
+ const contentDataKey = await deriveKey(opened, "Happy EnCoder", ["content"]);
97
+ await logger.writeCredentialsDataKey({
98
+ publicKey: logger.libsodiumPublicKeyFromSecretKey(contentDataKey),
99
+ machineKey: node_crypto.randomBytes(32),
100
+ token: response.token
101
+ });
102
+ return { alreadyEnrolled: false };
103
+ }
104
+ if (opened[0] === 0 && opened.length >= 33) {
105
+ await logger.writeCredentialsDataKey({
106
+ publicKey: opened.slice(1, 33),
107
+ machineKey: node_crypto.randomBytes(32),
108
+ token: response.token
109
+ });
110
+ return { alreadyEnrolled: false };
111
+ }
112
+ throw new Error("Enrollment failed: unsupported account key format");
113
+ }
114
+
115
+ exports.enrollDevice = enrollDevice;
@@ -0,0 +1,113 @@
1
+ import axios from 'axios';
2
+ import tweetnacl from 'tweetnacl';
3
+ import { createHmac, randomBytes } from 'node:crypto';
4
+ import { r as readCredentials, c as configuration, e as decodeBase64, u as updateSettings, w as writeCredentialsDataKey, o as libsodiumPublicKeyFromSecretKey } from './persistence-BFjvtUad.mjs';
5
+ import { decryptWithEphemeralKey } from './auth-BE8dD20T.mjs';
6
+ import 'node:fs/promises';
7
+ import 'node:fs';
8
+ import 'node:os';
9
+ import 'node:path';
10
+ import 'zod';
11
+ import 'chalk';
12
+ import 'fs';
13
+ import 'qrcode-terminal';
14
+ import './time-DgsBLJA_.mjs';
15
+ import './browser-Dll5hxOV.mjs';
16
+ import 'open';
17
+ import 'react';
18
+ import 'ink';
19
+
20
+ async function hmac_sha512(key, data) {
21
+ const hmac = createHmac("sha512", key);
22
+ hmac.update(data);
23
+ return new Uint8Array(hmac.digest());
24
+ }
25
+
26
+ async function deriveSecretKeyTreeRoot(seed, usage) {
27
+ const I = await hmac_sha512(new TextEncoder().encode(usage + " Master Seed"), seed);
28
+ return {
29
+ key: I.slice(0, 32),
30
+ chainCode: I.slice(32)
31
+ };
32
+ }
33
+ async function deriveSecretKeyTreeChild(chainCode, index) {
34
+ const data = new Uint8Array([0, ...new TextEncoder().encode(index)]);
35
+ const I = await hmac_sha512(chainCode, data);
36
+ return {
37
+ key: I.subarray(0, 32),
38
+ chainCode: I.subarray(32)
39
+ };
40
+ }
41
+ async function deriveKey(master, usage, path) {
42
+ let state = await deriveSecretKeyTreeRoot(master, usage);
43
+ let remaining = [...path];
44
+ while (remaining.length > 0) {
45
+ let index = remaining[0];
46
+ remaining = remaining.slice(1);
47
+ state = await deriveSecretKeyTreeChild(state.chainCode, index);
48
+ }
49
+ return state.key;
50
+ }
51
+
52
+ function parseEnrollToken(raw) {
53
+ const trimmed = raw.trim();
54
+ const separator = trimmed.indexOf(".");
55
+ if (separator <= 0 || separator === trimmed.length - 1) {
56
+ throw new Error("Invalid enrollment token format");
57
+ }
58
+ const lookupId = trimmed.slice(0, separator);
59
+ const secret = decodeBase64(trimmed.slice(separator + 1), "base64url");
60
+ if (secret.length !== 32) {
61
+ throw new Error("Invalid enrollment token secret");
62
+ }
63
+ return { lookupId, secret };
64
+ }
65
+ async function enrollDevice(rawToken, options = {}) {
66
+ const existing = await readCredentials();
67
+ if (existing && !options.force) {
68
+ return { alreadyEnrolled: true };
69
+ }
70
+ const { lookupId, secret } = parseEnrollToken(rawToken);
71
+ let response;
72
+ try {
73
+ const result = await axios.post(
74
+ `${configuration.serverUrl}/v1/devices/enroll`,
75
+ { lookupId },
76
+ { timeout: 3e4 }
77
+ );
78
+ response = result.data;
79
+ } catch (error) {
80
+ if (axios.isAxiosError(error) && error.response) {
81
+ const message = error.response.data?.error ?? `HTTP ${error.response.status}`;
82
+ throw new Error(`Enrollment failed: ${message}`);
83
+ }
84
+ throw error;
85
+ }
86
+ const sealed = decodeBase64(response.response);
87
+ const boxSecretKey = tweetnacl.hash(secret).slice(0, 32);
88
+ const opened = decryptWithEphemeralKey(sealed, boxSecretKey);
89
+ if (!opened) {
90
+ throw new Error("Enrollment failed: could not open the account key with this token");
91
+ }
92
+ await updateSettings((settings) => ({ ...settings, deviceMode: true }));
93
+ if (opened.length === 32) {
94
+ const contentDataKey = await deriveKey(opened, "Happy EnCoder", ["content"]);
95
+ await writeCredentialsDataKey({
96
+ publicKey: libsodiumPublicKeyFromSecretKey(contentDataKey),
97
+ machineKey: randomBytes(32),
98
+ token: response.token
99
+ });
100
+ return { alreadyEnrolled: false };
101
+ }
102
+ if (opened[0] === 0 && opened.length >= 33) {
103
+ await writeCredentialsDataKey({
104
+ publicKey: opened.slice(1, 33),
105
+ machineKey: randomBytes(32),
106
+ token: response.token
107
+ });
108
+ return { alreadyEnrolled: false };
109
+ }
110
+ throw new Error("Enrollment failed: unsupported account key format");
111
+ }
112
+
113
+ export { enrollDevice };
@@ -0,0 +1,127 @@
1
+ import chalk from 'chalk';
2
+ import { constants } from 'node:fs';
3
+ import { mkdir, appendFile, access, readFile } from 'node:fs/promises';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { basename, dirname, resolve } from 'node:path';
6
+ import { c as configuration } from './persistence-BFjvtUad.mjs';
7
+ import { r as renderEvidenceReport, i as inferEvidenceKind } from './workflowReports-Dd8-4-8f.mjs';
8
+ import { p as parseWorkflowArgs, s as stringFlag, b as booleanFlag, a as printOrWriteOutput } from './workflowCommon-B0d01Y_a.mjs';
9
+ import 'node:os';
10
+ import 'zod';
11
+ import 'tweetnacl';
12
+ import 'fs';
13
+
14
+ const EVIDENCE_FILE = `${configuration.happyHomeDir}/evidence/evidence.jsonl`;
15
+ function printEvidenceHelp() {
16
+ console.log(`
17
+ ${chalk.bold("happy evidence")} - Collect and render UI/debug evidence
18
+
19
+ ${chalk.bold("Usage:")}
20
+ happy evidence add <path-or-url> [options]
21
+ happy evidence list [options]
22
+ happy evidence report [options]
23
+
24
+ ${chalk.bold("Options:")}
25
+ --title <text> Human-readable evidence title
26
+ --kind <kind> image | log | url | file (default: inferred)
27
+ --session <id> Attach evidence to a Happy session
28
+ --note <text> Short note shown in reports
29
+ --out <path> Write report/list markdown to a file
30
+ --json Print machine-readable JSON
31
+ -h, --help Show this help
32
+ `);
33
+ }
34
+ function normalizeEvidenceTarget(target) {
35
+ if (/^(https?:|file:)/i.test(target)) return target;
36
+ return resolve(target);
37
+ }
38
+ async function readEvidenceEntries() {
39
+ try {
40
+ const content = await readFile(EVIDENCE_FILE, "utf8");
41
+ return content.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
42
+ } catch (error) {
43
+ if (error.code === "ENOENT") return [];
44
+ throw error;
45
+ }
46
+ }
47
+ function filterEvidenceEntries(entries, sessionId) {
48
+ if (!sessionId) return entries;
49
+ return entries.filter((entry) => entry.sessionId === sessionId);
50
+ }
51
+ async function assertLocalFileExists(target) {
52
+ if (/^https?:/i.test(target) || /^file:/i.test(target)) return;
53
+ await access(target, constants.R_OK);
54
+ }
55
+ function parseKind(kind, target) {
56
+ if (!kind) return inferEvidenceKind(target);
57
+ if (kind === "image" || kind === "log" || kind === "url" || kind === "file") return kind;
58
+ throw new Error("--kind must be one of: image, log, url, file.");
59
+ }
60
+ async function addEvidence(args) {
61
+ const parsed = parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--title", "--kind", "--session", "--note"]));
62
+ const target = parsed.positionals[0];
63
+ if (!target) {
64
+ throw new Error('Evidence target required. Use "happy evidence add <path-or-url>".');
65
+ }
66
+ const normalizedTarget = normalizeEvidenceTarget(target);
67
+ await assertLocalFileExists(normalizedTarget);
68
+ const entry = {
69
+ id: randomUUID(),
70
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
71
+ target: normalizedTarget,
72
+ title: stringFlag(parsed, "--title") ?? basename(normalizedTarget),
73
+ kind: parseKind(stringFlag(parsed, "--kind"), normalizedTarget),
74
+ sessionId: stringFlag(parsed, "--session"),
75
+ note: stringFlag(parsed, "--note")
76
+ };
77
+ await mkdir(dirname(EVIDENCE_FILE), { recursive: true });
78
+ await appendFile(EVIDENCE_FILE, `${JSON.stringify(entry)}
79
+ `, "utf8");
80
+ if (booleanFlag(parsed, "--json")) {
81
+ console.log(JSON.stringify(entry, null, 2));
82
+ } else {
83
+ console.log(`Added evidence ${entry.id}: ${entry.target}`);
84
+ }
85
+ }
86
+ async function listEvidence(args) {
87
+ const parsed = parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--session", "--out"]));
88
+ const entries = filterEvidenceEntries(await readEvidenceEntries(), stringFlag(parsed, "--session"));
89
+ if (booleanFlag(parsed, "--json")) {
90
+ console.log(JSON.stringify(entries, null, 2));
91
+ return;
92
+ }
93
+ const output = entries.length === 0 ? "No evidence recorded." : entries.map((entry) => `- ${entry.createdAt} [${entry.kind}] ${entry.title}: ${entry.target}`).join("\n");
94
+ await printOrWriteOutput(output, stringFlag(parsed, "--out"));
95
+ }
96
+ async function reportEvidence(args) {
97
+ const parsed = parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--session", "--out", "--title"]));
98
+ const entries = filterEvidenceEntries(await readEvidenceEntries(), stringFlag(parsed, "--session"));
99
+ const title = stringFlag(parsed, "--title") ?? "Evidence Report";
100
+ if (booleanFlag(parsed, "--json")) {
101
+ console.log(JSON.stringify({ title, entries }, null, 2));
102
+ return;
103
+ }
104
+ await printOrWriteOutput(renderEvidenceReport(entries, title), stringFlag(parsed, "--out"));
105
+ }
106
+ async function handleEvidenceCommand(args) {
107
+ const action = args[0];
108
+ if (!action || action === "-h" || action === "--help") {
109
+ printEvidenceHelp();
110
+ return;
111
+ }
112
+ if (action === "add") {
113
+ await addEvidence(args.slice(1));
114
+ return;
115
+ }
116
+ if (action === "list") {
117
+ await listEvidence(args.slice(1));
118
+ return;
119
+ }
120
+ if (action === "report") {
121
+ await reportEvidence(args.slice(1));
122
+ return;
123
+ }
124
+ throw new Error(`Unknown evidence command: ${action}. Use add, list, or report.`);
125
+ }
126
+
127
+ export { handleEvidenceCommand };
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ var chalk = require('chalk');
4
+ var fs = require('node:fs');
5
+ var promises = require('node:fs/promises');
6
+ var node_crypto = require('node:crypto');
7
+ var path = require('node:path');
8
+ var logger = require('./persistence-JLUn_Nou.cjs');
9
+ var workflowReports = require('./workflowReports-BEWuQesA.cjs');
10
+ var workflowCommon = require('./workflowCommon-Bl568-rt.cjs');
11
+ require('node:os');
12
+ require('zod');
13
+ require('tweetnacl');
14
+ require('fs');
15
+
16
+ const EVIDENCE_FILE = `${logger.configuration.happyHomeDir}/evidence/evidence.jsonl`;
17
+ function printEvidenceHelp() {
18
+ console.log(`
19
+ ${chalk.bold("happy evidence")} - Collect and render UI/debug evidence
20
+
21
+ ${chalk.bold("Usage:")}
22
+ happy evidence add <path-or-url> [options]
23
+ happy evidence list [options]
24
+ happy evidence report [options]
25
+
26
+ ${chalk.bold("Options:")}
27
+ --title <text> Human-readable evidence title
28
+ --kind <kind> image | log | url | file (default: inferred)
29
+ --session <id> Attach evidence to a Happy session
30
+ --note <text> Short note shown in reports
31
+ --out <path> Write report/list markdown to a file
32
+ --json Print machine-readable JSON
33
+ -h, --help Show this help
34
+ `);
35
+ }
36
+ function normalizeEvidenceTarget(target) {
37
+ if (/^(https?:|file:)/i.test(target)) return target;
38
+ return path.resolve(target);
39
+ }
40
+ async function readEvidenceEntries() {
41
+ try {
42
+ const content = await promises.readFile(EVIDENCE_FILE, "utf8");
43
+ return content.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
44
+ } catch (error) {
45
+ if (error.code === "ENOENT") return [];
46
+ throw error;
47
+ }
48
+ }
49
+ function filterEvidenceEntries(entries, sessionId) {
50
+ if (!sessionId) return entries;
51
+ return entries.filter((entry) => entry.sessionId === sessionId);
52
+ }
53
+ async function assertLocalFileExists(target) {
54
+ if (/^https?:/i.test(target) || /^file:/i.test(target)) return;
55
+ await promises.access(target, fs.constants.R_OK);
56
+ }
57
+ function parseKind(kind, target) {
58
+ if (!kind) return workflowReports.inferEvidenceKind(target);
59
+ if (kind === "image" || kind === "log" || kind === "url" || kind === "file") return kind;
60
+ throw new Error("--kind must be one of: image, log, url, file.");
61
+ }
62
+ async function addEvidence(args) {
63
+ const parsed = workflowCommon.parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--title", "--kind", "--session", "--note"]));
64
+ const target = parsed.positionals[0];
65
+ if (!target) {
66
+ throw new Error('Evidence target required. Use "happy evidence add <path-or-url>".');
67
+ }
68
+ const normalizedTarget = normalizeEvidenceTarget(target);
69
+ await assertLocalFileExists(normalizedTarget);
70
+ const entry = {
71
+ id: node_crypto.randomUUID(),
72
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
73
+ target: normalizedTarget,
74
+ title: workflowCommon.stringFlag(parsed, "--title") ?? path.basename(normalizedTarget),
75
+ kind: parseKind(workflowCommon.stringFlag(parsed, "--kind"), normalizedTarget),
76
+ sessionId: workflowCommon.stringFlag(parsed, "--session"),
77
+ note: workflowCommon.stringFlag(parsed, "--note")
78
+ };
79
+ await promises.mkdir(path.dirname(EVIDENCE_FILE), { recursive: true });
80
+ await promises.appendFile(EVIDENCE_FILE, `${JSON.stringify(entry)}
81
+ `, "utf8");
82
+ if (workflowCommon.booleanFlag(parsed, "--json")) {
83
+ console.log(JSON.stringify(entry, null, 2));
84
+ } else {
85
+ console.log(`Added evidence ${entry.id}: ${entry.target}`);
86
+ }
87
+ }
88
+ async function listEvidence(args) {
89
+ const parsed = workflowCommon.parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--session", "--out"]));
90
+ const entries = filterEvidenceEntries(await readEvidenceEntries(), workflowCommon.stringFlag(parsed, "--session"));
91
+ if (workflowCommon.booleanFlag(parsed, "--json")) {
92
+ console.log(JSON.stringify(entries, null, 2));
93
+ return;
94
+ }
95
+ const output = entries.length === 0 ? "No evidence recorded." : entries.map((entry) => `- ${entry.createdAt} [${entry.kind}] ${entry.title}: ${entry.target}`).join("\n");
96
+ await workflowCommon.printOrWriteOutput(output, workflowCommon.stringFlag(parsed, "--out"));
97
+ }
98
+ async function reportEvidence(args) {
99
+ const parsed = workflowCommon.parseWorkflowArgs(args, /* @__PURE__ */ new Set(["--session", "--out", "--title"]));
100
+ const entries = filterEvidenceEntries(await readEvidenceEntries(), workflowCommon.stringFlag(parsed, "--session"));
101
+ const title = workflowCommon.stringFlag(parsed, "--title") ?? "Evidence Report";
102
+ if (workflowCommon.booleanFlag(parsed, "--json")) {
103
+ console.log(JSON.stringify({ title, entries }, null, 2));
104
+ return;
105
+ }
106
+ await workflowCommon.printOrWriteOutput(workflowReports.renderEvidenceReport(entries, title), workflowCommon.stringFlag(parsed, "--out"));
107
+ }
108
+ async function handleEvidenceCommand(args) {
109
+ const action = args[0];
110
+ if (!action || action === "-h" || action === "--help") {
111
+ printEvidenceHelp();
112
+ return;
113
+ }
114
+ if (action === "add") {
115
+ await addEvidence(args.slice(1));
116
+ return;
117
+ }
118
+ if (action === "list") {
119
+ await listEvidence(args.slice(1));
120
+ return;
121
+ }
122
+ if (action === "report") {
123
+ await reportEvidence(args.slice(1));
124
+ return;
125
+ }
126
+ throw new Error(`Unknown evidence command: ${action}. Use add, list, or report.`);
127
+ }
128
+
129
+ exports.handleEvidenceCommand = handleEvidenceCommand;
@@ -0,0 +1,43 @@
1
+ import { d as downloadImage } from './downloadImage-DfSFLb-h.mjs';
2
+
3
+ async function formatMessageForClaude(text, images) {
4
+ const content = [];
5
+ for (const image of images) {
6
+ const downloaded = await downloadImage(image.url);
7
+ content.push({
8
+ type: "image",
9
+ source: {
10
+ type: "base64",
11
+ media_type: downloaded.mimeType,
12
+ data: downloaded.base64
13
+ }
14
+ });
15
+ }
16
+ if (text.trim()) {
17
+ content.push({
18
+ type: "text",
19
+ text
20
+ });
21
+ }
22
+ return content;
23
+ }
24
+ async function formatMessageForGemini(text, images) {
25
+ const parts = [];
26
+ for (const image of images) {
27
+ const downloaded = await downloadImage(image.url);
28
+ parts.push({
29
+ inlineData: {
30
+ mimeType: downloaded.mimeType,
31
+ data: downloaded.base64
32
+ }
33
+ });
34
+ }
35
+ if (text.trim()) {
36
+ parts.push({
37
+ text
38
+ });
39
+ }
40
+ return { parts };
41
+ }
42
+
43
+ export { formatMessageForClaude as a, formatMessageForGemini as f };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ var downloadImage = require('./downloadImage-MQMrUUxS.cjs');
4
+
5
+ async function formatMessageForClaude(text, images) {
6
+ const content = [];
7
+ for (const image of images) {
8
+ const downloaded = await downloadImage.downloadImage(image.url);
9
+ content.push({
10
+ type: "image",
11
+ source: {
12
+ type: "base64",
13
+ media_type: downloaded.mimeType,
14
+ data: downloaded.base64
15
+ }
16
+ });
17
+ }
18
+ if (text.trim()) {
19
+ content.push({
20
+ type: "text",
21
+ text
22
+ });
23
+ }
24
+ return content;
25
+ }
26
+ async function formatMessageForGemini(text, images) {
27
+ const parts = [];
28
+ for (const image of images) {
29
+ const downloaded = await downloadImage.downloadImage(image.url);
30
+ parts.push({
31
+ inlineData: {
32
+ mimeType: downloaded.mimeType,
33
+ data: downloaded.base64
34
+ }
35
+ });
36
+ }
37
+ if (text.trim()) {
38
+ parts.push({
39
+ text
40
+ });
41
+ }
42
+ return { parts };
43
+ }
44
+
45
+ exports.formatMessageForClaude = formatMessageForClaude;
46
+ exports.formatMessageForGemini = formatMessageForGemini;
@@ -0,0 +1,57 @@
1
+ import chalk from 'chalk';
2
+ import { b as buildGithubTakeoverReport } from './workflowReports-Dd8-4-8f.mjs';
3
+ import { p as parseWorkflowArgs, b as booleanFlag, s as stringFlag, a as printOrWriteOutput } from './workflowCommon-B0d01Y_a.mjs';
4
+ import 'node:path';
5
+ import 'node:fs/promises';
6
+
7
+ function printGithubHelp() {
8
+ console.log(`
9
+ ${chalk.bold("happy github")} - GitHub workflow helpers
10
+
11
+ ${chalk.bold("Usage:")}
12
+ happy github takeover <issue-or-pr-url-or-number> [options]
13
+
14
+ ${chalk.bold("Options:")}
15
+ --repo <owner/name> Repository when target is not a full URL
16
+ --mode <auto|pr|issue> Target type hint (default: auto)
17
+ --out <path> Write the prompt/checklist to a markdown file
18
+ --json Print machine-readable JSON
19
+ -h, --help Show this help
20
+ `);
21
+ }
22
+ async function handleGithubCommand(args) {
23
+ const action = args[0];
24
+ if (!action || action === "-h" || action === "--help") {
25
+ printGithubHelp();
26
+ return;
27
+ }
28
+ if (action !== "takeover") {
29
+ throw new Error(`Unknown github command: ${action}. Use "happy github takeover".`);
30
+ }
31
+ const parsed = parseWorkflowArgs(args.slice(1), /* @__PURE__ */ new Set(["--repo", "--mode", "--out"]));
32
+ if (booleanFlag(parsed, "--help") || booleanFlag(parsed, "-h")) {
33
+ printGithubHelp();
34
+ return;
35
+ }
36
+ const target = parsed.positionals[0];
37
+ if (!target) {
38
+ throw new Error('Target required. Use "happy github takeover <issue-or-pr-url-or-number>".');
39
+ }
40
+ const mode = stringFlag(parsed, "--mode") ?? "auto";
41
+ if (mode !== "auto" && mode !== "pr" && mode !== "issue") {
42
+ throw new Error("--mode must be one of: auto, pr, issue.");
43
+ }
44
+ const input = {
45
+ target,
46
+ repo: stringFlag(parsed, "--repo"),
47
+ mode
48
+ };
49
+ const report = buildGithubTakeoverReport(input);
50
+ if (booleanFlag(parsed, "--json")) {
51
+ console.log(JSON.stringify({ ...input, report }, null, 2));
52
+ return;
53
+ }
54
+ await printOrWriteOutput(report, stringFlag(parsed, "--out"));
55
+ }
56
+
57
+ export { handleGithubCommand };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ var chalk = require('chalk');
4
+ var workflowReports = require('./workflowReports-BEWuQesA.cjs');
5
+ var workflowCommon = require('./workflowCommon-Bl568-rt.cjs');
6
+ require('node:path');
7
+ require('node:fs/promises');
8
+
9
+ function printGithubHelp() {
10
+ console.log(`
11
+ ${chalk.bold("happy github")} - GitHub workflow helpers
12
+
13
+ ${chalk.bold("Usage:")}
14
+ happy github takeover <issue-or-pr-url-or-number> [options]
15
+
16
+ ${chalk.bold("Options:")}
17
+ --repo <owner/name> Repository when target is not a full URL
18
+ --mode <auto|pr|issue> Target type hint (default: auto)
19
+ --out <path> Write the prompt/checklist to a markdown file
20
+ --json Print machine-readable JSON
21
+ -h, --help Show this help
22
+ `);
23
+ }
24
+ async function handleGithubCommand(args) {
25
+ const action = args[0];
26
+ if (!action || action === "-h" || action === "--help") {
27
+ printGithubHelp();
28
+ return;
29
+ }
30
+ if (action !== "takeover") {
31
+ throw new Error(`Unknown github command: ${action}. Use "happy github takeover".`);
32
+ }
33
+ const parsed = workflowCommon.parseWorkflowArgs(args.slice(1), /* @__PURE__ */ new Set(["--repo", "--mode", "--out"]));
34
+ if (workflowCommon.booleanFlag(parsed, "--help") || workflowCommon.booleanFlag(parsed, "-h")) {
35
+ printGithubHelp();
36
+ return;
37
+ }
38
+ const target = parsed.positionals[0];
39
+ if (!target) {
40
+ throw new Error('Target required. Use "happy github takeover <issue-or-pr-url-or-number>".');
41
+ }
42
+ const mode = workflowCommon.stringFlag(parsed, "--mode") ?? "auto";
43
+ if (mode !== "auto" && mode !== "pr" && mode !== "issue") {
44
+ throw new Error("--mode must be one of: auto, pr, issue.");
45
+ }
46
+ const input = {
47
+ target,
48
+ repo: workflowCommon.stringFlag(parsed, "--repo"),
49
+ mode
50
+ };
51
+ const report = workflowReports.buildGithubTakeoverReport(input);
52
+ if (workflowCommon.booleanFlag(parsed, "--json")) {
53
+ console.log(JSON.stringify({ ...input, report }, null, 2));
54
+ return;
55
+ }
56
+ await workflowCommon.printOrWriteOutput(report, workflowCommon.stringFlag(parsed, "--out"));
57
+ }
58
+
59
+ exports.handleGithubCommand = handleGithubCommand;