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.
- package/README.md +132 -178
- package/bin/offwatch.js +6 -7
- package/lib/downloader.js +112 -0
- package/package.json +17 -7
- 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,80 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import type { PaperclipConfig } from "../config/schema.js";
|
|
6
|
-
import { addAllowedHostname } from "../commands/allowed-hostname.js";
|
|
7
|
-
|
|
8
|
-
function createTempConfigPath() {
|
|
9
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-allowed-hostname-"));
|
|
10
|
-
return path.join(dir, "config.json");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function writeBaseConfig(configPath: string) {
|
|
14
|
-
const base: PaperclipConfig = {
|
|
15
|
-
$meta: {
|
|
16
|
-
version: 1,
|
|
17
|
-
updatedAt: new Date("2026-01-01T00:00:00.000Z").toISOString(),
|
|
18
|
-
source: "configure",
|
|
19
|
-
},
|
|
20
|
-
database: {
|
|
21
|
-
mode: "embedded-postgres",
|
|
22
|
-
embeddedPostgresDataDir: "/tmp/paperclip-db",
|
|
23
|
-
embeddedPostgresPort: 54329,
|
|
24
|
-
backup: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
intervalMinutes: 60,
|
|
27
|
-
retentionDays: 30,
|
|
28
|
-
dir: "/tmp/paperclip-backups",
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
logging: {
|
|
32
|
-
mode: "file",
|
|
33
|
-
logDir: "/tmp/paperclip-logs",
|
|
34
|
-
},
|
|
35
|
-
server: {
|
|
36
|
-
deploymentMode: "authenticated",
|
|
37
|
-
exposure: "private",
|
|
38
|
-
host: "0.0.0.0",
|
|
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: { baseDir: "/tmp/paperclip-storage" },
|
|
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: { keyFilePath: "/tmp/paperclip-secrets/master.key" },
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
fs.writeFileSync(configPath, JSON.stringify(base, null, 2));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
describe("allowed-hostname command", () => {
|
|
70
|
-
it("adds and normalizes hostnames", async () => {
|
|
71
|
-
const configPath = createTempConfigPath();
|
|
72
|
-
writeBaseConfig(configPath);
|
|
73
|
-
|
|
74
|
-
await addAllowedHostname("https://Dotta-MacBook-Pro:3100", { config: configPath });
|
|
75
|
-
await addAllowedHostname("dotta-macbook-pro", { config: configPath });
|
|
76
|
-
|
|
77
|
-
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")) as PaperclipConfig;
|
|
78
|
-
expect(raw.server.allowedHostnames).toEqual(["dotta-macbook-pro"]);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { registerClientAuthCommands } from "../commands/client/auth.js";
|
|
4
|
-
|
|
5
|
-
describe("registerClientAuthCommands", () => {
|
|
6
|
-
it("registers auth commands without duplicate company-id flags", () => {
|
|
7
|
-
const program = new Command();
|
|
8
|
-
const auth = program.command("auth");
|
|
9
|
-
|
|
10
|
-
expect(() => registerClientAuthCommands(auth)).not.toThrow();
|
|
11
|
-
|
|
12
|
-
const login = auth.commands.find((command) => command.name() === "login");
|
|
13
|
-
expect(login).toBeDefined();
|
|
14
|
-
expect(login?.options.filter((option) => option.long === "--company-id")).toHaveLength(1);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
6
|
-
getStoredBoardCredential,
|
|
7
|
-
readBoardAuthStore,
|
|
8
|
-
removeStoredBoardCredential,
|
|
9
|
-
setStoredBoardCredential,
|
|
10
|
-
} from "../client/board-auth.js";
|
|
11
|
-
|
|
12
|
-
function createTempAuthPath(): string {
|
|
13
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-auth-"));
|
|
14
|
-
return path.join(dir, "auth.json");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("board auth store", () => {
|
|
18
|
-
it("returns an empty store when the file does not exist", () => {
|
|
19
|
-
const authPath = createTempAuthPath();
|
|
20
|
-
expect(readBoardAuthStore(authPath)).toEqual({
|
|
21
|
-
version: 1,
|
|
22
|
-
credentials: {},
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("stores and retrieves credentials by normalized api base", () => {
|
|
27
|
-
const authPath = createTempAuthPath();
|
|
28
|
-
setStoredBoardCredential({
|
|
29
|
-
apiBase: "http://localhost:3100/",
|
|
30
|
-
token: "token-123",
|
|
31
|
-
userId: "user-1",
|
|
32
|
-
storePath: authPath,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(getStoredBoardCredential("http://localhost:3100", authPath)).toMatchObject({
|
|
36
|
-
apiBase: "http://localhost:3100",
|
|
37
|
-
token: "token-123",
|
|
38
|
-
userId: "user-1",
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("removes stored credentials", () => {
|
|
43
|
-
const authPath = createTempAuthPath();
|
|
44
|
-
setStoredBoardCredential({
|
|
45
|
-
apiBase: "http://localhost:3100",
|
|
46
|
-
token: "token-123",
|
|
47
|
-
storePath: authPath,
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
expect(removeStoredBoardCredential("http://localhost:3100", authPath)).toBe(true);
|
|
51
|
-
expect(getStoredBoardCredential("http://localhost:3100", authPath)).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,98 +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 { writeContext } from "../client/context.js";
|
|
6
|
-
import { resolveCommandContext } from "../commands/client/common.js";
|
|
7
|
-
|
|
8
|
-
const ORIGINAL_ENV = { ...process.env };
|
|
9
|
-
|
|
10
|
-
function createTempPath(name: string): string {
|
|
11
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cli-common-"));
|
|
12
|
-
return path.join(dir, name);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("resolveCommandContext", () => {
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
process.env = { ...ORIGINAL_ENV };
|
|
18
|
-
delete process.env.PAPERCLIP_API_URL;
|
|
19
|
-
delete process.env.PAPERCLIP_API_KEY;
|
|
20
|
-
delete process.env.PAPERCLIP_COMPANY_ID;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(() => {
|
|
24
|
-
process.env = { ...ORIGINAL_ENV };
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("uses profile defaults when options/env are not provided", () => {
|
|
28
|
-
const contextPath = createTempPath("context.json");
|
|
29
|
-
|
|
30
|
-
writeContext(
|
|
31
|
-
{
|
|
32
|
-
version: 1,
|
|
33
|
-
currentProfile: "ops",
|
|
34
|
-
profiles: {
|
|
35
|
-
ops: {
|
|
36
|
-
apiBase: "http://127.0.0.1:9999",
|
|
37
|
-
companyId: "company-profile",
|
|
38
|
-
apiKeyEnvVarName: "AGENT_KEY",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
contextPath,
|
|
43
|
-
);
|
|
44
|
-
process.env.AGENT_KEY = "key-from-env";
|
|
45
|
-
|
|
46
|
-
const resolved = resolveCommandContext({ context: contextPath }, { requireCompany: true });
|
|
47
|
-
expect(resolved.api.apiBase).toBe("http://127.0.0.1:9999");
|
|
48
|
-
expect(resolved.companyId).toBe("company-profile");
|
|
49
|
-
expect(resolved.api.apiKey).toBe("key-from-env");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("prefers explicit options over profile values", () => {
|
|
53
|
-
const contextPath = createTempPath("context.json");
|
|
54
|
-
writeContext(
|
|
55
|
-
{
|
|
56
|
-
version: 1,
|
|
57
|
-
currentProfile: "default",
|
|
58
|
-
profiles: {
|
|
59
|
-
default: {
|
|
60
|
-
apiBase: "http://profile:3100",
|
|
61
|
-
companyId: "company-profile",
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
contextPath,
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
const resolved = resolveCommandContext(
|
|
69
|
-
{
|
|
70
|
-
context: contextPath,
|
|
71
|
-
apiBase: "http://override:3200",
|
|
72
|
-
apiKey: "direct-token",
|
|
73
|
-
companyId: "company-override",
|
|
74
|
-
},
|
|
75
|
-
{ requireCompany: true },
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
expect(resolved.api.apiBase).toBe("http://override:3200");
|
|
79
|
-
expect(resolved.companyId).toBe("company-override");
|
|
80
|
-
expect(resolved.api.apiKey).toBe("direct-token");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("throws when company is required but unresolved", () => {
|
|
84
|
-
const contextPath = createTempPath("context.json");
|
|
85
|
-
writeContext(
|
|
86
|
-
{
|
|
87
|
-
version: 1,
|
|
88
|
-
currentProfile: "default",
|
|
89
|
-
profiles: { default: {} },
|
|
90
|
-
},
|
|
91
|
-
contextPath,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
expect(() =>
|
|
95
|
-
resolveCommandContext({ context: contextPath, apiBase: "http://localhost:3100" }, { requireCompany: true }),
|
|
96
|
-
).toThrow(/Company ID is required/);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { Company } from "@paperclipai/shared";
|
|
3
|
-
import { assertDeleteConfirmation, resolveCompanyForDeletion } from "../commands/client/company.js";
|
|
4
|
-
|
|
5
|
-
function makeCompany(overrides: Partial<Company>): Company {
|
|
6
|
-
return {
|
|
7
|
-
id: "11111111-1111-1111-1111-111111111111",
|
|
8
|
-
name: "Alpha",
|
|
9
|
-
description: null,
|
|
10
|
-
status: "active",
|
|
11
|
-
pauseReason: null,
|
|
12
|
-
pausedAt: null,
|
|
13
|
-
issuePrefix: "ALP",
|
|
14
|
-
issueCounter: 1,
|
|
15
|
-
budgetMonthlyCents: 0,
|
|
16
|
-
spentMonthlyCents: 0,
|
|
17
|
-
requireBoardApprovalForNewAgents: false,
|
|
18
|
-
feedbackDataSharingEnabled: false,
|
|
19
|
-
feedbackDataSharingConsentAt: null,
|
|
20
|
-
feedbackDataSharingConsentByUserId: null,
|
|
21
|
-
feedbackDataSharingTermsVersion: null,
|
|
22
|
-
brandColor: null,
|
|
23
|
-
logoAssetId: null,
|
|
24
|
-
logoUrl: null,
|
|
25
|
-
createdAt: new Date(),
|
|
26
|
-
updatedAt: new Date(),
|
|
27
|
-
...overrides,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
describe("resolveCompanyForDeletion", () => {
|
|
32
|
-
const companies: Company[] = [
|
|
33
|
-
makeCompany({
|
|
34
|
-
id: "11111111-1111-1111-1111-111111111111",
|
|
35
|
-
name: "Alpha",
|
|
36
|
-
issuePrefix: "ALP",
|
|
37
|
-
}),
|
|
38
|
-
makeCompany({
|
|
39
|
-
id: "22222222-2222-2222-2222-222222222222",
|
|
40
|
-
name: "Paperclip",
|
|
41
|
-
issuePrefix: "PAP",
|
|
42
|
-
}),
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
it("resolves by ID in auto mode", () => {
|
|
46
|
-
const result = resolveCompanyForDeletion(companies, "22222222-2222-2222-2222-222222222222", "auto");
|
|
47
|
-
expect(result.issuePrefix).toBe("PAP");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("resolves by prefix in auto mode", () => {
|
|
51
|
-
const result = resolveCompanyForDeletion(companies, "pap", "auto");
|
|
52
|
-
expect(result.id).toBe("22222222-2222-2222-2222-222222222222");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("throws when selector is not found", () => {
|
|
56
|
-
expect(() => resolveCompanyForDeletion(companies, "MISSING", "auto")).toThrow(/No company found/);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("respects explicit id mode", () => {
|
|
60
|
-
expect(() => resolveCompanyForDeletion(companies, "PAP", "id")).toThrow(/No company found by ID/);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("respects explicit prefix mode", () => {
|
|
64
|
-
expect(() => resolveCompanyForDeletion(companies, "22222222-2222-2222-2222-222222222222", "prefix"))
|
|
65
|
-
.toThrow(/No company found by shortname/);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("assertDeleteConfirmation", () => {
|
|
70
|
-
const company = makeCompany({
|
|
71
|
-
id: "22222222-2222-2222-2222-222222222222",
|
|
72
|
-
issuePrefix: "PAP",
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("requires --yes", () => {
|
|
76
|
-
expect(() => assertDeleteConfirmation(company, { confirm: "PAP" })).toThrow(/requires --yes/);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("accepts matching prefix confirmation", () => {
|
|
80
|
-
expect(() => assertDeleteConfirmation(company, { yes: true, confirm: "pap" })).not.toThrow();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("accepts matching id confirmation", () => {
|
|
84
|
-
expect(() =>
|
|
85
|
-
assertDeleteConfirmation(company, {
|
|
86
|
-
yes: true,
|
|
87
|
-
confirm: "22222222-2222-2222-2222-222222222222",
|
|
88
|
-
})).not.toThrow();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("rejects mismatched confirmation", () => {
|
|
92
|
-
expect(() => assertDeleteConfirmation(company, { yes: true, confirm: "nope" }))
|
|
93
|
-
.toThrow(/does not match target company/);
|
|
94
|
-
});
|
|
95
|
-
});
|