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,185 @@
1
+ import { daemonFetch, daemonJson, daemonPost } from "../lib/api";
2
+ import { getFlag, getOption, getPositional, getSocket } from "../lib/args";
3
+ import { colors, formatDate, info, success, table } from "../lib/format";
4
+
5
+ interface PolicySet {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ isDefault: boolean;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ policies?: Array<{ id: string; name: string; enabled: boolean; priority: number }>;
13
+ }
14
+
15
+ async function listPolicySets(args: string[]): Promise<void> {
16
+ const socket = getSocket(args);
17
+ const data = await daemonJson<{ policySets: PolicySet[] }>("/policy-sets", { socket });
18
+
19
+ if (data.policySets.length === 0) {
20
+ console.log("No policy sets found.");
21
+ return;
22
+ }
23
+
24
+ const rows = data.policySets.map((ps) => [
25
+ ps.id.slice(0, 8),
26
+ ps.name,
27
+ ps.isDefault ? `${colors.green}yes${colors.reset}` : "no",
28
+ ps.description || `${colors.dim}-${colors.reset}`,
29
+ formatDate(ps.createdAt),
30
+ ]);
31
+
32
+ console.log(table(["ID", "NAME", "DEFAULT", "DESCRIPTION", "CREATED"], rows));
33
+ console.log(`\n${colors.bold}Total:${colors.reset} ${data.policySets.length} policy set(s)`);
34
+ }
35
+
36
+ async function getPolicySet(args: string[]): Promise<void> {
37
+ const socket = getSocket(args);
38
+ const id = getPositional(args, 0);
39
+ if (!id) throw new Error("policy-set ID is required");
40
+
41
+ const data = await daemonJson<{ policySet: PolicySet }>(`/policy-sets/${id}`, { socket });
42
+ const ps = data.policySet;
43
+
44
+ console.log(`${colors.bold}Policy Set ${ps.id.slice(0, 8)}${colors.reset}`);
45
+ info("Name", ps.name);
46
+ if (ps.description) info("Description", ps.description);
47
+ info("Default", ps.isDefault ? `${colors.green}yes${colors.reset}` : "no");
48
+ info("Created", formatDate(ps.createdAt));
49
+ info("Updated", formatDate(ps.updatedAt));
50
+
51
+ if (ps.policies && ps.policies.length > 0) {
52
+ console.log(`\n${colors.bold}Policies:${colors.reset}`);
53
+ const rows = ps.policies.map((p) => [
54
+ p.id.slice(0, 8),
55
+ p.name,
56
+ p.enabled ? `${colors.green}yes${colors.reset}` : `${colors.gray}no${colors.reset}`,
57
+ String(p.priority),
58
+ ]);
59
+ console.log(table(["ID", "NAME", "ENABLED", "PRI"], rows));
60
+ } else {
61
+ console.log(`\n${colors.dim}No policies in this set.${colors.reset}`);
62
+ }
63
+ }
64
+
65
+ async function createPolicySet(args: string[]): Promise<void> {
66
+ const socket = getSocket(args);
67
+ const name = getOption(args, "name");
68
+ const description = getOption(args, "description");
69
+ const isDefault = getFlag(args, "default");
70
+
71
+ if (!name) throw new Error("--name is required");
72
+
73
+ const body: Record<string, unknown> = {
74
+ id: crypto.randomUUID(),
75
+ name,
76
+ };
77
+ if (description) body.description = description;
78
+ if (isDefault) body.isDefault = true;
79
+
80
+ const data = await daemonPost<{ policySet: PolicySet }>("/policy-sets", body, { socket });
81
+
82
+ success(
83
+ `Policy set created: ${colors.bold}${data.policySet.name}${colors.reset} (ID: ${data.policySet.id})`
84
+ );
85
+ }
86
+
87
+ async function updatePolicySet(args: string[]): Promise<void> {
88
+ const socket = getSocket(args);
89
+ const id = getPositional(args, 0);
90
+ if (!id) throw new Error("policy-set ID is required");
91
+
92
+ const body: Record<string, unknown> = {};
93
+ const name = getOption(args, "name");
94
+ const description = getOption(args, "description");
95
+ const isDefault = getFlag(args, "default");
96
+
97
+ if (name) body.name = name;
98
+ if (description) body.description = description;
99
+ if (isDefault) body.isDefault = true;
100
+
101
+ if (Object.keys(body).length === 0) {
102
+ throw new Error("at least one field to update is required (--name, --description, --default)");
103
+ }
104
+
105
+ await daemonFetch(`/policy-sets/${id}`, {
106
+ method: "PUT",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify(body),
109
+ socket,
110
+ });
111
+
112
+ success(`Policy set #${id} updated`);
113
+ }
114
+
115
+ async function deletePolicySet(args: string[]): Promise<void> {
116
+ const socket = getSocket(args);
117
+ const id = getPositional(args, 0);
118
+ if (!id) throw new Error("policy-set ID is required");
119
+
120
+ await daemonFetch(`/policy-sets/${id}`, { method: "DELETE", socket });
121
+
122
+ success(`Policy set #${id} deleted`);
123
+ }
124
+
125
+ async function addPolicyToSet(args: string[]): Promise<void> {
126
+ const socket = getSocket(args);
127
+ const setId = getPositional(args, 0);
128
+ const policyId = getPositional(args, 1);
129
+
130
+ if (!setId) throw new Error("policy-set ID is required");
131
+ if (!policyId) throw new Error("policy ID is required");
132
+
133
+ await daemonPost(`/policy-sets/${setId}/policies`, { policyId }, { socket });
134
+
135
+ success(`Policy #${policyId} added to set #${setId}`);
136
+ }
137
+
138
+ async function removePolicyFromSet(args: string[]): Promise<void> {
139
+ const socket = getSocket(args);
140
+ const setId = getPositional(args, 0);
141
+ const policyId = getPositional(args, 1);
142
+
143
+ if (!setId) throw new Error("policy-set ID is required");
144
+ if (!policyId) throw new Error("policy ID is required");
145
+
146
+ await daemonFetch(`/policy-sets/${setId}/policies/${policyId}`, {
147
+ method: "DELETE",
148
+ socket,
149
+ });
150
+
151
+ success(`Policy #${policyId} removed from set #${setId}`);
152
+ }
153
+
154
+ export async function runPolicySetCommand(args: string[]): Promise<void> {
155
+ if (args.length === 0) {
156
+ throw new Error(
157
+ "subcommand required (list, get, create, update, delete, add-policy, remove-policy)"
158
+ );
159
+ }
160
+
161
+ const subcommand = args[0];
162
+ const subArgs = args.slice(1);
163
+
164
+ switch (subcommand) {
165
+ case "list":
166
+ case "ls":
167
+ return listPolicySets(subArgs);
168
+ case "get":
169
+ case "show":
170
+ return getPolicySet(subArgs);
171
+ case "create":
172
+ return createPolicySet(subArgs);
173
+ case "update":
174
+ return updatePolicySet(subArgs);
175
+ case "delete":
176
+ case "rm":
177
+ return deletePolicySet(subArgs);
178
+ case "add-policy":
179
+ return addPolicyToSet(subArgs);
180
+ case "remove-policy":
181
+ return removePolicyFromSet(subArgs);
182
+ default:
183
+ throw new Error(`Unknown policy-set subcommand: ${subcommand}`);
184
+ }
185
+ }
@@ -0,0 +1,227 @@
1
+ import { daemonFetch, daemonJson, daemonPost } from "../lib/api";
2
+ import { getOption, getPositional, getSocket } from "../lib/args";
3
+ import { colors, formatDate, info, success, table } from "../lib/format";
4
+
5
+ interface Policy {
6
+ id: string;
7
+ name: string;
8
+ description?: string;
9
+ enabled: boolean;
10
+ priority: number;
11
+ sessionId: string | null;
12
+ rules: Array<Record<string, unknown>>;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ }
16
+
17
+ async function listPolicies(args: string[]): Promise<void> {
18
+ const socket = getSocket(args);
19
+ const sessionId = getOption(args, "session");
20
+
21
+ const params = sessionId ? `?sessionId=${sessionId}` : "";
22
+ const data = await daemonJson<{ policies: Policy[] }>(`/policies${params}`, { socket });
23
+
24
+ if (data.policies.length === 0) {
25
+ console.log("No policies found.");
26
+ return;
27
+ }
28
+
29
+ const rows = data.policies.map((p) => [
30
+ p.id.slice(0, 8),
31
+ p.name,
32
+ p.enabled ? `${colors.green}yes${colors.reset}` : `${colors.gray}no${colors.reset}`,
33
+ String(p.priority),
34
+ p.sessionId ? p.sessionId.slice(0, 8) : `${colors.dim}global${colors.reset}`,
35
+ formatDate(p.createdAt),
36
+ ]);
37
+
38
+ console.log(table(["ID", "NAME", "ENABLED", "PRI", "SCOPE", "CREATED"], rows));
39
+ console.log(`\n${colors.bold}Total:${colors.reset} ${data.policies.length} policy(ies)`);
40
+ }
41
+
42
+ async function getPolicy(args: string[]): Promise<void> {
43
+ const socket = getSocket(args);
44
+ const id = getPositional(args, 0);
45
+ if (!id) throw new Error("policy ID is required");
46
+
47
+ const data = await daemonJson<{ policy: Policy }>(`/policies/${id}`, { socket });
48
+ const p = data.policy;
49
+
50
+ console.log(`${colors.bold}Policy ${p.id.slice(0, 8)}${colors.reset}`);
51
+ info("ID", p.id);
52
+ info("Name", p.name);
53
+ if (p.description) info("Description", p.description);
54
+ info(
55
+ "Enabled",
56
+ p.enabled ? `${colors.green}yes${colors.reset}` : `${colors.red}no${colors.reset}`
57
+ );
58
+ info("Priority", String(p.priority));
59
+ info("Scope", p.sessionId ? `session ${p.sessionId.slice(0, 8)}...` : "global");
60
+ info("Created", formatDate(p.createdAt));
61
+ info("Updated", formatDate(p.updatedAt));
62
+ console.log(`\n${colors.bold}Rules:${colors.reset}`);
63
+ console.log(JSON.stringify(p.rules, null, 2));
64
+ }
65
+
66
+ async function createPolicy(args: string[]): Promise<void> {
67
+ const socket = getSocket(args);
68
+ const name = getOption(args, "name");
69
+ const rulesStr = getOption(args, "rules");
70
+ const priority = getOption(args, "priority");
71
+ const sessionId = getOption(args, "session");
72
+
73
+ if (!name) throw new Error("--name is required");
74
+ if (!rulesStr) throw new Error("--rules is required (JSON string)");
75
+
76
+ let rules: unknown;
77
+ try {
78
+ rules = JSON.parse(rulesStr);
79
+ } catch {
80
+ throw new Error("--rules must be valid JSON");
81
+ }
82
+
83
+ const body: Record<string, unknown> = {
84
+ id: crypto.randomUUID(),
85
+ name,
86
+ rules,
87
+ enabled: true,
88
+ priority: priority ? Number.parseInt(priority, 10) : 0,
89
+ sessionId: sessionId ?? null,
90
+ };
91
+
92
+ const data = await daemonPost<{ policy: Policy }>("/policies", body, { socket });
93
+
94
+ success(
95
+ `Policy created: ${colors.bold}${data.policy.name}${colors.reset} (ID: ${data.policy.id})`
96
+ );
97
+ }
98
+
99
+ async function updatePolicy(args: string[]): Promise<void> {
100
+ const socket = getSocket(args);
101
+ const id = getPositional(args, 0);
102
+ if (!id) throw new Error("policy ID is required");
103
+
104
+ const body: Record<string, unknown> = {};
105
+ const name = getOption(args, "name");
106
+ const rulesStr = getOption(args, "rules");
107
+ const priority = getOption(args, "priority");
108
+ const enabled = getOption(args, "enabled");
109
+
110
+ if (name) body.name = name;
111
+ if (rulesStr) {
112
+ try {
113
+ body.rules = JSON.parse(rulesStr);
114
+ } catch {
115
+ throw new Error("--rules must be valid JSON");
116
+ }
117
+ }
118
+ if (priority) body.priority = Number.parseInt(priority, 10);
119
+ if (enabled !== undefined) body.enabled = enabled === "true";
120
+
121
+ if (Object.keys(body).length === 0) {
122
+ throw new Error(
123
+ "at least one field to update is required (--name, --rules, --priority, --enabled)"
124
+ );
125
+ }
126
+
127
+ await daemonFetch(`/policies/${id}`, {
128
+ method: "PUT",
129
+ headers: { "Content-Type": "application/json" },
130
+ body: JSON.stringify(body),
131
+ socket,
132
+ });
133
+
134
+ success(`Policy #${id} updated`);
135
+ }
136
+
137
+ async function deletePolicy(args: string[]): Promise<void> {
138
+ const socket = getSocket(args);
139
+ const id = getPositional(args, 0);
140
+ if (!id) throw new Error("policy ID is required");
141
+
142
+ await daemonFetch(`/policies/${id}`, { method: "DELETE", socket });
143
+
144
+ success(`Policy #${id} deleted`);
145
+ }
146
+
147
+ async function togglePolicy(args: string[]): Promise<void> {
148
+ const socket = getSocket(args);
149
+ const id = getPositional(args, 0);
150
+ if (!id) throw new Error("policy ID is required");
151
+
152
+ // Get current state
153
+ const data = await daemonJson<{ policy: Policy }>(`/policies/${id}`, { socket });
154
+ const newEnabled = !data.policy.enabled;
155
+
156
+ await daemonFetch(`/policies/${id}`, {
157
+ method: "PUT",
158
+ headers: { "Content-Type": "application/json" },
159
+ body: JSON.stringify({ enabled: newEnabled }),
160
+ socket,
161
+ });
162
+
163
+ const state = newEnabled
164
+ ? `${colors.green}enabled${colors.reset}`
165
+ : `${colors.red}disabled${colors.reset}`;
166
+ success(`Policy #${id} ${state}`);
167
+ }
168
+
169
+ async function defaultPolicyAction(args: string[]): Promise<void> {
170
+ const socket = getSocket(args);
171
+ const action = getPositional(args, 0);
172
+
173
+ if (!action) {
174
+ // Show current default
175
+ const data = await daemonJson<{ settings: { defaultPolicyAction: string } }>(
176
+ "/settings/daemon",
177
+ { socket }
178
+ );
179
+ info("Default policy action", data.settings.defaultPolicyAction);
180
+ return;
181
+ }
182
+
183
+ if (action !== "ask" && action !== "deny") {
184
+ throw new Error('action must be "ask" or "deny"');
185
+ }
186
+
187
+ await daemonFetch("/settings/daemon", {
188
+ method: "PUT",
189
+ headers: { "Content-Type": "application/json" },
190
+ body: JSON.stringify({ defaultPolicyAction: action }),
191
+ socket,
192
+ });
193
+
194
+ success(`Default policy action set to ${colors.bold}${action}${colors.reset}`);
195
+ }
196
+
197
+ export async function runPolicyCommand(args: string[]): Promise<void> {
198
+ if (args.length === 0) {
199
+ throw new Error("subcommand required (list, get, create, update, delete, toggle, default)");
200
+ }
201
+
202
+ const subcommand = args[0];
203
+ const subArgs = args.slice(1);
204
+
205
+ switch (subcommand) {
206
+ case "list":
207
+ case "ls":
208
+ return listPolicies(subArgs);
209
+ case "get":
210
+ case "show":
211
+ return getPolicy(subArgs);
212
+ case "create":
213
+ case "add":
214
+ return createPolicy(subArgs);
215
+ case "update":
216
+ return updatePolicy(subArgs);
217
+ case "delete":
218
+ case "rm":
219
+ return deletePolicy(subArgs);
220
+ case "toggle":
221
+ return togglePolicy(subArgs);
222
+ case "default":
223
+ return defaultPolicyAction(subArgs);
224
+ default:
225
+ throw new Error(`Unknown policy subcommand: ${subcommand}`);
226
+ }
227
+ }
@@ -0,0 +1,114 @@
1
+ import { readErrorJson, readJson, responseErrorMessage } from "../lib/api";
2
+ import { getRequiredValue } from "../lib/args";
3
+ import { colors, table } from "../lib/format";
4
+
5
+ type OutputFormat = "table" | "json";
6
+
7
+ interface ProviderCapabilities {
8
+ nativeHooks: boolean;
9
+ supportsDangerousMode: boolean;
10
+ supportsModelSwitch: boolean;
11
+ supportsTranscriptTailing: boolean;
12
+ supportsTmuxAdoption: boolean;
13
+ policyChannel: "native-hooks" | "input-preflight" | "none";
14
+ metricsChannel: "transcript" | "pty" | "none";
15
+ }
16
+
17
+ interface ProviderInfo {
18
+ id: string;
19
+ label: string;
20
+ runtime: "tmux" | "pty";
21
+ capabilities: ProviderCapabilities;
22
+ }
23
+
24
+ export interface ProvidersOptions {
25
+ socket: string;
26
+ format: OutputFormat;
27
+ }
28
+
29
+ const VALID_FORMATS: OutputFormat[] = ["table", "json"];
30
+
31
+ export function parseProvidersOptions(args: string[]): ProvidersOptions {
32
+ let socket = "/tmp/codepiper.sock";
33
+ let format: OutputFormat = "table";
34
+
35
+ for (let i = 0; i < args.length; i++) {
36
+ const arg = args[i];
37
+ if (arg === undefined) {
38
+ continue;
39
+ }
40
+
41
+ if (arg === "--socket" || arg === "-s") {
42
+ socket = getRequiredValue(args, i, arg);
43
+ i++;
44
+ continue;
45
+ }
46
+
47
+ if (arg === "--format" || arg === "-f") {
48
+ const formatValue = getRequiredValue(args, i, arg);
49
+ i++;
50
+ if (!VALID_FORMATS.includes(formatValue as OutputFormat)) {
51
+ throw new Error(
52
+ `Invalid format: ${formatValue}. Valid options: ${VALID_FORMATS.join(", ")}`
53
+ );
54
+ }
55
+ format = formatValue as OutputFormat;
56
+ }
57
+ }
58
+
59
+ return { socket, format };
60
+ }
61
+
62
+ export async function listProviders(options: ProvidersOptions): Promise<ProviderInfo[]> {
63
+ try {
64
+ const response = await fetch("http://localhost/providers", {
65
+ unix: options.socket,
66
+ method: "GET",
67
+ });
68
+
69
+ if (!response.ok) {
70
+ const errorData = await readErrorJson(response);
71
+ throw new Error(responseErrorMessage(response, errorData));
72
+ }
73
+
74
+ const data = await readJson<{ providers?: ProviderInfo[] }>(response);
75
+ return data.providers ?? [];
76
+ } catch (error: any) {
77
+ if (error.code === "ENOENT" || error.message?.includes("ENOENT")) {
78
+ throw new Error(`Failed to connect to daemon at ${options.socket}. Is the daemon running?`);
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ function printProviderTable(providers: ProviderInfo[]): void {
85
+ if (providers.length === 0) {
86
+ console.log(`${colors.dim}No providers reported by daemon.${colors.reset}`);
87
+ return;
88
+ }
89
+
90
+ const rows = providers.map((provider) => [
91
+ provider.id,
92
+ provider.runtime,
93
+ provider.capabilities.nativeHooks ? "yes" : "no",
94
+ provider.capabilities.policyChannel,
95
+ provider.capabilities.metricsChannel,
96
+ provider.capabilities.supportsModelSwitch ? "yes" : "no",
97
+ provider.capabilities.supportsDangerousMode ? "yes" : "no",
98
+ ]);
99
+
100
+ console.log(`${colors.bold}Available Providers${colors.reset}\n`);
101
+ console.log(table(["ID", "RUNTIME", "HOOKS", "POLICY", "METRICS", "MODEL", "DANGEROUS"], rows));
102
+ }
103
+
104
+ export async function runProvidersCommand(args: string[]): Promise<void> {
105
+ const options = parseProvidersOptions(args);
106
+ const providers = await listProviders(options);
107
+
108
+ if (options.format === "json") {
109
+ console.log(JSON.stringify(providers, null, 2));
110
+ return;
111
+ }
112
+
113
+ printProviderTable(providers);
114
+ }
@@ -0,0 +1,34 @@
1
+ import { daemonPost } from "../lib/api";
2
+ import { getSocket } from "../lib/args";
3
+ import { colors, success } from "../lib/format";
4
+
5
+ export async function runResizeCommand(args: string[]): Promise<void> {
6
+ const positionals = args.filter((a) => !a.startsWith("-"));
7
+
8
+ if (positionals.length < 3) {
9
+ throw new Error("Usage: codepiper resize <session-id> <cols> <rows>");
10
+ }
11
+
12
+ const sessionId = positionals[0];
13
+ const colsStr = positionals[1];
14
+ const rowsStr = positionals[2];
15
+
16
+ if (sessionId === undefined || colsStr === undefined || rowsStr === undefined) {
17
+ throw new Error("Usage: codepiper resize <session-id> <cols> <rows>");
18
+ }
19
+
20
+ const cols = Number.parseInt(colsStr, 10);
21
+ const rows = Number.parseInt(rowsStr, 10);
22
+
23
+ if (Number.isNaN(cols) || Number.isNaN(rows) || cols < 1 || rows < 1) {
24
+ throw new Error("cols and rows must be positive integers");
25
+ }
26
+
27
+ const socket = getSocket(args);
28
+
29
+ await daemonPost(`/sessions/${sessionId}/resize`, { cols, rows }, { socket });
30
+
31
+ success(
32
+ `Session ${colors.cyan}${sessionId.slice(0, 8)}...${colors.reset} resized to ${cols}x${rows}`
33
+ );
34
+ }