codepiper 0.1.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.
Files changed (149) hide show
  1. package/.env.example +28 -0
  2. package/CHANGELOG.md +10 -0
  3. package/LEGAL_NOTICE.md +39 -0
  4. package/LICENSE +21 -0
  5. package/README.md +524 -0
  6. package/package.json +90 -0
  7. package/packages/cli/package.json +13 -0
  8. package/packages/cli/src/commands/analytics.ts +157 -0
  9. package/packages/cli/src/commands/attach.ts +299 -0
  10. package/packages/cli/src/commands/audit.ts +50 -0
  11. package/packages/cli/src/commands/auth.ts +261 -0
  12. package/packages/cli/src/commands/daemon.ts +162 -0
  13. package/packages/cli/src/commands/doctor.ts +303 -0
  14. package/packages/cli/src/commands/env-set.ts +162 -0
  15. package/packages/cli/src/commands/hook-forward.ts +268 -0
  16. package/packages/cli/src/commands/keys.ts +77 -0
  17. package/packages/cli/src/commands/kill.ts +19 -0
  18. package/packages/cli/src/commands/logs.ts +419 -0
  19. package/packages/cli/src/commands/model.ts +172 -0
  20. package/packages/cli/src/commands/policy-set.ts +185 -0
  21. package/packages/cli/src/commands/policy.ts +227 -0
  22. package/packages/cli/src/commands/providers.ts +114 -0
  23. package/packages/cli/src/commands/resize.ts +34 -0
  24. package/packages/cli/src/commands/send.ts +184 -0
  25. package/packages/cli/src/commands/sessions.ts +202 -0
  26. package/packages/cli/src/commands/slash.ts +92 -0
  27. package/packages/cli/src/commands/start.ts +243 -0
  28. package/packages/cli/src/commands/stop.ts +19 -0
  29. package/packages/cli/src/commands/tail.ts +137 -0
  30. package/packages/cli/src/commands/workflow.ts +786 -0
  31. package/packages/cli/src/commands/workspace.ts +127 -0
  32. package/packages/cli/src/lib/api.ts +78 -0
  33. package/packages/cli/src/lib/args.ts +72 -0
  34. package/packages/cli/src/lib/format.ts +93 -0
  35. package/packages/cli/src/main.ts +563 -0
  36. package/packages/core/package.json +7 -0
  37. package/packages/core/src/config.ts +30 -0
  38. package/packages/core/src/errors.ts +38 -0
  39. package/packages/core/src/eventBus.ts +56 -0
  40. package/packages/core/src/eventBusAdapter.ts +143 -0
  41. package/packages/core/src/index.ts +10 -0
  42. package/packages/core/src/sqliteEventBus.ts +336 -0
  43. package/packages/core/src/types.ts +63 -0
  44. package/packages/daemon/package.json +11 -0
  45. package/packages/daemon/src/api/analyticsRoutes.ts +343 -0
  46. package/packages/daemon/src/api/authRoutes.ts +344 -0
  47. package/packages/daemon/src/api/bodyLimit.ts +133 -0
  48. package/packages/daemon/src/api/envSetRoutes.ts +170 -0
  49. package/packages/daemon/src/api/gitRoutes.ts +409 -0
  50. package/packages/daemon/src/api/hooks.ts +588 -0
  51. package/packages/daemon/src/api/inputPolicy.ts +249 -0
  52. package/packages/daemon/src/api/notificationRoutes.ts +532 -0
  53. package/packages/daemon/src/api/policyRoutes.ts +234 -0
  54. package/packages/daemon/src/api/policySetRoutes.ts +445 -0
  55. package/packages/daemon/src/api/routeUtils.ts +28 -0
  56. package/packages/daemon/src/api/routes.ts +1004 -0
  57. package/packages/daemon/src/api/server.ts +1388 -0
  58. package/packages/daemon/src/api/settingsRoutes.ts +367 -0
  59. package/packages/daemon/src/api/sqliteErrors.ts +47 -0
  60. package/packages/daemon/src/api/stt.ts +143 -0
  61. package/packages/daemon/src/api/terminalRoutes.ts +200 -0
  62. package/packages/daemon/src/api/validation.ts +287 -0
  63. package/packages/daemon/src/api/validationRoutes.ts +174 -0
  64. package/packages/daemon/src/api/workflowRoutes.ts +567 -0
  65. package/packages/daemon/src/api/workspaceRoutes.ts +151 -0
  66. package/packages/daemon/src/api/ws.ts +1588 -0
  67. package/packages/daemon/src/auth/apiRateLimiter.ts +73 -0
  68. package/packages/daemon/src/auth/authMiddleware.ts +305 -0
  69. package/packages/daemon/src/auth/authService.ts +496 -0
  70. package/packages/daemon/src/auth/rateLimiter.ts +137 -0
  71. package/packages/daemon/src/config/pricing.ts +79 -0
  72. package/packages/daemon/src/crypto/encryption.ts +196 -0
  73. package/packages/daemon/src/db/db.ts +2745 -0
  74. package/packages/daemon/src/db/index.ts +16 -0
  75. package/packages/daemon/src/db/migrations.ts +182 -0
  76. package/packages/daemon/src/db/policyDb.ts +349 -0
  77. package/packages/daemon/src/db/schema.sql +408 -0
  78. package/packages/daemon/src/db/workflowDb.ts +464 -0
  79. package/packages/daemon/src/git/gitUtils.ts +544 -0
  80. package/packages/daemon/src/index.ts +6 -0
  81. package/packages/daemon/src/main.ts +525 -0
  82. package/packages/daemon/src/notifications/pushNotifier.ts +369 -0
  83. package/packages/daemon/src/providers/codexAppServerScaffold.ts +49 -0
  84. package/packages/daemon/src/providers/registry.ts +111 -0
  85. package/packages/daemon/src/providers/types.ts +82 -0
  86. package/packages/daemon/src/sessions/auditLogger.ts +103 -0
  87. package/packages/daemon/src/sessions/policyEngine.ts +165 -0
  88. package/packages/daemon/src/sessions/policyMatcher.ts +114 -0
  89. package/packages/daemon/src/sessions/policyTypes.ts +94 -0
  90. package/packages/daemon/src/sessions/ptyProcess.ts +141 -0
  91. package/packages/daemon/src/sessions/sessionManager.ts +1770 -0
  92. package/packages/daemon/src/sessions/tmuxSession.ts +1073 -0
  93. package/packages/daemon/src/sessions/transcriptManager.ts +110 -0
  94. package/packages/daemon/src/sessions/transcriptParser.ts +149 -0
  95. package/packages/daemon/src/sessions/transcriptTailer.ts +214 -0
  96. package/packages/daemon/src/tracking/tokenTracker.ts +168 -0
  97. package/packages/daemon/src/workflows/contextManager.ts +83 -0
  98. package/packages/daemon/src/workflows/index.ts +31 -0
  99. package/packages/daemon/src/workflows/resultExtractor.ts +118 -0
  100. package/packages/daemon/src/workflows/waitConditionPoller.ts +131 -0
  101. package/packages/daemon/src/workflows/workflowParser.ts +217 -0
  102. package/packages/daemon/src/workflows/workflowRunner.ts +969 -0
  103. package/packages/daemon/src/workflows/workflowTypes.ts +188 -0
  104. package/packages/daemon/src/workflows/workflowValidator.ts +533 -0
  105. package/packages/providers/claude-code/package.json +11 -0
  106. package/packages/providers/claude-code/src/index.ts +7 -0
  107. package/packages/providers/claude-code/src/overlaySettings.ts +198 -0
  108. package/packages/providers/claude-code/src/provider.ts +311 -0
  109. package/packages/web/dist/android-chrome-192x192.png +0 -0
  110. package/packages/web/dist/android-chrome-512x512.png +0 -0
  111. package/packages/web/dist/apple-touch-icon.png +0 -0
  112. package/packages/web/dist/assets/AnalyticsPage-BIopKWRf.js +17 -0
  113. package/packages/web/dist/assets/PoliciesPage-CjdLN3dl.js +11 -0
  114. package/packages/web/dist/assets/SessionDetailPage-BtSA0V0M.js +179 -0
  115. package/packages/web/dist/assets/SettingsPage-Dbbz4Ca5.js +37 -0
  116. package/packages/web/dist/assets/WorkflowsPage-Dv6f3GgU.js +1 -0
  117. package/packages/web/dist/assets/chart-vendor-DlOHLaCG.js +49 -0
  118. package/packages/web/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  119. package/packages/web/dist/assets/css.worker-BvV5MPou.js +93 -0
  120. package/packages/web/dist/assets/editor.worker-CKy7Pnvo.js +26 -0
  121. package/packages/web/dist/assets/html.worker-BLJhxQJQ.js +470 -0
  122. package/packages/web/dist/assets/index-BbdhRfr2.css +1 -0
  123. package/packages/web/dist/assets/index-hgphORiw.js +204 -0
  124. package/packages/web/dist/assets/json.worker-usMZ-FED.js +58 -0
  125. package/packages/web/dist/assets/monaco-core-B_19GPAS.css +1 -0
  126. package/packages/web/dist/assets/monaco-core-DQ5Mk8AK.js +1234 -0
  127. package/packages/web/dist/assets/monaco-react-DfZNWvtW.js +11 -0
  128. package/packages/web/dist/assets/monacoSetup-DvBj52bT.js +1 -0
  129. package/packages/web/dist/assets/pencil-Dbczxz59.js +11 -0
  130. package/packages/web/dist/assets/react-vendor-B5MgMUHH.js +136 -0
  131. package/packages/web/dist/assets/refresh-cw-B0MGsYPL.js +6 -0
  132. package/packages/web/dist/assets/tabs-C8LsWiR5.js +1 -0
  133. package/packages/web/dist/assets/terminal-vendor-Cs8KPbV3.js +9 -0
  134. package/packages/web/dist/assets/terminal-vendor-LcAfv9l9.css +32 -0
  135. package/packages/web/dist/assets/trash-2-Btlg0d4l.js +6 -0
  136. package/packages/web/dist/assets/ts.worker-DGHjMaqB.js +67731 -0
  137. package/packages/web/dist/favicon.ico +0 -0
  138. package/packages/web/dist/icon.svg +1 -0
  139. package/packages/web/dist/index.html +29 -0
  140. package/packages/web/dist/manifest.json +29 -0
  141. package/packages/web/dist/og-image.png +0 -0
  142. package/packages/web/dist/originals/android-chrome-192x192.png +0 -0
  143. package/packages/web/dist/originals/android-chrome-512x512.png +0 -0
  144. package/packages/web/dist/originals/apple-touch-icon.png +0 -0
  145. package/packages/web/dist/originals/favicon.ico +0 -0
  146. package/packages/web/dist/piper.svg +1 -0
  147. package/packages/web/dist/sounds/codepiper-soft-chime.wav +0 -0
  148. package/packages/web/dist/sw.js +257 -0
  149. package/scripts/postinstall-link-workspaces.mjs +58 -0
