offwatch 0.5.11 → 0.5.13

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 (95) hide show
  1. package/README.md +132 -178
  2. package/bin/offwatch.js +6 -7
  3. package/lib/downloader.js +112 -0
  4. package/package.json +17 -7
  5. package/postinstall.js +21 -0
  6. package/src/__tests__/agent-jwt-env.test.ts +0 -79
  7. package/src/__tests__/allowed-hostname.test.ts +0 -80
  8. package/src/__tests__/auth-command-registration.test.ts +0 -16
  9. package/src/__tests__/board-auth.test.ts +0 -53
  10. package/src/__tests__/common.test.ts +0 -98
  11. package/src/__tests__/company-delete.test.ts +0 -95
  12. package/src/__tests__/company-import-export-e2e.test.ts +0 -502
  13. package/src/__tests__/company-import-url.test.ts +0 -74
  14. package/src/__tests__/company-import-zip.test.ts +0 -44
  15. package/src/__tests__/company.test.ts +0 -599
  16. package/src/__tests__/context.test.ts +0 -70
  17. package/src/__tests__/data-dir.test.ts +0 -79
  18. package/src/__tests__/doctor.test.ts +0 -102
  19. package/src/__tests__/feedback.test.ts +0 -177
  20. package/src/__tests__/helpers/embedded-postgres.ts +0 -6
  21. package/src/__tests__/helpers/zip.ts +0 -87
  22. package/src/__tests__/home-paths.test.ts +0 -44
  23. package/src/__tests__/http.test.ts +0 -106
  24. package/src/__tests__/network-bind.test.ts +0 -62
  25. package/src/__tests__/onboard.test.ts +0 -166
  26. package/src/__tests__/routines.test.ts +0 -249
  27. package/src/__tests__/telemetry.test.ts +0 -117
  28. package/src/__tests__/worktree-merge-history.test.ts +0 -492
  29. package/src/__tests__/worktree.test.ts +0 -982
  30. package/src/adapters/http/format-event.ts +0 -4
  31. package/src/adapters/http/index.ts +0 -7
  32. package/src/adapters/index.ts +0 -2
  33. package/src/adapters/process/format-event.ts +0 -4
  34. package/src/adapters/process/index.ts +0 -7
  35. package/src/adapters/registry.ts +0 -63
  36. package/src/checks/agent-jwt-secret-check.ts +0 -40
  37. package/src/checks/config-check.ts +0 -33
  38. package/src/checks/database-check.ts +0 -59
  39. package/src/checks/deployment-auth-check.ts +0 -88
  40. package/src/checks/index.ts +0 -18
  41. package/src/checks/llm-check.ts +0 -82
  42. package/src/checks/log-check.ts +0 -30
  43. package/src/checks/path-resolver.ts +0 -1
  44. package/src/checks/port-check.ts +0 -24
  45. package/src/checks/secrets-check.ts +0 -146
  46. package/src/checks/storage-check.ts +0 -51
  47. package/src/client/board-auth.ts +0 -282
  48. package/src/client/command-label.ts +0 -4
  49. package/src/client/context.ts +0 -175
  50. package/src/client/http.ts +0 -255
  51. package/src/commands/allowed-hostname.ts +0 -40
  52. package/src/commands/auth-bootstrap-ceo.ts +0 -138
  53. package/src/commands/client/activity.ts +0 -71
  54. package/src/commands/client/agent.ts +0 -315
  55. package/src/commands/client/approval.ts +0 -259
  56. package/src/commands/client/auth.ts +0 -113
  57. package/src/commands/client/common.ts +0 -221
  58. package/src/commands/client/company.ts +0 -1578
  59. package/src/commands/client/context.ts +0 -125
  60. package/src/commands/client/dashboard.ts +0 -34
  61. package/src/commands/client/feedback.ts +0 -645
  62. package/src/commands/client/issue.ts +0 -411
  63. package/src/commands/client/plugin.ts +0 -374
  64. package/src/commands/client/zip.ts +0 -129
  65. package/src/commands/configure.ts +0 -201
  66. package/src/commands/db-backup.ts +0 -102
  67. package/src/commands/doctor.ts +0 -203
  68. package/src/commands/env.ts +0 -411
  69. package/src/commands/heartbeat-run.ts +0 -344
  70. package/src/commands/onboard.ts +0 -692
  71. package/src/commands/routines.ts +0 -352
  72. package/src/commands/run.ts +0 -216
  73. package/src/commands/worktree-lib.ts +0 -279
  74. package/src/commands/worktree-merge-history-lib.ts +0 -764
  75. package/src/commands/worktree.ts +0 -2876
  76. package/src/config/data-dir.ts +0 -48
  77. package/src/config/env.ts +0 -125
  78. package/src/config/home.ts +0 -80
  79. package/src/config/hostnames.ts +0 -26
  80. package/src/config/schema.ts +0 -30
  81. package/src/config/secrets-key.ts +0 -48
  82. package/src/config/server-bind.ts +0 -183
  83. package/src/config/store.ts +0 -120
  84. package/src/index.ts +0 -182
  85. package/src/prompts/database.ts +0 -157
  86. package/src/prompts/llm.ts +0 -43
  87. package/src/prompts/logging.ts +0 -37
  88. package/src/prompts/secrets.ts +0 -99
  89. package/src/prompts/server.ts +0 -221
  90. package/src/prompts/storage.ts +0 -146
  91. package/src/telemetry.ts +0 -49
  92. package/src/utils/banner.ts +0 -24
  93. package/src/utils/net.ts +0 -18
  94. package/src/utils/path-resolver.ts +0 -25
  95. package/src/version.ts +0 -10
