@nanhara/hara 0.119.2 → 0.120.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/CHANGELOG.md +12 -0
- package/dist/feedback.js +55 -0
- package/dist/index.js +46 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.120.0 — `hara feedback`: one door for humans and agents
|
|
9
|
+
|
|
10
|
+
- **`hara feedback "what happened"`** files a structured GitHub issue (repo hara-cli/hara,
|
|
11
|
+
label `feedback`) via the gh CLI, with a copy-paste fallback. Auto-collects hara version /
|
|
12
|
+
OS / Node / provider:model (never keys) and aggressively redacts credentials
|
|
13
|
+
(sk-/ghp_/AWS/Bearer/JWT/key=value families). `--session` opt-in attaches a redacted session
|
|
14
|
+
tail (issues are public); `--dry-run` prints without filing. Matching .github issue forms
|
|
15
|
+
ship in the repo — hand-filed and command-filed reports read identically.
|
|
16
|
+
- **Security stance (documented)**: hara agents exchange structured data — they never accept
|
|
17
|
+
task instructions from untrusted parties. Cross-trust-boundary agent task-passing is
|
|
18
|
+
permanently out of scope; feedback/issues are the entire agent-to-agent surface.
|
|
19
|
+
|
|
8
20
|
## 0.119.2 — TUI: update notices actually visible · CJK-correct input wrapping
|
|
9
21
|
|
|
10
22
|
- **Update notices now render INSIDE the TUI** (yellow line under the header card). They used to
|
package/dist/feedback.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// `hara feedback` — the hara-hub verdict made flesh: feedback is a COMMAND, not a server.
|
|
2
|
+
// Collects environment facts, redacts secrets, and builds a structured GitHub issue body that
|
|
3
|
+
// humans and agents file through the same door (gh CLI when present, copy-paste text otherwise).
|
|
4
|
+
// The session tail is OFF by default and explicitly opt-in (--session) because issues are public.
|
|
5
|
+
import { platform, release, arch } from "node:os";
|
|
6
|
+
export function collectEnv(version, model) {
|
|
7
|
+
return {
|
|
8
|
+
version,
|
|
9
|
+
os: `${platform()} ${release()} ${arch()}`,
|
|
10
|
+
node: process.version,
|
|
11
|
+
model,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Strip credential-looking material from text that is about to become a PUBLIC issue.
|
|
15
|
+
* Deliberately aggressive: false positives cost a little readability, false negatives leak keys. */
|
|
16
|
+
export function redact(text) {
|
|
17
|
+
return text
|
|
18
|
+
.replace(/\bsk-[A-Za-z0-9_-]{8,}/g, "sk-***")
|
|
19
|
+
.replace(/\bgh[pousr]_[A-Za-z0-9]{20,}/g, "gh*_***")
|
|
20
|
+
.replace(/\b(AKIA|ASIA)[A-Z0-9]{16}\b/g, "AWS-KEY-***")
|
|
21
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/gi, "Bearer ***")
|
|
22
|
+
.replace(/\b(api[_-]?key|apikey|token|secret|password|passwd|authorization)(["']?\s*[:=]\s*["']?)[^\s"',;]{6,}/gi, "$1$2***")
|
|
23
|
+
.replace(/\beyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, "JWT-***");
|
|
24
|
+
}
|
|
25
|
+
/** The structured issue body — same shape as .github/ISSUE_TEMPLATE/bug_report.yml so hand-filed
|
|
26
|
+
* and command-filed issues read identically to the triage side. */
|
|
27
|
+
export function buildIssueBody(description, env, sessionTail) {
|
|
28
|
+
const parts = [
|
|
29
|
+
"## What happened",
|
|
30
|
+
"",
|
|
31
|
+
redact(description.trim() || "(no description provided)"),
|
|
32
|
+
"",
|
|
33
|
+
"## Environment",
|
|
34
|
+
"",
|
|
35
|
+
"| | |",
|
|
36
|
+
"|---|---|",
|
|
37
|
+
`| hara | ${env.version} |`,
|
|
38
|
+
`| os | ${env.os} |`,
|
|
39
|
+
`| node | ${env.node} |`,
|
|
40
|
+
...(env.model ? [`| model | ${redact(env.model)} |`] : []),
|
|
41
|
+
"",
|
|
42
|
+
];
|
|
43
|
+
if (sessionTail && sessionTail.trim()) {
|
|
44
|
+
parts.push("## Session tail (redacted, shared with --session)", "", "```", redact(sessionTail).slice(0, 4000), "```", "");
|
|
45
|
+
}
|
|
46
|
+
parts.push("---", "_filed via `hara feedback`_");
|
|
47
|
+
return parts.join("\n");
|
|
48
|
+
}
|
|
49
|
+
/** Issue title from the description: first line, trimmed, capped. */
|
|
50
|
+
export function issueTitle(description) {
|
|
51
|
+
const first = (description.trim().split("\n")[0] || "feedback").trim();
|
|
52
|
+
return first.length > 70 ? first.slice(0, 67) + "…" : first;
|
|
53
|
+
}
|
|
54
|
+
export const FEEDBACK_REPO = "hara-cli/hara";
|
|
55
|
+
export const NEW_ISSUE_URL = `https://github.com/${FEEDBACK_REPO}/issues/new`;
|
package/dist/index.js
CHANGED
|
@@ -1990,6 +1990,52 @@ rolesCmd.action(() => {
|
|
|
1990
1990
|
out(`${c.bold(r.id)}${r.model ? c.dim(` (${r.model})`) : ""} ${c.dim("owns: " + r.owns.join(", "))}\n ${r.description}\n`);
|
|
1991
1991
|
}
|
|
1992
1992
|
});
|
|
1993
|
+
program
|
|
1994
|
+
.command("feedback [description...]")
|
|
1995
|
+
.description("file a structured bug/feature report to GitHub (hara-cli/hara) — humans and agents use the same door")
|
|
1996
|
+
.option("--session", "append a REDACTED tail of the most recent session (the issue is PUBLIC)")
|
|
1997
|
+
.option("--dry-run", "print the issue body without filing")
|
|
1998
|
+
.action(async (parts, o) => {
|
|
1999
|
+
// the hara-hub verdict: feedback is a command, not a server — GitHub Issues is the bus
|
|
2000
|
+
const { collectEnv, buildIssueBody, issueTitle, FEEDBACK_REPO, NEW_ISSUE_URL } = await import("./feedback.js");
|
|
2001
|
+
const desc = (parts ?? []).join(" ").trim();
|
|
2002
|
+
if (!desc) {
|
|
2003
|
+
out(`Usage: hara feedback "what happened…" [--session] [--dry-run] — files a structured issue to ${FEEDBACK_REPO}\n`);
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
let tail;
|
|
2007
|
+
if (o.session) {
|
|
2008
|
+
const { listSessions, loadSession } = await import("./session/store.js");
|
|
2009
|
+
const metas = listSessions();
|
|
2010
|
+
const last = metas[0] ? loadSession(metas[0].id) : null;
|
|
2011
|
+
if (last) {
|
|
2012
|
+
tail = last.history
|
|
2013
|
+
.slice(-8)
|
|
2014
|
+
.map((m) => (m.role === "user" ? `user: ${m.content}` : m.role === "assistant" && m.text ? `assistant: ${m.text}` : ""))
|
|
2015
|
+
.filter(Boolean)
|
|
2016
|
+
.join("\n")
|
|
2017
|
+
.slice(-3000);
|
|
2018
|
+
out(c.yellow("⚠ --session attaches a redacted tail of your last session to a PUBLIC issue.\n"));
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
const { readRawConfig } = await import("./config.js");
|
|
2022
|
+
const raw = readRawConfig();
|
|
2023
|
+
const modelLabel = raw.provider && raw.model ? `${raw.provider}:${raw.model}` : undefined;
|
|
2024
|
+
const body = buildIssueBody(desc, collectEnv(pkg.version, modelLabel), tail);
|
|
2025
|
+
if (o.dryRun) {
|
|
2026
|
+
out(body + "\n");
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
const { execFileSync } = await import("node:child_process");
|
|
2030
|
+
try {
|
|
2031
|
+
execFileSync("gh", ["--version"], { stdio: "ignore" });
|
|
2032
|
+
const url = execFileSync("gh", ["issue", "create", "--repo", FEEDBACK_REPO, "--title", issueTitle(desc), "--body", body, "--label", "feedback"], { encoding: "utf8" });
|
|
2033
|
+
out(c.green("✓ filed: ") + url.trim() + "\n");
|
|
2034
|
+
}
|
|
2035
|
+
catch {
|
|
2036
|
+
out(`\n(gh CLI unavailable or filing failed — copy everything below into ${NEW_ISSUE_URL})\n\n# ${issueTitle(desc)}\n\n${body}\n`);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
1993
2039
|
const skillsCmd = program.command("skills").description("manage skills (.hara/skills/<name>/SKILL.md)");
|
|
1994
2040
|
skillsCmd
|
|
1995
2041
|
.command("init")
|