@iiwish/agentrecord 0.0.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.
@@ -0,0 +1,81 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { spawn } from "node:child_process";
4
+
5
+ import { loadConfig } from "../core/config.mjs";
6
+
7
+ export async function runOpen({ options }) {
8
+ const config = loadConfig(options);
9
+ const htmlFile = path.join(config.resolved.profileDir, "index.html");
10
+
11
+ if (!fs.existsSync(htmlFile)) {
12
+ console.error(JSON.stringify({
13
+ ok: false,
14
+ file: htmlFile,
15
+ message: "index.html was not found. Run `agentrecord build` first, then retry `agentrecord open`."
16
+ }, null, 2));
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+
21
+ const result = await openFile(htmlFile);
22
+ if (!result.ok) {
23
+ console.error(JSON.stringify({
24
+ ok: false,
25
+ file: htmlFile,
26
+ message: result.message,
27
+ fallback: "Open the file manually in a browser."
28
+ }, null, 2));
29
+ process.exitCode = 1;
30
+ return;
31
+ }
32
+
33
+ console.log(JSON.stringify({
34
+ ok: true,
35
+ file: htmlFile,
36
+ opener: result.opener
37
+ }, null, 2));
38
+ }
39
+
40
+ async function openFile(file) {
41
+ const candidates = openerCandidates(file);
42
+ const failures = [];
43
+
44
+ for (const candidate of candidates) {
45
+ const result = await spawnAndWait(candidate.command, candidate.args);
46
+ if (result.ok) return { ok: true, opener: candidate.command };
47
+ failures.push(`${candidate.command}: ${result.message}`);
48
+ }
49
+
50
+ return {
51
+ ok: false,
52
+ message: `No supported opener succeeded. ${failures.join(" | ")}`
53
+ };
54
+ }
55
+
56
+ function openerCandidates(file) {
57
+ if (process.platform === "darwin") return [{ command: "open", args: [file] }];
58
+ if (process.platform === "win32") return [{ command: "cmd", args: ["/c", "start", "", file] }];
59
+ return [
60
+ { command: "xdg-open", args: [file] },
61
+ { command: "gio", args: ["open", file] }
62
+ ];
63
+ }
64
+
65
+ function spawnAndWait(command, args) {
66
+ return new Promise((resolve) => {
67
+ const child = spawn(command, args, {
68
+ detached: false,
69
+ stdio: "ignore"
70
+ });
71
+
72
+ child.on("error", (error) => {
73
+ resolve({ ok: false, message: error.message });
74
+ });
75
+
76
+ child.on("close", (code) => {
77
+ if (code === 0) resolve({ ok: true });
78
+ else resolve({ ok: false, message: `exited with code ${code}` });
79
+ });
80
+ });
81
+ }
@@ -0,0 +1,58 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { loadConfig } from "../core/config.mjs";
5
+
6
+ export async function runScan({ options }) {
7
+ const config = loadConfig(options);
8
+ const sources = config.resolved.codex.sessionRoots.map((sessionRoot) => {
9
+ const exists = fs.existsSync(sessionRoot);
10
+ return {
11
+ client_id: "codex",
12
+ path: sessionRoot,
13
+ found: exists,
14
+ status: exists ? "available" : "missing",
15
+ rollout_files: exists ? countRolloutFiles(sessionRoot) : 0
16
+ };
17
+ });
18
+
19
+ console.log(JSON.stringify({
20
+ ok: true,
21
+ config: {
22
+ path: config.configPath,
23
+ exists: config.exists,
24
+ owner: config.resolved.owner,
25
+ profile_dir: config.resolved.profileDir,
26
+ private_state_dir: config.resolved.privateStateDir,
27
+ locale: config.resolved.report.locale,
28
+ privacy: config.resolved.privacy
29
+ },
30
+ sources
31
+ }, null, 2));
32
+ }
33
+
34
+ function countRolloutFiles(rootDir) {
35
+ let count = 0;
36
+ const stack = [rootDir];
37
+
38
+ while (stack.length) {
39
+ const current = stack.pop();
40
+ let entries = [];
41
+ try {
42
+ entries = fs.readdirSync(current, { withFileTypes: true });
43
+ } catch {
44
+ continue;
45
+ }
46
+
47
+ for (const entry of entries) {
48
+ const target = path.join(current, entry.name);
49
+ if (entry.isDirectory()) {
50
+ stack.push(target);
51
+ } else if (entry.isFile() && /^rollout-.*\.jsonl$/.test(entry.name)) {
52
+ count += 1;
53
+ }
54
+ }
55
+ }
56
+
57
+ return count;
58
+ }
@@ -0,0 +1,365 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { loadConfig, readJsonIfExists } from "../core/config.mjs";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const repoRoot = path.resolve(__dirname, "..", "..");
9
+
10
+ const requiredArtifacts = [
11
+ "profile.json",
12
+ "evidence.jsonl",
13
+ "index.html",
14
+ "profile.md",
15
+ "redaction-report.md",
16
+ "run-report.md"
17
+ ];
18
+
19
+ const optionalPublicArtifacts = [
20
+ "agent-context.md",
21
+ "agent-context.json"
22
+ ];
23
+
24
+ export async function runValidate({ options }) {
25
+ const config = loadConfig(options);
26
+ const errors = [];
27
+ const profileDir = config.resolved.profileDir;
28
+ const stateFile = path.join(config.resolved.privateStateDir, "state.json");
29
+
30
+ const profile = readRequiredJson(path.join(profileDir, "profile.json"), errors, "required_artifact");
31
+ const evidence = readEvidenceJsonl(path.join(profileDir, "evidence.jsonl"), errors);
32
+
33
+ checkRequiredArtifacts(profileDir, errors);
34
+ if (profile) checkProfileStructure(profile, errors);
35
+ if (profile && evidence) checkEvidenceRefs(profile, evidence, config, errors);
36
+ checkLocaleParity(errors);
37
+ checkPrivateState(stateFile, profile, errors);
38
+ checkPublicArtifactPrivacy(profileDir, profile, errors);
39
+
40
+ if (errors.length) {
41
+ console.error(JSON.stringify({
42
+ ok: false,
43
+ profile_dir: profileDir,
44
+ error_count: errors.length,
45
+ errors
46
+ }, null, 2));
47
+ process.exitCode = 1;
48
+ return;
49
+ }
50
+
51
+ console.log(JSON.stringify({
52
+ ok: true,
53
+ profile_dir: profileDir,
54
+ required_artifacts: requiredArtifacts.length,
55
+ evidence_cards: evidence.length,
56
+ locale: profile.report.locale,
57
+ private_state: "present",
58
+ privacy_scan: "pass"
59
+ }, null, 2));
60
+ }
61
+
62
+ function addError(errors, file, rule, message) {
63
+ errors.push({ file, rule, message });
64
+ }
65
+
66
+ function readRequiredJson(file, errors, rule) {
67
+ if (!fs.existsSync(file)) {
68
+ addError(errors, file, rule, "File is missing.");
69
+ return null;
70
+ }
71
+ try {
72
+ return JSON.parse(fs.readFileSync(file, "utf8"));
73
+ } catch (error) {
74
+ addError(errors, file, "valid_json", error.message);
75
+ return null;
76
+ }
77
+ }
78
+
79
+ function readEvidenceJsonl(file, errors) {
80
+ if (!fs.existsSync(file)) {
81
+ addError(errors, file, "required_artifact", "File is missing.");
82
+ return null;
83
+ }
84
+
85
+ const lines = fs.readFileSync(file, "utf8").split(/\n/).filter(Boolean);
86
+ const cards = [];
87
+ lines.forEach((line, index) => {
88
+ try {
89
+ cards.push(JSON.parse(line));
90
+ } catch (error) {
91
+ addError(errors, file, "valid_evidence_jsonl", `Line ${index + 1}: ${error.message}`);
92
+ }
93
+ });
94
+ return cards;
95
+ }
96
+
97
+ function checkRequiredArtifacts(profileDir, errors) {
98
+ for (const artifact of requiredArtifacts) {
99
+ const file = path.join(profileDir, artifact);
100
+ if (!fs.existsSync(file)) {
101
+ addError(errors, file, "required_artifact", "Required public artifact is missing.");
102
+ continue;
103
+ }
104
+ if (fs.statSync(file).size < 20) {
105
+ addError(errors, file, "required_artifact_size", "Required public artifact is unexpectedly small.");
106
+ }
107
+ }
108
+
109
+ for (const forbidden of ["hiring.html", "job-agent-profile.md"]) {
110
+ const file = path.join(profileDir, forbidden);
111
+ if (fs.existsSync(file)) {
112
+ addError(errors, file, "default_audience_boundary", "Hiring or job-agent artifact must not be generated by default.");
113
+ }
114
+ }
115
+ }
116
+
117
+ function checkProfileStructure(profile, errors) {
118
+ const file = "profile.json";
119
+ const requiredKeys = [
120
+ "schema_version",
121
+ "owner",
122
+ "generated_at",
123
+ "report",
124
+ "work_identity",
125
+ "work_role_signals",
126
+ "ability_model",
127
+ "agent_ledger",
128
+ "evidence_notes",
129
+ "calibration_notes",
130
+ "privacy_boundary",
131
+ "run_metadata"
132
+ ];
133
+
134
+ for (const key of requiredKeys) {
135
+ if (!Object.hasOwn(profile, key)) addError(errors, file, "profile_required_key", `Missing required key: ${key}`);
136
+ }
137
+
138
+ if (profile.schema_version !== "agentrecord.profile.v0") addError(errors, file, "profile_schema_version", "schema_version must be agentrecord.profile.v0.");
139
+ if (!profile.owner?.id || !profile.owner?.display_name) addError(errors, file, "profile_owner", "owner.id and owner.display_name are required.");
140
+ if (!["en-US", "zh-CN"].includes(profile.report?.locale)) addError(errors, file, "profile_locale", "report.locale must be en-US or zh-CN.");
141
+ if (profile.report?.schema_language !== "en-US") addError(errors, file, "profile_schema_language", "report.schema_language must remain en-US.");
142
+ const audiences = profile.report?.audiences;
143
+ const allowedAudiences = new Set(["self", "share"]);
144
+ if (!Array.isArray(audiences)) {
145
+ addError(errors, file, "profile_audiences", "report.audiences must be exactly self and share.");
146
+ } else {
147
+ const audienceSet = new Set(audiences);
148
+ for (const audience of audiences) {
149
+ if (!allowedAudiences.has(audience)) addError(errors, file, "profile_audiences", `report.audiences contains unsupported audience: ${audience}`);
150
+ }
151
+ if (audiences.length !== 2 || audienceSet.size !== 2 || !audienceSet.has("self") || !audienceSet.has("share")) {
152
+ addError(errors, file, "profile_audiences", "report.audiences must be exactly self and share.");
153
+ }
154
+ }
155
+ for (const forbidden of ["role_fit", "seniority_signal", "evidence_briefs", "agent_prompt_profile", "hiring_score", "candidate_score", "market_rank"]) {
156
+ if (Object.hasOwn(profile, forbidden)) addError(errors, file, "default_audience_boundary", `Default profile must not include ${forbidden}.`);
157
+ }
158
+ if (!profile.work_identity?.primary_label || !Array.isArray(profile.work_identity?.evidence_ids)) {
159
+ addError(errors, file, "work_identity", "work_identity must include primary_label and evidence_ids.");
160
+ }
161
+ if (!Array.isArray(profile.work_role_signals) || profile.work_role_signals.length < 5) {
162
+ addError(errors, file, "work_role_signals", "work_role_signals must contain at least 5 role signals.");
163
+ }
164
+ if (!Array.isArray(profile.ability_model) || profile.ability_model.length < 10) {
165
+ addError(errors, file, "ability_model", "ability_model must contain the v0.1 ability dimensions.");
166
+ }
167
+ if (!Array.isArray(profile.evidence_notes) || profile.evidence_notes.length < 1) {
168
+ addError(errors, file, "evidence_notes", "evidence_notes must contain at least one evidence note.");
169
+ }
170
+ if (!Array.isArray(profile.agent_ledger?.clients) || !profile.agent_ledger.clients.some((client) => client.client_id === "codex")) {
171
+ addError(errors, file, "agent_ledger", "agent_ledger.clients must include codex.");
172
+ }
173
+ if (profile.privacy_boundary?.raw_logs_included !== false) addError(errors, file, "privacy_boundary", "privacy_boundary.raw_logs_included must be false.");
174
+ if (profile.privacy_boundary?.public_session_ids_included !== false) addError(errors, file, "privacy_boundary", "privacy_boundary.public_session_ids_included must be false.");
175
+ if (profile.run_metadata?.public_session_ids_included !== false) addError(errors, file, "run_metadata", "run_metadata.public_session_ids_included must be false.");
176
+ }
177
+
178
+ function checkEvidenceRefs(profile, evidence, config, errors) {
179
+ const file = "evidence.jsonl";
180
+ const evidenceIds = new Set();
181
+
182
+ for (const card of evidence) {
183
+ if (!card.id || typeof card.id !== "string") addError(errors, file, "evidence_structure", "Evidence card missing id.");
184
+ evidenceIds.add(card.id);
185
+ if (!Array.isArray(card.level) || card.level.length === 0) addError(errors, file, "evidence_structure", `${card.id || "unknown"} missing level.`);
186
+ if (!card.title || !card.summary) addError(errors, file, "evidence_structure", `${card.id || "unknown"} missing title or summary.`);
187
+ if (!Array.isArray(card.refs)) addError(errors, file, "evidence_refs", `${card.id || "unknown"} refs must be an array.`);
188
+ if (card.privacy?.public_safe !== true) addError(errors, file, "evidence_privacy", `${card.id || "unknown"} must be marked public_safe.`);
189
+ for (const ref of card.refs || []) checkSingleEvidenceRef(ref, card.id, config, errors);
190
+ }
191
+
192
+ for (const note of profile.evidence_notes || []) {
193
+ if (!evidenceIds.has(note.evidence_id)) {
194
+ addError(errors, "profile.json", "evidence_reference", `profile evidence_notes references missing evidence id: ${note.evidence_id}`);
195
+ }
196
+ for (const ref of note.refs || []) checkSingleEvidenceRef(ref, note.evidence_id, config, errors);
197
+ }
198
+
199
+ for (const id of profile.work_identity?.evidence_ids || []) {
200
+ if (!evidenceIds.has(id)) addError(errors, "profile.json", "evidence_reference", `work_identity references missing evidence id: ${id}`);
201
+ }
202
+
203
+ for (const role of profile.work_role_signals || []) {
204
+ for (const id of role.evidence_ids || []) {
205
+ if (!evidenceIds.has(id)) addError(errors, "profile.json", "evidence_reference", `role ${role.role_id} references missing evidence id: ${id}`);
206
+ }
207
+ }
208
+
209
+ for (const ability of profile.ability_model || []) {
210
+ for (const id of ability.evidence_ids || []) {
211
+ if (!evidenceIds.has(id)) addError(errors, "profile.json", "evidence_reference", `ability ${ability.dimension_id} references missing evidence id: ${id}`);
212
+ }
213
+ }
214
+ }
215
+
216
+ function checkSingleEvidenceRef(ref, evidenceId, config, errors) {
217
+ if (!ref || typeof ref !== "object") {
218
+ addError(errors, "evidence.jsonl", "evidence_ref_parse", `${evidenceId} has a malformed ref.`);
219
+ return;
220
+ }
221
+
222
+ if (ref.type === "metadata" && ref.source === "codex_session_metadata") return;
223
+
224
+ if (ref.type === "memory") {
225
+ if (!ref.source || !Number.isInteger(ref.start_line) || !Number.isInteger(ref.end_line) || ref.start_line <= 0 || ref.end_line < ref.start_line) {
226
+ addError(errors, "evidence.jsonl", "evidence_ref_parse", `${evidenceId} has an invalid memory ref.`);
227
+ return;
228
+ }
229
+ const memoryPath = config.resolved.memory.registryPath;
230
+ if (!memoryPath || path.basename(memoryPath) !== ref.source || !fs.existsSync(memoryPath)) {
231
+ addError(errors, "evidence.jsonl", "evidence_ref_resolve", `${evidenceId} memory ref cannot resolve ${ref.source}.`);
232
+ return;
233
+ }
234
+ const lineCount = fs.readFileSync(memoryPath, "utf8").split(/\n/).length;
235
+ if (ref.end_line > lineCount) {
236
+ addError(errors, "evidence.jsonl", "evidence_ref_resolve", `${evidenceId} memory ref line range exceeds ${ref.source}.`);
237
+ }
238
+ return;
239
+ }
240
+
241
+ addError(errors, "evidence.jsonl", "evidence_ref_parse", `${evidenceId} has unsupported ref type: ${ref.type || "missing"}.`);
242
+ }
243
+
244
+ function checkLocaleParity(errors) {
245
+ const enFile = path.join(repoRoot, "locales", "en-US.json");
246
+ const zhFile = path.join(repoRoot, "locales", "zh-CN.json");
247
+ const en = readRequiredJson(enFile, errors, "locale_required");
248
+ const zh = readRequiredJson(zhFile, errors, "locale_required");
249
+ if (!en || !zh) return;
250
+
251
+ for (const namespace of ["ui", "roles", "abilities"]) {
252
+ const enKeys = new Set(collectKeys(en[namespace]).map((key) => `${namespace}.${key}`));
253
+ const zhKeys = new Set(collectKeys(zh[namespace]).map((key) => `${namespace}.${key}`));
254
+ for (const key of enKeys) {
255
+ if (!zhKeys.has(key)) addError(errors, zhFile, "locale_parity", `zh-CN missing key: ${key}`);
256
+ }
257
+ for (const key of zhKeys) {
258
+ if (!enKeys.has(key)) addError(errors, enFile, "locale_parity", `en-US missing key: ${key}`);
259
+ }
260
+ }
261
+ }
262
+
263
+ function collectKeys(value, prefix = "") {
264
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [];
265
+ return Object.entries(value).flatMap(([key, child]) => {
266
+ const next = prefix ? `${prefix}.${key}` : key;
267
+ if (child && typeof child === "object" && !Array.isArray(child)) return [next, ...collectKeys(child, next)];
268
+ return [next];
269
+ });
270
+ }
271
+
272
+ function checkPrivateState(stateFile, profile, errors) {
273
+ const state = readRequiredJson(stateFile, errors, "private_state_required");
274
+ if (!state) return;
275
+ if (state.schema_version !== "agentrecord.state.v0") addError(errors, stateFile, "private_state_schema", "state schema_version must be agentrecord.state.v0.");
276
+ if (!Array.isArray(state.processed_session_ids)) addError(errors, stateFile, "private_state_sessions", "processed_session_ids must be an array.");
277
+ if (state.processed_session_ids?.length !== state.processed_sessions_count) {
278
+ addError(errors, stateFile, "private_state_sessions", "processed_session_ids length must match processed_sessions_count.");
279
+ }
280
+ if (!state.session_token_totals || typeof state.session_token_totals !== "object") {
281
+ addError(errors, stateFile, "private_state_tokens", "session_token_totals must exist.");
282
+ }
283
+ if (!state.last_profile_hash) addError(errors, stateFile, "private_state_hash", "last_profile_hash must exist.");
284
+ if (profile?.run_metadata?.private_state_present !== true) {
285
+ addError(errors, "profile.json", "private_state_public_marker", "run_metadata.private_state_present must be true.");
286
+ }
287
+ }
288
+
289
+ function checkPublicArtifactPrivacy(profileDir, profile, errors) {
290
+ const locale = profile?.report?.locale;
291
+ const publicArtifacts = [...requiredArtifacts, ...optionalPublicArtifacts].filter((artifact) => fs.existsSync(path.join(profileDir, artifact)));
292
+ for (const artifact of publicArtifacts) {
293
+ const file = path.join(profileDir, artifact);
294
+ const content = fs.readFileSync(file, "utf8");
295
+ for (const rule of privacyRules()) {
296
+ if (rule.pattern.test(content)) addError(errors, file, rule.id, rule.message);
297
+ }
298
+ const terminalIssue = detectTerminalOutput(content);
299
+ if (terminalIssue) addError(errors, file, "suspected_terminal_output", terminalIssue);
300
+ if (locale && artifact === "index.html" && !content.includes(`lang="${locale === "zh-CN" ? "zh-CN" : "en"}"`)) {
301
+ addError(errors, file, "html_locale", "index.html language attribute must match profile locale.");
302
+ }
303
+ }
304
+ }
305
+
306
+ function privacyRules() {
307
+ return [
308
+ {
309
+ id: "raw_uuid_session_like_id",
310
+ pattern: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i,
311
+ message: "Public artifact contains a raw UUID/session-like id."
312
+ },
313
+ {
314
+ id: "codex_session_path",
315
+ pattern: /\/\.codex\/sessions\//,
316
+ message: "Public artifact contains a raw Codex session path."
317
+ },
318
+ {
319
+ id: "raw_user_role",
320
+ pattern: /"role"\s*:\s*"user"/,
321
+ message: "Public artifact contains raw user-message role payload."
322
+ },
323
+ {
324
+ id: "raw_input_payload",
325
+ pattern: /\binput_text\b/,
326
+ message: "Public artifact contains raw input payload marker."
327
+ },
328
+ {
329
+ id: "openai_secret_prefix",
330
+ pattern: /\bsk-[A-Za-z0-9_-]{8,}/,
331
+ message: "Public artifact contains an OpenAI secret-like prefix."
332
+ },
333
+ {
334
+ id: "github_token_prefix",
335
+ pattern: /\bghp_[A-Za-z0-9_]{8,}/,
336
+ message: "Public artifact contains a GitHub token-like prefix."
337
+ },
338
+ {
339
+ id: "private_key",
340
+ pattern: /BEGIN PRIVATE KEY/,
341
+ message: "Public artifact contains a private key marker."
342
+ },
343
+ {
344
+ id: "private_state_path",
345
+ pattern: /\/\.agentrecord\/|\.agentrecord\/state\.json/,
346
+ message: "Public artifact exposes private state path."
347
+ }
348
+ ];
349
+ }
350
+
351
+ function detectTerminalOutput(content) {
352
+ const lines = content.split(/\n/);
353
+ const codexOutputMarkers = [
354
+ /^Chunk ID:/m,
355
+ /^Wall time:/m,
356
+ /^Process exited with code/m,
357
+ /^Original token count:/m,
358
+ /^Output:/m
359
+ ].filter((pattern) => pattern.test(content)).length;
360
+ if (codexOutputMarkers >= 2) return "Public artifact appears to contain copied command output.";
361
+
362
+ const shellPromptLines = lines.filter((line) => /^\s*(\$|>|%)\s+\S+/.test(line) || /^\s*[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+.*[$#]\s+\S+/.test(line)).length;
363
+ if (shellPromptLines >= 10) return "Public artifact contains many shell-prompt-like lines.";
364
+ return null;
365
+ }
@@ -0,0 +1,60 @@
1
+ export function parseArgs(argv) {
2
+ const options = {};
3
+ const positional = [];
4
+
5
+ for (let index = 0; index < argv.length; index += 1) {
6
+ const arg = argv[index];
7
+
8
+ if (arg === "--") {
9
+ positional.push(...argv.slice(index + 1));
10
+ break;
11
+ }
12
+
13
+ if (!arg.startsWith("-") || arg === "-") {
14
+ positional.push(arg);
15
+ continue;
16
+ }
17
+
18
+ if (arg.startsWith("--no-")) {
19
+ options[toCamelCase(arg.slice(5))] = false;
20
+ continue;
21
+ }
22
+
23
+ if (arg.startsWith("--")) {
24
+ const raw = arg.slice(2);
25
+ const equalsIndex = raw.indexOf("=");
26
+ if (equalsIndex !== -1) {
27
+ options[toCamelCase(raw.slice(0, equalsIndex))] = raw.slice(equalsIndex + 1);
28
+ continue;
29
+ }
30
+
31
+ const name = toCamelCase(raw);
32
+ const next = argv[index + 1];
33
+ if (next && !next.startsWith("-")) {
34
+ options[name] = next;
35
+ index += 1;
36
+ } else {
37
+ options[name] = true;
38
+ }
39
+ continue;
40
+ }
41
+
42
+ if (arg === "-h") {
43
+ options.help = true;
44
+ continue;
45
+ }
46
+
47
+ if (arg === "-v") {
48
+ options.version = true;
49
+ continue;
50
+ }
51
+
52
+ positional.push(arg);
53
+ }
54
+
55
+ return { options, positional };
56
+ }
57
+
58
+ export function toCamelCase(value) {
59
+ return String(value || "").replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
60
+ }