@@ -1,411 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import pc from "picocolors";
3
- import type { PaperclipConfig } from "../config/schema.js";
4
- import { configExists, readConfig, resolveConfigPath } from "../config/store.js";
5
- import {
6
- readAgentJwtSecretFromEnv,
7
- readAgentJwtSecretFromEnvFile,
8
- resolveAgentJwtEnvFile,
9
- } from "../config/env.js";
10
- import {
11
- resolveDefaultSecretsKeyFilePath,
12
- resolveDefaultStorageDir,
13
- resolvePaperclipInstanceId,
14
- } from "../config/home.js";
15
-
16
- type EnvSource = "env" | "config" | "file" | "default" | "missing";
17
-
18
- type EnvVarRow = {
19
- key: string;
20
- value: string;
21
- source: EnvSource;
22
- required: boolean;
23
- note: string;
24
- };
25
-
26
- const DEFAULT_AGENT_JWT_TTL_SECONDS = "172800";
27
- const DEFAULT_AGENT_JWT_ISSUER = "paperclip";
28
- const DEFAULT_AGENT_JWT_AUDIENCE = "paperclip-api";
29
- const DEFAULT_HEARTBEAT_SCHEDULER_INTERVAL_MS = "30000";
30
- const DEFAULT_SECRETS_PROVIDER = "local_encrypted";
31
- const DEFAULT_STORAGE_PROVIDER = "local_disk";
32
- function defaultSecretsKeyFilePath(): string {
33
- return resolveDefaultSecretsKeyFilePath(resolvePaperclipInstanceId());
34
- }
35
- function defaultStorageBaseDir(): string {
36
- return resolveDefaultStorageDir(resolvePaperclipInstanceId());
37
- }
38
-
39
- export async function envCommand(opts: { config?: string }): Promise<void> {
40
- p.intro(pc.bgCyan(pc.black(" paperclip env ")));
41
-
42
- const configPath = resolveConfigPath(opts.config);
43
- let config: PaperclipConfig | null = null;
44
- let configReadError: string | null = null;
45
-
46
- if (configExists(opts.config)) {
47
- p.log.message(pc.dim(`Config file: ${configPath}`));
48
- try {
49
- config = readConfig(opts.config);
50
- } catch (err) {
51
- configReadError = err instanceof Error ? err.message : String(err);
52
- p.log.message(pc.yellow(`Could not parse config: ${configReadError}`));
53
- }
54
- } else {
55
- p.log.message(pc.dim(`Config file missing: ${configPath}`));
56
- }
57
-
58
- const rows = collectDeploymentEnvRows(config, configPath);
59
- const missingRequired = rows.filter((row) => row.required && row.source === "missing");
60
- const sortedRows = rows.sort((a, b) => Number(b.required) - Number(a.required) || a.key.localeCompare(b.key));
61
-
62
- const requiredRows = sortedRows.filter((row) => row.required);
63
- const optionalRows = sortedRows.filter((row) => !row.required);
64
-
65
- const formatSection = (title: string, entries: EnvVarRow[]) => {
66
- if (entries.length === 0) return;
67
-
68
- p.log.message(pc.bold(title));
69
- for (const entry of entries) {
70
- const status = entry.source === "missing" ? pc.red("missing") : entry.source === "default" ? pc.yellow("default") : pc.green("set");
71
- const sourceNote = {
72
- env: "environment",
73
- config: "config",
74
- file: "file",
75
- default: "default",
76
- missing: "missing",
77
- }[entry.source];
78
- p.log.message(
79
- `${pc.cyan(entry.key)} ${status.padEnd(7)} ${pc.dim(`[${sourceNote}] ${entry.note}`)}${entry.source === "missing" ? "" : ` ${pc.dim("=>")} ${pc.white(quoteShellValue(entry.value))}`}`,
80
- );
81
- }
82
- };
83
-
84
- formatSection("Required environment variables", requiredRows);
85
- formatSection("Optional environment variables", optionalRows);
86
-
87
- const exportRows = rows.map((row) => (row.source === "missing" ? { ...row, value: "<set-this-value>" } : row));
88
- const uniqueRows = uniqueByKey(exportRows);
89
- const exportBlock = uniqueRows.map((row) => `export ${row.key}=${quoteShellValue(row.value)}`).join("\n");
90
-
91
- if (configReadError) {
92
- p.log.error(`Could not load config cleanly: ${configReadError}`);
93
- }
94
-
95
- p.note(
96
- exportBlock || "No values detected. Set required variables manually.",
97
- "Deployment export block",
98
- );
99
-
100
- if (missingRequired.length > 0) {
101
- p.log.message(
102
- pc.yellow(
103
- `Missing required values: ${missingRequired.map((row) => row.key).join(", ")}. Set these before deployment.`,
104
- ),
105
- );
106
- } else {
107
- p.log.message(pc.green("All required deployment variables are present."));
108
- }
109
- p.outro("Done");
110
- }
111
-
112
- function collectDeploymentEnvRows(config: PaperclipConfig | null, configPath: string): EnvVarRow[] {
113
- const agentJwtEnvFile = resolveAgentJwtEnvFile(configPath);
114
- const jwtEnv = readAgentJwtSecretFromEnv(configPath);
115
- const jwtFile = jwtEnv ? null : readAgentJwtSecretFromEnvFile(agentJwtEnvFile);
116
- const jwtSource = jwtEnv ? "env" : jwtFile ? "file" : "missing";
117
-
118
- const dbUrl = process.env.DATABASE_URL ?? config?.database?.connectionString ?? "";
119
- const databaseMode = config?.database?.mode ?? "embedded-postgres";
120
- const dbUrlSource: EnvSource = process.env.DATABASE_URL ? "env" : config?.database?.connectionString ? "config" : "missing";
121
- const publicUrl =
122
- process.env.PAPERCLIP_PUBLIC_URL ??
123
- process.env.PAPERCLIP_AUTH_PUBLIC_BASE_URL ??
124
- process.env.BETTER_AUTH_URL ??
125
- process.env.BETTER_AUTH_BASE_URL ??
126
- config?.auth?.publicBaseUrl ??
127
- "";
128
- const publicUrlSource: EnvSource =
129
- process.env.PAPERCLIP_PUBLIC_URL
130
- ? "env"
131
- : process.env.PAPERCLIP_AUTH_PUBLIC_BASE_URL || process.env.BETTER_AUTH_URL || process.env.BETTER_AUTH_BASE_URL
132
- ? "env"
133
- : config?.auth?.publicBaseUrl
134
- ? "config"
135
- : "missing";
136
- let trustedOriginsDefault = "";
137
- if (publicUrl) {
138
- try {
139
- trustedOriginsDefault = new URL(publicUrl).origin;
140
- } catch {
141
- trustedOriginsDefault = "";
142
- }
143
- }
144
-
145
- const heartbeatInterval = process.env.HEARTBEAT_SCHEDULER_INTERVAL_MS ?? DEFAULT_HEARTBEAT_SCHEDULER_INTERVAL_MS;
146
- const heartbeatEnabled = process.env.HEARTBEAT_SCHEDULER_ENABLED ?? "true";
147
- const secretsProvider =
148
- process.env.PAPERCLIP_SECRETS_PROVIDER ??
149
- config?.secrets?.provider ??
150
- DEFAULT_SECRETS_PROVIDER;
151
- const secretsStrictMode =
152
- process.env.PAPERCLIP_SECRETS_STRICT_MODE ??
153
- String(config?.secrets?.strictMode ?? false);
154
- const secretsKeyFilePath =
155
- process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE ??
156
- config?.secrets?.localEncrypted?.keyFilePath ??
157
- defaultSecretsKeyFilePath();
158
- const storageProvider =
159
- process.env.PAPERCLIP_STORAGE_PROVIDER ??
160
- config?.storage?.provider ??
161
- DEFAULT_STORAGE_PROVIDER;
162
- const storageLocalDir =
163
- process.env.PAPERCLIP_STORAGE_LOCAL_DIR ??
164
- config?.storage?.localDisk?.baseDir ??
165
- defaultStorageBaseDir();
166
- const storageS3Bucket =
167
- process.env.PAPERCLIP_STORAGE_S3_BUCKET ??
168
- config?.storage?.s3?.bucket ??
169
- "paperclip";
170
- const storageS3Region =
171
- process.env.PAPERCLIP_STORAGE_S3_REGION ??
172
- config?.storage?.s3?.region ??
173
- "us-east-1";
174
- const storageS3Endpoint =
175
- process.env.PAPERCLIP_STORAGE_S3_ENDPOINT ??
176
- config?.storage?.s3?.endpoint ??
177
- "";
178
- const storageS3Prefix =
179
- process.env.PAPERCLIP_STORAGE_S3_PREFIX ??
180
- config?.storage?.s3?.prefix ??
181
- "";
182
- const storageS3ForcePathStyle =
183
- process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE ??
184
- String(config?.storage?.s3?.forcePathStyle ?? false);
185
-
186
- const rows: EnvVarRow[] = [
187
- {
188
- key: "PAPERCLIP_AGENT_JWT_SECRET",
189
- value: jwtEnv ?? jwtFile ?? "",
190
- source: jwtSource,
191
- required: true,
192
- note:
193
- jwtSource === "missing"
194
- ? "Generate during onboard or set manually (required for local adapter authentication)"
195
- : jwtSource === "env"
196
- ? "Set in process environment"
197
- : `Set in ${agentJwtEnvFile}`,
198
- },
199
- {
200
- key: "DATABASE_URL",
201
- value: dbUrl,
202
- source: dbUrlSource,
203
- required: true,
204
- note:
205
- databaseMode === "postgres"
206
- ? "Configured for postgres mode (required)"
207
- : "Required for live deployment with managed PostgreSQL",
208
- },
209
- {
210
- key: "PORT",
211
- value:
212
- process.env.PORT ??
213
- (config?.server?.port !== undefined ? String(config.server.port) : "3100"),
214
- source: process.env.PORT ? "env" : config?.server?.port !== undefined ? "config" : "default",
215
- required: false,
216
- note: "HTTP listen port",
217
- },
218
- {
219
- key: "PAPERCLIP_PUBLIC_URL",
220
- value: publicUrl,
221
- source: publicUrlSource,
222
- required: false,
223
- note: "Canonical public URL for auth/callback/invite origin wiring",
224
- },
225
- {
226
- key: "BETTER_AUTH_TRUSTED_ORIGINS",
227
- value: process.env.BETTER_AUTH_TRUSTED_ORIGINS ?? trustedOriginsDefault,
228
- source: process.env.BETTER_AUTH_TRUSTED_ORIGINS
229
- ? "env"
230
- : trustedOriginsDefault
231
- ? "default"
232
- : "missing",
233
- required: false,
234
- note: "Comma-separated auth origin allowlist (auto-derived from PAPERCLIP_PUBLIC_URL when possible)",
235
- },
236
- {
237
- key: "PAPERCLIP_AGENT_JWT_TTL_SECONDS",
238
- value: process.env.PAPERCLIP_AGENT_JWT_TTL_SECONDS ?? DEFAULT_AGENT_JWT_TTL_SECONDS,
239
- source: process.env.PAPERCLIP_AGENT_JWT_TTL_SECONDS ? "env" : "default",
240
- required: false,
241
- note: "JWT lifetime in seconds",
242
- },
243
- {
244
- key: "PAPERCLIP_AGENT_JWT_ISSUER",
245
- value: process.env.PAPERCLIP_AGENT_JWT_ISSUER ?? DEFAULT_AGENT_JWT_ISSUER,
246
- source: process.env.PAPERCLIP_AGENT_JWT_ISSUER ? "env" : "default",
247
- required: false,
248
- note: "JWT issuer",
249
- },
250
- {
251
- key: "PAPERCLIP_AGENT_JWT_AUDIENCE",
252
- value: process.env.PAPERCLIP_AGENT_JWT_AUDIENCE ?? DEFAULT_AGENT_JWT_AUDIENCE,
253
- source: process.env.PAPERCLIP_AGENT_JWT_AUDIENCE ? "env" : "default",
254
- required: false,
255
- note: "JWT audience",
256
- },
257
- {
258
- key: "HEARTBEAT_SCHEDULER_INTERVAL_MS",
259
- value: heartbeatInterval,
260
- source: process.env.HEARTBEAT_SCHEDULER_INTERVAL_MS ? "env" : "default",
261
- required: false,
262
- note: "Heartbeat worker interval in ms",
263
- },
264
- {
265
- key: "HEARTBEAT_SCHEDULER_ENABLED",
266
- value: heartbeatEnabled,
267
- source: process.env.HEARTBEAT_SCHEDULER_ENABLED ? "env" : "default",
268
- required: false,
269
- note: "Set to `false` to disable timer scheduling",
270
- },
271
- {
272
- key: "PAPERCLIP_SECRETS_PROVIDER",
273
- value: secretsProvider,
274
- source: process.env.PAPERCLIP_SECRETS_PROVIDER
275
- ? "env"
276
- : config?.secrets?.provider
277
- ? "config"
278
- : "default",
279
- required: false,
280
- note: "Default provider for new secrets",
281
- },
282
- {
283
- key: "PAPERCLIP_SECRETS_STRICT_MODE",
284
- value: secretsStrictMode,
285
- source: process.env.PAPERCLIP_SECRETS_STRICT_MODE
286
- ? "env"
287
- : config?.secrets?.strictMode !== undefined
288
- ? "config"
289
- : "default",
290
- required: false,
291
- note: "Require secret refs for sensitive env keys",
292
- },
293
- {
294
- key: "PAPERCLIP_SECRETS_MASTER_KEY_FILE",
295
- value: secretsKeyFilePath,
296
- source: process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE
297
- ? "env"
298
- : config?.secrets?.localEncrypted?.keyFilePath
299
- ? "config"
300
- : "default",
301
- required: false,
302
- note: "Path to local encrypted secrets key file",
303
- },
304
- {
305
- key: "PAPERCLIP_STORAGE_PROVIDER",
306
- value: storageProvider,
307
- source: process.env.PAPERCLIP_STORAGE_PROVIDER
308
- ? "env"
309
- : config?.storage?.provider
310
- ? "config"
311
- : "default",
312
- required: false,
313
- note: "Storage provider (local_disk or s3)",
314
- },
315
- {
316
- key: "PAPERCLIP_STORAGE_LOCAL_DIR",
317
- value: storageLocalDir,
318
- source: process.env.PAPERCLIP_STORAGE_LOCAL_DIR
319
- ? "env"
320
- : config?.storage?.localDisk?.baseDir
321
- ? "config"
322
- : "default",
323
- required: false,
324
- note: "Local storage base directory for local_disk provider",
325
- },
326
- {
327
- key: "PAPERCLIP_STORAGE_S3_BUCKET",
328
- value: storageS3Bucket,
329
- source: process.env.PAPERCLIP_STORAGE_S3_BUCKET
330
- ? "env"
331
- : config?.storage?.s3?.bucket
332
- ? "config"
333
- : "default",
334
- required: false,
335
- note: "S3 bucket name for s3 provider",
336
- },
337
- {
338
- key: "PAPERCLIP_STORAGE_S3_REGION",
339
- value: storageS3Region,
340
- source: process.env.PAPERCLIP_STORAGE_S3_REGION
341
- ? "env"
342
- : config?.storage?.s3?.region
343
- ? "config"
344
- : "default",
345
- required: false,
346
- note: "S3 region for s3 provider",
347
- },
348
- {
349
- key: "PAPERCLIP_STORAGE_S3_ENDPOINT",
350
- value: storageS3Endpoint,
351
- source: process.env.PAPERCLIP_STORAGE_S3_ENDPOINT
352
- ? "env"
353
- : config?.storage?.s3?.endpoint
354
- ? "config"
355
- : "default",
356
- required: false,
357
- note: "Optional custom endpoint for S3-compatible providers",
358
- },
359
- {
360
- key: "PAPERCLIP_STORAGE_S3_PREFIX",
361
- value: storageS3Prefix,
362
- source: process.env.PAPERCLIP_STORAGE_S3_PREFIX
363
- ? "env"
364
- : config?.storage?.s3?.prefix
365
- ? "config"
366
- : "default",
367
- required: false,
368
- note: "Optional object key prefix",
369
- },
370
- {
371
- key: "PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE",
372
- value: storageS3ForcePathStyle,
373
- source: process.env.PAPERCLIP_STORAGE_S3_FORCE_PATH_STYLE
374
- ? "env"
375
- : config?.storage?.s3?.forcePathStyle !== undefined
376
- ? "config"
377
- : "default",
378
- required: false,
379
- note: "Set true for path-style access on compatible providers",
380
- },
381
- ];
382
-
383
- const defaultConfigPath = resolveConfigPath();
384
- if (process.env.PAPERCLIP_CONFIG || configPath !== defaultConfigPath) {
385
- rows.push({
386
- key: "PAPERCLIP_CONFIG",
387
- value: process.env.PAPERCLIP_CONFIG ?? configPath,
388
- source: process.env.PAPERCLIP_CONFIG ? "env" : "default",
389
- required: false,
390
- note: "Optional path override for config file",
391
- });
392
- }
393
-
394
- return rows;
395
- }
396
-
397
- function uniqueByKey(rows: EnvVarRow[]): EnvVarRow[] {
398
- const seen = new Set<string>();
399
- const result: EnvVarRow[] = [];
400
- for (const row of rows) {
401
- if (seen.has(row.key)) continue;
402
- seen.add(row.key);
403
- result.push(row);
404
- }
405
- return result;
406
- }
407
-
408
- function quoteShellValue(value: string): string {
409
- if (value === "") return "\"\"";
410
- return `'${value.replaceAll("'", "'\\''")}'`;
411
- }