@ai-coders/installer 0.3.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 (127) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +18 -0
  3. package/README.md +64 -0
  4. package/dist/callback-page.d.ts +11 -0
  5. package/dist/callback-page.js +230 -0
  6. package/dist/callback-page.js.map +1 -0
  7. package/dist/clerk-auth.d.ts +18 -0
  8. package/dist/clerk-auth.js +296 -0
  9. package/dist/clerk-auth.js.map +1 -0
  10. package/dist/cli-command.d.ts +10 -0
  11. package/dist/cli-command.js +17 -0
  12. package/dist/cli-command.js.map +1 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +84 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/i18n.d.ts +52 -0
  17. package/dist/i18n.js +163 -0
  18. package/dist/i18n.js.map +1 -0
  19. package/dist/product-manifest.d.ts +43 -0
  20. package/dist/product-manifest.js +140 -0
  21. package/dist/product-manifest.js.map +1 -0
  22. package/dist/products/second-brain/apply.d.ts +86 -0
  23. package/dist/products/second-brain/apply.js +277 -0
  24. package/dist/products/second-brain/apply.js.map +1 -0
  25. package/dist/products/second-brain/bearer.d.ts +29 -0
  26. package/dist/products/second-brain/bearer.js +78 -0
  27. package/dist/products/second-brain/bearer.js.map +1 -0
  28. package/dist/products/second-brain/bootstrap.d.ts +33 -0
  29. package/dist/products/second-brain/bootstrap.js +39 -0
  30. package/dist/products/second-brain/bootstrap.js.map +1 -0
  31. package/dist/products/second-brain/envfile.d.ts +35 -0
  32. package/dist/products/second-brain/envfile.js +136 -0
  33. package/dist/products/second-brain/envfile.js.map +1 -0
  34. package/dist/products/second-brain/github.d.ts +77 -0
  35. package/dist/products/second-brain/github.js +195 -0
  36. package/dist/products/second-brain/github.js.map +1 -0
  37. package/dist/products/second-brain/hostinger.d.ts +140 -0
  38. package/dist/products/second-brain/hostinger.js +607 -0
  39. package/dist/products/second-brain/hostinger.js.map +1 -0
  40. package/dist/products/second-brain/i18n.d.ts +887 -0
  41. package/dist/products/second-brain/i18n.js +587 -0
  42. package/dist/products/second-brain/i18n.js.map +1 -0
  43. package/dist/products/second-brain/keys.d.ts +5 -0
  44. package/dist/products/second-brain/keys.js +16 -0
  45. package/dist/products/second-brain/keys.js.map +1 -0
  46. package/dist/products/second-brain/main.d.ts +3 -0
  47. package/dist/products/second-brain/main.js +78 -0
  48. package/dist/products/second-brain/main.js.map +1 -0
  49. package/dist/products/second-brain/ports.d.ts +34 -0
  50. package/dist/products/second-brain/ports.js +77 -0
  51. package/dist/products/second-brain/ports.js.map +1 -0
  52. package/dist/products/second-brain/recovery.d.ts +50 -0
  53. package/dist/products/second-brain/recovery.js +96 -0
  54. package/dist/products/second-brain/recovery.js.map +1 -0
  55. package/dist/products/second-brain/sealed-box.d.ts +1 -0
  56. package/dist/products/second-brain/sealed-box.js +11 -0
  57. package/dist/products/second-brain/sealed-box.js.map +1 -0
  58. package/dist/products/second-brain/server.d.ts +143 -0
  59. package/dist/products/second-brain/server.js +280 -0
  60. package/dist/products/second-brain/server.js.map +1 -0
  61. package/dist/products/second-brain/service.d.ts +26 -0
  62. package/dist/products/second-brain/service.js +806 -0
  63. package/dist/products/second-brain/service.js.map +1 -0
  64. package/dist/products/second-brain/ssh.d.ts +35 -0
  65. package/dist/products/second-brain/ssh.js +140 -0
  66. package/dist/products/second-brain/ssh.js.map +1 -0
  67. package/dist/products/second-brain/state.d.ts +27 -0
  68. package/dist/products/second-brain/state.js +60 -0
  69. package/dist/products/second-brain/state.js.map +1 -0
  70. package/dist/products/second-brain/ui/assets/index-BoXpakUt.css +2 -0
  71. package/dist/products/second-brain/ui/assets/index-MgfOpW_R.js +66 -0
  72. package/dist/products/second-brain/ui/index.html +15 -0
  73. package/dist/products/second-brain/verify.d.ts +23 -0
  74. package/dist/products/second-brain/verify.js +59 -0
  75. package/dist/products/second-brain/verify.js.map +1 -0
  76. package/dist/products/severino/apply.d.ts +87 -0
  77. package/dist/products/severino/apply.js +250 -0
  78. package/dist/products/severino/apply.js.map +1 -0
  79. package/dist/products/severino/domain.d.ts +6 -0
  80. package/dist/products/severino/domain.js +20 -0
  81. package/dist/products/severino/domain.js.map +1 -0
  82. package/dist/products/severino/envfile.d.ts +20 -0
  83. package/dist/products/severino/envfile.js +89 -0
  84. package/dist/products/severino/envfile.js.map +1 -0
  85. package/dist/products/severino/github.d.ts +77 -0
  86. package/dist/products/severino/github.js +195 -0
  87. package/dist/products/severino/github.js.map +1 -0
  88. package/dist/products/severino/hostinger.d.ts +140 -0
  89. package/dist/products/severino/hostinger.js +607 -0
  90. package/dist/products/severino/hostinger.js.map +1 -0
  91. package/dist/products/severino/i18n.d.ts +887 -0
  92. package/dist/products/severino/i18n.js +587 -0
  93. package/dist/products/severino/i18n.js.map +1 -0
  94. package/dist/products/severino/keys.d.ts +5 -0
  95. package/dist/products/severino/keys.js +16 -0
  96. package/dist/products/severino/keys.js.map +1 -0
  97. package/dist/products/severino/main.d.ts +3 -0
  98. package/dist/products/severino/main.js +78 -0
  99. package/dist/products/severino/main.js.map +1 -0
  100. package/dist/products/severino/recovery.d.ts +50 -0
  101. package/dist/products/severino/recovery.js +96 -0
  102. package/dist/products/severino/recovery.js.map +1 -0
  103. package/dist/products/severino/sealed-box.d.ts +1 -0
  104. package/dist/products/severino/sealed-box.js +11 -0
  105. package/dist/products/severino/sealed-box.js.map +1 -0
  106. package/dist/products/severino/server.d.ts +123 -0
  107. package/dist/products/severino/server.js +274 -0
  108. package/dist/products/severino/server.js.map +1 -0
  109. package/dist/products/severino/service.d.ts +28 -0
  110. package/dist/products/severino/service.js +752 -0
  111. package/dist/products/severino/service.js.map +1 -0
  112. package/dist/products/severino/slack-manifest.d.ts +3 -0
  113. package/dist/products/severino/slack-manifest.js +45 -0
  114. package/dist/products/severino/slack-manifest.js.map +1 -0
  115. package/dist/products/severino/ssh.d.ts +35 -0
  116. package/dist/products/severino/ssh.js +140 -0
  117. package/dist/products/severino/ssh.js.map +1 -0
  118. package/dist/products/severino/state.d.ts +26 -0
  119. package/dist/products/severino/state.js +61 -0
  120. package/dist/products/severino/state.js.map +1 -0
  121. package/dist/products/severino/ui/assets/index-7S8Hl4jq.js +96 -0
  122. package/dist/products/severino/ui/assets/index-D5kql1Bh.css +2 -0
  123. package/dist/products/severino/ui/index.html +15 -0
  124. package/dist/products/severino/verify.d.ts +22 -0
  125. package/dist/products/severino/verify.js +149 -0
  126. package/dist/products/severino/verify.js.map +1 -0
  127. package/package.json +53 -0
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Renders and merges the VPS /opt/second-brain/.env file. Merging preserves a
3
+ * previously initialized database password (rotating it after the postgres
4
+ * volume exists would break the stack) and the workflow-managed image pins.
5
+ */
6
+ import { randomBytes } from "node:crypto";
7
+ import { parseEnv } from "node:util";
8
+ /** The placeholder written by deploy/vps-setup.sh before any real install. */
9
+ export const databasePasswordPlaceholder = "replace-with-a-strong-database-password";
10
+ export const vpsEnvPath = "/opt/second-brain/.env";
11
+ /**
12
+ * Members paste domains in every shape — with https://, trailing slashes,
13
+ * paths, uppercase. The OAuth server refuses to start unless
14
+ * SECOND_BRAIN_PUBLIC_URL is a clean HTTPS origin, so normalize here.
15
+ */
16
+ export function normalizeDomain(value) {
17
+ let domain = value.trim().toLowerCase();
18
+ // Strip a leading scheme, tolerating the missing-colon typo (https//x).
19
+ domain = domain.replace(/^[a-z][a-z0-9+.-]*:?\/\//, "");
20
+ domain = domain.split("/")[0] ?? "";
21
+ domain = domain.replace(/\.+$/, "");
22
+ // Requiring a dot rejects scheme fragments such as "https" that would
23
+ // otherwise pass as a syntactically valid single-label hostname.
24
+ if (!domain.includes(".") ||
25
+ !/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/.test(domain)) {
26
+ throw new Error("The domain must be a hostname like brain.example.com.");
27
+ }
28
+ return domain;
29
+ }
30
+ export function generateDatabasePassword() {
31
+ return randomBytes(24).toString("base64url");
32
+ }
33
+ export function parseEnvContent(text) {
34
+ return { ...parseEnv(text) };
35
+ }
36
+ const managedKeys = [
37
+ "POSTGRES_PASSWORD",
38
+ "OPENROUTER_API_KEY",
39
+ "SECOND_BRAIN_PUBLIC_URL",
40
+ "SECOND_BRAIN_PUBLIC_HOST",
41
+ "SECOND_BRAIN_HTTP_PORT",
42
+ "SECOND_BRAIN_OAUTH_PORT",
43
+ "SECOND_BRAIN_IMAGE",
44
+ "SECOND_BRAIN_IMAGE_TAG",
45
+ ];
46
+ function assertSafeValue(name, value) {
47
+ if (value.includes("\n") || value.includes("\r") || value.includes("\0")) {
48
+ throw new Error(`The value for ${name} must not contain line breaks.`);
49
+ }
50
+ }
51
+ /**
52
+ * Adds or replaces the static bearer credential lines in the VPS .env. This
53
+ * runs after owner bootstrap — the principal id does not exist earlier — so
54
+ * it edits the file in place instead of re-rendering: deploy.sh may have
55
+ * upserted image pins since write-env, and those must survive.
56
+ */
57
+ export function withBearerCredentials(content, credentials) {
58
+ const managed = [
59
+ ["INVOKTA_HTTP_BEARER_TOKEN", credentials.token],
60
+ ["INVOKTA_HTTP_PRINCIPAL_ID", credentials.principalId],
61
+ ];
62
+ for (const [name, value] of managed)
63
+ assertSafeValue(name, value);
64
+ const replaced = new Set();
65
+ const lines = content
66
+ .replace(/\n$/, "")
67
+ .split("\n")
68
+ .map((line) => {
69
+ const match = managed.find(([name]) => line.startsWith(`${name}=`));
70
+ if (match === undefined)
71
+ return line;
72
+ replaced.add(match[0]);
73
+ return `${match[0]}=${match[1]}`;
74
+ });
75
+ for (const [name, value] of managed) {
76
+ if (!replaced.has(name))
77
+ lines.push(`${name}=${value}`);
78
+ }
79
+ return `${lines.join("\n")}\n`;
80
+ }
81
+ export function mergeVpsEnv(input) {
82
+ const existing = input.existingContent === undefined
83
+ ? {}
84
+ : parseEnvContent(input.existingContent);
85
+ const previousPassword = existing["POSTGRES_PASSWORD"];
86
+ const keptExistingDatabasePassword = previousPassword !== undefined &&
87
+ previousPassword !== "" &&
88
+ previousPassword !== databasePasswordPlaceholder;
89
+ const databasePassword = keptExistingDatabasePassword
90
+ ? previousPassword
91
+ : input.freshDatabasePassword;
92
+ const domain = normalizeDomain(input.domain);
93
+ const values = {
94
+ POSTGRES_PASSWORD: databasePassword,
95
+ OPENROUTER_API_KEY: input.openRouterApiKey,
96
+ SECOND_BRAIN_PUBLIC_URL: `https://${domain}`,
97
+ SECOND_BRAIN_PUBLIC_HOST: domain,
98
+ SECOND_BRAIN_HTTP_PORT: String(input.hostPorts.httpPort),
99
+ SECOND_BRAIN_OAUTH_PORT: String(input.hostPorts.oauthPort),
100
+ SECOND_BRAIN_IMAGE: existing["SECOND_BRAIN_IMAGE"] ?? "second-brain",
101
+ SECOND_BRAIN_IMAGE_TAG: existing["SECOND_BRAIN_IMAGE_TAG"] ?? "local",
102
+ };
103
+ for (const [name, value] of Object.entries(values)) {
104
+ assertSafeValue(name, value);
105
+ }
106
+ const unknownEntries = Object.entries(existing).filter(([key]) => !managedKeys.includes(key));
107
+ for (const [name, value] of unknownEntries) {
108
+ assertSafeValue(name, value);
109
+ }
110
+ const lines = [
111
+ "# Required — full variable reference: .env.example in the repo.",
112
+ `POSTGRES_PASSWORD=${values["POSTGRES_PASSWORD"]}`,
113
+ `OPENROUTER_API_KEY=${values["OPENROUTER_API_KEY"]}`,
114
+ `SECOND_BRAIN_PUBLIC_URL=${values["SECOND_BRAIN_PUBLIC_URL"]}`,
115
+ `SECOND_BRAIN_PUBLIC_HOST=${values["SECOND_BRAIN_PUBLIC_HOST"]}`,
116
+ "",
117
+ "# Loopback host ports — provisioned to avoid clashes with other apps:",
118
+ `SECOND_BRAIN_HTTP_PORT=${values["SECOND_BRAIN_HTTP_PORT"]}`,
119
+ `SECOND_BRAIN_OAUTH_PORT=${values["SECOND_BRAIN_OAUTH_PORT"]}`,
120
+ "",
121
+ "# Managed by deploys — do not edit:",
122
+ `SECOND_BRAIN_IMAGE=${values["SECOND_BRAIN_IMAGE"]}`,
123
+ `SECOND_BRAIN_IMAGE_TAG=${values["SECOND_BRAIN_IMAGE_TAG"]}`,
124
+ ];
125
+ if (unknownEntries.length > 0) {
126
+ lines.push("");
127
+ for (const [name, value] of unknownEntries) {
128
+ lines.push(`${name}=${value}`);
129
+ }
130
+ }
131
+ return {
132
+ content: `${lines.join("\n")}\n`,
133
+ keptExistingDatabasePassword,
134
+ };
135
+ }
136
+ //# sourceMappingURL=envfile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envfile.js","sourceRoot":"","sources":["../../../packages/second-brain/src/envfile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Minimal GitHub REST client for the installer. Uses an injected fetch so
3
+ * tests never touch the network, and never logs the token.
4
+ */
5
+ export interface RepositoryRef {
6
+ readonly owner: string;
7
+ readonly repo: string;
8
+ }
9
+ export declare function parseRepositoryFromRemote(url: string): RepositoryRef | null;
10
+ export interface PermissionCheck {
11
+ readonly permission: "contents" | "secrets" | "actions";
12
+ readonly ok: boolean;
13
+ }
14
+ export interface TokenValidation {
15
+ readonly ok: boolean;
16
+ readonly unauthorized: boolean;
17
+ readonly checks: readonly PermissionCheck[];
18
+ readonly tokenExpiresAt: Date | null;
19
+ }
20
+ export interface WorkflowRun {
21
+ readonly id: number;
22
+ readonly headSha: string;
23
+ readonly displayTitle: string;
24
+ readonly event: string;
25
+ readonly status: string;
26
+ readonly conclusion: string | null;
27
+ readonly createdAt: Date;
28
+ readonly htmlUrl: string;
29
+ }
30
+ export interface WorkflowJob {
31
+ readonly name: string;
32
+ readonly conclusion: string | null;
33
+ readonly htmlUrl: string;
34
+ }
35
+ interface GitHubClientOptions {
36
+ readonly token: string;
37
+ readonly owner: string;
38
+ readonly repo: string;
39
+ readonly fetchLike?: typeof fetch;
40
+ readonly apiBase?: string;
41
+ readonly sealSecret?: (publicKey: string, value: string) => Promise<string>;
42
+ }
43
+ export declare class GitHubClient {
44
+ private readonly token;
45
+ private readonly owner;
46
+ private readonly repo;
47
+ private readonly fetchLike;
48
+ private readonly apiBase;
49
+ private readonly sealSecret;
50
+ private publicKey;
51
+ constructor(options: GitHubClientOptions);
52
+ private request;
53
+ private repoPath;
54
+ /**
55
+ * Read-only best-effort probes: real write access is only proven by the
56
+ * later PUT/POST calls, but a fine-grained token missing a permission
57
+ * fails these reads with 403/404.
58
+ */
59
+ validate(): Promise<TokenValidation>;
60
+ private getPublicKey;
61
+ putSecret(name: string, value: string): Promise<void>;
62
+ dispatchDeploy(imageTag: string | undefined, dispatchedAt: Date): Promise<Date>;
63
+ /**
64
+ * Dispatching returns no run id, so correlate: poll for the newest
65
+ * workflow_dispatch run created at or after dispatch time (minus a minute
66
+ * of clock skew).
67
+ */
68
+ findDispatchedRun(dispatchedAt: Date, options?: {
69
+ attempts?: number;
70
+ delayMs?: number;
71
+ sleep?: (ms: number) => Promise<void>;
72
+ }): Promise<WorkflowRun>;
73
+ getRun(id: number): Promise<WorkflowRun>;
74
+ listRuns(perPage?: number): Promise<WorkflowRun[]>;
75
+ listJobs(runId: number): Promise<WorkflowJob[]>;
76
+ }
77
+ export {};
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Minimal GitHub REST client for the installer. Uses an injected fetch so
3
+ * tests never touch the network, and never logs the token.
4
+ */
5
+ export function parseRepositoryFromRemote(url) {
6
+ const trimmed = url.trim();
7
+ const patterns = [
8
+ /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
9
+ /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
10
+ /^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
11
+ ];
12
+ for (const pattern of patterns) {
13
+ const match = trimmed.match(pattern);
14
+ if (match?.[1] !== undefined && match[2] !== undefined) {
15
+ return { owner: match[1], repo: match[2] };
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ const workflowFile = "deploy.yml";
21
+ function toRun(run) {
22
+ return {
23
+ id: run.id,
24
+ headSha: run.head_sha,
25
+ displayTitle: run.display_title,
26
+ event: run.event,
27
+ status: run.status,
28
+ conclusion: run.conclusion,
29
+ createdAt: new Date(run.created_at),
30
+ htmlUrl: run.html_url,
31
+ };
32
+ }
33
+ export class GitHubClient {
34
+ token;
35
+ owner;
36
+ repo;
37
+ fetchLike;
38
+ apiBase;
39
+ sealSecret;
40
+ publicKey = null;
41
+ constructor(options) {
42
+ this.token = options.token;
43
+ this.owner = options.owner;
44
+ this.repo = options.repo;
45
+ this.fetchLike = options.fetchLike ?? fetch;
46
+ this.apiBase = options.apiBase ?? "https://api.github.com";
47
+ this.sealSecret = options.sealSecret;
48
+ }
49
+ async request(method, path, body) {
50
+ const init = {
51
+ method,
52
+ headers: {
53
+ authorization: `Bearer ${this.token}`,
54
+ accept: "application/vnd.github+json",
55
+ "x-github-api-version": "2022-11-28",
56
+ "user-agent": "second-brain-installer",
57
+ ...(body === undefined ? {} : { "content-type": "application/json" }),
58
+ },
59
+ };
60
+ if (body !== undefined)
61
+ init.body = JSON.stringify(body);
62
+ return this.fetchLike(`${this.apiBase}${path}`, init);
63
+ }
64
+ repoPath(suffix = "") {
65
+ return `/repos/${this.owner}/${this.repo}${suffix}`;
66
+ }
67
+ /**
68
+ * Read-only best-effort probes: real write access is only proven by the
69
+ * later PUT/POST calls, but a fine-grained token missing a permission
70
+ * fails these reads with 403/404.
71
+ */
72
+ async validate() {
73
+ const probes = [
74
+ { permission: "contents", path: this.repoPath() },
75
+ { permission: "secrets", path: this.repoPath("/actions/secrets/public-key") },
76
+ {
77
+ permission: "actions",
78
+ path: this.repoPath(`/actions/workflows/${workflowFile}`),
79
+ },
80
+ ];
81
+ const checks = [];
82
+ let unauthorized = false;
83
+ let tokenExpiresAt = null;
84
+ for (const probe of probes) {
85
+ const response = await this.request("GET", probe.path);
86
+ if (response.status === 401)
87
+ unauthorized = true;
88
+ const expiration = response.headers.get("github-authentication-token-expiration");
89
+ if (expiration !== null && tokenExpiresAt === null) {
90
+ const parsed = new Date(expiration);
91
+ if (!Number.isNaN(parsed.getTime()))
92
+ tokenExpiresAt = parsed;
93
+ }
94
+ checks.push({ permission: probe.permission, ok: response.ok });
95
+ }
96
+ return {
97
+ ok: !unauthorized && checks.every((check) => check.ok),
98
+ unauthorized,
99
+ checks,
100
+ tokenExpiresAt,
101
+ };
102
+ }
103
+ async getPublicKey() {
104
+ if (this.publicKey !== null)
105
+ return this.publicKey;
106
+ const response = await this.request("GET", this.repoPath("/actions/secrets/public-key"));
107
+ if (!response.ok) {
108
+ throw new Error(`GitHub did not return the secrets public key (HTTP ${response.status}).`);
109
+ }
110
+ const data = (await response.json());
111
+ this.publicKey = { keyId: data.key_id, key: data.key };
112
+ return this.publicKey;
113
+ }
114
+ async putSecret(name, value) {
115
+ if (this.sealSecret === undefined) {
116
+ throw new Error("A secret sealer was not configured.");
117
+ }
118
+ const publicKey = await this.getPublicKey();
119
+ const encrypted = await this.sealSecret(publicKey.key, value);
120
+ const response = await this.request("PUT", this.repoPath(`/actions/secrets/${name}`), { encrypted_value: encrypted, key_id: publicKey.keyId });
121
+ if (response.status !== 201 && response.status !== 204) {
122
+ throw new Error(`GitHub rejected the secret ${name} (HTTP ${response.status}).`);
123
+ }
124
+ }
125
+ async dispatchDeploy(imageTag, dispatchedAt) {
126
+ const response = await this.request("POST", this.repoPath(`/actions/workflows/${workflowFile}/dispatches`), {
127
+ ref: "main",
128
+ inputs: imageTag === undefined ? {} : { image_tag: imageTag },
129
+ });
130
+ if (response.status !== 204) {
131
+ throw new Error(`GitHub did not accept the deploy request (HTTP ${response.status}).`);
132
+ }
133
+ return dispatchedAt;
134
+ }
135
+ /**
136
+ * Dispatching returns no run id, so correlate: poll for the newest
137
+ * workflow_dispatch run created at or after dispatch time (minus a minute
138
+ * of clock skew).
139
+ */
140
+ async findDispatchedRun(dispatchedAt, options) {
141
+ const attempts = options?.attempts ?? 12;
142
+ const delayMs = options?.delayMs ?? 5000;
143
+ const sleep = options?.sleep ??
144
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
145
+ const threshold = new Date(dispatchedAt.getTime() - 60_000);
146
+ const query = new URLSearchParams({
147
+ event: "workflow_dispatch",
148
+ created: `>=${threshold.toISOString().slice(0, 16)}`,
149
+ per_page: "10",
150
+ });
151
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
152
+ const response = await this.request("GET", this.repoPath(`/actions/workflows/${workflowFile}/runs?${query}`));
153
+ if (response.ok) {
154
+ const data = (await response.json());
155
+ const candidates = data.workflow_runs
156
+ .map(toRun)
157
+ .filter((run) => run.event === "workflow_dispatch" &&
158
+ run.createdAt.getTime() >= threshold.getTime())
159
+ .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
160
+ const newest = candidates[0];
161
+ if (newest !== undefined)
162
+ return newest;
163
+ }
164
+ await sleep(delayMs);
165
+ }
166
+ throw new Error("The deploy was requested but its run was not found. Check the repository's Actions tab.");
167
+ }
168
+ async getRun(id) {
169
+ const response = await this.request("GET", this.repoPath(`/actions/runs/${id}`));
170
+ if (!response.ok) {
171
+ throw new Error(`GitHub did not return run ${id} (HTTP ${response.status}).`);
172
+ }
173
+ return toRun((await response.json()));
174
+ }
175
+ async listRuns(perPage = 15) {
176
+ const response = await this.request("GET", this.repoPath(`/actions/workflows/${workflowFile}/runs?per_page=${perPage}`));
177
+ if (!response.ok) {
178
+ throw new Error(`GitHub did not return the run list (HTTP ${response.status}).`);
179
+ }
180
+ const data = (await response.json());
181
+ return data.workflow_runs.map(toRun);
182
+ }
183
+ async listJobs(runId) {
184
+ const response = await this.request("GET", this.repoPath(`/actions/runs/${runId}/jobs`));
185
+ if (!response.ok)
186
+ return [];
187
+ const data = (await response.json());
188
+ return data.jobs.map((job) => ({
189
+ name: job.name,
190
+ conclusion: job.conclusion,
191
+ htmlUrl: job.html_url,
192
+ }));
193
+ }
194
+ }
195
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../../packages/second-brain/src/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ export interface HostingerIpv4Address {
2
+ readonly id: number;
3
+ readonly address: string;
4
+ readonly ptr: string | null;
5
+ }
6
+ export interface HostingerTemplate {
7
+ readonly id: number;
8
+ readonly name: string;
9
+ readonly description: string | null;
10
+ readonly documentation: string | null;
11
+ }
12
+ export interface HostingerVirtualMachine {
13
+ readonly id: number;
14
+ readonly hostname: string;
15
+ readonly state: string;
16
+ readonly actionsLock: string;
17
+ readonly plan: string;
18
+ readonly template: HostingerTemplate | null;
19
+ readonly ipv4: readonly HostingerIpv4Address[];
20
+ }
21
+ export type HostingerActionState = "created" | "sent" | "delayed" | "success" | "error";
22
+ export interface HostingerAction {
23
+ readonly id: number;
24
+ readonly name: string;
25
+ readonly state: HostingerActionState;
26
+ readonly createdAt: string;
27
+ readonly updatedAt: string;
28
+ }
29
+ export type HostingerPasswordIssue = "minimum_length" | "uppercase" | "lowercase" | "number" | "symbol" | "unsupported_character";
30
+ export interface HostingerPasswordValidation {
31
+ readonly valid: boolean;
32
+ readonly issues: readonly HostingerPasswordIssue[];
33
+ }
34
+ export interface HostingerPollingOptions {
35
+ readonly intervalMs?: number;
36
+ readonly maxIntervalMs?: number;
37
+ readonly timeoutMs?: number;
38
+ }
39
+ export interface HostingerGetRetryOptions {
40
+ readonly maxAttempts?: number;
41
+ readonly baseDelayMs?: number;
42
+ readonly maxDelayMs?: number;
43
+ }
44
+ export interface HostingerClientOptions {
45
+ readonly token: string;
46
+ readonly fetchLike?: typeof fetch;
47
+ readonly apiBase?: string;
48
+ readonly sleep?: (ms: number) => Promise<void>;
49
+ readonly polling?: HostingerPollingOptions;
50
+ readonly retry?: HostingerGetRetryOptions;
51
+ readonly random?: () => number;
52
+ readonly now?: () => number;
53
+ readonly requestTimeoutMs?: number;
54
+ }
55
+ export type HostingerApiErrorCode = "network_error" | "request_timeout" | "client_closed" | "unauthorized" | "validation_failed" | "rate_limited" | "server_error" | "unexpected_status" | "invalid_response";
56
+ interface HostingerApiErrorOptions {
57
+ readonly status: number;
58
+ readonly code: HostingerApiErrorCode;
59
+ readonly correlationId: string | null;
60
+ readonly retryable: boolean;
61
+ readonly retryAfterMs: number | null;
62
+ readonly validationFields?: readonly string[];
63
+ readonly message: string;
64
+ readonly cause?: unknown;
65
+ }
66
+ export declare class HostingerApiError extends Error {
67
+ name: string;
68
+ readonly status: number;
69
+ readonly code: HostingerApiErrorCode;
70
+ readonly correlationId: string | null;
71
+ readonly retryable: boolean;
72
+ readonly retryAfterMs: number | null;
73
+ readonly validationFields: readonly string[];
74
+ constructor(options: HostingerApiErrorOptions);
75
+ }
76
+ export declare class HostingerPasswordValidationError extends Error {
77
+ name: string;
78
+ readonly issues: readonly HostingerPasswordIssue[];
79
+ constructor(issues: readonly HostingerPasswordIssue[]);
80
+ }
81
+ export declare class HostingerActionFailedError extends Error {
82
+ name: string;
83
+ readonly virtualMachineId: number;
84
+ readonly actionId: number;
85
+ readonly correlationId: string | null;
86
+ constructor(options: {
87
+ virtualMachineId: number;
88
+ actionId: number;
89
+ correlationId: string | null;
90
+ });
91
+ }
92
+ export declare class HostingerActionTimeoutError extends Error {
93
+ name: string;
94
+ readonly virtualMachineId: number;
95
+ readonly actionId: number;
96
+ readonly timeoutMs: number;
97
+ readonly correlationId: string | null;
98
+ constructor(options: {
99
+ virtualMachineId: number;
100
+ actionId: number;
101
+ timeoutMs: number;
102
+ correlationId: string | null;
103
+ });
104
+ }
105
+ export declare class HostingerActionOutcomeUnknownError extends Error {
106
+ name: string;
107
+ readonly virtualMachineId: number;
108
+ readonly actionId: number;
109
+ readonly correlationId: string | null;
110
+ readonly reason: "poll_failed" | "poll_timeout";
111
+ constructor(options: {
112
+ virtualMachineId: number;
113
+ actionId: number;
114
+ correlationId: string | null;
115
+ reason: "poll_failed" | "poll_timeout";
116
+ });
117
+ }
118
+ export declare const hostingerRootPasswordSymbols = "-().&@?'#,/;+";
119
+ export declare function validateHostingerRootPassword(password: string): HostingerPasswordValidation;
120
+ export declare class HostingerClient {
121
+ private token;
122
+ private readonly fetchLike;
123
+ private readonly apiBase;
124
+ private readonly sleep;
125
+ private readonly polling;
126
+ private readonly retry;
127
+ private readonly random;
128
+ private readonly now;
129
+ private readonly requestTimeoutMs;
130
+ private readonly activeRequests;
131
+ constructor(options: HostingerClientOptions);
132
+ private request;
133
+ private getRetryDelayMs;
134
+ private getJitteredDelayMs;
135
+ private requestActionWithRetries;
136
+ listVirtualMachines(): Promise<readonly HostingerVirtualMachine[]>;
137
+ close(): void;
138
+ setRootPassword(virtualMachineId: number, password: string, polling?: HostingerPollingOptions): Promise<HostingerAction>;
139
+ }
140
+ export {};