@jamaynor/hal-config 1.0.2 → 1.1.1
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/index.d.ts +4 -0
- package/lib/config.d.ts +111 -0
- package/lib/config.js +257 -18
- package/lib/types.d.ts +218 -0
- package/package.json +23 -4
- package/security/index.d.ts +33 -0
- package/test-utils.d.ts +30 -0
- package/CLAUDE.md +0 -84
- package/publish.ps1 +0 -30
- package/test/config-io.test.js +0 -326
- package/test/security.test.js +0 -488
- package/test/test-utils.test.js +0 -360
- package/test/test.js +0 -586
package/index.d.ts
ADDED
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HalCommunicationAccountEntry,
|
|
3
|
+
HalRuntimeContext,
|
|
4
|
+
HalVaultAreas,
|
|
5
|
+
} from './types.js';
|
|
6
|
+
|
|
7
|
+
export interface ResolveWorkspaceOptions {
|
|
8
|
+
workspace?: string;
|
|
9
|
+
envPrefix?: string;
|
|
10
|
+
fallback?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RuntimeContextOptions {
|
|
14
|
+
callingAgentRuntimePath?: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SkillRuntimeData {
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SkillInstallData {
|
|
22
|
+
binary?: string | string[];
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SkillEntry {
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
homeAgent?: string | null;
|
|
29
|
+
runtime?: SkillRuntimeData;
|
|
30
|
+
install?: SkillInstallData;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface HalSystemConfig {
|
|
35
|
+
skills?: Record<string, SkillEntry>;
|
|
36
|
+
'hal-obsidian-vaults'?: Array<Record<string, unknown>>;
|
|
37
|
+
'hal-communication-accounts'?: HalCommunicationAccountEntry[];
|
|
38
|
+
'hal-timezone'?: string;
|
|
39
|
+
'hal-work-hours'?: { start: string; end: string } | null;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface VaultDirectoryPaths {
|
|
44
|
+
vaultPath: string | null;
|
|
45
|
+
projectsPath: string | null;
|
|
46
|
+
dailyNotesPath: string | null;
|
|
47
|
+
emailPath: string | null;
|
|
48
|
+
meetingsPath: string | null;
|
|
49
|
+
peoplePath: string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface HalSkillConfigApi {
|
|
53
|
+
normalizeSkillName(skillScope: string): string;
|
|
54
|
+
adminRoot(skillName: string): string;
|
|
55
|
+
configDir(skillName: string): string;
|
|
56
|
+
logDir(skillName: string): string;
|
|
57
|
+
configPath(skillName: string): string;
|
|
58
|
+
read(skillScope: string): Record<string, unknown>;
|
|
59
|
+
getSetting(skillScope: string, key: string): unknown;
|
|
60
|
+
write(skillScope: string, data: Record<string, unknown>): void;
|
|
61
|
+
merge(skillScope: string, patch: Record<string, unknown>): Record<string, unknown>;
|
|
62
|
+
setSetting(skillScope: string, key: string, value: unknown): Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare function load(configDir?: string): HalSystemConfig;
|
|
66
|
+
export declare function reload(): HalSystemConfig;
|
|
67
|
+
export declare function raw(): HalSystemConfig;
|
|
68
|
+
export declare function configDir(): string;
|
|
69
|
+
|
|
70
|
+
export declare function openclawSharedWorkspaceRoot(): string;
|
|
71
|
+
export declare function openclawConfigFilePath(): string;
|
|
72
|
+
export declare function halSystemConfigDir(): string;
|
|
73
|
+
export declare function halSystemConfigFilePath(): string;
|
|
74
|
+
export declare function halSkillAdminRoot(skillName: string): string;
|
|
75
|
+
export declare function halSkillConfigDir(skillName: string): string;
|
|
76
|
+
export declare function halSkillLogDir(skillName: string): string;
|
|
77
|
+
|
|
78
|
+
export declare const halSkillConfig: HalSkillConfigApi;
|
|
79
|
+
export declare function skillConfigPath(name: string): string;
|
|
80
|
+
export declare function skillConfigDir(name: string): string;
|
|
81
|
+
|
|
82
|
+
export declare function vaults(): Array<Record<string, unknown>>;
|
|
83
|
+
export declare function vaultFolders(vaultName: string): HalVaultAreas;
|
|
84
|
+
export declare function vaultDirectoryPaths(vaultName: string): VaultDirectoryPaths;
|
|
85
|
+
export declare function accounts(provider?: string): HalCommunicationAccountEntry[];
|
|
86
|
+
export declare function timezone(): string | null;
|
|
87
|
+
export declare function workHours(): { start: string; end: string } | null;
|
|
88
|
+
|
|
89
|
+
export declare function getSetting(scopeOrKey: string, key?: string): unknown;
|
|
90
|
+
export declare const GetSetting: typeof getSetting;
|
|
91
|
+
|
|
92
|
+
export declare function skill(name: string): SkillEntry | null;
|
|
93
|
+
export declare function isEnabled(name: string): boolean;
|
|
94
|
+
export declare function skillWorkspace(name: string): string | null;
|
|
95
|
+
export declare function skillBinaries(name: string): string[];
|
|
96
|
+
export declare function homeAgent(name: string): string | null;
|
|
97
|
+
export declare function isInstalled(name: string): boolean;
|
|
98
|
+
export declare function isInstalledAsync(name: string): Promise<boolean>;
|
|
99
|
+
|
|
100
|
+
export declare function getRuntimeContext(opts?: RuntimeContextOptions): HalRuntimeContext | { general: HalRuntimeContext['general'] };
|
|
101
|
+
|
|
102
|
+
export declare function register(name: string, runtimeData: Record<string, unknown>): void;
|
|
103
|
+
export declare function upsertCommunicationAccount(account: HalCommunicationAccountEntry): void;
|
|
104
|
+
export declare function deleteCommunicationAccount(label: string): boolean;
|
|
105
|
+
export declare function writeSkillConfig(name: string, data: Record<string, unknown>): void;
|
|
106
|
+
export declare function writeSharedSettings(settings: Record<string, unknown>): void;
|
|
107
|
+
|
|
108
|
+
export declare function resolveWorkspace(opts?: ResolveWorkspaceOptions): string;
|
|
109
|
+
export declare function getSkillConfig(skillName: string, workspace: string): Record<string, unknown>;
|
|
110
|
+
export declare const loadSkillConfig: typeof getSkillConfig;
|
|
111
|
+
export declare function saveSkillConfig(skillName: string, workspace: string, data: Record<string, unknown>): void;
|
package/lib/config.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
|
-
// hal-
|
|
2
|
+
// hal-config/lib/config
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// SRP (Single Responsibility)
|
|
5
|
+
// - Load + cache `hal-system-config.json` and provide a single, stable API for
|
|
6
|
+
// HAL/OpenClaw skills to read shared settings (vaults, accounts, timezone,
|
|
7
|
+
// work hours), resolve workspaces, and locate per-skill config/log directories.
|
|
7
8
|
//
|
|
8
|
-
//
|
|
9
|
+
// Public Interface
|
|
10
|
+
// config
|
|
11
|
+
// ├── Loading / cache
|
|
12
|
+
// │ ├── load(configDir?)
|
|
13
|
+
// │ ├── reload()
|
|
14
|
+
// │ └── configDir()
|
|
15
|
+
// ├── Runtime context (primary read API)
|
|
16
|
+
// │ ├── getRuntimeContext(opts?) -> HalRuntimeContext
|
|
17
|
+
// │ │ opts.callingAgentRuntimePath?: string|null
|
|
18
|
+
// │ │ - If null/unresolved, returns only `general` (no `callingAgent`)
|
|
19
|
+
// │ └── HalRuntimeContext
|
|
20
|
+
// │ ├── general
|
|
21
|
+
// │ └── callingAgent? (optional)
|
|
22
|
+
// │ └── skills[*].agentHasAccess (resolved from homeAgent + caller)
|
|
23
|
+
// ├── Installation checks
|
|
24
|
+
// │ ├── isInstalled(skillName)
|
|
25
|
+
// │ └── isInstalledAsync(skillName)
|
|
26
|
+
// ├── Per-skill config I/O (skill-owned files; separate from hal-system-config.json)
|
|
27
|
+
// │ ├── getSkillConfig(skillName, workspace)
|
|
28
|
+
// │ └── saveSkillConfig(skillName, workspace, data)
|
|
29
|
+
// └── Init-wizard writes (mutate hal-system-config.json)
|
|
30
|
+
// ├── register(name, runtimeData)
|
|
31
|
+
// ├── upsertCommunicationAccount(account)
|
|
32
|
+
// ├── deleteCommunicationAccount(label)
|
|
33
|
+
// ├── writeSkillConfig(name, data)
|
|
34
|
+
// └── writeSharedSettings(settings)
|
|
9
35
|
// =============================================================================
|
|
10
36
|
|
|
11
37
|
import fs from 'node:fs';
|
|
@@ -474,6 +500,190 @@ export function isInstalledAsync(name) {
|
|
|
474
500
|
return _isBinaryOnPathAsync(bins[0]);
|
|
475
501
|
}
|
|
476
502
|
|
|
503
|
+
function _canWriteDir(dirPath) {
|
|
504
|
+
try {
|
|
505
|
+
if (!dirPath) return false;
|
|
506
|
+
fs.accessSync(dirPath, fs.constants.W_OK);
|
|
507
|
+
return true;
|
|
508
|
+
} catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function _deriveAgentIdFromRuntimePath(runtimePath) {
|
|
514
|
+
const normalized = path.resolve(String(runtimePath || '').trim());
|
|
515
|
+
if (!normalized) return null;
|
|
516
|
+
const id = path.basename(normalized);
|
|
517
|
+
return id || null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function _vaultDirectoryName(vaultPath) {
|
|
521
|
+
const p = String(vaultPath || '').trim();
|
|
522
|
+
if (!p) return '';
|
|
523
|
+
const normalized = p.replace(/[\\/]+$/, '');
|
|
524
|
+
const useWin = normalized.includes('\\') || /^[a-zA-Z]:\\/.test(normalized);
|
|
525
|
+
return useWin ? path.win32.basename(normalized) : path.posix.basename(normalized);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function _resolveMasterVault(vaultEntries) {
|
|
529
|
+
const masters = vaultEntries.filter(v => v.role === 'master');
|
|
530
|
+
if (masters.length === 0) return null;
|
|
531
|
+
if (masters.length > 1) {
|
|
532
|
+
throw new Error('ERROR: Multiple master vaults configured in hal-obsidian-vaults.');
|
|
533
|
+
}
|
|
534
|
+
const m = masters[0];
|
|
535
|
+
return {
|
|
536
|
+
name: m.name,
|
|
537
|
+
label: m.label,
|
|
538
|
+
absolutePath: m.absolutePath,
|
|
539
|
+
directoryName: m.directoryName,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Build a normalized runtime context for HAL/OpenClaw consumers.
|
|
545
|
+
*
|
|
546
|
+
* @param {{ callingAgentRuntimePath?: string|null }} [opts]
|
|
547
|
+
* @returns {{ general: object, callingAgent?: object }}
|
|
548
|
+
*/
|
|
549
|
+
export function getRuntimeContext(opts) {
|
|
550
|
+
_ensure();
|
|
551
|
+
const o = opts || {};
|
|
552
|
+
|
|
553
|
+
const openClawRoot = '/data/openclaw';
|
|
554
|
+
const openClawAgentRoot = '/data/agents';
|
|
555
|
+
const openClawConfigFile = path.join(openClawRoot, 'openclaw.json');
|
|
556
|
+
const openClawSharedAgentWorkspace = openclawSharedWorkspaceRoot();
|
|
557
|
+
const halRoot = halSystemConfigDir();
|
|
558
|
+
const halSystemConfigFile = halSystemConfigFilePath();
|
|
559
|
+
|
|
560
|
+
const rawVaults = vaults();
|
|
561
|
+
const vaultEntries = rawVaults.map(v => {
|
|
562
|
+
const name = String(v?.name || '').trim();
|
|
563
|
+
const label = String(v?.label || name).trim();
|
|
564
|
+
const absolutePath = String(v?.path || '').trim();
|
|
565
|
+
const role = String(v?.role || v?.vaultType || 'standard').trim().toLowerCase() === 'master'
|
|
566
|
+
? 'master'
|
|
567
|
+
: 'standard';
|
|
568
|
+
const folderNames = vaultFolders(name);
|
|
569
|
+
return {
|
|
570
|
+
name,
|
|
571
|
+
label,
|
|
572
|
+
role,
|
|
573
|
+
absolutePath,
|
|
574
|
+
directoryName: _vaultDirectoryName(absolutePath),
|
|
575
|
+
knownAreas: {
|
|
576
|
+
projectsPath: _joinVaultSubpath(absolutePath, folderNames.projectsFolder),
|
|
577
|
+
dailyNotesPath: _joinVaultSubpath(absolutePath, folderNames.dailyNotesFolder),
|
|
578
|
+
emailPath: _joinVaultSubpath(absolutePath, folderNames.emailFolder),
|
|
579
|
+
meetingsPath: _joinVaultSubpath(absolutePath, folderNames.meetingsFolder),
|
|
580
|
+
peoplePath: _joinVaultSubpath(absolutePath, folderNames.peopleFolder),
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const skillMap = _cache.skills || {};
|
|
586
|
+
const skills = {};
|
|
587
|
+
for (const [skillName, skillData] of Object.entries(skillMap)) {
|
|
588
|
+
const bins = (() => {
|
|
589
|
+
const bin = skillData?.install?.binary;
|
|
590
|
+
if (!bin) return [];
|
|
591
|
+
return Array.isArray(bin) ? bin : [bin];
|
|
592
|
+
})();
|
|
593
|
+
skills[skillName] = {
|
|
594
|
+
enabled: skillData?.enabled !== false,
|
|
595
|
+
homeAgent: skillData?.homeAgent || null,
|
|
596
|
+
binaries: bins,
|
|
597
|
+
configPath: skillConfigPath(skillName),
|
|
598
|
+
configDir: skillConfigDir(skillName),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const general = {
|
|
603
|
+
openClawPaths: {
|
|
604
|
+
openClawRoot,
|
|
605
|
+
openClawAgentRoot,
|
|
606
|
+
openClawConfigFile,
|
|
607
|
+
openClawSharedAgentWorkspace,
|
|
608
|
+
},
|
|
609
|
+
halPaths: {
|
|
610
|
+
halRoot,
|
|
611
|
+
halSystemConfigDir: halRoot,
|
|
612
|
+
halSystemConfigFile,
|
|
613
|
+
},
|
|
614
|
+
masterTemplates: {
|
|
615
|
+
masterUserPath: path.join(openClawSharedAgentWorkspace, 'USER.md'),
|
|
616
|
+
masterSafetyPath: path.join(openClawSharedAgentWorkspace, 'SAFETY.md'),
|
|
617
|
+
},
|
|
618
|
+
vaults: vaultEntries,
|
|
619
|
+
masterVault: _resolveMasterVault(vaultEntries),
|
|
620
|
+
communicationAccounts: _cache['hal-communication-accounts'] || [],
|
|
621
|
+
settings: {
|
|
622
|
+
timezone: _cache['hal-timezone'] || null,
|
|
623
|
+
workHours: _cache['hal-work-hours'] || null,
|
|
624
|
+
},
|
|
625
|
+
skills,
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const runtimePathRaw = (o.callingAgentRuntimePath ?? process.env.HAL_AGENT_WORKSPACE ?? '').toString().trim();
|
|
629
|
+
if (!runtimePathRaw) return { general };
|
|
630
|
+
|
|
631
|
+
const runtimePath = path.resolve(runtimePathRaw);
|
|
632
|
+
const agentId = _deriveAgentIdFromRuntimePath(runtimePath);
|
|
633
|
+
|
|
634
|
+
const skillPathsBySkill = {};
|
|
635
|
+
const accessBySkill = {};
|
|
636
|
+
const agentSkillState = {};
|
|
637
|
+
for (const [skillName, skillData] of Object.entries(skills)) {
|
|
638
|
+
const sharedConfigDir = halSkillConfigDir(skillName);
|
|
639
|
+
const sharedLogDir = halSkillLogDir(skillName);
|
|
640
|
+
const agentWorkingDir = path.join(runtimePath, 'hal', skillName);
|
|
641
|
+
skillPathsBySkill[skillName] = {
|
|
642
|
+
skillName,
|
|
643
|
+
sharedConfigDir,
|
|
644
|
+
sharedLogDir,
|
|
645
|
+
agentWorkingDir,
|
|
646
|
+
agentStateDir: path.join(agentWorkingDir, 'state'),
|
|
647
|
+
agentCacheDir: path.join(agentWorkingDir, 'cache'),
|
|
648
|
+
agentTmpDir: path.join(agentWorkingDir, 'tmp'),
|
|
649
|
+
agentSweepDir: path.join(agentWorkingDir, 'sweep'),
|
|
650
|
+
};
|
|
651
|
+
const hasAccess = !skillData.homeAgent || (agentId && skillData.homeAgent === agentId);
|
|
652
|
+
accessBySkill[skillName] = !!hasAccess;
|
|
653
|
+
agentSkillState[skillName] = { agentHasAccess: !!hasAccess };
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const callingAgent = {
|
|
657
|
+
id: agentId,
|
|
658
|
+
workspacePath: runtimePath,
|
|
659
|
+
identityFiles: {
|
|
660
|
+
soul: path.join(runtimePath, 'SOUL.md'),
|
|
661
|
+
agents: path.join(runtimePath, 'AGENTS.md'),
|
|
662
|
+
identity: path.join(runtimePath, 'IDENTITY.md'),
|
|
663
|
+
heartbeat: path.join(runtimePath, 'HEARTBEAT.md'),
|
|
664
|
+
tools: path.join(runtimePath, 'TOOLS.md'),
|
|
665
|
+
user: path.join(runtimePath, 'USER.md'),
|
|
666
|
+
safety: path.join(runtimePath, 'SAFETY.md'),
|
|
667
|
+
},
|
|
668
|
+
stores: {
|
|
669
|
+
memoryDir: path.join(runtimePath, 'memory'),
|
|
670
|
+
knowledgeDir: path.join(runtimePath, 'knowledge'),
|
|
671
|
+
skillsDir: path.join(runtimePath, 'skills'),
|
|
672
|
+
},
|
|
673
|
+
skillPaths: {
|
|
674
|
+
bySkill: skillPathsBySkill,
|
|
675
|
+
},
|
|
676
|
+
skills: agentSkillState,
|
|
677
|
+
capabilities: {
|
|
678
|
+
canWriteAgentWorkspace: _canWriteDir(runtimePath),
|
|
679
|
+
canWriteSharedWorkspace: _canWriteDir(openClawSharedAgentWorkspace),
|
|
680
|
+
isHomeAgentForSkill: accessBySkill,
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
return { general, callingAgent };
|
|
685
|
+
}
|
|
686
|
+
|
|
477
687
|
// ---------------------------------------------------------------------------
|
|
478
688
|
// Binary helpers
|
|
479
689
|
// ---------------------------------------------------------------------------
|
|
@@ -521,25 +731,51 @@ export function register(name, runtimeData) {
|
|
|
521
731
|
}
|
|
522
732
|
|
|
523
733
|
/**
|
|
524
|
-
*
|
|
525
|
-
*
|
|
526
|
-
* @param {
|
|
734
|
+
* Upsert a single communication account by label.
|
|
735
|
+
*
|
|
736
|
+
* @param {import('./types').HalCommunicationAccountEntry} account
|
|
527
737
|
*/
|
|
528
|
-
export function
|
|
738
|
+
export function upsertCommunicationAccount(account) {
|
|
529
739
|
_ensure();
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
} else {
|
|
536
|
-
existing.push(acc);
|
|
537
|
-
}
|
|
740
|
+
if (!account || typeof account !== 'object') {
|
|
741
|
+
throw new Error('ERROR: upsertCommunicationAccount requires an account object.');
|
|
742
|
+
}
|
|
743
|
+
if (!account.label || typeof account.label !== 'string') {
|
|
744
|
+
throw new Error('ERROR: upsertCommunicationAccount requires account.label (string).');
|
|
538
745
|
}
|
|
746
|
+
if (!account.provider || typeof account.provider !== 'string') {
|
|
747
|
+
throw new Error('ERROR: upsertCommunicationAccount requires account.provider (string).');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const existing = _cache['hal-communication-accounts'] || [];
|
|
751
|
+
const idx = existing.findIndex(e => e.label === account.label);
|
|
752
|
+
if (idx >= 0) existing[idx] = account;
|
|
753
|
+
else existing.push(account);
|
|
754
|
+
|
|
539
755
|
_cache['hal-communication-accounts'] = existing;
|
|
540
756
|
_flush();
|
|
541
757
|
}
|
|
542
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Delete a communication account by label.
|
|
761
|
+
*
|
|
762
|
+
* @param {string} label
|
|
763
|
+
* @returns {boolean} true when an entry was removed
|
|
764
|
+
*/
|
|
765
|
+
export function deleteCommunicationAccount(label) {
|
|
766
|
+
_ensure();
|
|
767
|
+
if (!label || typeof label !== 'string') {
|
|
768
|
+
throw new Error('ERROR: deleteCommunicationAccount requires label (string).');
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const existing = _cache['hal-communication-accounts'] || [];
|
|
772
|
+
const before = existing.length;
|
|
773
|
+
_cache['hal-communication-accounts'] = existing.filter(e => e?.label !== label);
|
|
774
|
+
const removed = _cache['hal-communication-accounts'].length < before;
|
|
775
|
+
if (removed) _flush();
|
|
776
|
+
return removed;
|
|
777
|
+
}
|
|
778
|
+
|
|
543
779
|
/**
|
|
544
780
|
* Merge key/value pairs into a skill's config block (not runtime — top level
|
|
545
781
|
* of the skill entry). Used by init wizards to store skill-specific settings.
|
|
@@ -623,7 +859,7 @@ export function resolveWorkspace(opts) {
|
|
|
623
859
|
* @param {string} workspace
|
|
624
860
|
* @returns {object}
|
|
625
861
|
*/
|
|
626
|
-
export function
|
|
862
|
+
export function getSkillConfig(skillName, workspace) {
|
|
627
863
|
const filePath = path.join(workspace, 'hal', 'config', `${skillName}.json`);
|
|
628
864
|
try {
|
|
629
865
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -633,6 +869,9 @@ export function loadSkillConfig(skillName, workspace) {
|
|
|
633
869
|
}
|
|
634
870
|
}
|
|
635
871
|
|
|
872
|
+
// Backward compatibility alias; prefer getSkillConfig().
|
|
873
|
+
export const loadSkillConfig = getSkillConfig;
|
|
874
|
+
|
|
636
875
|
/**
|
|
637
876
|
* Deep-merge source into target. Plain objects are merged recursively.
|
|
638
877
|
* Arrays are replaced atomically. Non-object values overwrite.
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* hal-config/lib/types
|
|
4
|
+
*
|
|
5
|
+
* SRP (Single Responsibility)
|
|
6
|
+
* - Define the shared TypeScript contracts for HAL/OpenClaw runtime context:
|
|
7
|
+
* global platform state plus calling-agent state used by skills at runtime.
|
|
8
|
+
*
|
|
9
|
+
* Interface Tree
|
|
10
|
+
HalRuntimeContext
|
|
11
|
+
├── general: HalGeneralState
|
|
12
|
+
│ ├── openClawPaths
|
|
13
|
+
│ │ ├── openClawRoot
|
|
14
|
+
│ │ ├── openClawAgentRoot
|
|
15
|
+
│ │ ├── openClawConfigFile
|
|
16
|
+
│ │ └── openClawSharedAgentWorkspace
|
|
17
|
+
│ ├── halPaths
|
|
18
|
+
│ │ ├── halRoot
|
|
19
|
+
│ │ ├── halSystemConfigDir
|
|
20
|
+
│ │ └── halSystemConfigFile
|
|
21
|
+
│ ├── masterTemplates
|
|
22
|
+
│ │ ├── masterUserPath (e.g. {openClawSharedAgentWorkspace}/USER.md)
|
|
23
|
+
│ │ └── masterSafetyPath (e.g. {openClawSharedAgentWorkspace}/SAFETY.md)
|
|
24
|
+
│ ├── vaults: HalVaultEntry[]
|
|
25
|
+
│ │ └── HalVaultEntry
|
|
26
|
+
│ │ ├── name
|
|
27
|
+
│ │ ├── label
|
|
28
|
+
│ │ ├── role (master|standard)
|
|
29
|
+
│ │ ├── absolutePath
|
|
30
|
+
│ │ ├── directoryName
|
|
31
|
+
│ │ └── knownAreas
|
|
32
|
+
│ │ ├── projectsPath
|
|
33
|
+
│ │ ├── dailyNotesPath
|
|
34
|
+
│ │ ├── emailPath
|
|
35
|
+
│ │ ├── meetingsPath
|
|
36
|
+
│ │ └── peoplePath
|
|
37
|
+
│ ├── masterVault
|
|
38
|
+
│ │ ├── name
|
|
39
|
+
│ │ ├── label
|
|
40
|
+
│ │ ├── absolutePath
|
|
41
|
+
│ │ └── directoryName
|
|
42
|
+
│ ├── communicationAccounts: HalCommunicationAccountEntry[]
|
|
43
|
+
│ │ └── HalCommunicationAccountEntry
|
|
44
|
+
│ │ ├── label
|
|
45
|
+
│ │ ├── provider
|
|
46
|
+
│ │ ├── email?
|
|
47
|
+
│ │ └── scopes?
|
|
48
|
+
│ ├── settings
|
|
49
|
+
│ │ ├── timezone
|
|
50
|
+
│ │ └── workHours
|
|
51
|
+
│ │ ├── start
|
|
52
|
+
│ │ └── end
|
|
53
|
+
│ └── skills: Record<string, HalSkillRuntimeEntry>
|
|
54
|
+
│ └── HalSkillRuntimeEntry
|
|
55
|
+
│ ├── enabled
|
|
56
|
+
│ ├── homeAgent
|
|
57
|
+
│ ├── binaries[]
|
|
58
|
+
│ ├── configPath
|
|
59
|
+
│ └── configDir
|
|
60
|
+
└── callingAgent: HalCallingAgentState
|
|
61
|
+
├── id
|
|
62
|
+
├── workspacePath
|
|
63
|
+
├── identityFiles
|
|
64
|
+
│ ├── soul
|
|
65
|
+
│ ├── agents
|
|
66
|
+
│ ├── identity
|
|
67
|
+
│ ├── heartbeat
|
|
68
|
+
│ ├── tools
|
|
69
|
+
│ ├── user
|
|
70
|
+
│ └── safety
|
|
71
|
+
├── stores
|
|
72
|
+
│ ├── memoryDir
|
|
73
|
+
│ ├── knowledgeDir
|
|
74
|
+
│ └── skillsDir
|
|
75
|
+
├── skillPaths
|
|
76
|
+
│ └── bySkill: Record<string, HalSkillPathSet>
|
|
77
|
+
│ └── HalSkillPathSet
|
|
78
|
+
│ ├── skillName
|
|
79
|
+
│ ├── sharedConfigDir
|
|
80
|
+
│ ├── sharedLogDir
|
|
81
|
+
│ ├── agentWorkingDir
|
|
82
|
+
│ ├── agentStateDir
|
|
83
|
+
│ ├── agentCacheDir
|
|
84
|
+
│ ├── agentTmpDir
|
|
85
|
+
│ └── agentSweepDir
|
|
86
|
+
└── capabilities
|
|
87
|
+
├── canWriteAgentWorkspace
|
|
88
|
+
├── canWriteSharedWorkspace
|
|
89
|
+
└── isHomeAgentForSkill: Record<string, boolean>
|
|
90
|
+
* =============================================================================
|
|
91
|
+
*/
|
|
92
|
+
export type VaultRole = 'master' | 'standard';
|
|
93
|
+
|
|
94
|
+
export interface HalVaultAreas {
|
|
95
|
+
projectsFolder: string;
|
|
96
|
+
dailyNotesFolder: string;
|
|
97
|
+
emailFolder: string;
|
|
98
|
+
meetingsFolder: string;
|
|
99
|
+
peopleFolder: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface HalVaultResolvedAreas {
|
|
103
|
+
projectsPath: string;
|
|
104
|
+
dailyNotesPath: string;
|
|
105
|
+
emailPath: string;
|
|
106
|
+
meetingsPath: string;
|
|
107
|
+
peoplePath: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface HalVaultEntry {
|
|
111
|
+
name: string;
|
|
112
|
+
label: string;
|
|
113
|
+
role: VaultRole;
|
|
114
|
+
absolutePath: string;
|
|
115
|
+
directoryName: string;
|
|
116
|
+
knownAreas: HalVaultResolvedAreas;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface HalMasterVaultRef {
|
|
120
|
+
name: string;
|
|
121
|
+
label: string;
|
|
122
|
+
absolutePath: string;
|
|
123
|
+
directoryName: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface HalCommunicationAccountEntry {
|
|
127
|
+
label: string;
|
|
128
|
+
provider: string;
|
|
129
|
+
email?: string;
|
|
130
|
+
scopes?: string[];
|
|
131
|
+
[key: string]: unknown;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface HalSkillRuntimeEntry {
|
|
135
|
+
enabled: boolean;
|
|
136
|
+
homeAgent: string | null;
|
|
137
|
+
binaries: string[];
|
|
138
|
+
configPath: string;
|
|
139
|
+
configDir: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface HalSkillPathSet {
|
|
143
|
+
skillName: string;
|
|
144
|
+
sharedConfigDir: string;
|
|
145
|
+
sharedLogDir: string;
|
|
146
|
+
agentWorkingDir: string;
|
|
147
|
+
agentStateDir: string;
|
|
148
|
+
agentCacheDir: string;
|
|
149
|
+
agentTmpDir: string;
|
|
150
|
+
agentSweepDir: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface HalGeneralState {
|
|
154
|
+
openClawPaths: {
|
|
155
|
+
openClawRoot: string;
|
|
156
|
+
openClawAgentRoot: string;
|
|
157
|
+
openClawConfigFile: string;
|
|
158
|
+
openClawSharedAgentWorkspace: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
halPaths: {
|
|
162
|
+
halRoot: string;
|
|
163
|
+
halSystemConfigDir: string;
|
|
164
|
+
halSystemConfigFile: string;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
masterTemplates: {
|
|
168
|
+
masterUserPath: string;
|
|
169
|
+
masterSafetyPath: string;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
vaults: HalVaultEntry[];
|
|
173
|
+
masterVault: HalMasterVaultRef | null;
|
|
174
|
+
communicationAccounts: HalCommunicationAccountEntry[];
|
|
175
|
+
|
|
176
|
+
settings: {
|
|
177
|
+
timezone: string | null;
|
|
178
|
+
workHours: { start: string; end: string } | null;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
skills: Record<string, HalSkillRuntimeEntry>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface HalCallingAgentState {
|
|
185
|
+
id: string | null;
|
|
186
|
+
workspacePath: string | null;
|
|
187
|
+
|
|
188
|
+
identityFiles: {
|
|
189
|
+
soul: string | null;
|
|
190
|
+
agents: string | null;
|
|
191
|
+
identity: string | null;
|
|
192
|
+
heartbeat: string | null;
|
|
193
|
+
tools: string | null;
|
|
194
|
+
user: string | null;
|
|
195
|
+
safety: string | null;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
stores: {
|
|
199
|
+
memoryDir: string | null;
|
|
200
|
+
knowledgeDir: string | null;
|
|
201
|
+
skillsDir: string | null;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
skillPaths: {
|
|
205
|
+
bySkill: Record<string, HalSkillPathSet>;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
capabilities: {
|
|
209
|
+
canWriteAgentWorkspace: boolean;
|
|
210
|
+
canWriteSharedWorkspace: boolean;
|
|
211
|
+
isHomeAgentForSkill: Record<string, boolean>;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface HalRuntimeContext {
|
|
216
|
+
general: HalGeneralState;
|
|
217
|
+
callingAgent: HalCallingAgentState;
|
|
218
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jamaynor/hal-config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Shared configuration loader for HAL OpenClaw skills — reads system-config.json, provides skill discovery, and runtime registration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"index.js",
|
|
10
|
+
"index.d.ts",
|
|
11
|
+
"test-utils.js",
|
|
12
|
+
"test-utils.d.ts",
|
|
13
|
+
"lib/",
|
|
14
|
+
"security/",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
7
17
|
"publishConfig": {
|
|
8
18
|
"access": "public"
|
|
9
19
|
},
|
|
10
20
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./index.d.ts",
|
|
23
|
+
"default": "./index.js"
|
|
24
|
+
},
|
|
25
|
+
"./test-utils": {
|
|
26
|
+
"types": "./test-utils.d.ts",
|
|
27
|
+
"default": "./test-utils.js"
|
|
28
|
+
},
|
|
29
|
+
"./security": {
|
|
30
|
+
"types": "./security/index.d.ts",
|
|
31
|
+
"default": "./security/index.js"
|
|
32
|
+
}
|
|
14
33
|
},
|
|
15
34
|
"scripts": {
|
|
16
35
|
"test": "node --test test/test.js test/test-utils.test.js test/config-io.test.js test/security.test.js"
|