@@ -0,0 +1,303 @@
1
+ import { readJson } from "../lib/api";
2
+ import { getRequiredValue } from "../lib/args";
3
+
4
+ export interface DoctorOptions {
5
+ socket: string;
6
+ }
7
+
8
+ export interface ClaudeCheckResult {
9
+ installed: boolean;
10
+ path?: string;
11
+ version?: string;
12
+ error?: string;
13
+ }
14
+
15
+ export interface CodexCheckResult {
16
+ installed: boolean;
17
+ path?: string;
18
+ version?: string;
19
+ error?: string;
20
+ }
21
+
22
+ export interface TmuxCheckResult {
23
+ installed: boolean;
24
+ path?: string;
25
+ version?: string;
26
+ versionOk?: boolean;
27
+ error?: string;
28
+ }
29
+
30
+ export interface DaemonCheckResult {
31
+ running: boolean;
32
+ version?: string;
33
+ error?: string;
34
+ }
35
+
36
+ export interface PlatformCheckResult {
37
+ platform: string;
38
+ arch: string;
39
+ supported: boolean;
40
+ }
41
+
42
+ const SUPPORTED_PLATFORMS = new Set(["linux", "darwin"]);
43
+ const SUPPORTED_ARCHITECTURES = new Set(["x64", "arm64"]);
44
+
45
+ export function parseDoctorOptions(args: string[]): DoctorOptions {
46
+ let socket = "/tmp/codepiper.sock";
47
+
48
+ for (let i = 0; i < args.length; i++) {
49
+ const arg = args[i];
50
+ if (arg === undefined) {
51
+ continue;
52
+ }
53
+
54
+ if (arg === "--socket" || arg === "-s") {
55
+ socket = getRequiredValue(args, i, arg);
56
+ i++;
57
+ }
58
+ }
59
+
60
+ return { socket };
61
+ }
62
+
63
+ async function readStream(stream: ReadableStream): Promise<string> {
64
+ const buffer = await new Response(stream).arrayBuffer();
65
+ return Buffer.from(buffer).toString("utf-8").trim();
66
+ }
67
+
68
+ /**
69
+ * Check whether a command exists in PATH and retrieve its version.
70
+ * Returns { path, version } on success, or { error } on failure.
71
+ */
72
+ async function checkCommand(
73
+ name: string,
74
+ versionArgs: string[]
75
+ ): Promise<
76
+ { installed: true; path: string; version: string } | { installed: false; error: string }
77
+ > {
78
+ try {
79
+ const whichProc = Bun.spawn(["which", name], {
80
+ stdout: "pipe",
81
+ stderr: "pipe",
82
+ });
83
+ await whichProc.exited;
84
+
85
+ if ((await whichProc.exitCode) !== 0) {
86
+ return { installed: false, error: `${name} command not found in PATH` };
87
+ }
88
+
89
+ const cmdPath = await readStream(whichProc.stdout);
90
+
91
+ const versionProc = Bun.spawn(versionArgs, {
92
+ stdout: "pipe",
93
+ stderr: "pipe",
94
+ });
95
+ await versionProc.exited;
96
+ const version = await readStream(versionProc.stdout);
97
+
98
+ return { installed: true, path: cmdPath, version };
99
+ } catch (error: any) {
100
+ return { installed: false, error: error.message };
101
+ }
102
+ }
103
+
104
+ export async function checkClaudeInstallation(): Promise<ClaudeCheckResult> {
105
+ return checkCommand("claude", ["claude", "-v"]);
106
+ }
107
+
108
+ export async function checkCodexInstallation(): Promise<CodexCheckResult> {
109
+ return checkCommand("codex", ["codex", "--version"]);
110
+ }
111
+
112
+ export async function checkTmux(): Promise<TmuxCheckResult> {
113
+ const result = await checkCommand("tmux", ["tmux", "-V"]);
114
+ if (!result.installed) return result;
115
+
116
+ // Parse version from "tmux 3.4" or "tmux 3.3a"
117
+ const match = result.version.match(/tmux\s+(\d+)\.(\d+)/);
118
+ const majorVersion = match?.[1];
119
+ const versionOk = majorVersion ? Number.parseInt(majorVersion, 10) >= 3 : false;
120
+
121
+ return { ...result, versionOk };
122
+ }
123
+
124
+ export async function checkDaemon(socket: string): Promise<DaemonCheckResult> {
125
+ try {
126
+ const response = await fetch("http://localhost/health", {
127
+ unix: socket,
128
+ method: "GET",
129
+ });
130
+
131
+ if (!response.ok) {
132
+ return {
133
+ running: false,
134
+ error: `HTTP ${response.status}: ${response.statusText}`,
135
+ };
136
+ }
137
+
138
+ const data = await readJson<{ version?: string }>(response);
139
+
140
+ const result: DaemonCheckResult = { running: true };
141
+ if (typeof data.version === "string" && data.version.length > 0) {
142
+ result.version = data.version;
143
+ }
144
+
145
+ return result;
146
+ } catch (error: any) {
147
+ return {
148
+ running: false,
149
+ error: error.message,
150
+ };
151
+ }
152
+ }
153
+
154
+ export function evaluatePlatform(platform: string, arch: string): PlatformCheckResult {
155
+ return {
156
+ platform,
157
+ arch,
158
+ supported: SUPPORTED_PLATFORMS.has(platform) && SUPPORTED_ARCHITECTURES.has(arch),
159
+ };
160
+ }
161
+
162
+ export function checkPlatform(): PlatformCheckResult {
163
+ return evaluatePlatform(process.platform, process.arch);
164
+ }
165
+
166
+ function checkEnvironment(): {
167
+ apiKeySet: boolean;
168
+ info: string;
169
+ } {
170
+ const apiKeySet = !!process.env.ANTHROPIC_API_KEY;
171
+
172
+ if (apiKeySet) {
173
+ return {
174
+ apiKeySet: true,
175
+ info: 'ANTHROPIC_API_KEY detected. Sessions with billingMode "api" will use API billing. Default sessions will scrub this key and use subscription billing.',
176
+ };
177
+ }
178
+
179
+ return {
180
+ apiKeySet: false,
181
+ info: "ANTHROPIC_API_KEY not set. All sessions use subscription billing. Set it if you need API billing for automated use.",
182
+ };
183
+ }
184
+
185
+ function printCheckResult(
186
+ name: string,
187
+ status: "ok" | "warning" | "error",
188
+ message?: string
189
+ ): void {
190
+ const symbols = {
191
+ ok: "✓",
192
+ warning: "⚠",
193
+ error: "✗",
194
+ };
195
+
196
+ const colors = {
197
+ ok: "\x1b[32m", // green
198
+ warning: "\x1b[33m", // yellow
199
+ error: "\x1b[31m", // red
200
+ reset: "\x1b[0m",
201
+ };
202
+
203
+ const symbol = symbols[status];
204
+ const color = colors[status];
205
+
206
+ console.log(`${color}${symbol}${colors.reset} ${name}${message ? `: ${message}` : ""}`);
207
+ }
208
+
209
+ export async function runDoctorCommand(args: string[]): Promise<void> {
210
+ const options = parseDoctorOptions(args);
211
+
212
+ console.log("Running codepiper diagnostics...\n");
213
+
214
+ // Check platform
215
+ console.log("Checking platform...");
216
+ const platformCheck = checkPlatform();
217
+ const platformStatus = platformCheck.supported ? "ok" : "warning";
218
+ printCheckResult("Platform", platformStatus, `${platformCheck.platform}/${platformCheck.arch}`);
219
+ if (!platformCheck.supported) {
220
+ console.log(
221
+ " CodePiper is tested on linux/darwin with x64/arm64 architectures.\n" +
222
+ " Other platforms may work but are not currently in the supported target matrix."
223
+ );
224
+ }
225
+
226
+ // Check provider installations
227
+ console.log("Checking provider binaries...");
228
+ const claudeCheck = await checkClaudeInstallation();
229
+ const codexCheck = await checkCodexInstallation();
230
+ const hasAnyProvider = claudeCheck.installed || codexCheck.installed;
231
+
232
+ if (claudeCheck.installed) {
233
+ printCheckResult("Claude Code installed", "ok", `Found at ${claudeCheck.path}`);
234
+ if (claudeCheck.version) {
235
+ printCheckResult("Claude Code version", "ok", claudeCheck.version);
236
+ }
237
+ } else {
238
+ printCheckResult("Claude Code check", hasAnyProvider ? "warning" : "error", claudeCheck.error);
239
+ console.log(" Install Claude Code: https://code.claude.com/docs/en/installation");
240
+ }
241
+
242
+ if (codexCheck.installed) {
243
+ printCheckResult("Codex CLI installed", "ok", `Found at ${codexCheck.path}`);
244
+ if (codexCheck.version) {
245
+ printCheckResult("Codex CLI version", "ok", codexCheck.version);
246
+ }
247
+ } else {
248
+ printCheckResult("Codex CLI check", hasAnyProvider ? "warning" : "error", codexCheck.error);
249
+ console.log(" Install Codex CLI: https://developers.openai.com/codex");
250
+ }
251
+
252
+ if (!hasAnyProvider) {
253
+ console.log("\n At least one provider binary is required to start sessions.\n");
254
+ } else {
255
+ console.log("");
256
+ }
257
+
258
+ // Check tmux
259
+ console.log("\nChecking tmux...");
260
+ const tmuxCheck = await checkTmux();
261
+
262
+ if (tmuxCheck.installed) {
263
+ printCheckResult("tmux installed", "ok", `Found at ${tmuxCheck.path}`);
264
+ if (tmuxCheck.version) {
265
+ const versionStatus = tmuxCheck.versionOk ? "ok" : "warning";
266
+ printCheckResult("tmux version", versionStatus, tmuxCheck.version);
267
+ if (!tmuxCheck.versionOk) {
268
+ console.log("\n tmux 3.0+ is recommended for CodePiper sessions.\n");
269
+ }
270
+ }
271
+ } else {
272
+ printCheckResult("tmux check", "warning", tmuxCheck.error);
273
+ console.log("\n Install tmux: https://github.com/tmux/tmux/wiki/Installing\n");
274
+ }
275
+
276
+ // Check environment
277
+ console.log("\nChecking environment...");
278
+ const envCheck = checkEnvironment();
279
+
280
+ printCheckResult("API key check", "ok", envCheck.info);
281
+
282
+ // Check daemon
283
+ console.log("\nChecking daemon...");
284
+ const daemonCheck = await checkDaemon(options.socket);
285
+
286
+ if (daemonCheck.running) {
287
+ printCheckResult("Daemon status", "ok", "Daemon is running");
288
+ if (daemonCheck.version) {
289
+ printCheckResult("Daemon version", "ok", daemonCheck.version);
290
+ }
291
+ } else {
292
+ printCheckResult("Daemon status", "error", "Daemon is not running");
293
+ console.log("\n Start the daemon with:");
294
+ console.log(" codepiper daemon\n");
295
+ }
296
+
297
+ console.log("\nDiagnostics complete.");
298
+
299
+ // Exit with error code if critical checks failed
300
+ if (!(hasAnyProvider && daemonCheck.running)) {
301
+ process.exit(1);
302
+ }
303
+ }
@@ -0,0 +1,162 @@
1
+ import { daemonFetch, daemonJson, daemonPost } from "../lib/api";
2
+ import { getOption, getPositional, getRequiredValue, getSocket } from "../lib/args";
3
+ import { colors, formatDate, info, success, table } from "../lib/format";
4
+
5
+ interface EnvSet {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ maskedVars: Record<string, string>;
10
+ varCount: number;
11
+ createdAt: string;
12
+ updatedAt: string;
13
+ }
14
+
15
+ function parseVars(args: string[]): Record<string, string> {
16
+ const vars: Record<string, string> = {};
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i] === "--var") {
19
+ const pair = getRequiredValue(args, i, "--var");
20
+ i++;
21
+ const eqIdx = pair.indexOf("=");
22
+ if (eqIdx === -1) {
23
+ throw new Error(`Invalid variable format: ${pair}. Expected KEY=VALUE`);
24
+ }
25
+ vars[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
26
+ }
27
+ }
28
+ return vars;
29
+ }
30
+
31
+ async function listEnvSets(args: string[]): Promise<void> {
32
+ const socket = getSocket(args);
33
+ const data = await daemonJson<{ envSets: EnvSet[] }>("/env-sets", { socket });
34
+
35
+ if (data.envSets.length === 0) {
36
+ console.log("No environment sets found.");
37
+ return;
38
+ }
39
+
40
+ const rows = data.envSets.map((e) => [
41
+ String(e.id).slice(0, 8),
42
+ e.name,
43
+ String(e.varCount ?? Object.keys(e.maskedVars || {}).length),
44
+ e.description || `${colors.dim}-${colors.reset}`,
45
+ formatDate(e.createdAt),
46
+ ]);
47
+
48
+ console.log(table(["ID", "NAME", "VARS", "DESCRIPTION", "CREATED"], rows));
49
+ console.log(`\n${colors.bold}Total:${colors.reset} ${data.envSets.length} env set(s)`);
50
+ }
51
+
52
+ async function getEnvSet(args: string[]): Promise<void> {
53
+ const socket = getSocket(args);
54
+ const id = getPositional(args, 0);
55
+ if (!id) throw new Error("env-set ID is required");
56
+
57
+ const data = await daemonJson<{ envSet: EnvSet }>(`/env-sets/${id}`, { socket });
58
+ const e = data.envSet;
59
+
60
+ console.log(`${colors.bold}Env Set #${e.id}${colors.reset}`);
61
+ info("Name", e.name);
62
+ if (e.description) info("Description", e.description);
63
+ info("Created", formatDate(e.createdAt));
64
+ info("Updated", formatDate(e.updatedAt));
65
+
66
+ const vars = e.maskedVars || {};
67
+ const keys = Object.keys(vars);
68
+ if (keys.length > 0) {
69
+ console.log(`\n${colors.bold}Variables (masked):${colors.reset}`);
70
+ for (const key of keys) {
71
+ console.log(` ${colors.cyan}${key}${colors.reset}=${vars[key]}`);
72
+ }
73
+ } else {
74
+ console.log(`\n${colors.dim}No variables defined.${colors.reset}`);
75
+ }
76
+ }
77
+
78
+ async function createEnvSet(args: string[]): Promise<void> {
79
+ const socket = getSocket(args);
80
+ const name = getOption(args, "name");
81
+ const description = getOption(args, "description");
82
+ const variables = parseVars(args);
83
+
84
+ if (!name) throw new Error("--name is required");
85
+ if (Object.keys(variables).length === 0) {
86
+ throw new Error("at least one --var KEY=VALUE is required");
87
+ }
88
+
89
+ const body: Record<string, unknown> = { name, vars: variables };
90
+ if (description) body.description = description;
91
+
92
+ const data = await daemonPost<{ envSet: EnvSet }>("/env-sets", body, { socket });
93
+
94
+ success(
95
+ `Env set created: ${colors.bold}${data.envSet.name}${colors.reset} (ID: ${data.envSet.id})`
96
+ );
97
+ }
98
+
99
+ async function updateEnvSet(args: string[]): Promise<void> {
100
+ const socket = getSocket(args);
101
+ const id = getPositional(args, 0);
102
+ if (!id) throw new Error("env-set ID is required");
103
+
104
+ const body: Record<string, unknown> = {};
105
+ const name = getOption(args, "name");
106
+ const description = getOption(args, "description");
107
+ const variables = parseVars(args);
108
+
109
+ if (name) body.name = name;
110
+ if (description) body.description = description;
111
+ if (Object.keys(variables).length > 0) body.vars = variables;
112
+
113
+ if (Object.keys(body).length === 0) {
114
+ throw new Error("at least one field to update is required (--name, --description, --var)");
115
+ }
116
+
117
+ await daemonFetch(`/env-sets/${id}`, {
118
+ method: "PUT",
119
+ headers: { "Content-Type": "application/json" },
120
+ body: JSON.stringify(body),
121
+ socket,
122
+ });
123
+
124
+ success(`Env set #${id} updated`);
125
+ }
126
+
127
+ async function deleteEnvSet(args: string[]): Promise<void> {
128
+ const socket = getSocket(args);
129
+ const id = getPositional(args, 0);
130
+ if (!id) throw new Error("env-set ID is required");
131
+
132
+ await daemonFetch(`/env-sets/${id}`, { method: "DELETE", socket });
133
+
134
+ success(`Env set #${id} deleted`);
135
+ }
136
+
137
+ export async function runEnvSetCommand(args: string[]): Promise<void> {
138
+ if (args.length === 0) {
139
+ throw new Error("subcommand required (list, get, create, update, delete)");
140
+ }
141
+
142
+ const subcommand = args[0];
143
+ const subArgs = args.slice(1);
144
+
145
+ switch (subcommand) {
146
+ case "list":
147
+ case "ls":
148
+ return listEnvSets(subArgs);
149
+ case "get":
150
+ case "show":
151
+ return getEnvSet(subArgs);
152
+ case "create":
153
+ return createEnvSet(subArgs);
154
+ case "update":
155
+ return updateEnvSet(subArgs);
156
+ case "delete":
157
+ case "rm":
158
+ return deleteEnvSet(subArgs);
159
+ default:
160
+ throw new Error(`Unknown env-set subcommand: ${subcommand}`);
161
+ }
162
+ }