@gricha/perry 0.3.15 → 0.3.17

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.
@@ -260,6 +260,58 @@ export function createRouter(ctx) {
260
260
  mapErrorToORPC(err, 'Failed to clone workspace');
261
261
  }
262
262
  });
263
+ const execInWorkspace = os
264
+ .input(z.object({
265
+ name: z.string(),
266
+ command: z.union([z.string(), z.array(z.string())]),
267
+ timeout: z.number().optional(),
268
+ }))
269
+ .output(z.object({
270
+ stdout: z.string(),
271
+ stderr: z.string(),
272
+ exitCode: z.number(),
273
+ }))
274
+ .handler(async ({ input }) => {
275
+ const workspace = await ctx.workspaces.get(input.name);
276
+ if (!workspace) {
277
+ throw new ORPCError('NOT_FOUND', { message: 'Workspace not found' });
278
+ }
279
+ if (workspace.status !== 'running') {
280
+ throw new ORPCError('PRECONDITION_FAILED', { message: 'Workspace is not running' });
281
+ }
282
+ const containerName = getContainerName(input.name);
283
+ const commandArray = Array.isArray(input.command)
284
+ ? input.command
285
+ : ['/bin/sh', '-c', input.command];
286
+ try {
287
+ const execPromise = execInContainer(containerName, commandArray, { user: 'workspace' });
288
+ let result;
289
+ if (input.timeout) {
290
+ const timeoutPromise = new Promise((_, reject) => {
291
+ setTimeout(() => {
292
+ reject(new Error(`Command execution timed out after ${input.timeout}ms`));
293
+ }, input.timeout);
294
+ });
295
+ result = await Promise.race([execPromise, timeoutPromise]);
296
+ }
297
+ else {
298
+ result = await execPromise;
299
+ }
300
+ return {
301
+ stdout: result.stdout,
302
+ stderr: result.stderr,
303
+ exitCode: result.exitCode,
304
+ };
305
+ }
306
+ catch (err) {
307
+ if (err instanceof Error && err.message.includes('timed out')) {
308
+ throw new ORPCError('TIMEOUT', { message: err.message });
309
+ }
310
+ throw new ORPCError('INTERNAL_SERVER_ERROR', {
311
+ message: `Failed to execute command: ${err.message}`,
312
+ });
313
+ }
314
+ });
263
315
  const getInfo = os.handler(async () => {
264
316
  let dockerVersion = 'unknown';
265
317
  try {
@@ -1125,6 +1177,7 @@ export function createRouter(ctx) {
1125
1177
  getPortForwards: getPortForwards,
1126
1178
  setPortForwards: setPortForwards,
1127
1179
  updateWorker: updateWorker,
1180
+ exec: execInWorkspace,
1128
1181
  },
1129
1182
  sessions: {
1130
1183
  list: listSessions,
package/dist/perry-worker CHANGED
Binary file
@@ -137,6 +137,37 @@ export class WorkspaceManager {
137
137
  tempPrefix: 'ws-gitconfig',
138
138
  });
139
139
  }
140
+ async syncEnvironmentFile(containerName) {
141
+ const env = {
142
+ ...this.config.credentials.env,
143
+ };
144
+ if (this.config.agents?.github?.token) {
145
+ env.GITHUB_TOKEN = this.config.agents.github.token;
146
+ }
147
+ if (this.config.agents?.claude_code?.oauth_token) {
148
+ env.CLAUDE_CODE_OAUTH_TOKEN = this.config.agents.claude_code.oauth_token;
149
+ }
150
+ if (Object.keys(env).length === 0) {
151
+ return;
152
+ }
153
+ const lines = Object.entries(env)
154
+ .map(([key, value]) => {
155
+ const escaped = value.includes(' ') || value.includes('"') || value.includes("'")
156
+ ? `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
157
+ : value;
158
+ return `${key}=${escaped}`;
159
+ })
160
+ .sort();
161
+ const content = lines.join('\n') + '\n';
162
+ const tempFile = path.join(os.tmpdir(), `ws-env-${Date.now()}`);
163
+ try {
164
+ await fs.writeFile(tempFile, content);
165
+ await docker.copyToContainer(containerName, tempFile, '/etc/environment');
166
+ }
167
+ finally {
168
+ await fs.unlink(tempFile).catch(() => { });
169
+ }
170
+ }
140
171
  async setupSSHKeys(containerName, workspaceName) {
141
172
  if (!this.config.ssh) {
142
173
  return;
@@ -189,6 +220,7 @@ export class WorkspaceManager {
189
220
  async setupWorkspaceCredentials(containerName, workspaceName) {
190
221
  await this.copyGitConfig(containerName);
191
222
  await this.copyCredentialFiles(containerName);
223
+ await this.syncEnvironmentFile(containerName);
192
224
  await syncAllAgents(containerName, this.config);
193
225
  await this.copyPerryWorker(containerName);
194
226
  await this.startWorkerServer(containerName);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gricha/perry",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "Self-contained CLI for spinning up Docker-in-Docker development environments with SSH and proxy helpers.",
5
5
  "type": "module",
6
6
  "bin": {