chief-helm 0.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/Readme.md +220 -0
- package/dist/commands/config.d.ts +26 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +111 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/inputs.d.ts +21 -0
- package/dist/commands/inputs.d.ts.map +1 -0
- package/dist/commands/inputs.js +158 -0
- package/dist/commands/inputs.js.map +1 -0
- package/dist/commands/push.d.ts +21 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +51 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/secrets.d.ts +21 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +110 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/setup.d.ts +24 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +421 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +20 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +184 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +19 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +47 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/core/config.d.ts +74 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +182 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/git.d.ts +73 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +188 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/inputs.d.ts +40 -0
- package/dist/core/inputs.d.ts.map +1 -0
- package/dist/core/inputs.js +360 -0
- package/dist/core/inputs.js.map +1 -0
- package/dist/core/repo.d.ts +71 -0
- package/dist/core/repo.d.ts.map +1 -0
- package/dist/core/repo.js +152 -0
- package/dist/core/repo.js.map +1 -0
- package/dist/core/secrets.d.ts +79 -0
- package/dist/core/secrets.d.ts.map +1 -0
- package/dist/core/secrets.js +168 -0
- package/dist/core/secrets.js.map +1 -0
- package/dist/core/state.d.ts +46 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +119 -0
- package/dist/core/state.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +210 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/components/Header.d.ts +32 -0
- package/dist/ui/components/Header.d.ts.map +1 -0
- package/dist/ui/components/Header.js +15 -0
- package/dist/ui/components/Header.js.map +1 -0
- package/dist/ui/components/Panel.d.ts +30 -0
- package/dist/ui/components/Panel.d.ts.map +1 -0
- package/dist/ui/components/Panel.js +15 -0
- package/dist/ui/components/Panel.js.map +1 -0
- package/dist/ui/components/StatusRow.d.ts +39 -0
- package/dist/ui/components/StatusRow.d.ts.map +1 -0
- package/dist/ui/components/StatusRow.js +27 -0
- package/dist/ui/components/StatusRow.js.map +1 -0
- package/dist/ui/theme.d.ts +60 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +60 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/utils/errors.d.ts +50 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +63 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/format.d.ts +66 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +107 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/logger.d.ts +35 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +71 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Local machine config and instance repo management.
|
|
3
|
+
*
|
|
4
|
+
* Owns the single conf store instance that backs ~/.chief/config.json,
|
|
5
|
+
* provides typed accessors for LocalConfig, and validates that the
|
|
6
|
+
* personal instance repo contains the expected CHIEF directory structure
|
|
7
|
+
* before any command reads from or writes to it.
|
|
8
|
+
*
|
|
9
|
+
* Every command that operates against the instance repo calls
|
|
10
|
+
* requireSetup() first so that missing or incomplete configuration
|
|
11
|
+
* produces a clear, actionable error rather than a crash.
|
|
12
|
+
*/
|
|
13
|
+
import Conf from "conf";
|
|
14
|
+
import type { LocalConfig } from "../types/index.js";
|
|
15
|
+
/**
|
|
16
|
+
* Singleton conf store backed by ~/.chief/config.json.
|
|
17
|
+
*
|
|
18
|
+
* The cwd override places the file at ~/.chief/ rather than the
|
|
19
|
+
* platform default config directory, matching the path specified
|
|
20
|
+
* in the HELM PRD.
|
|
21
|
+
*
|
|
22
|
+
* Access this store through the typed helpers below rather than
|
|
23
|
+
* calling store.get/set directly in command files.
|
|
24
|
+
*/
|
|
25
|
+
export declare const localStore: Conf<LocalConfig>;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the full local config object.
|
|
28
|
+
* The conf store guarantees all fields are present due to the defaults
|
|
29
|
+
* provided at initialisation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getLocalConfig(): LocalConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Applies a partial update to the local config.
|
|
34
|
+
* Only the provided keys are modified; all others are left unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export declare function updateLocalConfig(updates: Partial<LocalConfig>): void;
|
|
37
|
+
/**
|
|
38
|
+
* Asserts that helm setup has been completed successfully.
|
|
39
|
+
*
|
|
40
|
+
* Must be called at the top of every command action except `setup`
|
|
41
|
+
* itself and the root `--version` flag. Throws HelmError if setup
|
|
42
|
+
* is incomplete, directing the user to run helm setup.
|
|
43
|
+
*
|
|
44
|
+
* @throws {HelmError} When setup_complete is false or the repo path is empty.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requireSetup(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Validates that the directory at repoPath is a properly initialised
|
|
49
|
+
* CHIEF instance repo by checking for required subdirectories and
|
|
50
|
+
* config files. Does not validate YAML content — only structure.
|
|
51
|
+
*
|
|
52
|
+
* @param repoPath - Absolute path to the candidate repo directory.
|
|
53
|
+
* @returns True if all required structure is present.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isValidRepoStructure(repoPath: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Returns the absolute path to a file or directory inside the instance
|
|
58
|
+
* repo, joining the stored repo root with the given relative segments.
|
|
59
|
+
*
|
|
60
|
+
* Requires setup to be complete. Use requireSetup() before calling this
|
|
61
|
+
* in command handlers.
|
|
62
|
+
*
|
|
63
|
+
* @param segments - Path segments relative to the repo root.
|
|
64
|
+
*/
|
|
65
|
+
export declare function repoPath(...segments: string[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* Expands a ~ prefix in a path string to the current user's home directory.
|
|
68
|
+
* Used when accepting repo paths from inquirer prompts.
|
|
69
|
+
*/
|
|
70
|
+
export declare function expandHomePath(inputPath: string): string;
|
|
71
|
+
//# sourceMappingURL=repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAgDrD;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,mBAIrB,CAAC;AAIH;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAE5C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAIrE;AAID;;;;;;;;GAQG;AACH,wBAAgB,YAAY,IAAI,IAAI,CASnC;AAID;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAY9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAGtD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKxD"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Local machine config and instance repo management.
|
|
3
|
+
*
|
|
4
|
+
* Owns the single conf store instance that backs ~/.chief/config.json,
|
|
5
|
+
* provides typed accessors for LocalConfig, and validates that the
|
|
6
|
+
* personal instance repo contains the expected CHIEF directory structure
|
|
7
|
+
* before any command reads from or writes to it.
|
|
8
|
+
*
|
|
9
|
+
* Every command that operates against the instance repo calls
|
|
10
|
+
* requireSetup() first so that missing or incomplete configuration
|
|
11
|
+
* produces a clear, actionable error rather than a crash.
|
|
12
|
+
*/
|
|
13
|
+
import os from "os";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import Conf from "conf";
|
|
17
|
+
import { HelmError } from "../utils/errors.js";
|
|
18
|
+
// ─── Directory / File Constants ───────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Top-level directories that must exist in a valid CHIEF instance repo.
|
|
21
|
+
* Validated during helm setup Step 1 and requireSetup().
|
|
22
|
+
*/
|
|
23
|
+
const REQUIRED_REPO_DIRS = [
|
|
24
|
+
"config",
|
|
25
|
+
"users",
|
|
26
|
+
"instructions",
|
|
27
|
+
"templates",
|
|
28
|
+
"context",
|
|
29
|
+
"outputs",
|
|
30
|
+
"state",
|
|
31
|
+
"logs",
|
|
32
|
+
"knowledge",
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Config files that must exist under /config/ in the instance repo.
|
|
36
|
+
* If these are absent the repo is not a properly initialised CHIEF instance.
|
|
37
|
+
*/
|
|
38
|
+
const REQUIRED_CONFIG_FILES = [
|
|
39
|
+
"inputs.yaml",
|
|
40
|
+
"agents.yaml",
|
|
41
|
+
"flows.yaml",
|
|
42
|
+
"triggers.yaml",
|
|
43
|
+
];
|
|
44
|
+
// ─── Conf Store ───────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Default values written to ~/.chief/config.json on first access.
|
|
47
|
+
* All fields must be present so TypeScript can infer the full LocalConfig
|
|
48
|
+
* shape without optional markers on the conf store generic.
|
|
49
|
+
*/
|
|
50
|
+
const LOCAL_CONFIG_DEFAULTS = {
|
|
51
|
+
instance_repo_path: "",
|
|
52
|
+
active_user: "",
|
|
53
|
+
editor: process.env["EDITOR"] ?? "code",
|
|
54
|
+
setup_complete: false,
|
|
55
|
+
last_sync: "",
|
|
56
|
+
secrets_manifest: [],
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Singleton conf store backed by ~/.chief/config.json.
|
|
60
|
+
*
|
|
61
|
+
* The cwd override places the file at ~/.chief/ rather than the
|
|
62
|
+
* platform default config directory, matching the path specified
|
|
63
|
+
* in the HELM PRD.
|
|
64
|
+
*
|
|
65
|
+
* Access this store through the typed helpers below rather than
|
|
66
|
+
* calling store.get/set directly in command files.
|
|
67
|
+
*/
|
|
68
|
+
export const localStore = new Conf({
|
|
69
|
+
projectName: "chief",
|
|
70
|
+
cwd: path.join(os.homedir(), ".chief"),
|
|
71
|
+
defaults: LOCAL_CONFIG_DEFAULTS,
|
|
72
|
+
});
|
|
73
|
+
// ─── Typed Accessors ──────────────────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Returns the full local config object.
|
|
76
|
+
* The conf store guarantees all fields are present due to the defaults
|
|
77
|
+
* provided at initialisation.
|
|
78
|
+
*/
|
|
79
|
+
export function getLocalConfig() {
|
|
80
|
+
return localStore.store;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Applies a partial update to the local config.
|
|
84
|
+
* Only the provided keys are modified; all others are left unchanged.
|
|
85
|
+
*/
|
|
86
|
+
export function updateLocalConfig(updates) {
|
|
87
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
88
|
+
localStore.set(key, value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ─── Setup Guard ──────────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Asserts that helm setup has been completed successfully.
|
|
94
|
+
*
|
|
95
|
+
* Must be called at the top of every command action except `setup`
|
|
96
|
+
* itself and the root `--version` flag. Throws HelmError if setup
|
|
97
|
+
* is incomplete, directing the user to run helm setup.
|
|
98
|
+
*
|
|
99
|
+
* @throws {HelmError} When setup_complete is false or the repo path is empty.
|
|
100
|
+
*/
|
|
101
|
+
export function requireSetup() {
|
|
102
|
+
const config = getLocalConfig();
|
|
103
|
+
if (!config.setup_complete || config.instance_repo_path === "") {
|
|
104
|
+
throw new HelmError("HELM has not been set up on this machine.", "Run: helm setup");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ─── Repo Validation ──────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Validates that the directory at repoPath is a properly initialised
|
|
110
|
+
* CHIEF instance repo by checking for required subdirectories and
|
|
111
|
+
* config files. Does not validate YAML content — only structure.
|
|
112
|
+
*
|
|
113
|
+
* @param repoPath - Absolute path to the candidate repo directory.
|
|
114
|
+
* @returns True if all required structure is present.
|
|
115
|
+
*/
|
|
116
|
+
export function isValidRepoStructure(repoPath) {
|
|
117
|
+
if (!fs.existsSync(repoPath))
|
|
118
|
+
return false;
|
|
119
|
+
for (const dir of REQUIRED_REPO_DIRS) {
|
|
120
|
+
if (!fs.existsSync(path.join(repoPath, dir)))
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
for (const file of REQUIRED_CONFIG_FILES) {
|
|
124
|
+
if (!fs.existsSync(path.join(repoPath, "config", file)))
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Returns the absolute path to a file or directory inside the instance
|
|
131
|
+
* repo, joining the stored repo root with the given relative segments.
|
|
132
|
+
*
|
|
133
|
+
* Requires setup to be complete. Use requireSetup() before calling this
|
|
134
|
+
* in command handlers.
|
|
135
|
+
*
|
|
136
|
+
* @param segments - Path segments relative to the repo root.
|
|
137
|
+
*/
|
|
138
|
+
export function repoPath(...segments) {
|
|
139
|
+
const root = localStore.get("instance_repo_path");
|
|
140
|
+
return path.join(root, ...segments);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Expands a ~ prefix in a path string to the current user's home directory.
|
|
144
|
+
* Used when accepting repo paths from inquirer prompts.
|
|
145
|
+
*/
|
|
146
|
+
export function expandHomePath(inputPath) {
|
|
147
|
+
if (inputPath.startsWith("~/")) {
|
|
148
|
+
return path.join(os.homedir(), inputPath.slice(2));
|
|
149
|
+
}
|
|
150
|
+
return inputPath;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/core/repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file OS keychain integration for HELM.
|
|
3
|
+
*
|
|
4
|
+
* All secret storage and retrieval is routed through this module.
|
|
5
|
+
* Secret values are handled as transiently as possible:
|
|
6
|
+
*
|
|
7
|
+
* - Values are never written to any file, log, or error message.
|
|
8
|
+
* - The only moment a value appears in memory outside this module is
|
|
9
|
+
* during the masked inquirer prompt in the setup wizard and
|
|
10
|
+
* helm secrets set — immediately passed to setSecret() and discarded.
|
|
11
|
+
* - listSecretKeys() returns key names only. Values are never returned
|
|
12
|
+
* to callers that don't explicitly call getSecret().
|
|
13
|
+
*
|
|
14
|
+
* A manifest of key names (not values) is stored in the local conf store
|
|
15
|
+
* at ~/.chief/config.json because the OS keychain API does not provide
|
|
16
|
+
* an enumeration method. The manifest is always kept in sync with the
|
|
17
|
+
* keychain by setSecret() and deleteSecret().
|
|
18
|
+
*
|
|
19
|
+
* Keychain service name: "chief"
|
|
20
|
+
* Account format: "[username]/[KEY_NAME]"
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Stores a secret value in the OS keychain and records the key name
|
|
24
|
+
* in the local manifest.
|
|
25
|
+
*
|
|
26
|
+
* The value is accepted as a parameter and immediately forwarded to
|
|
27
|
+
* keytar. Callers must not retain the value after this call returns.
|
|
28
|
+
*
|
|
29
|
+
* @param username - Active username (used as the account prefix).
|
|
30
|
+
* @param key - Secret key name, e.g. "GMAIL_CLIENT_ID".
|
|
31
|
+
* @param value - The secret value. Never logged.
|
|
32
|
+
* @throws {HelmError} If the keychain write fails.
|
|
33
|
+
*/
|
|
34
|
+
export declare function setSecret(username: string, key: string, value: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Retrieves a secret value from the OS keychain.
|
|
37
|
+
*
|
|
38
|
+
* Returns null if the key does not exist. Callers must handle the null
|
|
39
|
+
* case by throwing an appropriate HelmError naming the missing key and
|
|
40
|
+
* directing the user to run helm secrets set.
|
|
41
|
+
*
|
|
42
|
+
* The returned value must not be logged, printed, or included in any
|
|
43
|
+
* user-facing string.
|
|
44
|
+
*
|
|
45
|
+
* @param username - Active username.
|
|
46
|
+
* @param key - Secret key name to retrieve.
|
|
47
|
+
* @throws {HelmError} If the keychain read fails unexpectedly.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getSecret(username: string, key: string): Promise<string | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Returns the list of secret key names stored for the given username.
|
|
52
|
+
* Values are never returned — only names.
|
|
53
|
+
*
|
|
54
|
+
* Reads from the local manifest rather than querying the keychain
|
|
55
|
+
* directly, because the keychain API does not support enumeration.
|
|
56
|
+
*
|
|
57
|
+
* @param username - Active username to filter by.
|
|
58
|
+
*/
|
|
59
|
+
export declare function listSecretKeys(username: string): string[];
|
|
60
|
+
/**
|
|
61
|
+
* Returns true if a secret with the given key name exists in the
|
|
62
|
+
* OS keychain for the given username.
|
|
63
|
+
*
|
|
64
|
+
* @param username - Active username.
|
|
65
|
+
* @param key - Secret key name to check.
|
|
66
|
+
* @throws {HelmError} If the keychain read fails.
|
|
67
|
+
*/
|
|
68
|
+
export declare function verifySecret(username: string, key: string): Promise<boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Removes a secret from the OS keychain and removes its name from the
|
|
71
|
+
* local manifest.
|
|
72
|
+
*
|
|
73
|
+
* @param username - Active username.
|
|
74
|
+
* @param key - Secret key name to delete.
|
|
75
|
+
* @returns True if the key existed and was deleted; false if it did not exist.
|
|
76
|
+
* @throws {HelmError} If the keychain deletion fails.
|
|
77
|
+
*/
|
|
78
|
+
export declare function deleteSecret(username: string, key: string): Promise<boolean>;
|
|
79
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/core/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAiEH;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAQzD;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,OAAO,CAAC,CAuBlB"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file OS keychain integration for HELM.
|
|
3
|
+
*
|
|
4
|
+
* All secret storage and retrieval is routed through this module.
|
|
5
|
+
* Secret values are handled as transiently as possible:
|
|
6
|
+
*
|
|
7
|
+
* - Values are never written to any file, log, or error message.
|
|
8
|
+
* - The only moment a value appears in memory outside this module is
|
|
9
|
+
* during the masked inquirer prompt in the setup wizard and
|
|
10
|
+
* helm secrets set — immediately passed to setSecret() and discarded.
|
|
11
|
+
* - listSecretKeys() returns key names only. Values are never returned
|
|
12
|
+
* to callers that don't explicitly call getSecret().
|
|
13
|
+
*
|
|
14
|
+
* A manifest of key names (not values) is stored in the local conf store
|
|
15
|
+
* at ~/.chief/config.json because the OS keychain API does not provide
|
|
16
|
+
* an enumeration method. The manifest is always kept in sync with the
|
|
17
|
+
* keychain by setSecret() and deleteSecret().
|
|
18
|
+
*
|
|
19
|
+
* Keychain service name: "chief"
|
|
20
|
+
* Account format: "[username]/[KEY_NAME]"
|
|
21
|
+
*/
|
|
22
|
+
import keytar from "keytar";
|
|
23
|
+
import chalk from "chalk";
|
|
24
|
+
import { localStore } from "./repo.js";
|
|
25
|
+
import { HelmError } from "../utils/errors.js";
|
|
26
|
+
/** Service name used for all entries in the OS keychain. */
|
|
27
|
+
const KEYCHAIN_SERVICE = "chief";
|
|
28
|
+
// ─── Keychain Heads-Up ────────────────────────────────────────────────────────
|
|
29
|
+
/**
|
|
30
|
+
* Module-level flag ensuring the macOS keychain access prompt
|
|
31
|
+
* explanation is printed only once per process invocation.
|
|
32
|
+
*/
|
|
33
|
+
let keychainHeadsUpShown = false;
|
|
34
|
+
/**
|
|
35
|
+
* Prints a one-line notice before the first keychain operation in a
|
|
36
|
+
* process. macOS will show a native permission dialog the first time
|
|
37
|
+
* an app accesses the keychain; without this notice users may be
|
|
38
|
+
* confused or alarmed by the unexpected OS popup.
|
|
39
|
+
*/
|
|
40
|
+
function ensureKeychainHeadsUp() {
|
|
41
|
+
if (!keychainHeadsUpShown) {
|
|
42
|
+
process.stdout.write(chalk.dim(" ℹ Your OS may prompt you to allow keychain access — this is expected.\n"));
|
|
43
|
+
keychainHeadsUpShown = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ─── Manifest Helpers ─────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Adds a fully-qualified key name ("[username]/[KEY_NAME]") to the
|
|
49
|
+
* manifest in local config if it is not already present.
|
|
50
|
+
*/
|
|
51
|
+
function addToManifest(username, key) {
|
|
52
|
+
const fullKey = `${username}/${key}`;
|
|
53
|
+
const manifest = localStore.get("secrets_manifest") ?? [];
|
|
54
|
+
if (!manifest.includes(fullKey)) {
|
|
55
|
+
localStore.set("secrets_manifest", [...manifest, fullKey]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Removes a fully-qualified key name from the manifest in local config.
|
|
60
|
+
* No-op if the key is not present.
|
|
61
|
+
*/
|
|
62
|
+
function removeFromManifest(username, key) {
|
|
63
|
+
const fullKey = `${username}/${key}`;
|
|
64
|
+
const manifest = localStore.get("secrets_manifest") ?? [];
|
|
65
|
+
localStore.set("secrets_manifest", manifest.filter((k) => k !== fullKey));
|
|
66
|
+
}
|
|
67
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Stores a secret value in the OS keychain and records the key name
|
|
70
|
+
* in the local manifest.
|
|
71
|
+
*
|
|
72
|
+
* The value is accepted as a parameter and immediately forwarded to
|
|
73
|
+
* keytar. Callers must not retain the value after this call returns.
|
|
74
|
+
*
|
|
75
|
+
* @param username - Active username (used as the account prefix).
|
|
76
|
+
* @param key - Secret key name, e.g. "GMAIL_CLIENT_ID".
|
|
77
|
+
* @param value - The secret value. Never logged.
|
|
78
|
+
* @throws {HelmError} If the keychain write fails.
|
|
79
|
+
*/
|
|
80
|
+
export async function setSecret(username, key, value) {
|
|
81
|
+
ensureKeychainHeadsUp();
|
|
82
|
+
try {
|
|
83
|
+
await keytar.setPassword(KEYCHAIN_SERVICE, `${username}/${key}`, value);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
87
|
+
throw new HelmError(`Failed to store secret "${key}" in the OS keychain: ${detail}`, "Ensure your OS keychain is accessible and try again.");
|
|
88
|
+
}
|
|
89
|
+
addToManifest(username, key);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Retrieves a secret value from the OS keychain.
|
|
93
|
+
*
|
|
94
|
+
* Returns null if the key does not exist. Callers must handle the null
|
|
95
|
+
* case by throwing an appropriate HelmError naming the missing key and
|
|
96
|
+
* directing the user to run helm secrets set.
|
|
97
|
+
*
|
|
98
|
+
* The returned value must not be logged, printed, or included in any
|
|
99
|
+
* user-facing string.
|
|
100
|
+
*
|
|
101
|
+
* @param username - Active username.
|
|
102
|
+
* @param key - Secret key name to retrieve.
|
|
103
|
+
* @throws {HelmError} If the keychain read fails unexpectedly.
|
|
104
|
+
*/
|
|
105
|
+
export async function getSecret(username, key) {
|
|
106
|
+
ensureKeychainHeadsUp();
|
|
107
|
+
try {
|
|
108
|
+
return await keytar.getPassword(KEYCHAIN_SERVICE, `${username}/${key}`);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
112
|
+
throw new HelmError(`Failed to read secret "${key}" from the OS keychain: ${detail}`, "Ensure your OS keychain is accessible and try again.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Returns the list of secret key names stored for the given username.
|
|
117
|
+
* Values are never returned — only names.
|
|
118
|
+
*
|
|
119
|
+
* Reads from the local manifest rather than querying the keychain
|
|
120
|
+
* directly, because the keychain API does not support enumeration.
|
|
121
|
+
*
|
|
122
|
+
* @param username - Active username to filter by.
|
|
123
|
+
*/
|
|
124
|
+
export function listSecretKeys(username) {
|
|
125
|
+
const manifest = localStore.get("secrets_manifest") ?? [];
|
|
126
|
+
const prefix = `${username}/`;
|
|
127
|
+
return manifest
|
|
128
|
+
.filter((k) => k.startsWith(prefix))
|
|
129
|
+
.map((k) => k.slice(prefix.length))
|
|
130
|
+
.sort();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns true if a secret with the given key name exists in the
|
|
134
|
+
* OS keychain for the given username.
|
|
135
|
+
*
|
|
136
|
+
* @param username - Active username.
|
|
137
|
+
* @param key - Secret key name to check.
|
|
138
|
+
* @throws {HelmError} If the keychain read fails.
|
|
139
|
+
*/
|
|
140
|
+
export async function verifySecret(username, key) {
|
|
141
|
+
const value = await getSecret(username, key);
|
|
142
|
+
return value !== null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Removes a secret from the OS keychain and removes its name from the
|
|
146
|
+
* local manifest.
|
|
147
|
+
*
|
|
148
|
+
* @param username - Active username.
|
|
149
|
+
* @param key - Secret key name to delete.
|
|
150
|
+
* @returns True if the key existed and was deleted; false if it did not exist.
|
|
151
|
+
* @throws {HelmError} If the keychain deletion fails.
|
|
152
|
+
*/
|
|
153
|
+
export async function deleteSecret(username, key) {
|
|
154
|
+
ensureKeychainHeadsUp();
|
|
155
|
+
let deleted;
|
|
156
|
+
try {
|
|
157
|
+
deleted = await keytar.deletePassword(KEYCHAIN_SERVICE, `${username}/${key}`);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
161
|
+
throw new HelmError(`Failed to delete secret "${key}" from the OS keychain: ${detail}`, "Ensure your OS keychain is accessible and try again.");
|
|
162
|
+
}
|
|
163
|
+
if (deleted) {
|
|
164
|
+
removeFromManifest(username, key);
|
|
165
|
+
}
|
|
166
|
+
return deleted;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/core/secrets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file State file I/O for the instance repo.
|
|
3
|
+
*
|
|
4
|
+
* Manages the JSON state files under state/[username]/ in the personal
|
|
5
|
+
* instance repo. These files track run history, deferred tasks, and
|
|
6
|
+
* idempotency identifiers across flow executions.
|
|
7
|
+
*
|
|
8
|
+
* State files are written by HELM after flow runs and read by helm status
|
|
9
|
+
* and helm logs. They are committed to git as part of the post-run commit.
|
|
10
|
+
*/
|
|
11
|
+
import type { LastRunState } from "../types/index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Reads state/[username]/last_run.json and returns the parsed object.
|
|
14
|
+
* Returns an empty object when the file does not yet exist (first run).
|
|
15
|
+
*
|
|
16
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
17
|
+
* @param username - Active username.
|
|
18
|
+
*/
|
|
19
|
+
export declare function readLastRunState(repoRoot: string, username: string): LastRunState;
|
|
20
|
+
/**
|
|
21
|
+
* Writes a complete LastRunState to state/[username]/last_run.json.
|
|
22
|
+
*
|
|
23
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
24
|
+
* @param username - Active username.
|
|
25
|
+
* @param state - Full state object to persist.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeLastRunState(repoRoot: string, username: string, state: LastRunState): void;
|
|
28
|
+
/**
|
|
29
|
+
* Records the result of a single flow run into last_run.json.
|
|
30
|
+
* Reads the current state first and merges the new record.
|
|
31
|
+
*
|
|
32
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
33
|
+
* @param username - Active username.
|
|
34
|
+
* @param flowId - Flow identifier to update.
|
|
35
|
+
* @param status - Terminal status of the completed run.
|
|
36
|
+
*/
|
|
37
|
+
export declare function recordFlowRun(repoRoot: string, username: string, flowId: string, status: "success" | "failed"): void;
|
|
38
|
+
/**
|
|
39
|
+
* Ensures the state/[username]/ directory exists in the repo,
|
|
40
|
+
* creating it if absent. Called during helm setup.
|
|
41
|
+
*
|
|
42
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
43
|
+
* @param username - Active username.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ensureStateDir(repoRoot: string, username: string): void;
|
|
46
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAmEtD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,YAAY,CAId;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,YAAY,GAClB,IAAI,CAGN;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,GAAG,QAAQ,GAC3B,IAAI,CAMN;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAKvE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file State file I/O for the instance repo.
|
|
3
|
+
*
|
|
4
|
+
* Manages the JSON state files under state/[username]/ in the personal
|
|
5
|
+
* instance repo. These files track run history, deferred tasks, and
|
|
6
|
+
* idempotency identifiers across flow executions.
|
|
7
|
+
*
|
|
8
|
+
* State files are written by HELM after flow runs and read by helm status
|
|
9
|
+
* and helm logs. They are committed to git as part of the post-run commit.
|
|
10
|
+
*/
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { HelmError } from "../utils/errors.js";
|
|
14
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Returns the absolute path to the state directory for the given user.
|
|
17
|
+
*
|
|
18
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
19
|
+
* @param username - Active username.
|
|
20
|
+
*/
|
|
21
|
+
function stateDir(repoRoot, username) {
|
|
22
|
+
return path.join(repoRoot, "state", username);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Reads and parses a JSON state file. Returns null if the file does not
|
|
26
|
+
* exist (treated as initial state rather than an error). Throws HelmError
|
|
27
|
+
* if the file exists but cannot be parsed.
|
|
28
|
+
*
|
|
29
|
+
* @param filePath - Absolute path to the JSON file.
|
|
30
|
+
* @param label - Human-readable name for error messages.
|
|
31
|
+
*/
|
|
32
|
+
function readStateFile(filePath, label) {
|
|
33
|
+
if (!fs.existsSync(filePath))
|
|
34
|
+
return null;
|
|
35
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
41
|
+
throw new HelmError(`State file ${label} is corrupted: ${detail}`, `Delete or reset ${filePath} to clear the corrupt state.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Writes a value to a JSON state file, creating the parent directory
|
|
46
|
+
* if needed. Throws HelmError on write failure.
|
|
47
|
+
*
|
|
48
|
+
* @param filePath - Absolute path to write.
|
|
49
|
+
* @param data - Value to serialise.
|
|
50
|
+
* @param label - Human-readable name for error messages.
|
|
51
|
+
*/
|
|
52
|
+
function writeStateFile(filePath, data, label) {
|
|
53
|
+
const dir = path.dirname(filePath);
|
|
54
|
+
if (!fs.existsSync(dir)) {
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
62
|
+
throw new HelmError(`Failed to write state file ${label}: ${detail}`, `Check file permissions for ${filePath}.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ─── Last Run State ───────────────────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Reads state/[username]/last_run.json and returns the parsed object.
|
|
68
|
+
* Returns an empty object when the file does not yet exist (first run).
|
|
69
|
+
*
|
|
70
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
71
|
+
* @param username - Active username.
|
|
72
|
+
*/
|
|
73
|
+
export function readLastRunState(repoRoot, username) {
|
|
74
|
+
const filePath = path.join(stateDir(repoRoot, username), "last_run.json");
|
|
75
|
+
const parsed = readStateFile(filePath, "last_run.json");
|
|
76
|
+
return parsed ?? {};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Writes a complete LastRunState to state/[username]/last_run.json.
|
|
80
|
+
*
|
|
81
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
82
|
+
* @param username - Active username.
|
|
83
|
+
* @param state - Full state object to persist.
|
|
84
|
+
*/
|
|
85
|
+
export function writeLastRunState(repoRoot, username, state) {
|
|
86
|
+
const filePath = path.join(stateDir(repoRoot, username), "last_run.json");
|
|
87
|
+
writeStateFile(filePath, state, "last_run.json");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Records the result of a single flow run into last_run.json.
|
|
91
|
+
* Reads the current state first and merges the new record.
|
|
92
|
+
*
|
|
93
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
94
|
+
* @param username - Active username.
|
|
95
|
+
* @param flowId - Flow identifier to update.
|
|
96
|
+
* @param status - Terminal status of the completed run.
|
|
97
|
+
*/
|
|
98
|
+
export function recordFlowRun(repoRoot, username, flowId, status) {
|
|
99
|
+
const current = readLastRunState(repoRoot, username);
|
|
100
|
+
writeLastRunState(repoRoot, username, {
|
|
101
|
+
...current,
|
|
102
|
+
[flowId]: { timestamp: new Date().toISOString(), status },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// ─── User State Directory Bootstrap ──────────────────────────────────────────
|
|
106
|
+
/**
|
|
107
|
+
* Ensures the state/[username]/ directory exists in the repo,
|
|
108
|
+
* creating it if absent. Called during helm setup.
|
|
109
|
+
*
|
|
110
|
+
* @param repoRoot - Absolute path to the instance repo root.
|
|
111
|
+
* @param username - Active username.
|
|
112
|
+
*/
|
|
113
|
+
export function ensureStateDir(repoRoot, username) {
|
|
114
|
+
const dir = stateDir(repoRoot, username);
|
|
115
|
+
if (!fs.existsSync(dir)) {
|
|
116
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"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"}
|