hoomanjs 1.10.0 → 1.11.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/README.md CHANGED
@@ -163,6 +163,12 @@ Runtime tools and prompt sections are controlled from `config.json` under `featu
163
163
  - `features.filesystem.enabled`
164
164
  - `features.shell.enabled`
165
165
  - `features.ltm.enabled`
166
+ - `features.wiki.enabled`
167
+
168
+ Both `ltm` and `wiki` include dedicated Chroma settings under:
169
+
170
+ - `features.ltm.chroma` (default collection: `memory`)
171
+ - `features.wiki.chroma` (default collection: `wiki`)
166
172
 
167
173
  ### `hooman configure`
168
174
 
@@ -205,7 +211,7 @@ Hooman stores its data in:
205
211
 
206
212
  Important files and folders:
207
213
 
208
- - `config.json` - app name, LLM provider/model, tool approvals, long-term memory, compaction
214
+ - `config.json` - app name, LLM provider/model, tool approvals, feature flags, LTM/wiki settings, compaction
209
215
  - `instructions.md` - system instructions used to build the agent prompt
210
216
  - `mcp.json` - MCP server definitions
211
217
  - `skills/` - installed skills
@@ -245,6 +251,15 @@ This is the shape managed by `hooman configure`:
245
251
  "memory": "memory"
246
252
  }
247
253
  }
254
+ },
255
+ "wiki": {
256
+ "enabled": false,
257
+ "chroma": {
258
+ "url": "http://127.0.0.1:8000",
259
+ "collection": {
260
+ "wiki": "wiki"
261
+ }
262
+ }
248
263
  }
249
264
  },
250
265
  "compaction": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Bun-powered local AI agent CLI with chat, exec, ACP, MCP, and skills support.",
5
5
  "author": {
6
6
  "name": "Vaibhav Pandey",
@@ -6,6 +6,7 @@ import type {
6
6
  import type { Config } from "../../core/config.ts";
7
7
 
8
8
  export const HOOMAN_LTM_CONFIG_ID = "hooman.longTermMemory" as const;
9
+ export const HOOMAN_WIKI_CONFIG_ID = "hooman.wiki" as const;
9
10
 
10
11
  export function buildSessionConfigOptions(
11
12
  config: Config,
@@ -24,6 +25,19 @@ export function buildSessionConfigOptions(
24
25
  { value: "off", name: "Off" },
25
26
  ],
26
27
  },
28
+ {
29
+ type: "select",
30
+ id: HOOMAN_WIKI_CONFIG_ID,
31
+ name: "Wiki",
32
+ description:
33
+ "When enabled, the agent can read, write, and search wiki pages (requires Chroma at the configured URL).",
34
+ category: "_hooman",
35
+ currentValue: config.wiki.enabled ? "on" : "off",
36
+ options: [
37
+ { value: "on", name: "On" },
38
+ { value: "off", name: "Off" },
39
+ ],
40
+ },
27
41
  ];
28
42
  }
29
43
 
@@ -36,22 +50,42 @@ export function applySessionConfigOption(
36
50
  message: "Boolean session config options are not supported.",
37
51
  });
38
52
  }
39
- if (params.configId !== HOOMAN_LTM_CONFIG_ID) {
53
+ if (
54
+ params.configId !== HOOMAN_LTM_CONFIG_ID &&
55
+ params.configId !== HOOMAN_WIKI_CONFIG_ID
56
+ ) {
40
57
  throw RequestError.invalidParams({ configId: params.configId });
41
58
  }
42
59
  const value = params.value;
43
60
  if (value !== "on" && value !== "off") {
44
61
  throw RequestError.invalidParams({ value });
45
62
  }
46
- const chroma = config.ltm.chroma;
63
+ if (params.configId === HOOMAN_LTM_CONFIG_ID) {
64
+ const chroma = config.ltm.chroma;
65
+ config.update({
66
+ features: {
67
+ ...config.features,
68
+ ltm: {
69
+ enabled: value === "on",
70
+ chroma: {
71
+ url: chroma.url,
72
+ collection: { memory: chroma.collection.memory },
73
+ },
74
+ },
75
+ },
76
+ });
77
+ return;
78
+ }
79
+
80
+ const chroma = config.wiki.chroma;
47
81
  config.update({
48
82
  features: {
49
83
  ...config.features,
50
- ltm: {
84
+ wiki: {
51
85
  enabled: value === "on",
52
86
  chroma: {
53
87
  url: chroma.url,
54
- collection: { memory: chroma.collection.memory },
88
+ collection: { wiki: chroma.collection.wiki },
55
89
  },
56
90
  },
57
91
  },
@@ -15,6 +15,12 @@ const KNOWN_TOOL_KINDS = new Map<string, ToolKind>([
15
15
  ["get_file_info", "read"],
16
16
  ["shell", "execute"],
17
17
  ["fetch", "fetch"],
18
+ ["wiki_list_files", "read"],
19
+ ["wiki_read_file", "read"],
20
+ ["wiki_write_file", "edit"],
21
+ ["wiki_knowledge_graph", "read"],
22
+ ["wiki_stats", "read"],
23
+ ["wiki_search", "search"],
18
24
  ["think", "think"],
19
25
  ["get_current_time", "other"],
20
26
  ["convert_time", "other"],
@@ -11,6 +11,8 @@ const KNOWN_TOOL_LOCATION_KEYS = new Map<string, readonly string[]>([
11
11
  ["move_file", ["source", "destination"]],
12
12
  ["search_files", ["path"]],
13
13
  ["get_file_info", ["path"]],
14
+ ["wiki_read_file", ["path"]],
15
+ ["wiki_write_file", ["path"]],
14
16
  ]);
15
17
 
16
18
  /** ACP `locations` extracted only from known core filesystem tools. */
@@ -39,6 +39,7 @@ import {
39
39
  noticeColor,
40
40
  parseNumber,
41
41
  parseObjectRecord,
42
+ maskSensitiveParamsForDisplay,
42
43
  parseStringArray,
43
44
  parseStringRecord,
44
45
  transportSummary,
@@ -412,7 +413,9 @@ export function ConfigureApp({
412
413
  }),
413
414
  },
414
415
  {
415
- label: `LLM params • ${truncate(compactJson(configData.llm.params))}`,
416
+ label: `LLM params • ${truncate(
417
+ compactJson(maskSensitiveParamsForDisplay(configData.llm.params)),
418
+ )}`,
416
419
  value: () =>
417
420
  promptValue({
418
421
  title: "Update LLM params",
@@ -496,7 +499,112 @@ export function ConfigureApp({
496
499
  },
497
500
  },
498
501
  {
499
- label: `Long-term memory • ${configData.features.ltm.enabled ? "Enabled" : "Disabled"}`,
502
+ label: `Long-term memory • ${configData.features.ltm.enabled ? "Enabled" : "Disabled"} • ${configData.features.ltm.chroma.collection.memory}`,
503
+ value: () => setScreen({ kind: "config-ltm" }),
504
+ },
505
+ {
506
+ label: `Wiki feature • ${configData.features.wiki.enabled ? "Enabled" : "Disabled"} • ${configData.features.wiki.chroma.collection.wiki}`,
507
+ value: () => setScreen({ kind: "config-wiki" }),
508
+ },
509
+ {
510
+ label: `Compaction ratio • ${configData.compaction.ratio}`,
511
+ value: () =>
512
+ promptValue({
513
+ title: "Update compaction ratio",
514
+ label: "Ratio",
515
+ initialValue: String(configData.compaction.ratio),
516
+ onSubmit: async (value) => {
517
+ const ratio = parseNumber(value, "Compaction ratio", {
518
+ min: 0,
519
+ max: 1,
520
+ });
521
+ updateConfig(
522
+ {
523
+ compaction: {
524
+ ...config.compaction,
525
+ ratio,
526
+ },
527
+ },
528
+ "Updated compaction ratio.",
529
+ );
530
+ setPrompt(null);
531
+ },
532
+ }),
533
+ },
534
+ {
535
+ label: `Compaction keep • ${configData.compaction.keep}`,
536
+ value: () =>
537
+ promptValue({
538
+ title: "Update compaction keep",
539
+ label: "Keep",
540
+ initialValue: String(configData.compaction.keep),
541
+ onSubmit: async (value) => {
542
+ const keep = parseNumber(value, "Compaction keep", {
543
+ min: 0,
544
+ integer: true,
545
+ });
546
+ updateConfig(
547
+ {
548
+ compaction: {
549
+ ...config.compaction,
550
+ keep,
551
+ },
552
+ },
553
+ "Updated compaction keep.",
554
+ );
555
+ setPrompt(null);
556
+ },
557
+ }),
558
+ },
559
+ {
560
+ label: "Back",
561
+ value: () => setScreen({ kind: "home" }),
562
+ },
563
+ ];
564
+
565
+ return (
566
+ <MenuScreen
567
+ title="Configuration"
568
+ description="Edit the same values loaded from ~/.hooman/config.json."
569
+ items={items}
570
+ />
571
+ );
572
+ };
573
+
574
+ const renderProviderMenu = () => {
575
+ const items: MenuItem[] = [
576
+ ...Object.values(LlmProvider).map((provider) => ({
577
+ label:
578
+ provider === configData.llm.provider
579
+ ? `${provider} • current`
580
+ : provider,
581
+ value: () => {
582
+ updateConfig(
583
+ { llm: { ...config.llm, provider } },
584
+ `Updated LLM provider to "${provider}".`,
585
+ );
586
+ setScreen({ kind: "config" });
587
+ },
588
+ })),
589
+ {
590
+ label: "Back",
591
+ value: () => setScreen({ kind: "config" }),
592
+ },
593
+ ];
594
+
595
+ return (
596
+ <MenuScreen
597
+ title="Choose Provider"
598
+ description="Pick which model provider to use for future sessions."
599
+ items={items}
600
+ />
601
+ );
602
+ };
603
+
604
+ const renderLtmConfigMenu = () => {
605
+ const items: MenuItem[] = [
606
+ {
607
+ label: `Enabled • ${configData.features.ltm.enabled ? "On" : "Off"}`,
500
608
  value: () => {
501
609
  updateConfig(
502
610
  {
@@ -510,14 +618,14 @@ export function ConfigureApp({
510
618
  },
511
619
  `Long-term memory ${configData.features.ltm.enabled ? "disabled" : "enabled"}.`,
512
620
  );
513
- setScreen({ kind: "config" });
621
+ setScreen({ kind: "config-ltm" });
514
622
  },
515
623
  },
516
624
  {
517
625
  label: `Chroma URL • ${configData.features.ltm.chroma.url}`,
518
626
  value: () =>
519
627
  promptValue({
520
- title: "Update Chroma URL",
628
+ title: "Update LTM Chroma URL",
521
629
  label: "URL",
522
630
  initialValue: configData.features.ltm.chroma.url,
523
631
  onSubmit: async (value) => {
@@ -538,17 +646,17 @@ export function ConfigureApp({
538
646
  },
539
647
  },
540
648
  },
541
- "Updated Chroma URL.",
649
+ "Updated LTM Chroma URL.",
542
650
  );
543
651
  setPrompt(null);
544
652
  },
545
653
  }),
546
654
  },
547
655
  {
548
- label: `Chroma memory collection • ${configData.features.ltm.chroma.collection.memory}`,
656
+ label: `Chroma collection • ${configData.features.ltm.chroma.collection.memory}`,
549
657
  value: () =>
550
658
  promptValue({
551
- title: "Update Chroma memory collection",
659
+ title: "Update LTM Chroma collection",
552
660
  label: "Collection name",
553
661
  initialValue: configData.features.ltm.chroma.collection.memory,
554
662
  onSubmit: async (value) => {
@@ -569,92 +677,109 @@ export function ConfigureApp({
569
677
  },
570
678
  },
571
679
  },
572
- "Updated Chroma collection.",
680
+ "Updated LTM Chroma collection.",
573
681
  );
574
682
  setPrompt(null);
575
683
  },
576
684
  }),
577
685
  },
578
686
  {
579
- label: `Compaction ratio • ${configData.compaction.ratio}`,
687
+ label: "Back",
688
+ value: () => setScreen({ kind: "config" }),
689
+ },
690
+ ];
691
+
692
+ return (
693
+ <MenuScreen
694
+ title="Long-term Memory"
695
+ description="Configure LTM memory storage and Chroma settings."
696
+ items={items}
697
+ />
698
+ );
699
+ };
700
+
701
+ const renderWikiConfigMenu = () => {
702
+ const items: MenuItem[] = [
703
+ {
704
+ label: `Enabled • ${configData.features.wiki.enabled ? "On" : "Off"}`,
705
+ value: () => {
706
+ updateConfig(
707
+ {
708
+ features: {
709
+ ...config.features,
710
+ wiki: {
711
+ ...config.features.wiki,
712
+ enabled: !configData.features.wiki.enabled,
713
+ },
714
+ },
715
+ },
716
+ `Wiki feature ${configData.features.wiki.enabled ? "disabled" : "enabled"}.`,
717
+ );
718
+ setScreen({ kind: "config-wiki" });
719
+ },
720
+ },
721
+ {
722
+ label: `Chroma URL • ${configData.features.wiki.chroma.url}`,
580
723
  value: () =>
581
724
  promptValue({
582
- title: "Update compaction ratio",
583
- label: "Ratio",
584
- initialValue: String(configData.compaction.ratio),
725
+ title: "Update Wiki Chroma URL",
726
+ label: "URL",
727
+ initialValue: configData.features.wiki.chroma.url,
585
728
  onSubmit: async (value) => {
586
- const ratio = parseNumber(value, "Compaction ratio", {
587
- min: 0,
588
- max: 1,
589
- });
729
+ const url = value.trim();
730
+ if (!url) {
731
+ throw new Error("URL is required.");
732
+ }
590
733
  updateConfig(
591
734
  {
592
- compaction: {
593
- ...config.compaction,
594
- ratio,
735
+ features: {
736
+ ...config.features,
737
+ wiki: {
738
+ ...config.features.wiki,
739
+ chroma: {
740
+ ...config.features.wiki.chroma,
741
+ url,
742
+ },
743
+ },
595
744
  },
596
745
  },
597
- "Updated compaction ratio.",
746
+ "Updated Wiki Chroma URL.",
598
747
  );
599
748
  setPrompt(null);
600
749
  },
601
750
  }),
602
751
  },
603
752
  {
604
- label: `Compaction keep • ${configData.compaction.keep}`,
753
+ label: `Chroma collection • ${configData.features.wiki.chroma.collection.wiki}`,
605
754
  value: () =>
606
755
  promptValue({
607
- title: "Update compaction keep",
608
- label: "Keep",
609
- initialValue: String(configData.compaction.keep),
756
+ title: "Update Wiki Chroma collection",
757
+ label: "Collection name",
758
+ initialValue: configData.features.wiki.chroma.collection.wiki,
610
759
  onSubmit: async (value) => {
611
- const keep = parseNumber(value, "Compaction keep", {
612
- min: 0,
613
- integer: true,
614
- });
760
+ const wiki = value.trim();
761
+ if (!wiki) {
762
+ throw new Error("Collection name is required.");
763
+ }
615
764
  updateConfig(
616
765
  {
617
- compaction: {
618
- ...config.compaction,
619
- keep,
766
+ features: {
767
+ ...config.features,
768
+ wiki: {
769
+ ...config.features.wiki,
770
+ chroma: {
771
+ ...config.features.wiki.chroma,
772
+ collection: { wiki },
773
+ },
774
+ },
620
775
  },
621
776
  },
622
- "Updated compaction keep.",
777
+ "Updated Wiki Chroma collection.",
623
778
  );
624
779
  setPrompt(null);
625
780
  },
626
781
  }),
627
782
  },
628
- {
629
- label: "Back",
630
- value: () => setScreen({ kind: "home" }),
631
- },
632
- ];
633
-
634
- return (
635
- <MenuScreen
636
- title="Configuration"
637
- description="Edit the same values loaded from ~/.hooman/config.json."
638
- items={items}
639
- />
640
- );
641
- };
642
-
643
- const renderProviderMenu = () => {
644
- const items: MenuItem[] = [
645
- ...Object.values(LlmProvider).map((provider) => ({
646
- label:
647
- provider === configData.llm.provider
648
- ? `${provider} • current`
649
- : provider,
650
- value: () => {
651
- updateConfig(
652
- { llm: { ...config.llm, provider } },
653
- `Updated LLM provider to "${provider}".`,
654
- );
655
- setScreen({ kind: "config" });
656
- },
657
- })),
658
783
  {
659
784
  label: "Back",
660
785
  value: () => setScreen({ kind: "config" }),
@@ -663,8 +788,8 @@ export function ConfigureApp({
663
788
 
664
789
  return (
665
790
  <MenuScreen
666
- title="Choose Provider"
667
- description="Pick which model provider to use for future sessions."
791
+ title="Wiki"
792
+ description="Configure wiki features and Chroma-backed wiki search."
668
793
  items={items}
669
794
  />
670
795
  );
@@ -936,6 +1061,10 @@ export function ConfigureApp({
936
1061
  return renderConfigMenu();
937
1062
  case "config-provider":
938
1063
  return renderProviderMenu();
1064
+ case "config-ltm":
1065
+ return renderLtmConfigMenu();
1066
+ case "config-wiki":
1067
+ return renderWikiConfigMenu();
939
1068
  case "mcp":
940
1069
  return renderMcpMenu();
941
1070
  case "mcp-delete-confirm":
@@ -13,6 +13,8 @@ export type Screen =
13
13
  | { kind: "home" }
14
14
  | { kind: "config" }
15
15
  | { kind: "config-provider" }
16
+ | { kind: "config-ltm" }
17
+ | { kind: "config-wiki" }
16
18
  | { kind: "mcp" }
17
19
  | { kind: "mcp-delete-confirm"; name: string }
18
20
  | { kind: "skills" }
@@ -16,6 +16,27 @@ export function compactJson(value: unknown): string {
16
16
  return JSON.stringify(value);
17
17
  }
18
18
 
19
+ const MASKED_PARAM_KEYS = new Set(["apikey", "clientconfig"]);
20
+
21
+ export function maskSensitiveParamsForDisplay(value: unknown): unknown {
22
+ if (Array.isArray(value)) {
23
+ return value.map((item) => maskSensitiveParamsForDisplay(item));
24
+ }
25
+ if (!value || typeof value !== "object") {
26
+ return value;
27
+ }
28
+ const input = value as Record<string, unknown>;
29
+ const output: Record<string, unknown> = {};
30
+ for (const [key, itemValue] of Object.entries(input)) {
31
+ if (MASKED_PARAM_KEYS.has(key.toLowerCase())) {
32
+ output[key] = "[REDACTED]";
33
+ continue;
34
+ }
35
+ output[key] = maskSensitiveParamsForDisplay(itemValue);
36
+ }
37
+ return output;
38
+ }
39
+
19
40
  export function truncate(text: string, max: number = 88): string {
20
41
  return text.length > max ? `${text.slice(0, max - 1)}…` : text;
21
42
  }
@@ -16,6 +16,7 @@ import {
16
16
  createShellTools,
17
17
  createThinkingTools,
18
18
  createTimeTools,
19
+ createWikiTools,
19
20
  } from "../tools";
20
21
 
21
22
  const SECTION_BREAK = "\n\n---\n\n";
@@ -59,6 +60,7 @@ export async function create(
59
60
  ...(ltm ? createLongTermMemoryTools(ltm) : []),
60
61
  ...(config.features.filesystem.enabled ? createFilesystemTools() : []),
61
62
  ...(config.features.shell.enabled ? createShellTools() : []),
63
+ ...(config.features.wiki.enabled ? createWikiTools(config) : []),
62
64
  ...createThinkingTools(),
63
65
  ...tools,
64
66
  ],
@@ -34,7 +34,12 @@ const DEFAULT_CHROMA = {
34
34
  collection: { memory: "memory" },
35
35
  } as const;
36
36
 
37
- const ChromaPartialSchema = z.object({
37
+ const DEFAULT_WIKI_CHROMA = {
38
+ url: "http://127.0.0.1:8000",
39
+ collection: { wiki: "wiki" },
40
+ } as const;
41
+
42
+ const LtmChromaPartialSchema = z.object({
38
43
  url: z.string().min(1).optional(),
39
44
  collection: z
40
45
  .object({
@@ -45,7 +50,21 @@ const ChromaPartialSchema = z.object({
45
50
 
46
51
  const LtmPartialSchema = z.object({
47
52
  enabled: z.boolean().optional(),
48
- chroma: ChromaPartialSchema.optional(),
53
+ chroma: LtmChromaPartialSchema.optional(),
54
+ });
55
+
56
+ const WikiChromaPartialSchema = z.object({
57
+ url: z.string().min(1).optional(),
58
+ collection: z
59
+ .object({
60
+ wiki: z.string().min(1).optional(),
61
+ })
62
+ .optional(),
63
+ });
64
+
65
+ const WikiPartialSchema = z.object({
66
+ enabled: z.boolean().optional(),
67
+ chroma: WikiChromaPartialSchema.optional(),
49
68
  });
50
69
 
51
70
  const FeatureTogglePartialSchema = z.object({
@@ -57,6 +76,7 @@ const FeaturesPartialSchema = z.object({
57
76
  filesystem: FeatureTogglePartialSchema.optional(),
58
77
  shell: FeatureTogglePartialSchema.optional(),
59
78
  ltm: LtmPartialSchema.optional(),
79
+ wiki: WikiPartialSchema.optional(),
60
80
  });
61
81
 
62
82
  const ToolsPartialSchema = z.object({
@@ -76,6 +96,7 @@ const ConfigSchema = z
76
96
  })
77
97
  .transform((input) => {
78
98
  const ltm = input.features?.ltm;
99
+ const wiki = input.features?.wiki;
79
100
  return {
80
101
  name: input.name,
81
102
  llm: input.llm,
@@ -101,6 +122,17 @@ const ConfigSchema = z
101
122
  },
102
123
  },
103
124
  },
125
+ wiki: {
126
+ enabled: wiki?.enabled ?? false,
127
+ chroma: {
128
+ url: wiki?.chroma?.url ?? DEFAULT_WIKI_CHROMA.url,
129
+ collection: {
130
+ wiki:
131
+ wiki?.chroma?.collection?.wiki ??
132
+ DEFAULT_WIKI_CHROMA.collection.wiki,
133
+ },
134
+ },
135
+ },
104
136
  },
105
137
  compaction: input.compaction,
106
138
  };
@@ -110,6 +142,7 @@ export type ConfigData = z.infer<typeof ConfigSchema>;
110
142
  export type LlmConfig = z.infer<typeof LlmSchema>;
111
143
  export type CompactionConfig = ConfigData["compaction"];
112
144
  export type LtmConfig = ConfigData["features"]["ltm"];
145
+ export type WikiConfig = ConfigData["features"]["wiki"];
113
146
  export type ToolsConfig = ConfigData["tools"];
114
147
  export type FeaturesConfig = ConfigData["features"];
115
148
 
@@ -140,6 +173,13 @@ const defaultConfigData = (): ConfigData => ({
140
173
  collection: { memory: "memory" },
141
174
  },
142
175
  },
176
+ wiki: {
177
+ enabled: false,
178
+ chroma: {
179
+ url: "http://127.0.0.1:8000",
180
+ collection: { wiki: "wiki" },
181
+ },
182
+ },
143
183
  },
144
184
  compaction: {
145
185
  ratio: 0.75,
@@ -188,6 +228,13 @@ export class Config {
188
228
  collection: { ...this.data.features.ltm.chroma.collection },
189
229
  },
190
230
  },
231
+ wiki: {
232
+ ...this.data.features.wiki,
233
+ chroma: {
234
+ ...this.data.features.wiki.chroma,
235
+ collection: { ...this.data.features.wiki.chroma.collection },
236
+ },
237
+ },
191
238
  };
192
239
  }
193
240
 
@@ -195,6 +242,10 @@ export class Config {
195
242
  return this.features.ltm;
196
243
  }
197
244
 
245
+ get wiki(): WikiConfig {
246
+ return this.features.wiki;
247
+ }
248
+
198
249
  private readJson(): unknown {
199
250
  if (!existsSync(this.path)) {
200
251
  return defaultConfigData();