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,268 @@
1
+ /**
2
+ * Hook forwarding command for Claude Code hooks
3
+ *
4
+ * This command is called by Claude Code hooks to forward events to the daemon.
5
+ * It reads JSON from stdin, POSTs to the daemon's hooks endpoint, and handles
6
+ * PermissionRequest responses.
7
+ *
8
+ * Environment variables:
9
+ * - CODEPIPER_UNIX_SOCK: Path to daemon socket
10
+ * - CODEPIPER_SESSION: Session ID for metadata
11
+ * - CODEPIPER_SECRET: Authentication token
12
+ *
13
+ * Exit codes:
14
+ * - 0: Success
15
+ * - 2: Block action (for PermissionRequest deny)
16
+ * - 1: Error
17
+ */
18
+
19
+ import { readErrorJson, readJson, responseErrorMessage } from "../lib/api";
20
+
21
+ interface HookEvent {
22
+ event?: string; // Legacy format
23
+ hook_event_name?: string; // Claude Code 2.1.42+ format
24
+ session_id?: string;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ interface PermissionResponse {
29
+ decision?: "allow" | "deny" | "ask";
30
+ allow?: boolean; // Backward compatibility with older daemon responses
31
+ denialMessage?: string;
32
+ message?: string;
33
+ updatedInput?: unknown;
34
+ updatedPermissions?: unknown;
35
+ }
36
+
37
+ interface ClaudePermissionDecision {
38
+ decision: "allow" | "deny";
39
+ denialMessage?: string;
40
+ updatedInput?: unknown;
41
+ updatedPermissions?: unknown;
42
+ }
43
+
44
+ /**
45
+ * Read all data from stdin
46
+ */
47
+ async function readStdin(): Promise<string> {
48
+ return new Promise((resolve, reject) => {
49
+ const chunks: Buffer[] = [];
50
+
51
+ process.stdin.on("data", (chunk) => {
52
+ chunks.push(Buffer.from(chunk));
53
+ });
54
+
55
+ process.stdin.on("end", () => {
56
+ const data = Buffer.concat(chunks).toString("utf-8");
57
+ resolve(data);
58
+ });
59
+
60
+ process.stdin.on("error", (error) => {
61
+ reject(error);
62
+ });
63
+
64
+ // Resume stdin to start reading
65
+ process.stdin.resume();
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Validate required environment variables
71
+ */
72
+ function validateEnvironment(): {
73
+ socketPath: string;
74
+ sessionId: string;
75
+ secret: string;
76
+ } {
77
+ const socketPath = process.env.CODEPIPER_UNIX_SOCK;
78
+ const sessionId = process.env.CODEPIPER_SESSION;
79
+ const secret = process.env.CODEPIPER_SECRET;
80
+
81
+ if (!socketPath) {
82
+ throw new Error(
83
+ "CODEPIPER_UNIX_SOCK environment variable is required. " +
84
+ "This should be set by the daemon when spawning the session."
85
+ );
86
+ }
87
+
88
+ if (!sessionId) {
89
+ throw new Error(
90
+ "CODEPIPER_SESSION environment variable is required. " +
91
+ "This should be set by the daemon when spawning the session."
92
+ );
93
+ }
94
+
95
+ if (!secret) {
96
+ throw new Error(
97
+ "CODEPIPER_SECRET environment variable is required. " +
98
+ "This should be set by the daemon when spawning the session."
99
+ );
100
+ }
101
+
102
+ return { socketPath, sessionId, secret };
103
+ }
104
+
105
+ function resolveEnvironment(options?: {
106
+ socketPath?: string;
107
+ sessionId?: string;
108
+ secret?: string;
109
+ }): {
110
+ socketPath: string;
111
+ sessionId: string;
112
+ secret: string;
113
+ } {
114
+ if (!options) {
115
+ return validateEnvironment();
116
+ }
117
+
118
+ const socketPath = options.socketPath ?? process.env.CODEPIPER_UNIX_SOCK;
119
+ const sessionId = options.sessionId ?? process.env.CODEPIPER_SESSION;
120
+ const secret = options.secret ?? process.env.CODEPIPER_SECRET;
121
+
122
+ if (!(socketPath && sessionId && secret)) {
123
+ throw new Error(
124
+ "Missing hook-forward environment values. " +
125
+ "Required: socketPath, sessionId, secret (or CODEPIPER_* env vars)."
126
+ );
127
+ }
128
+
129
+ return { socketPath, sessionId, secret };
130
+ }
131
+
132
+ interface ForwardHookResult {
133
+ exitCode: number;
134
+ output?: string;
135
+ }
136
+
137
+ /**
138
+ * Forward hook event to daemon
139
+ */
140
+ export async function forwardHook(
141
+ input?: string,
142
+ options?: {
143
+ socketPath?: string;
144
+ sessionId?: string;
145
+ secret?: string;
146
+ }
147
+ ): Promise<ForwardHookResult> {
148
+ // Validate/resolve environment from options + process env.
149
+ const { socketPath, sessionId, secret } = resolveEnvironment(options);
150
+
151
+ // Read hook event from stdin or use provided input
152
+ const eventInput = input ?? (await readStdin());
153
+
154
+ if (!eventInput || eventInput.trim() === "") {
155
+ throw new Error("No input received from stdin");
156
+ }
157
+
158
+ // Parse JSON
159
+ let hookEvent: HookEvent;
160
+ try {
161
+ hookEvent = JSON.parse(eventInput);
162
+ } catch (error: any) {
163
+ throw new Error(`Invalid JSON input: ${error.message}`);
164
+ }
165
+
166
+ // Build request payload in format expected by daemon
167
+ // Daemon expects: { sessionId, event, data }
168
+ // Claude Code sends either 'event' (legacy) or 'hook_event_name' (new format)
169
+ const eventName = hookEvent.event || hookEvent.hook_event_name;
170
+
171
+ if (!eventName) {
172
+ throw new Error("Missing required field: event or hook_event_name");
173
+ }
174
+
175
+ const payload = {
176
+ sessionId,
177
+ event: eventName,
178
+ data: hookEvent,
179
+ };
180
+
181
+ // Send to daemon
182
+ let response: Response;
183
+ try {
184
+ response = await fetch("http://localhost/hooks/claude", {
185
+ unix: socketPath,
186
+ method: "POST",
187
+ headers: {
188
+ "Content-Type": "application/json",
189
+ "X-CodePiper-Secret": secret,
190
+ },
191
+ body: JSON.stringify(payload),
192
+ });
193
+ } catch (error: any) {
194
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
195
+ throw new Error(`Failed to connect to daemon at ${socketPath}. Is the daemon running?`);
196
+ }
197
+ throw error;
198
+ }
199
+
200
+ // Handle response
201
+ if (!response.ok) {
202
+ const errorData = await readErrorJson(response);
203
+ throw new Error(responseErrorMessage(response, errorData));
204
+ }
205
+
206
+ // Parse response
207
+ const responseData = await readJson<PermissionResponse>(response);
208
+
209
+ // Handle PermissionRequest response
210
+ const isPermissionRequest = eventName === "PermissionRequest";
211
+ if (isPermissionRequest) {
212
+ const decision = responseData as PermissionResponse;
213
+
214
+ // Normalize decision across daemon versions.
215
+ const normalizedDecision =
216
+ decision.decision ??
217
+ (decision.allow === false ? "deny" : decision.allow === true ? "allow" : "ask");
218
+
219
+ // For "ask", emit no output and let Claude Code show interactive prompt.
220
+ if (normalizedDecision === "ask") {
221
+ return { exitCode: 0 };
222
+ }
223
+
224
+ const outputPayload: ClaudePermissionDecision = {
225
+ decision: normalizedDecision,
226
+ };
227
+
228
+ if (decision.updatedInput !== undefined) {
229
+ outputPayload.updatedInput = decision.updatedInput;
230
+ }
231
+ if (decision.updatedPermissions !== undefined) {
232
+ outputPayload.updatedPermissions = decision.updatedPermissions;
233
+ }
234
+ if (normalizedDecision === "deny") {
235
+ const denialMessage = decision.denialMessage ?? decision.message;
236
+ if (typeof denialMessage === "string") {
237
+ outputPayload.denialMessage = denialMessage;
238
+ }
239
+ }
240
+
241
+ // Output decision JSON to stdout for Claude Code to read
242
+ const output = JSON.stringify(outputPayload);
243
+
244
+ // Exit with appropriate code
245
+ const exitCode = normalizedDecision === "deny" ? 2 : 0;
246
+
247
+ return { exitCode, output };
248
+ }
249
+
250
+ // For other events, exit successfully without output
251
+ // (Per CLAUDE.md: hook stdout can inject context, so we output nothing)
252
+ return { exitCode: 0 };
253
+ }
254
+
255
+ /**
256
+ * Run hook-forward command
257
+ */
258
+ export async function runHookForwardCommand(): Promise<void> {
259
+ const result = await forwardHook();
260
+
261
+ // Output to stdout if needed (PermissionRequest)
262
+ if (result.output) {
263
+ console.log(result.output);
264
+ }
265
+
266
+ // Exit with appropriate code
267
+ process.exit(result.exitCode);
268
+ }
@@ -0,0 +1,77 @@
1
+ import { readErrorJson, responseErrorMessage } from "../lib/api";
2
+ import { getRequiredValue } from "../lib/args";
3
+
4
+ export interface KeysOptions {
5
+ sessionId: string;
6
+ keys: string[];
7
+ socket: string;
8
+ }
9
+
10
+ export function parseKeysOptions(args: string[]): KeysOptions {
11
+ let sessionId: string | undefined;
12
+ let socket = "/tmp/codepiper.sock";
13
+ const keys: string[] = [];
14
+
15
+ for (let i = 0; i < args.length; i++) {
16
+ const arg = args[i];
17
+ if (arg === undefined) {
18
+ continue;
19
+ }
20
+
21
+ if (arg === "--socket" || arg === "-s") {
22
+ socket = getRequiredValue(args, i, arg);
23
+ i++;
24
+ } else if (!arg.startsWith("-")) {
25
+ if (!sessionId) {
26
+ sessionId = arg;
27
+ } else {
28
+ keys.push(arg);
29
+ }
30
+ }
31
+ }
32
+
33
+ if (!sessionId) {
34
+ throw new Error("session-id is required");
35
+ }
36
+
37
+ if (keys.length === 0) {
38
+ throw new Error("at least one key is required");
39
+ }
40
+
41
+ return { sessionId, keys, socket };
42
+ }
43
+
44
+ export async function sendKeys(options: KeysOptions): Promise<void> {
45
+ const payload = {
46
+ keys: options.keys,
47
+ };
48
+
49
+ try {
50
+ const response = await fetch(`http://localhost/sessions/${options.sessionId}/keys`, {
51
+ unix: options.socket,
52
+ method: "POST",
53
+ headers: {
54
+ "Content-Type": "application/json",
55
+ },
56
+ body: JSON.stringify(payload),
57
+ });
58
+
59
+ if (!response.ok) {
60
+ const errorData = await readErrorJson(response);
61
+ throw new Error(responseErrorMessage(response, errorData));
62
+ }
63
+ } catch (error: any) {
64
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
65
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
66
+ }
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ export async function runKeysCommand(args: string[]): Promise<void> {
72
+ const options = parseKeysOptions(args);
73
+
74
+ await sendKeys(options);
75
+
76
+ console.log(`Keys sent to session ${options.sessionId}: ${options.keys.join(", ")}`);
77
+ }
@@ -0,0 +1,19 @@
1
+ import { daemonFetch } from "../lib/api";
2
+ import { getSocket } from "../lib/args";
3
+ import { colors, success } from "../lib/format";
4
+
5
+ export async function runKillCommand(args: string[]): Promise<void> {
6
+ const sessionId = args.find((a) => !a.startsWith("-"));
7
+ if (!sessionId) {
8
+ throw new Error("session-id is required");
9
+ }
10
+
11
+ const socket = getSocket(args);
12
+
13
+ await daemonFetch(`/sessions/${sessionId}/kill`, {
14
+ method: "POST",
15
+ socket,
16
+ });
17
+
18
+ success(`Session ${colors.cyan}${sessionId.slice(0, 8)}...${colors.reset} killed`);
19
+ }