@nanhara/hara 0.53.0 → 0.62.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.
@@ -0,0 +1,115 @@
1
+ const isSpace = (ch) => ch === " " || ch === "\t";
2
+ const clamp = (c, n) => Math.max(0, Math.min(n, c));
3
+ /** Start of the next word (run of non-space), vim `w`. */
4
+ function nextWord(v, c) {
5
+ const n = v.length;
6
+ let i = c;
7
+ if (i < n && !isSpace(v[i]))
8
+ while (i < n && !isSpace(v[i]))
9
+ i++;
10
+ while (i < n && isSpace(v[i]))
11
+ i++;
12
+ return i;
13
+ }
14
+ /** Start of the previous word, vim `b`. */
15
+ function prevWord(v, c) {
16
+ let i = c - 1;
17
+ while (i > 0 && isSpace(v[i]))
18
+ i--;
19
+ while (i > 0 && !isSpace(v[i - 1]))
20
+ i--;
21
+ return Math.max(0, i);
22
+ }
23
+ /** End index of the current/next word, vim `e`. */
24
+ function wordEnd(v, c) {
25
+ const n = v.length;
26
+ let i = c + 1;
27
+ while (i < n && isSpace(v[i]))
28
+ i++;
29
+ while (i < n - 1 && !isSpace(v[i + 1]))
30
+ i++;
31
+ return Math.min(n, i);
32
+ }
33
+ /** Apply one printable key in NORMAL mode, returning the next state. Special keys (Esc/Enter/arrows/
34
+ * backspace) are handled by the caller. Unknown keys are no-ops. */
35
+ export function vimNormal(st, input) {
36
+ const { value, cursor } = st;
37
+ const n = value.length;
38
+ const move = (c) => ({ ...st, cursor: clamp(c, n), pending: "" });
39
+ const toInsert = (v, c, register = st.register) => ({ value: v, cursor: clamp(c, v.length), mode: "insert", pending: "", register });
40
+ const toNormal = (patch) => ({ ...st, pending: "", ...patch });
41
+ // operator-pending: d{motion} / c{motion}
42
+ if (st.pending === "d" || st.pending === "c") {
43
+ const op = st.pending;
44
+ if (input === op)
45
+ return op === "c" ? toInsert("", 0, value) : toNormal({ value: "", cursor: 0, register: value }); // dd / cc
46
+ const ranges = {
47
+ w: [cursor, nextWord(value, cursor)],
48
+ e: [cursor, Math.min(n, wordEnd(value, cursor) + 1)],
49
+ b: [prevWord(value, cursor), cursor],
50
+ $: [cursor, n],
51
+ "0": [0, cursor],
52
+ };
53
+ const r = ranges[op === "c" && input === "w" ? "e" : input]; // cw acts like ce (vim quirk)
54
+ if (!r)
55
+ return toNormal({}); // unknown motion → cancel the operator
56
+ const [from, to] = r;
57
+ const deleted = value.slice(from, to);
58
+ const next = value.slice(0, from) + value.slice(to);
59
+ return op === "c" ? toInsert(next, from, deleted) : toNormal({ value: next, cursor: clamp(from, next.length), register: deleted });
60
+ }
61
+ if (st.pending === "g")
62
+ return input === "g" ? move(0) : toNormal({});
63
+ switch (input) {
64
+ // motions
65
+ case "h":
66
+ return move(cursor - 1);
67
+ case "l":
68
+ return move(cursor + 1);
69
+ case "0":
70
+ return move(0);
71
+ case "$":
72
+ return move(n);
73
+ case "w":
74
+ return move(nextWord(value, cursor));
75
+ case "b":
76
+ return move(prevWord(value, cursor));
77
+ case "e":
78
+ return move(wordEnd(value, cursor));
79
+ case "G":
80
+ return move(n);
81
+ // enter insert mode
82
+ case "i":
83
+ return toInsert(value, cursor);
84
+ case "a":
85
+ return toInsert(value, cursor + 1);
86
+ case "I":
87
+ return toInsert(value, value.length - value.trimStart().length);
88
+ case "A":
89
+ case "o": // single-line: open ≈ append at end
90
+ return toInsert(value, n);
91
+ // edits
92
+ case "x": {
93
+ if (!n)
94
+ return toNormal({});
95
+ return toNormal({ value: value.slice(0, cursor) + value.slice(cursor + 1), cursor: clamp(cursor, n - 1), register: value[cursor] ?? "" });
96
+ }
97
+ case "D":
98
+ return toNormal({ value: value.slice(0, cursor), cursor: clamp(cursor, cursor), register: value.slice(cursor) });
99
+ case "C":
100
+ return toInsert(value.slice(0, cursor), cursor, value.slice(cursor));
101
+ case "p":
102
+ return toNormal({ value: value.slice(0, cursor + 1) + st.register + value.slice(cursor + 1), cursor: cursor + st.register.length });
103
+ case "P":
104
+ return toNormal({ value: value.slice(0, cursor) + st.register + value.slice(cursor), cursor: cursor + Math.max(0, st.register.length - 1) });
105
+ // operators
106
+ case "d":
107
+ return { ...st, pending: "d" };
108
+ case "c":
109
+ return { ...st, pending: "c" };
110
+ case "g":
111
+ return { ...st, pending: "g" };
112
+ default:
113
+ return toNormal({}); // ignore everything else (printable keys don't insert in normal mode)
114
+ }
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanhara/hara",
3
- "version": "0.53.0",
3
+ "version": "0.62.0",
4
4
  "description": "hara — a coding agent CLI that runs like an engineering org.",
5
5
  "bin": {
6
6
  "hara": "dist/index.js"
@@ -8,8 +8,10 @@
8
8
  "type": "module",
9
9
  "files": [
10
10
  "dist",
11
+ "!dist/bin",
11
12
  "README.md",
12
13
  "CHANGELOG.md",
14
+ "SECURITY.md",
13
15
  "LICENSE",
14
16
  "CLA.md",
15
17
  "plugins"
@@ -40,7 +42,9 @@
40
42
  "prepare": "tsc",
41
43
  "dev": "tsx src/index.ts",
42
44
  "start": "node dist/index.js",
43
- "test": "tsc && node --test test/*.test.mjs"
45
+ "test": "tsc && node --test test/*.test.mjs",
46
+ "build:binary": "tsc && bun scripts/build-binary.ts dist/bin/hara",
47
+ "build:binaries": "tsc && bun scripts/build-binary.ts dist/bin/hara-darwin-arm64 bun-darwin-arm64 && bun scripts/build-binary.ts dist/bin/hara-darwin-x64 bun-darwin-x64 && bun scripts/build-binary.ts dist/bin/hara-linux-x64 bun-linux-x64 && bun scripts/build-binary.ts dist/bin/hara-linux-arm64 bun-linux-arm64"
44
48
  },
45
49
  "publishConfig": {
46
50
  "access": "public"