offwatch 0.5.12 → 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.
- package/README.md +132 -178
- package/bin/offwatch.js +6 -7
- package/lib/downloader.js +112 -0
- package/package.json +18 -11
- package/postinstall.js +21 -0
- package/src/__tests__/agent-jwt-env.test.ts +0 -79
- package/src/__tests__/allowed-hostname.test.ts +0 -80
- package/src/__tests__/auth-command-registration.test.ts +0 -16
- package/src/__tests__/board-auth.test.ts +0 -53
- package/src/__tests__/common.test.ts +0 -98
- package/src/__tests__/company-delete.test.ts +0 -95
- package/src/__tests__/company-import-export-e2e.test.ts +0 -502
- package/src/__tests__/company-import-url.test.ts +0 -74
- package/src/__tests__/company-import-zip.test.ts +0 -44
- package/src/__tests__/company.test.ts +0 -599
- package/src/__tests__/context.test.ts +0 -70
- package/src/__tests__/data-dir.test.ts +0 -79
- package/src/__tests__/doctor.test.ts +0 -102
- package/src/__tests__/feedback.test.ts +0 -177
- package/src/__tests__/helpers/embedded-postgres.ts +0 -6
- package/src/__tests__/helpers/zip.ts +0 -87
- package/src/__tests__/home-paths.test.ts +0 -44
- package/src/__tests__/http.test.ts +0 -106
- package/src/__tests__/network-bind.test.ts +0 -62
- package/src/__tests__/onboard.test.ts +0 -166
- package/src/__tests__/routines.test.ts +0 -249
- package/src/__tests__/telemetry.test.ts +0 -117
- package/src/__tests__/worktree-merge-history.test.ts +0 -492
- package/src/__tests__/worktree.test.ts +0 -982
- package/src/adapters/http/format-event.ts +0 -4
- package/src/adapters/http/index.ts +0 -7
- package/src/adapters/index.ts +0 -2
- package/src/adapters/process/format-event.ts +0 -4
- package/src/adapters/process/index.ts +0 -7
- package/src/adapters/registry.ts +0 -63
- package/src/checks/agent-jwt-secret-check.ts +0 -40
- package/src/checks/config-check.ts +0 -33
- package/src/checks/database-check.ts +0 -59
- package/src/checks/deployment-auth-check.ts +0 -88
- package/src/checks/index.ts +0 -18
- package/src/checks/llm-check.ts +0 -82
- package/src/checks/log-check.ts +0 -30
- package/src/checks/path-resolver.ts +0 -1
- package/src/checks/port-check.ts +0 -24
- package/src/checks/secrets-check.ts +0 -146
- package/src/checks/storage-check.ts +0 -51
- package/src/client/board-auth.ts +0 -282
- package/src/client/command-label.ts +0 -4
- package/src/client/context.ts +0 -175
- package/src/client/http.ts +0 -255
- package/src/commands/allowed-hostname.ts +0 -40
- package/src/commands/auth-bootstrap-ceo.ts +0 -138
- package/src/commands/client/activity.ts +0 -71
- package/src/commands/client/agent.ts +0 -315
- package/src/commands/client/approval.ts +0 -259
- package/src/commands/client/auth.ts +0 -113
- package/src/commands/client/common.ts +0 -221
- package/src/commands/client/company.ts +0 -1578
- package/src/commands/client/context.ts +0 -125
- package/src/commands/client/dashboard.ts +0 -34
- package/src/commands/client/feedback.ts +0 -645
- package/src/commands/client/issue.ts +0 -411
- package/src/commands/client/plugin.ts +0 -374
- package/src/commands/client/zip.ts +0 -129
- package/src/commands/configure.ts +0 -201
- package/src/commands/db-backup.ts +0 -102
- package/src/commands/doctor.ts +0 -203
- package/src/commands/env.ts +0 -411
- package/src/commands/heartbeat-run.ts +0 -344
- package/src/commands/onboard.ts +0 -692
- package/src/commands/routines.ts +0 -352
- package/src/commands/run.ts +0 -216
- package/src/commands/worktree-lib.ts +0 -279
- package/src/commands/worktree-merge-history-lib.ts +0 -764
- package/src/commands/worktree.ts +0 -2876
- package/src/config/data-dir.ts +0 -48
- package/src/config/env.ts +0 -125
- package/src/config/home.ts +0 -80
- package/src/config/hostnames.ts +0 -26
- package/src/config/schema.ts +0 -30
- package/src/config/secrets-key.ts +0 -48
- package/src/config/server-bind.ts +0 -183
- package/src/config/store.ts +0 -120
- package/src/index.ts +0 -182
- package/src/prompts/database.ts +0 -157
- package/src/prompts/llm.ts +0 -43
- package/src/prompts/logging.ts +0 -37
- package/src/prompts/secrets.ts +0 -99
- package/src/prompts/server.ts +0 -221
- package/src/prompts/storage.ts +0 -146
- package/src/telemetry.ts +0 -49
- package/src/utils/banner.ts +0 -24
- package/src/utils/net.ts +0 -18
- package/src/utils/path-resolver.ts +0 -25
- package/src/version.ts +0 -10
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { onboard } from "../commands/onboard.js";
|
|
6
|
-
import type { PaperclipConfig } from "../config/schema.js";
|
|
7
|
-
|
|
8
|
-
const ORIGINAL_ENV = { ...process.env };
|
|
9
|
-
|
|
10
|
-
function createExistingConfigFixture() {
|
|
11
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-onboard-"));
|
|
12
|
-
const runtimeRoot = path.join(root, "runtime");
|
|
13
|
-
const configPath = path.join(root, ".paperclip", "config.json");
|
|
14
|
-
const config: PaperclipConfig = {
|
|
15
|
-
$meta: {
|
|
16
|
-
version: 1,
|
|
17
|
-
updatedAt: "2026-03-29T00:00:00.000Z",
|
|
18
|
-
source: "configure",
|
|
19
|
-
},
|
|
20
|
-
database: {
|
|
21
|
-
mode: "embedded-postgres",
|
|
22
|
-
embeddedPostgresDataDir: path.join(runtimeRoot, "db"),
|
|
23
|
-
embeddedPostgresPort: 54329,
|
|
24
|
-
backup: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
intervalMinutes: 60,
|
|
27
|
-
retentionDays: 30,
|
|
28
|
-
dir: path.join(runtimeRoot, "backups"),
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
logging: {
|
|
32
|
-
mode: "file",
|
|
33
|
-
logDir: path.join(runtimeRoot, "logs"),
|
|
34
|
-
},
|
|
35
|
-
server: {
|
|
36
|
-
deploymentMode: "local_trusted",
|
|
37
|
-
exposure: "private",
|
|
38
|
-
host: "127.0.0.1",
|
|
39
|
-
port: 3100,
|
|
40
|
-
allowedHostnames: [],
|
|
41
|
-
serveUi: true,
|
|
42
|
-
},
|
|
43
|
-
auth: {
|
|
44
|
-
baseUrlMode: "auto",
|
|
45
|
-
disableSignUp: false,
|
|
46
|
-
},
|
|
47
|
-
telemetry: {
|
|
48
|
-
enabled: true,
|
|
49
|
-
},
|
|
50
|
-
storage: {
|
|
51
|
-
provider: "local_disk",
|
|
52
|
-
localDisk: {
|
|
53
|
-
baseDir: path.join(runtimeRoot, "storage"),
|
|
54
|
-
},
|
|
55
|
-
s3: {
|
|
56
|
-
bucket: "paperclip",
|
|
57
|
-
region: "us-east-1",
|
|
58
|
-
prefix: "",
|
|
59
|
-
forcePathStyle: false,
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
secrets: {
|
|
63
|
-
provider: "local_encrypted",
|
|
64
|
-
strictMode: false,
|
|
65
|
-
localEncrypted: {
|
|
66
|
-
keyFilePath: path.join(runtimeRoot, "secrets", "master.key"),
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
72
|
-
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
73
|
-
|
|
74
|
-
return { configPath, configText: fs.readFileSync(configPath, "utf8") };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function createFreshConfigPath() {
|
|
78
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-onboard-fresh-"));
|
|
79
|
-
return path.join(root, ".paperclip", "config.json");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
describe("onboard", () => {
|
|
83
|
-
beforeEach(() => {
|
|
84
|
-
process.env = { ...ORIGINAL_ENV };
|
|
85
|
-
delete process.env.PAPERCLIP_AGENT_JWT_SECRET;
|
|
86
|
-
delete process.env.PAPERCLIP_SECRETS_MASTER_KEY;
|
|
87
|
-
delete process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
afterEach(() => {
|
|
91
|
-
process.env = { ...ORIGINAL_ENV };
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("preserves an existing config when rerun without flags", async () => {
|
|
95
|
-
const fixture = createExistingConfigFixture();
|
|
96
|
-
|
|
97
|
-
await onboard({ config: fixture.configPath });
|
|
98
|
-
|
|
99
|
-
expect(fs.readFileSync(fixture.configPath, "utf8")).toBe(fixture.configText);
|
|
100
|
-
expect(fs.existsSync(`${fixture.configPath}.backup`)).toBe(false);
|
|
101
|
-
expect(fs.existsSync(path.join(path.dirname(fixture.configPath), ".env"))).toBe(true);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("preserves an existing config when rerun with --yes", async () => {
|
|
105
|
-
const fixture = createExistingConfigFixture();
|
|
106
|
-
|
|
107
|
-
await onboard({ config: fixture.configPath, yes: true, invokedByRun: true });
|
|
108
|
-
|
|
109
|
-
expect(fs.readFileSync(fixture.configPath, "utf8")).toBe(fixture.configText);
|
|
110
|
-
expect(fs.existsSync(`${fixture.configPath}.backup`)).toBe(false);
|
|
111
|
-
expect(fs.existsSync(path.join(path.dirname(fixture.configPath), ".env"))).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("keeps --yes onboarding on local trusted loopback defaults", async () => {
|
|
115
|
-
const configPath = createFreshConfigPath();
|
|
116
|
-
process.env.HOST = "0.0.0.0";
|
|
117
|
-
process.env.PAPERCLIP_BIND = "lan";
|
|
118
|
-
|
|
119
|
-
await onboard({ config: configPath, yes: true, invokedByRun: true });
|
|
120
|
-
|
|
121
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as PaperclipConfig;
|
|
122
|
-
expect(raw.server.deploymentMode).toBe("local_trusted");
|
|
123
|
-
expect(raw.server.exposure).toBe("private");
|
|
124
|
-
expect(raw.server.bind).toBe("loopback");
|
|
125
|
-
expect(raw.server.host).toBe("127.0.0.1");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("supports authenticated/private quickstart bind presets", async () => {
|
|
129
|
-
const configPath = createFreshConfigPath();
|
|
130
|
-
process.env.PAPERCLIP_TAILNET_BIND_HOST = "100.64.0.8";
|
|
131
|
-
|
|
132
|
-
await onboard({ config: configPath, yes: true, invokedByRun: true, bind: "tailnet" });
|
|
133
|
-
|
|
134
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as PaperclipConfig;
|
|
135
|
-
expect(raw.server.deploymentMode).toBe("authenticated");
|
|
136
|
-
expect(raw.server.exposure).toBe("private");
|
|
137
|
-
expect(raw.server.bind).toBe("tailnet");
|
|
138
|
-
expect(raw.server.host).toBe("100.64.0.8");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("keeps tailnet quickstart on loopback until tailscale is available", async () => {
|
|
142
|
-
const configPath = createFreshConfigPath();
|
|
143
|
-
delete process.env.PAPERCLIP_TAILNET_BIND_HOST;
|
|
144
|
-
|
|
145
|
-
await onboard({ config: configPath, yes: true, invokedByRun: true, bind: "tailnet" });
|
|
146
|
-
|
|
147
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as PaperclipConfig;
|
|
148
|
-
expect(raw.server.deploymentMode).toBe("authenticated");
|
|
149
|
-
expect(raw.server.exposure).toBe("private");
|
|
150
|
-
expect(raw.server.bind).toBe("tailnet");
|
|
151
|
-
expect(raw.server.host).toBe("127.0.0.1");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("ignores deployment env overrides during --yes quickstart", async () => {
|
|
155
|
-
const configPath = createFreshConfigPath();
|
|
156
|
-
process.env.PAPERCLIP_DEPLOYMENT_MODE = "authenticated";
|
|
157
|
-
|
|
158
|
-
await onboard({ config: configPath, yes: true, invokedByRun: true });
|
|
159
|
-
|
|
160
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf8")) as PaperclipConfig;
|
|
161
|
-
expect(raw.server.deploymentMode).toBe("local_trusted");
|
|
162
|
-
expect(raw.server.exposure).toBe("private");
|
|
163
|
-
expect(raw.server.bind).toBe("loopback");
|
|
164
|
-
expect(raw.server.host).toBe("127.0.0.1");
|
|
165
|
-
});
|
|
166
|
-
});
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
6
|
-
import { eq } from "drizzle-orm";
|
|
7
|
-
import {
|
|
8
|
-
agents,
|
|
9
|
-
companies,
|
|
10
|
-
createDb,
|
|
11
|
-
projects,
|
|
12
|
-
routines,
|
|
13
|
-
} from "@paperclipai/db";
|
|
14
|
-
import {
|
|
15
|
-
getEmbeddedPostgresTestSupport,
|
|
16
|
-
startEmbeddedPostgresTestDatabase,
|
|
17
|
-
} from "./helpers/embedded-postgres.js";
|
|
18
|
-
import { disableAllRoutinesInConfig } from "../commands/routines.js";
|
|
19
|
-
|
|
20
|
-
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
|
|
21
|
-
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
|
|
22
|
-
|
|
23
|
-
if (!embeddedPostgresSupport.supported) {
|
|
24
|
-
console.warn(
|
|
25
|
-
`Skipping embedded Postgres routines CLI tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`,
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function writeTestConfig(configPath: string, tempRoot: string, connectionString: string) {
|
|
30
|
-
const config = {
|
|
31
|
-
$meta: {
|
|
32
|
-
version: 1,
|
|
33
|
-
updatedAt: new Date().toISOString(),
|
|
34
|
-
source: "doctor" as const,
|
|
35
|
-
},
|
|
36
|
-
database: {
|
|
37
|
-
mode: "postgres" as const,
|
|
38
|
-
connectionString,
|
|
39
|
-
embeddedPostgresDataDir: path.join(tempRoot, "embedded-db"),
|
|
40
|
-
embeddedPostgresPort: 54329,
|
|
41
|
-
backup: {
|
|
42
|
-
enabled: false,
|
|
43
|
-
intervalMinutes: 60,
|
|
44
|
-
retentionDays: 30,
|
|
45
|
-
dir: path.join(tempRoot, "backups"),
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
logging: {
|
|
49
|
-
mode: "file" as const,
|
|
50
|
-
logDir: path.join(tempRoot, "logs"),
|
|
51
|
-
},
|
|
52
|
-
server: {
|
|
53
|
-
deploymentMode: "local_trusted" as const,
|
|
54
|
-
exposure: "private" as const,
|
|
55
|
-
host: "127.0.0.1",
|
|
56
|
-
port: 3100,
|
|
57
|
-
allowedHostnames: [],
|
|
58
|
-
serveUi: false,
|
|
59
|
-
},
|
|
60
|
-
auth: {
|
|
61
|
-
baseUrlMode: "auto" as const,
|
|
62
|
-
disableSignUp: false,
|
|
63
|
-
},
|
|
64
|
-
storage: {
|
|
65
|
-
provider: "local_disk" as const,
|
|
66
|
-
localDisk: {
|
|
67
|
-
baseDir: path.join(tempRoot, "storage"),
|
|
68
|
-
},
|
|
69
|
-
s3: {
|
|
70
|
-
bucket: "paperclip",
|
|
71
|
-
region: "us-east-1",
|
|
72
|
-
prefix: "",
|
|
73
|
-
forcePathStyle: false,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
secrets: {
|
|
77
|
-
provider: "local_encrypted" as const,
|
|
78
|
-
strictMode: false,
|
|
79
|
-
localEncrypted: {
|
|
80
|
-
keyFilePath: path.join(tempRoot, "secrets", "master.key"),
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
mkdirSync(path.dirname(configPath), { recursive: true });
|
|
86
|
-
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
describeEmbeddedPostgres("disableAllRoutinesInConfig", () => {
|
|
90
|
-
let db!: ReturnType<typeof createDb>;
|
|
91
|
-
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
|
|
92
|
-
let tempRoot = "";
|
|
93
|
-
let configPath = "";
|
|
94
|
-
|
|
95
|
-
beforeAll(async () => {
|
|
96
|
-
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-routines-cli-db-");
|
|
97
|
-
db = createDb(tempDb.connectionString);
|
|
98
|
-
tempRoot = mkdtempSync(path.join(os.tmpdir(), "paperclip-routines-cli-config-"));
|
|
99
|
-
configPath = path.join(tempRoot, "config.json");
|
|
100
|
-
writeTestConfig(configPath, tempRoot, tempDb.connectionString);
|
|
101
|
-
}, 20_000);
|
|
102
|
-
|
|
103
|
-
afterEach(async () => {
|
|
104
|
-
await db.delete(routines);
|
|
105
|
-
await db.delete(projects);
|
|
106
|
-
await db.delete(agents);
|
|
107
|
-
await db.delete(companies);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
afterAll(async () => {
|
|
111
|
-
await tempDb?.cleanup();
|
|
112
|
-
if (tempRoot) {
|
|
113
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("pauses only non-archived routines for the selected company", async () => {
|
|
118
|
-
const companyId = randomUUID();
|
|
119
|
-
const otherCompanyId = randomUUID();
|
|
120
|
-
const projectId = randomUUID();
|
|
121
|
-
const otherProjectId = randomUUID();
|
|
122
|
-
const agentId = randomUUID();
|
|
123
|
-
const otherAgentId = randomUUID();
|
|
124
|
-
const activeRoutineId = randomUUID();
|
|
125
|
-
const pausedRoutineId = randomUUID();
|
|
126
|
-
const archivedRoutineId = randomUUID();
|
|
127
|
-
const otherCompanyRoutineId = randomUUID();
|
|
128
|
-
|
|
129
|
-
await db.insert(companies).values([
|
|
130
|
-
{
|
|
131
|
-
id: companyId,
|
|
132
|
-
name: "Paperclip",
|
|
133
|
-
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
|
134
|
-
requireBoardApprovalForNewAgents: false,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
id: otherCompanyId,
|
|
138
|
-
name: "Other company",
|
|
139
|
-
issuePrefix: `T${otherCompanyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
|
140
|
-
requireBoardApprovalForNewAgents: false,
|
|
141
|
-
},
|
|
142
|
-
]);
|
|
143
|
-
|
|
144
|
-
await db.insert(agents).values([
|
|
145
|
-
{
|
|
146
|
-
id: agentId,
|
|
147
|
-
companyId,
|
|
148
|
-
name: "Coder",
|
|
149
|
-
adapterType: "process",
|
|
150
|
-
adapterConfig: {},
|
|
151
|
-
runtimeConfig: {},
|
|
152
|
-
permissions: {},
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: otherAgentId,
|
|
156
|
-
companyId: otherCompanyId,
|
|
157
|
-
name: "Other coder",
|
|
158
|
-
adapterType: "process",
|
|
159
|
-
adapterConfig: {},
|
|
160
|
-
runtimeConfig: {},
|
|
161
|
-
permissions: {},
|
|
162
|
-
},
|
|
163
|
-
]);
|
|
164
|
-
|
|
165
|
-
await db.insert(projects).values([
|
|
166
|
-
{
|
|
167
|
-
id: projectId,
|
|
168
|
-
companyId,
|
|
169
|
-
name: "Project",
|
|
170
|
-
status: "in_progress",
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
id: otherProjectId,
|
|
174
|
-
companyId: otherCompanyId,
|
|
175
|
-
name: "Other project",
|
|
176
|
-
status: "in_progress",
|
|
177
|
-
},
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
await db.insert(routines).values([
|
|
181
|
-
{
|
|
182
|
-
id: activeRoutineId,
|
|
183
|
-
companyId,
|
|
184
|
-
projectId,
|
|
185
|
-
assigneeAgentId: agentId,
|
|
186
|
-
title: "Active routine",
|
|
187
|
-
status: "active",
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
id: pausedRoutineId,
|
|
191
|
-
companyId,
|
|
192
|
-
projectId,
|
|
193
|
-
assigneeAgentId: agentId,
|
|
194
|
-
title: "Paused routine",
|
|
195
|
-
status: "paused",
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
id: archivedRoutineId,
|
|
199
|
-
companyId,
|
|
200
|
-
projectId,
|
|
201
|
-
assigneeAgentId: agentId,
|
|
202
|
-
title: "Archived routine",
|
|
203
|
-
status: "archived",
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
id: otherCompanyRoutineId,
|
|
207
|
-
companyId: otherCompanyId,
|
|
208
|
-
projectId: otherProjectId,
|
|
209
|
-
assigneeAgentId: otherAgentId,
|
|
210
|
-
title: "Other company routine",
|
|
211
|
-
status: "active",
|
|
212
|
-
},
|
|
213
|
-
]);
|
|
214
|
-
|
|
215
|
-
const result = await disableAllRoutinesInConfig({
|
|
216
|
-
config: configPath,
|
|
217
|
-
companyId,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
expect(result).toMatchObject({
|
|
221
|
-
companyId,
|
|
222
|
-
totalRoutines: 3,
|
|
223
|
-
pausedCount: 1,
|
|
224
|
-
alreadyPausedCount: 1,
|
|
225
|
-
archivedCount: 1,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
const companyRoutines = await db
|
|
229
|
-
.select({
|
|
230
|
-
id: routines.id,
|
|
231
|
-
status: routines.status,
|
|
232
|
-
})
|
|
233
|
-
.from(routines)
|
|
234
|
-
.where(eq(routines.companyId, companyId));
|
|
235
|
-
const statusById = new Map(companyRoutines.map((routine) => [routine.id, routine.status]));
|
|
236
|
-
|
|
237
|
-
expect(statusById.get(activeRoutineId)).toBe("paused");
|
|
238
|
-
expect(statusById.get(pausedRoutineId)).toBe("paused");
|
|
239
|
-
expect(statusById.get(archivedRoutineId)).toBe("archived");
|
|
240
|
-
|
|
241
|
-
const otherCompanyRoutine = await db
|
|
242
|
-
.select({
|
|
243
|
-
status: routines.status,
|
|
244
|
-
})
|
|
245
|
-
.from(routines)
|
|
246
|
-
.where(eq(routines.id, otherCompanyRoutineId));
|
|
247
|
-
expect(otherCompanyRoutine[0]?.status).toBe("active");
|
|
248
|
-
});
|
|
249
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
|
|
6
|
-
const ORIGINAL_ENV = { ...process.env };
|
|
7
|
-
const CI_ENV_VARS = ["CI", "CONTINUOUS_INTEGRATION", "BUILD_NUMBER", "GITHUB_ACTIONS", "GITLAB_CI"];
|
|
8
|
-
|
|
9
|
-
function makeConfigPath(root: string, enabled: boolean): string {
|
|
10
|
-
const configPath = path.join(root, ".paperclip", "config.json");
|
|
11
|
-
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
12
|
-
fs.writeFileSync(configPath, JSON.stringify({
|
|
13
|
-
$meta: {
|
|
14
|
-
version: 1,
|
|
15
|
-
updatedAt: "2026-03-31T00:00:00.000Z",
|
|
16
|
-
source: "configure",
|
|
17
|
-
},
|
|
18
|
-
database: {
|
|
19
|
-
mode: "embedded-postgres",
|
|
20
|
-
embeddedPostgresDataDir: path.join(root, "runtime", "db"),
|
|
21
|
-
embeddedPostgresPort: 54329,
|
|
22
|
-
backup: {
|
|
23
|
-
enabled: true,
|
|
24
|
-
intervalMinutes: 60,
|
|
25
|
-
retentionDays: 30,
|
|
26
|
-
dir: path.join(root, "runtime", "backups"),
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
logging: {
|
|
30
|
-
mode: "file",
|
|
31
|
-
logDir: path.join(root, "runtime", "logs"),
|
|
32
|
-
},
|
|
33
|
-
server: {
|
|
34
|
-
deploymentMode: "local_trusted",
|
|
35
|
-
exposure: "private",
|
|
36
|
-
host: "127.0.0.1",
|
|
37
|
-
port: 3100,
|
|
38
|
-
allowedHostnames: [],
|
|
39
|
-
serveUi: true,
|
|
40
|
-
},
|
|
41
|
-
auth: {
|
|
42
|
-
baseUrlMode: "auto",
|
|
43
|
-
disableSignUp: false,
|
|
44
|
-
},
|
|
45
|
-
telemetry: {
|
|
46
|
-
enabled,
|
|
47
|
-
},
|
|
48
|
-
storage: {
|
|
49
|
-
provider: "local_disk",
|
|
50
|
-
localDisk: {
|
|
51
|
-
baseDir: path.join(root, "runtime", "storage"),
|
|
52
|
-
},
|
|
53
|
-
s3: {
|
|
54
|
-
bucket: "paperclip",
|
|
55
|
-
region: "us-east-1",
|
|
56
|
-
prefix: "",
|
|
57
|
-
forcePathStyle: false,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
secrets: {
|
|
61
|
-
provider: "local_encrypted",
|
|
62
|
-
strictMode: false,
|
|
63
|
-
localEncrypted: {
|
|
64
|
-
keyFilePath: path.join(root, "runtime", "secrets", "master.key"),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
}, null, 2));
|
|
68
|
-
return configPath;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
describe("cli telemetry", () => {
|
|
72
|
-
beforeEach(() => {
|
|
73
|
-
process.env = { ...ORIGINAL_ENV };
|
|
74
|
-
for (const key of CI_ENV_VARS) {
|
|
75
|
-
delete process.env[key];
|
|
76
|
-
}
|
|
77
|
-
vi.stubGlobal("fetch", vi.fn(async () => ({ ok: true })));
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
afterEach(() => {
|
|
81
|
-
process.env = { ...ORIGINAL_ENV };
|
|
82
|
-
vi.unstubAllGlobals();
|
|
83
|
-
vi.resetModules();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("respects telemetry.enabled=false from the config file", async () => {
|
|
87
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-telemetry-"));
|
|
88
|
-
const configPath = makeConfigPath(root, false);
|
|
89
|
-
process.env.PAPERCLIP_HOME = path.join(root, "home");
|
|
90
|
-
process.env.PAPERCLIP_INSTANCE_ID = "telemetry-test";
|
|
91
|
-
|
|
92
|
-
const { initTelemetryFromConfigFile } = await import("../telemetry.js");
|
|
93
|
-
const client = initTelemetryFromConfigFile(configPath);
|
|
94
|
-
|
|
95
|
-
expect(client).toBeNull();
|
|
96
|
-
expect(fs.existsSync(path.join(root, "home", "instances", "telemetry-test", "telemetry", "state.json"))).toBe(false);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("creates telemetry state only after the first event is tracked", async () => {
|
|
100
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-telemetry-"));
|
|
101
|
-
process.env.PAPERCLIP_HOME = path.join(root, "home");
|
|
102
|
-
process.env.PAPERCLIP_INSTANCE_ID = "telemetry-test";
|
|
103
|
-
|
|
104
|
-
const { initTelemetry, flushTelemetry } = await import("../telemetry.js");
|
|
105
|
-
const client = initTelemetry({ enabled: true });
|
|
106
|
-
const statePath = path.join(root, "home", "instances", "telemetry-test", "telemetry", "state.json");
|
|
107
|
-
|
|
108
|
-
expect(client).not.toBeNull();
|
|
109
|
-
expect(fs.existsSync(statePath)).toBe(false);
|
|
110
|
-
|
|
111
|
-
client!.track("install.started", { setupMode: "quickstart" });
|
|
112
|
-
|
|
113
|
-
expect(fs.existsSync(statePath)).toBe(true);
|
|
114
|
-
|
|
115
|
-
await flushTelemetry();
|
|
116
|
-
});
|
|
117
|
-
});
|