@primitive.ai/prim 0.1.0-alpha.74 → 0.1.0-alpha.75
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/dist/hook-runtime/index.js +437 -399
- package/dist/index.js +436 -398
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -149,85 +149,22 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
149
149
|
import { Command } from "commander";
|
|
150
150
|
import updateNotifier from "update-notifier";
|
|
151
151
|
|
|
152
|
-
// src/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
process.exit(1);
|
|
161
|
-
}
|
|
162
|
-
let phase = active ? "post-commit hook coverage" : "local deactivation";
|
|
152
|
+
// src/lib/confirmation.ts
|
|
153
|
+
function isNonInteractive(globals) {
|
|
154
|
+
return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
|
|
155
|
+
}
|
|
156
|
+
async function askConfirmation(question, output = process.stdout) {
|
|
157
|
+
if (!process.stdin.isTTY) return false;
|
|
158
|
+
const { createInterface } = await import("readline/promises");
|
|
159
|
+
const rl = createInterface({ input: process.stdin, output });
|
|
163
160
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
postRewriteHook = ensureEffectivePostRewriteHook(root).path;
|
|
171
|
-
} catch (error) {
|
|
172
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
173
|
-
process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
|
|
174
|
-
`);
|
|
175
|
-
}
|
|
176
|
-
phase = "repository binding";
|
|
177
|
-
binding = await bindRepository(root);
|
|
178
|
-
phase = "local activation";
|
|
179
|
-
}
|
|
180
|
-
setRepoActive(root, active);
|
|
181
|
-
await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
|
|
182
|
-
if (binding?.status === "unbound") {
|
|
183
|
-
process.stderr.write(`[prim] Prim is enabled locally in ${root}
|
|
184
|
-
`);
|
|
185
|
-
process.stderr.write(
|
|
186
|
-
"[prim] repository is not connected to Primitive; Moves still ingest into the team graph without repository-specific file attribution, Conflict Gate verification, or commit correlation\n"
|
|
187
|
-
);
|
|
188
|
-
process.stderr.write(
|
|
189
|
-
`[prim] ask an organization owner or administrator to grant Primitive's GitHub App access to this repository through Primitive's GitHub App onboarding; binding retries automatically at the next agent SessionStart
|
|
190
|
-
`
|
|
191
|
-
);
|
|
192
|
-
} else {
|
|
193
|
-
process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
|
|
194
|
-
`);
|
|
195
|
-
}
|
|
196
|
-
printJson({
|
|
197
|
-
active,
|
|
198
|
-
repo: root,
|
|
199
|
-
...binding ? {
|
|
200
|
-
bindingStatus: binding.status,
|
|
201
|
-
repositoryFullName: binding.repositoryFullName,
|
|
202
|
-
...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
|
|
203
|
-
} : {},
|
|
204
|
-
...postCommitHook ? { postCommitHook } : {},
|
|
205
|
-
...postRewriteHook ? { postRewriteHook } : {}
|
|
206
|
-
});
|
|
207
|
-
} catch (err) {
|
|
208
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
209
|
-
process.stderr.write(
|
|
210
|
-
active ? `[prim] failed to enable prim during ${phase}: ${detail}
|
|
211
|
-
` : `[prim] failed to disable prim: ${detail}
|
|
212
|
-
`
|
|
213
|
-
);
|
|
214
|
-
process.exit(1);
|
|
161
|
+
const answer = await rl.question(`${question} [y/N] `);
|
|
162
|
+
const normalized = answer.trim().toLowerCase();
|
|
163
|
+
return normalized === "y" || normalized === "yes";
|
|
164
|
+
} finally {
|
|
165
|
+
rl.close();
|
|
215
166
|
}
|
|
216
167
|
}
|
|
217
|
-
function registerActivationCommands(program2) {
|
|
218
|
-
program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action(() => applyActivation(true));
|
|
219
|
-
program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(() => applyActivation(false));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/commands/auth.ts
|
|
223
|
-
import { execFile } from "child_process";
|
|
224
|
-
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
225
|
-
import { existsSync, readFileSync } from "fs";
|
|
226
|
-
import { createServer } from "http";
|
|
227
|
-
import { platform } from "os";
|
|
228
|
-
|
|
229
|
-
// src/auth/api-keys.ts
|
|
230
|
-
import { randomBytes } from "crypto";
|
|
231
168
|
|
|
232
169
|
// src/generated/cli-http-v1.validators.js
|
|
233
170
|
var import_ucs2length = __toESM(require_ucs2length(), 1);
|
|
@@ -9961,7 +9898,113 @@ function isGitHubInstallIntentStatusResponse(value) {
|
|
|
9961
9898
|
}
|
|
9962
9899
|
}
|
|
9963
9900
|
|
|
9901
|
+
// src/lib/github-install-intent.ts
|
|
9902
|
+
var PROTOCOL_VERSION = 1;
|
|
9903
|
+
var MODE = "install_intent_v1";
|
|
9904
|
+
var START_PATH = "/api/cli/github/install-intents";
|
|
9905
|
+
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
9906
|
+
var MAX_INTENT_LIFETIME_MS = 15 * 6e4;
|
|
9907
|
+
var REPOSITORY_IDENTITY_MIGRATION_REQUIRED = "repository_identity_migration_required";
|
|
9908
|
+
function isRepositoryIdentityMigrationRequiredStatus(value) {
|
|
9909
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
9910
|
+
return false;
|
|
9911
|
+
}
|
|
9912
|
+
const record2 = value;
|
|
9913
|
+
if (record2.failureCode !== REPOSITORY_IDENTITY_MIGRATION_REQUIRED) {
|
|
9914
|
+
return false;
|
|
9915
|
+
}
|
|
9916
|
+
return isGitHubInstallIntentStatusResponse({
|
|
9917
|
+
...record2,
|
|
9918
|
+
failureCode: "proof_commit_failed"
|
|
9919
|
+
});
|
|
9920
|
+
}
|
|
9921
|
+
function parseGitHubInstallIntentStart(value, now = Date.now()) {
|
|
9922
|
+
if (!isGitHubInstallIntentStartResponse(value) || value.expiresAt <= now || value.expiresAt - now > MAX_INTENT_LIFETIME_MS) {
|
|
9923
|
+
return null;
|
|
9924
|
+
}
|
|
9925
|
+
return value;
|
|
9926
|
+
}
|
|
9927
|
+
function parseGitHubInstallIntentStatus(value, expectedExpiresAt) {
|
|
9928
|
+
if (isGitHubInstallIntentStatusResponse(value)) {
|
|
9929
|
+
return value.expiresAt === expectedExpiresAt ? value : null;
|
|
9930
|
+
}
|
|
9931
|
+
if (!isRepositoryIdentityMigrationRequiredStatus(value) || value.expiresAt !== expectedExpiresAt) {
|
|
9932
|
+
return null;
|
|
9933
|
+
}
|
|
9934
|
+
return value;
|
|
9935
|
+
}
|
|
9936
|
+
async function createGitHubInstallIntent(client, options = {}) {
|
|
9937
|
+
const { now = Date.now, ...requestOptions } = options;
|
|
9938
|
+
const raw = await client.post(START_PATH, void 0, requestOptions);
|
|
9939
|
+
const parsed = parseGitHubInstallIntentStart(raw, now());
|
|
9940
|
+
if (!parsed) throw new Error("server returned an invalid GitHub install-intent response");
|
|
9941
|
+
return parsed;
|
|
9942
|
+
}
|
|
9943
|
+
function defaultSleep(milliseconds, signal) {
|
|
9944
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
9945
|
+
return new Promise((resolvePromise, reject) => {
|
|
9946
|
+
const onAbort = () => {
|
|
9947
|
+
clearTimeout(timer);
|
|
9948
|
+
reject(signal.reason);
|
|
9949
|
+
};
|
|
9950
|
+
const timer = setTimeout(() => {
|
|
9951
|
+
signal.removeEventListener("abort", onAbort);
|
|
9952
|
+
resolvePromise();
|
|
9953
|
+
}, milliseconds);
|
|
9954
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
9955
|
+
});
|
|
9956
|
+
}
|
|
9957
|
+
function expiredStatus(start) {
|
|
9958
|
+
return {
|
|
9959
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
9960
|
+
mode: MODE,
|
|
9961
|
+
found: true,
|
|
9962
|
+
status: "expired",
|
|
9963
|
+
expiresAt: start.expiresAt,
|
|
9964
|
+
closedAt: start.expiresAt
|
|
9965
|
+
};
|
|
9966
|
+
}
|
|
9967
|
+
async function pollGitHubInstallIntent(client, start, options = {}) {
|
|
9968
|
+
const now = options.now ?? Date.now;
|
|
9969
|
+
const sleep2 = options.sleep ?? defaultSleep;
|
|
9970
|
+
const signal = options.signal ?? new AbortController().signal;
|
|
9971
|
+
const path = `${START_PATH}/${encodeURIComponent(start.intentId)}`;
|
|
9972
|
+
while (true) {
|
|
9973
|
+
if (signal.aborted) throw signal.reason;
|
|
9974
|
+
const remainingBeforeSleep = start.expiresAt - now();
|
|
9975
|
+
if (remainingBeforeSleep <= 0) return expiredStatus(start);
|
|
9976
|
+
await sleep2(Math.min(start.pollAfterMs, remainingBeforeSleep), signal);
|
|
9977
|
+
if (signal.aborted) throw signal.reason;
|
|
9978
|
+
const remainingBeforeRequest = start.expiresAt - now();
|
|
9979
|
+
if (remainingBeforeRequest <= 0) return expiredStatus(start);
|
|
9980
|
+
let raw;
|
|
9981
|
+
try {
|
|
9982
|
+
raw = await client.get(path, {
|
|
9983
|
+
signal: AbortSignal.any([
|
|
9984
|
+
signal,
|
|
9985
|
+
AbortSignal.timeout(Math.min(POLL_REQUEST_TIMEOUT_MS, remainingBeforeRequest))
|
|
9986
|
+
])
|
|
9987
|
+
});
|
|
9988
|
+
} catch (error) {
|
|
9989
|
+
if (signal.aborted) throw error;
|
|
9990
|
+
if (now() >= start.expiresAt) return expiredStatus(start);
|
|
9991
|
+
throw error;
|
|
9992
|
+
}
|
|
9993
|
+
const status = parseGitHubInstallIntentStatus(raw, start.expiresAt);
|
|
9994
|
+
if (!status) throw new Error("server returned an invalid GitHub install-intent status");
|
|
9995
|
+
if (status.status !== "pending" && status.status !== "claimed") return status;
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9998
|
+
|
|
9999
|
+
// src/commands/auth.ts
|
|
10000
|
+
import { execFile } from "child_process";
|
|
10001
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
10002
|
+
import { existsSync, readFileSync } from "fs";
|
|
10003
|
+
import { createServer } from "http";
|
|
10004
|
+
import { platform } from "os";
|
|
10005
|
+
|
|
9964
10006
|
// src/auth/api-keys.ts
|
|
10007
|
+
import { randomBytes } from "crypto";
|
|
9965
10008
|
var USER_API_KEY_TIMEOUT_MS = 1e4;
|
|
9966
10009
|
var USER_API_KEY_EXIT = {
|
|
9967
10010
|
ok: 0,
|
|
@@ -10850,71 +10893,296 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
10850
10893
|
};
|
|
10851
10894
|
}
|
|
10852
10895
|
|
|
10853
|
-
// src/commands/
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
|
|
10886
|
-
|
|
10887
|
-
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
"
|
|
10904
|
-
};
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
|
|
10909
|
-
]
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10896
|
+
// src/commands/github.ts
|
|
10897
|
+
var BIND_TIMEOUT_MS = 1e4;
|
|
10898
|
+
var START_TIMEOUT_MS = 1e4;
|
|
10899
|
+
var EXIT_OK2 = 0;
|
|
10900
|
+
var EXIT_FAILURE = 1;
|
|
10901
|
+
var EXIT_UNBOUND = 2;
|
|
10902
|
+
var defaultDependencies2 = {
|
|
10903
|
+
cwd: () => process.cwd(),
|
|
10904
|
+
gitToplevel,
|
|
10905
|
+
bindRepositoryWithClient,
|
|
10906
|
+
getPinnedClient,
|
|
10907
|
+
createInstallIntent: createGitHubInstallIntent,
|
|
10908
|
+
pollInstallIntent: pollGitHubInstallIntent,
|
|
10909
|
+
openBrowser,
|
|
10910
|
+
now: Date.now
|
|
10911
|
+
};
|
|
10912
|
+
function errorMessage(error) {
|
|
10913
|
+
return error instanceof Error ? error.message : String(error);
|
|
10914
|
+
}
|
|
10915
|
+
function reportConnectFailure(error) {
|
|
10916
|
+
const message = errorMessage(error);
|
|
10917
|
+
const human = boundedHealthError(message) ?? "unknown error";
|
|
10918
|
+
process.stderr.write(`[prim] github connect failed: ${human}
|
|
10919
|
+
`);
|
|
10920
|
+
printJson({
|
|
10921
|
+
status: "error",
|
|
10922
|
+
error: message,
|
|
10923
|
+
...error instanceof HttpError ? { httpStatus: error.status } : {}
|
|
10924
|
+
});
|
|
10925
|
+
process.exitCode = EXIT_FAILURE;
|
|
10926
|
+
}
|
|
10927
|
+
function reportBinding(binding, installAttempted = false) {
|
|
10928
|
+
if (binding.status === "connected") {
|
|
10929
|
+
process.stderr.write(
|
|
10930
|
+
`[prim] repository binding connected for GitHub origin ${binding.repositoryFullName}
|
|
10931
|
+
`
|
|
10932
|
+
);
|
|
10933
|
+
printJson(binding);
|
|
10934
|
+
process.exitCode = EXIT_OK2;
|
|
10935
|
+
return;
|
|
10936
|
+
}
|
|
10937
|
+
process.stderr.write(
|
|
10938
|
+
installAttempted ? `[prim] GitHub installation completed, but ${binding.repositoryFullName} was not granted admin access; repository binding remains unbound
|
|
10939
|
+
` : `[prim] repository binding unbound for GitHub origin ${binding.repositoryFullName}; starting GitHub App installation
|
|
10940
|
+
`
|
|
10941
|
+
);
|
|
10942
|
+
printJson(binding);
|
|
10943
|
+
process.exitCode = EXIT_UNBOUND;
|
|
10944
|
+
}
|
|
10945
|
+
function terminalInstallError(status) {
|
|
10946
|
+
if (status.status === "failed_terminal") {
|
|
10947
|
+
return new Error(`GitHub installation failed: ${status.failureCode}`);
|
|
10948
|
+
}
|
|
10949
|
+
return new Error(`GitHub installation ${status.status}`);
|
|
10950
|
+
}
|
|
10951
|
+
async function completeInstallIntent(client, start, dependencies, browser) {
|
|
10952
|
+
process.stderr.write(`[prim] complete GitHub App installation at:
|
|
10953
|
+
${start.browserUrl}
|
|
10954
|
+
`);
|
|
10955
|
+
if (browser) dependencies.openBrowser(start.browserUrl);
|
|
10956
|
+
return dependencies.pollInstallIntent(client, start, {
|
|
10957
|
+
now: dependencies.now
|
|
10958
|
+
});
|
|
10959
|
+
}
|
|
10960
|
+
async function runGithubConnect(dependencies = defaultDependencies2, options = {}) {
|
|
10961
|
+
const root = options.root ?? dependencies.gitToplevel(dependencies.cwd());
|
|
10962
|
+
if (!root) return { kind: "not-a-repo" };
|
|
10963
|
+
try {
|
|
10964
|
+
const client = await dependencies.getPinnedClient({
|
|
10965
|
+
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
10966
|
+
});
|
|
10967
|
+
const existing = await dependencies.bindRepositoryWithClient(root, client, {
|
|
10968
|
+
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
10969
|
+
});
|
|
10970
|
+
if (existing.status === "connected") {
|
|
10971
|
+
return { kind: "connected", binding: existing };
|
|
10972
|
+
}
|
|
10973
|
+
const start = await dependencies.createInstallIntent(client, {
|
|
10974
|
+
signal: AbortSignal.timeout(START_TIMEOUT_MS),
|
|
10975
|
+
now: dependencies.now
|
|
10976
|
+
});
|
|
10977
|
+
const status = await completeInstallIntent(
|
|
10978
|
+
client,
|
|
10979
|
+
start,
|
|
10980
|
+
dependencies,
|
|
10981
|
+
options.browser ?? true
|
|
10982
|
+
);
|
|
10983
|
+
if (status.status !== "consumed") throw terminalInstallError(status);
|
|
10984
|
+
process.stderr.write(
|
|
10985
|
+
`[prim] GitHub installation verified: ${status.adminRepositoryCount} admin repositories (${status.repositoryCount} total)
|
|
10986
|
+
`
|
|
10987
|
+
);
|
|
10988
|
+
const binding = await dependencies.bindRepositoryWithClient(root, client, {
|
|
10989
|
+
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
10990
|
+
});
|
|
10991
|
+
return binding.status === "connected" ? { kind: "connected", binding } : { kind: "unbound", binding, installAttempted: true };
|
|
10992
|
+
} catch (error) {
|
|
10993
|
+
return { kind: "error", error };
|
|
10994
|
+
}
|
|
10995
|
+
}
|
|
10996
|
+
async function performGithubConnect(dependencies = defaultDependencies2, options = {}) {
|
|
10997
|
+
const outcome = await runGithubConnect(dependencies, options);
|
|
10998
|
+
switch (outcome.kind) {
|
|
10999
|
+
case "not-a-repo":
|
|
11000
|
+
reportConnectFailure(
|
|
11001
|
+
new Error("not a git repository \u2014 run `prim github connect` inside a GitHub repository")
|
|
11002
|
+
);
|
|
11003
|
+
return;
|
|
11004
|
+
case "error":
|
|
11005
|
+
reportConnectFailure(outcome.error);
|
|
11006
|
+
return;
|
|
11007
|
+
case "connected":
|
|
11008
|
+
reportBinding(outcome.binding);
|
|
11009
|
+
return;
|
|
11010
|
+
case "unbound":
|
|
11011
|
+
reportBinding(outcome.binding, outcome.installAttempted);
|
|
11012
|
+
return;
|
|
11013
|
+
}
|
|
11014
|
+
}
|
|
11015
|
+
function registerGithubCommands(program2, dependencies = defaultDependencies2) {
|
|
11016
|
+
const github = program2.command("github").description("Manage GitHub repository bindings");
|
|
11017
|
+
github.command("connect").description("Install or reuse the Primitive GitHub App and bind the current GitHub origin").option("--no-browser", "Print the GitHub installation URL without opening a browser").action(async (options, command) => {
|
|
11018
|
+
const globals = command.optsWithGlobals();
|
|
11019
|
+
await performGithubConnect(dependencies, {
|
|
11020
|
+
browser: options.browser && globals.nonInteractive !== true
|
|
11021
|
+
});
|
|
11022
|
+
});
|
|
11023
|
+
}
|
|
11024
|
+
|
|
11025
|
+
// src/commands/activation.ts
|
|
11026
|
+
var CONNECT_PROMPT = "[prim] Connect this repository to Primitive via the GitHub App now?";
|
|
11027
|
+
async function maybeConnectRepository(root, globals) {
|
|
11028
|
+
if (isNonInteractive(globals)) return void 0;
|
|
11029
|
+
const approved = Boolean(globals.yes) || await askConfirmation(CONNECT_PROMPT, process.stderr);
|
|
11030
|
+
if (!approved) return void 0;
|
|
11031
|
+
const outcome = await runGithubConnect(void 0, { root, browser: true });
|
|
11032
|
+
if (outcome.kind === "connected") {
|
|
11033
|
+
process.stderr.write(
|
|
11034
|
+
`[prim] repository binding connected for GitHub origin ${outcome.binding.repositoryFullName}
|
|
11035
|
+
`
|
|
11036
|
+
);
|
|
11037
|
+
return outcome.binding;
|
|
11038
|
+
}
|
|
11039
|
+
if (outcome.kind === "error") {
|
|
11040
|
+
const detail = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
11041
|
+
process.stderr.write(`[prim] connect could not complete: ${detail}
|
|
11042
|
+
`);
|
|
11043
|
+
}
|
|
11044
|
+
return void 0;
|
|
11045
|
+
}
|
|
11046
|
+
async function applyActivation(active, globals = {}) {
|
|
11047
|
+
const root = gitToplevel();
|
|
11048
|
+
if (!root) {
|
|
11049
|
+
process.stderr.write(
|
|
11050
|
+
`[prim] not a git repository \u2014 run \`prim ${active ? "enable" : "disable"}\` inside a repo
|
|
11051
|
+
`
|
|
11052
|
+
);
|
|
11053
|
+
process.exit(1);
|
|
11054
|
+
}
|
|
11055
|
+
let phase = active ? "post-commit hook coverage" : "local deactivation";
|
|
11056
|
+
try {
|
|
11057
|
+
let binding;
|
|
11058
|
+
let postCommitHook;
|
|
11059
|
+
let postRewriteHook;
|
|
11060
|
+
if (active) {
|
|
11061
|
+
postCommitHook = ensureEffectivePostCommitHook(root).path;
|
|
11062
|
+
try {
|
|
11063
|
+
postRewriteHook = ensureEffectivePostRewriteHook(root).path;
|
|
11064
|
+
} catch (error) {
|
|
11065
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
11066
|
+
process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
|
|
11067
|
+
`);
|
|
11068
|
+
}
|
|
11069
|
+
phase = "repository binding";
|
|
11070
|
+
binding = await bindRepository(root);
|
|
11071
|
+
phase = "local activation";
|
|
11072
|
+
}
|
|
11073
|
+
setRepoActive(root, active);
|
|
11074
|
+
if (active && binding?.status === "unbound") {
|
|
11075
|
+
const connected = await maybeConnectRepository(root, globals);
|
|
11076
|
+
if (connected) binding = connected;
|
|
11077
|
+
}
|
|
11078
|
+
await daemonRequest("statusline_invalidate", {}, { timeoutMs: 250 });
|
|
11079
|
+
if (binding?.status === "unbound") {
|
|
11080
|
+
process.stderr.write(`[prim] Prim is enabled locally in ${root}
|
|
11081
|
+
`);
|
|
11082
|
+
process.stderr.write(
|
|
11083
|
+
"[prim] repository is not connected to Primitive; Moves still ingest into the team graph without repository-specific file attribution, Conflict Gate verification, or commit correlation\n"
|
|
11084
|
+
);
|
|
11085
|
+
process.stderr.write(
|
|
11086
|
+
`[prim] ask an organization owner or administrator to grant Primitive's GitHub App access to this repository through Primitive's GitHub App onboarding; binding retries automatically at the next agent SessionStart
|
|
11087
|
+
`
|
|
11088
|
+
);
|
|
11089
|
+
} else {
|
|
11090
|
+
process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
|
|
11091
|
+
`);
|
|
11092
|
+
}
|
|
11093
|
+
printJson({
|
|
11094
|
+
active,
|
|
11095
|
+
repo: root,
|
|
11096
|
+
...binding ? {
|
|
11097
|
+
bindingStatus: binding.status,
|
|
11098
|
+
repositoryFullName: binding.repositoryFullName,
|
|
11099
|
+
...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
|
|
11100
|
+
} : {},
|
|
11101
|
+
...postCommitHook ? { postCommitHook } : {},
|
|
11102
|
+
...postRewriteHook ? { postRewriteHook } : {}
|
|
11103
|
+
});
|
|
11104
|
+
} catch (err) {
|
|
11105
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
11106
|
+
process.stderr.write(
|
|
11107
|
+
active ? `[prim] failed to enable prim during ${phase}: ${detail}
|
|
11108
|
+
` : `[prim] failed to disable prim: ${detail}
|
|
11109
|
+
`
|
|
11110
|
+
);
|
|
11111
|
+
process.exit(1);
|
|
11112
|
+
}
|
|
11113
|
+
}
|
|
11114
|
+
function registerActivationCommands(program2) {
|
|
11115
|
+
program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action((_opts, command) => applyActivation(true, command.optsWithGlobals()));
|
|
11116
|
+
program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(
|
|
11117
|
+
(_opts, command) => applyActivation(false, command.optsWithGlobals())
|
|
11118
|
+
);
|
|
11119
|
+
}
|
|
11120
|
+
|
|
11121
|
+
// src/commands/claude-install.ts
|
|
11122
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
11123
|
+
import { homedir } from "os";
|
|
11124
|
+
import { join as join2 } from "path";
|
|
11125
|
+
|
|
11126
|
+
// src/lib/hook-runtime.ts
|
|
11127
|
+
import { createHash as createHash2, randomBytes as randomBytes3 } from "crypto";
|
|
11128
|
+
import {
|
|
11129
|
+
constants,
|
|
11130
|
+
accessSync,
|
|
11131
|
+
chmodSync,
|
|
11132
|
+
copyFileSync,
|
|
11133
|
+
existsSync as existsSync2,
|
|
11134
|
+
lstatSync,
|
|
11135
|
+
mkdirSync,
|
|
11136
|
+
mkdtempSync,
|
|
11137
|
+
readFileSync as readFileSync2,
|
|
11138
|
+
readdirSync,
|
|
11139
|
+
realpathSync,
|
|
11140
|
+
renameSync,
|
|
11141
|
+
rmSync,
|
|
11142
|
+
statSync,
|
|
11143
|
+
utimesSync,
|
|
11144
|
+
writeFileSync
|
|
11145
|
+
} from "fs";
|
|
11146
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
11147
|
+
var HOOK_RUNTIME_SCHEMA_VERSION = 1;
|
|
11148
|
+
var DIRECTORY_MODE = 448;
|
|
11149
|
+
var DATA_FILE_MODE = 384;
|
|
11150
|
+
var LAUNCHER_MODE = 448;
|
|
11151
|
+
var RELEASE_PREFIX = "release-";
|
|
11152
|
+
var RELEASE_NAME_RE = /^release-[0-9a-f]{64}$/u;
|
|
11153
|
+
var MAX_MANIFEST_BYTES = 64 * 1024;
|
|
11154
|
+
var MAX_RELEASES = 8;
|
|
11155
|
+
var MAX_RELEASE_DELETIONS_PER_STAGE = 8;
|
|
11156
|
+
var RETAIN_PREDECESSORS = 2;
|
|
11157
|
+
var RELEASE_GRACE_MS = 10 * 60 * 1e3;
|
|
11158
|
+
var HOOK_RUNTIME_ENTRIES = {
|
|
11159
|
+
// SessionStart self-healing resolves this exact CLI entry to run
|
|
11160
|
+
// `daemon ensure`; it must travel with the immutable hook release.
|
|
11161
|
+
prim: "dist/index.js",
|
|
11162
|
+
"prim-hook": "dist/hooks/prim-hook.js",
|
|
11163
|
+
"prim-pre-commit": "dist/hooks/pre-commit.js",
|
|
11164
|
+
"prim-post-commit": "dist/hooks/post-commit.js",
|
|
11165
|
+
"prim-post-rewrite": "dist/hooks/post-rewrite.js",
|
|
11166
|
+
"prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
|
|
11167
|
+
"prim-post-tool-use": "dist/hooks/post-tool-use.js",
|
|
11168
|
+
"prim-session-start": "dist/hooks/session-start.js",
|
|
11169
|
+
"prim-session-end": "dist/hooks/session-end.js",
|
|
11170
|
+
"prim-daemon-server": "dist/daemon/server.js",
|
|
11171
|
+
"prim-statusline": "dist/statusline-main.js"
|
|
11172
|
+
};
|
|
11173
|
+
var SOURCE_ENTRIES = Object.fromEntries(
|
|
11174
|
+
Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, target]) => [
|
|
11175
|
+
bin,
|
|
11176
|
+
target.replace(/^dist\//u, "")
|
|
11177
|
+
])
|
|
11178
|
+
);
|
|
11179
|
+
function hookRuntimePaths(options = {}) {
|
|
11180
|
+
const env = options.env ?? process.env;
|
|
11181
|
+
const configDir = primConfigDirectory({
|
|
11182
|
+
...options,
|
|
11183
|
+
env,
|
|
11184
|
+
homeDir: options.homeDir ?? env.HOME ?? ""
|
|
11185
|
+
});
|
|
10918
11186
|
const runtimeDir = join(configDir, "hook-runtime");
|
|
10919
11187
|
return {
|
|
10920
11188
|
configDir,
|
|
@@ -12652,7 +12920,7 @@ function atomicWrite2(path, content, mode, validate) {
|
|
|
12652
12920
|
}
|
|
12653
12921
|
return true;
|
|
12654
12922
|
}
|
|
12655
|
-
function
|
|
12923
|
+
function defaultSleep2(ms) {
|
|
12656
12924
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
12657
12925
|
}
|
|
12658
12926
|
function validatePlist(path, timeoutMs2) {
|
|
@@ -12971,7 +13239,7 @@ async function ensureMacDaemonLocked(options) {
|
|
|
12971
13239
|
PLIST_FILE_MODE,
|
|
12972
13240
|
options.validatePlist ?? validatePlist
|
|
12973
13241
|
);
|
|
12974
|
-
const sleep2 = options.sleep ??
|
|
13242
|
+
const sleep2 = options.sleep ?? defaultSleep2;
|
|
12975
13243
|
const nowMs = options.nowMs ?? performance.now.bind(performance);
|
|
12976
13244
|
const deadlineMs = nowMs() + TRANSITION_TIMEOUT_MS;
|
|
12977
13245
|
const inspect = options.inspectDaemon ?? inspectDaemon;
|
|
@@ -13025,7 +13293,7 @@ async function bootoutMacDaemon(options = {}) {
|
|
|
13025
13293
|
async function bootoutMacDaemonLocked(options) {
|
|
13026
13294
|
const runner = options.runner ?? runLaunchctl;
|
|
13027
13295
|
const inspect = options.inspectDaemon ?? inspectDaemon;
|
|
13028
|
-
const sleep2 = options.sleep ??
|
|
13296
|
+
const sleep2 = options.sleep ?? defaultSleep2;
|
|
13029
13297
|
const nowMs = options.nowMs ?? performance.now.bind(performance);
|
|
13030
13298
|
const deadlineMs = nowMs() + TRANSITION_TIMEOUT_MS;
|
|
13031
13299
|
const paths = launchdPaths(options);
|
|
@@ -13095,7 +13363,7 @@ var READY_POLL_MS2 = 100;
|
|
|
13095
13363
|
var READY_PROBE_TIMEOUT_MS2 = 250;
|
|
13096
13364
|
var HEALTHY_TIMEOUT_MS = 3e4;
|
|
13097
13365
|
var HEALTHY_POLL_MS = 250;
|
|
13098
|
-
var
|
|
13366
|
+
var EXIT_OK3 = 0;
|
|
13099
13367
|
var EXIT_NOT_RUNNING = 2;
|
|
13100
13368
|
var EXIT_BOOTING = 3;
|
|
13101
13369
|
function formatDaemonLifecycleMessage(message, decisionIngestion) {
|
|
@@ -13476,7 +13744,7 @@ function classifyStatus(pidAlive, responding, snapshot, pid) {
|
|
|
13476
13744
|
};
|
|
13477
13745
|
}
|
|
13478
13746
|
if (!snapshot) {
|
|
13479
|
-
return { json: { running: true, responding: true }, exitCode:
|
|
13747
|
+
return { json: { running: true, responding: true }, exitCode: EXIT_OK3 };
|
|
13480
13748
|
}
|
|
13481
13749
|
if (snapshot.healthy === false) {
|
|
13482
13750
|
return {
|
|
@@ -13484,7 +13752,7 @@ function classifyStatus(pidAlive, responding, snapshot, pid) {
|
|
|
13484
13752
|
exitCode: EXIT_BOOTING
|
|
13485
13753
|
};
|
|
13486
13754
|
}
|
|
13487
|
-
return { json: { running: true, responding: true, ...snapshot }, exitCode:
|
|
13755
|
+
return { json: { running: true, responding: true, ...snapshot }, exitCode: EXIT_OK3 };
|
|
13488
13756
|
}
|
|
13489
13757
|
function classifyLaunchdStatus(service, responding, snapshot, disabled) {
|
|
13490
13758
|
if (!service) {
|
|
@@ -13582,7 +13850,7 @@ function classifyLaunchdStatus(service, responding, snapshot, disabled) {
|
|
|
13582
13850
|
disabled,
|
|
13583
13851
|
...snapshot
|
|
13584
13852
|
},
|
|
13585
|
-
exitCode:
|
|
13853
|
+
exitCode: EXIT_OK3
|
|
13586
13854
|
};
|
|
13587
13855
|
}
|
|
13588
13856
|
function formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion, bindingState) {
|
|
@@ -13632,7 +13900,7 @@ async function detachedDaemonStatus() {
|
|
|
13632
13900
|
writeLiveSnapshot(snapshot);
|
|
13633
13901
|
}
|
|
13634
13902
|
console.log(JSON.stringify(json, null, 2));
|
|
13635
|
-
if (exitCode !==
|
|
13903
|
+
if (exitCode !== EXIT_OK3 && !process.exitCode) {
|
|
13636
13904
|
process.exitCode = exitCode;
|
|
13637
13905
|
}
|
|
13638
13906
|
}
|
|
@@ -13682,7 +13950,7 @@ async function macDaemonStatus() {
|
|
|
13682
13950
|
writeLiveSnapshot(snapshot, true);
|
|
13683
13951
|
}
|
|
13684
13952
|
console.log(JSON.stringify(json, null, 2));
|
|
13685
|
-
if (exitCode !==
|
|
13953
|
+
if (exitCode !== EXIT_OK3 && !process.exitCode) process.exitCode = exitCode;
|
|
13686
13954
|
}
|
|
13687
13955
|
async function daemonStatus() {
|
|
13688
13956
|
if (process.platform === "darwin") {
|
|
@@ -14278,7 +14546,7 @@ var DECISION_LIFECYCLE_EXIT = {
|
|
|
14278
14546
|
server: 3,
|
|
14279
14547
|
notFound: 4
|
|
14280
14548
|
};
|
|
14281
|
-
var
|
|
14549
|
+
var defaultDependencies3 = {
|
|
14282
14550
|
getClient,
|
|
14283
14551
|
signal: () => AbortSignal.timeout(DECISION_LIFECYCLE_TIMEOUT_MS),
|
|
14284
14552
|
writeStdout: (value) => console.log(value),
|
|
@@ -14491,19 +14759,19 @@ async function executeLifecycle(operation, request, dependencies) {
|
|
|
14491
14759
|
return writeFailure2(operation, classifyError2(error), dependencies);
|
|
14492
14760
|
}
|
|
14493
14761
|
}
|
|
14494
|
-
function publishDecision(id, dependencies =
|
|
14762
|
+
function publishDecision(id, dependencies = defaultDependencies3) {
|
|
14495
14763
|
const request = { id };
|
|
14496
14764
|
return executeLifecycle("publish", request, dependencies);
|
|
14497
14765
|
}
|
|
14498
|
-
function restoreDecision(id, dependencies =
|
|
14766
|
+
function restoreDecision(id, dependencies = defaultDependencies3) {
|
|
14499
14767
|
const request = { id };
|
|
14500
14768
|
return executeLifecycle("restore", request, dependencies);
|
|
14501
14769
|
}
|
|
14502
|
-
function ratifyDecision(id, dependencies =
|
|
14770
|
+
function ratifyDecision(id, dependencies = defaultDependencies3) {
|
|
14503
14771
|
const request = { id };
|
|
14504
14772
|
return executeLifecycle("ratify", request, dependencies);
|
|
14505
14773
|
}
|
|
14506
|
-
function supersedeDecision(id, by, dependencies =
|
|
14774
|
+
function supersedeDecision(id, by, dependencies = defaultDependencies3) {
|
|
14507
14775
|
const request = { id, by };
|
|
14508
14776
|
return executeLifecycle("supersede", request, dependencies);
|
|
14509
14777
|
}
|
|
@@ -15254,26 +15522,9 @@ function formatShowJson(result) {
|
|
|
15254
15522
|
return JSON.stringify(result, null, 2);
|
|
15255
15523
|
}
|
|
15256
15524
|
|
|
15257
|
-
// src/lib/confirmation.ts
|
|
15258
|
-
function isNonInteractive(globals) {
|
|
15259
|
-
return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
|
|
15260
|
-
}
|
|
15261
|
-
async function askConfirmation(question, output = process.stdout) {
|
|
15262
|
-
if (!process.stdin.isTTY) return false;
|
|
15263
|
-
const { createInterface } = await import("readline/promises");
|
|
15264
|
-
const rl = createInterface({ input: process.stdin, output });
|
|
15265
|
-
try {
|
|
15266
|
-
const answer = await rl.question(`${question} [y/N] `);
|
|
15267
|
-
const normalized = answer.trim().toLowerCase();
|
|
15268
|
-
return normalized === "y" || normalized === "yes";
|
|
15269
|
-
} finally {
|
|
15270
|
-
rl.close();
|
|
15271
|
-
}
|
|
15272
|
-
}
|
|
15273
|
-
|
|
15274
15525
|
// src/commands/decisions.ts
|
|
15275
15526
|
var EXIT_NOT_FOUND = 4;
|
|
15276
|
-
var
|
|
15527
|
+
var EXIT_FAILURE2 = 1;
|
|
15277
15528
|
var EXIT_USAGE = 2;
|
|
15278
15529
|
function handleRepairCommandError(error) {
|
|
15279
15530
|
if (error instanceof RepairProposalNotFoundError) {
|
|
@@ -15290,12 +15541,12 @@ function handleRepairCommandError(error) {
|
|
|
15290
15541
|
console.error(
|
|
15291
15542
|
`[prim] ${error instanceof RepairAuthorizationError ? error.message : "Not authorized to review commit repairs; active organization membership and current repository authorization are required"}`
|
|
15292
15543
|
);
|
|
15293
|
-
process.exitCode =
|
|
15544
|
+
process.exitCode = EXIT_FAILURE2;
|
|
15294
15545
|
return true;
|
|
15295
15546
|
}
|
|
15296
15547
|
if (error instanceof RepairEndpointVersionError || error instanceof RepairListContractError) {
|
|
15297
15548
|
console.error(`[prim] ${error.message}`);
|
|
15298
|
-
process.exitCode =
|
|
15549
|
+
process.exitCode = EXIT_FAILURE2;
|
|
15299
15550
|
return true;
|
|
15300
15551
|
}
|
|
15301
15552
|
return false;
|
|
@@ -16927,220 +17178,6 @@ function registerDoctorCommands(program2) {
|
|
|
16927
17178
|
});
|
|
16928
17179
|
}
|
|
16929
17180
|
|
|
16930
|
-
// src/lib/github-install-intent.ts
|
|
16931
|
-
var PROTOCOL_VERSION = 1;
|
|
16932
|
-
var MODE = "install_intent_v1";
|
|
16933
|
-
var START_PATH = "/api/cli/github/install-intents";
|
|
16934
|
-
var POLL_REQUEST_TIMEOUT_MS = 1e4;
|
|
16935
|
-
var MAX_INTENT_LIFETIME_MS = 15 * 6e4;
|
|
16936
|
-
var REPOSITORY_IDENTITY_MIGRATION_REQUIRED = "repository_identity_migration_required";
|
|
16937
|
-
function isRepositoryIdentityMigrationRequiredStatus(value) {
|
|
16938
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
16939
|
-
return false;
|
|
16940
|
-
}
|
|
16941
|
-
const record2 = value;
|
|
16942
|
-
if (record2.failureCode !== REPOSITORY_IDENTITY_MIGRATION_REQUIRED) {
|
|
16943
|
-
return false;
|
|
16944
|
-
}
|
|
16945
|
-
return isGitHubInstallIntentStatusResponse({
|
|
16946
|
-
...record2,
|
|
16947
|
-
failureCode: "proof_commit_failed"
|
|
16948
|
-
});
|
|
16949
|
-
}
|
|
16950
|
-
function parseGitHubInstallIntentStart(value, now = Date.now()) {
|
|
16951
|
-
if (!isGitHubInstallIntentStartResponse(value) || value.expiresAt <= now || value.expiresAt - now > MAX_INTENT_LIFETIME_MS) {
|
|
16952
|
-
return null;
|
|
16953
|
-
}
|
|
16954
|
-
return value;
|
|
16955
|
-
}
|
|
16956
|
-
function parseGitHubInstallIntentStatus(value, expectedExpiresAt) {
|
|
16957
|
-
if (isGitHubInstallIntentStatusResponse(value)) {
|
|
16958
|
-
return value.expiresAt === expectedExpiresAt ? value : null;
|
|
16959
|
-
}
|
|
16960
|
-
if (!isRepositoryIdentityMigrationRequiredStatus(value) || value.expiresAt !== expectedExpiresAt) {
|
|
16961
|
-
return null;
|
|
16962
|
-
}
|
|
16963
|
-
return value;
|
|
16964
|
-
}
|
|
16965
|
-
async function createGitHubInstallIntent(client, options = {}) {
|
|
16966
|
-
const { now = Date.now, ...requestOptions } = options;
|
|
16967
|
-
const raw = await client.post(START_PATH, void 0, requestOptions);
|
|
16968
|
-
const parsed = parseGitHubInstallIntentStart(raw, now());
|
|
16969
|
-
if (!parsed) throw new Error("server returned an invalid GitHub install-intent response");
|
|
16970
|
-
return parsed;
|
|
16971
|
-
}
|
|
16972
|
-
function defaultSleep2(milliseconds, signal) {
|
|
16973
|
-
if (signal.aborted) return Promise.reject(signal.reason);
|
|
16974
|
-
return new Promise((resolvePromise, reject) => {
|
|
16975
|
-
const onAbort = () => {
|
|
16976
|
-
clearTimeout(timer);
|
|
16977
|
-
reject(signal.reason);
|
|
16978
|
-
};
|
|
16979
|
-
const timer = setTimeout(() => {
|
|
16980
|
-
signal.removeEventListener("abort", onAbort);
|
|
16981
|
-
resolvePromise();
|
|
16982
|
-
}, milliseconds);
|
|
16983
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
16984
|
-
});
|
|
16985
|
-
}
|
|
16986
|
-
function expiredStatus(start) {
|
|
16987
|
-
return {
|
|
16988
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
16989
|
-
mode: MODE,
|
|
16990
|
-
found: true,
|
|
16991
|
-
status: "expired",
|
|
16992
|
-
expiresAt: start.expiresAt,
|
|
16993
|
-
closedAt: start.expiresAt
|
|
16994
|
-
};
|
|
16995
|
-
}
|
|
16996
|
-
async function pollGitHubInstallIntent(client, start, options = {}) {
|
|
16997
|
-
const now = options.now ?? Date.now;
|
|
16998
|
-
const sleep2 = options.sleep ?? defaultSleep2;
|
|
16999
|
-
const signal = options.signal ?? new AbortController().signal;
|
|
17000
|
-
const path = `${START_PATH}/${encodeURIComponent(start.intentId)}`;
|
|
17001
|
-
while (true) {
|
|
17002
|
-
if (signal.aborted) throw signal.reason;
|
|
17003
|
-
const remainingBeforeSleep = start.expiresAt - now();
|
|
17004
|
-
if (remainingBeforeSleep <= 0) return expiredStatus(start);
|
|
17005
|
-
await sleep2(Math.min(start.pollAfterMs, remainingBeforeSleep), signal);
|
|
17006
|
-
if (signal.aborted) throw signal.reason;
|
|
17007
|
-
const remainingBeforeRequest = start.expiresAt - now();
|
|
17008
|
-
if (remainingBeforeRequest <= 0) return expiredStatus(start);
|
|
17009
|
-
let raw;
|
|
17010
|
-
try {
|
|
17011
|
-
raw = await client.get(path, {
|
|
17012
|
-
signal: AbortSignal.any([
|
|
17013
|
-
signal,
|
|
17014
|
-
AbortSignal.timeout(Math.min(POLL_REQUEST_TIMEOUT_MS, remainingBeforeRequest))
|
|
17015
|
-
])
|
|
17016
|
-
});
|
|
17017
|
-
} catch (error) {
|
|
17018
|
-
if (signal.aborted) throw error;
|
|
17019
|
-
if (now() >= start.expiresAt) return expiredStatus(start);
|
|
17020
|
-
throw error;
|
|
17021
|
-
}
|
|
17022
|
-
const status = parseGitHubInstallIntentStatus(raw, start.expiresAt);
|
|
17023
|
-
if (!status) throw new Error("server returned an invalid GitHub install-intent status");
|
|
17024
|
-
if (status.status !== "pending" && status.status !== "claimed") return status;
|
|
17025
|
-
}
|
|
17026
|
-
}
|
|
17027
|
-
|
|
17028
|
-
// src/commands/github.ts
|
|
17029
|
-
var BIND_TIMEOUT_MS = 1e4;
|
|
17030
|
-
var START_TIMEOUT_MS = 1e4;
|
|
17031
|
-
var EXIT_OK3 = 0;
|
|
17032
|
-
var EXIT_FAILURE2 = 1;
|
|
17033
|
-
var EXIT_UNBOUND = 2;
|
|
17034
|
-
var defaultDependencies3 = {
|
|
17035
|
-
cwd: () => process.cwd(),
|
|
17036
|
-
gitToplevel,
|
|
17037
|
-
bindRepositoryWithClient,
|
|
17038
|
-
getPinnedClient,
|
|
17039
|
-
createInstallIntent: createGitHubInstallIntent,
|
|
17040
|
-
pollInstallIntent: pollGitHubInstallIntent,
|
|
17041
|
-
openBrowser,
|
|
17042
|
-
now: Date.now
|
|
17043
|
-
};
|
|
17044
|
-
function errorMessage(error) {
|
|
17045
|
-
return error instanceof Error ? error.message : String(error);
|
|
17046
|
-
}
|
|
17047
|
-
function reportConnectFailure(error) {
|
|
17048
|
-
const message = errorMessage(error);
|
|
17049
|
-
const human = boundedHealthError(message) ?? "unknown error";
|
|
17050
|
-
process.stderr.write(`[prim] github connect failed: ${human}
|
|
17051
|
-
`);
|
|
17052
|
-
printJson({
|
|
17053
|
-
status: "error",
|
|
17054
|
-
error: message,
|
|
17055
|
-
...error instanceof HttpError ? { httpStatus: error.status } : {}
|
|
17056
|
-
});
|
|
17057
|
-
process.exitCode = EXIT_FAILURE2;
|
|
17058
|
-
}
|
|
17059
|
-
function reportBinding(binding, installAttempted = false) {
|
|
17060
|
-
if (binding.status === "connected") {
|
|
17061
|
-
process.stderr.write(
|
|
17062
|
-
`[prim] repository binding connected for GitHub origin ${binding.repositoryFullName}
|
|
17063
|
-
`
|
|
17064
|
-
);
|
|
17065
|
-
printJson(binding);
|
|
17066
|
-
process.exitCode = EXIT_OK3;
|
|
17067
|
-
return;
|
|
17068
|
-
}
|
|
17069
|
-
process.stderr.write(
|
|
17070
|
-
installAttempted ? `[prim] GitHub installation completed, but ${binding.repositoryFullName} was not granted admin access; repository binding remains unbound
|
|
17071
|
-
` : `[prim] repository binding unbound for GitHub origin ${binding.repositoryFullName}; starting GitHub App installation
|
|
17072
|
-
`
|
|
17073
|
-
);
|
|
17074
|
-
printJson(binding);
|
|
17075
|
-
process.exitCode = EXIT_UNBOUND;
|
|
17076
|
-
}
|
|
17077
|
-
function terminalInstallError(status) {
|
|
17078
|
-
if (status.status === "failed_terminal") {
|
|
17079
|
-
return new Error(`GitHub installation failed: ${status.failureCode}`);
|
|
17080
|
-
}
|
|
17081
|
-
return new Error(`GitHub installation ${status.status}`);
|
|
17082
|
-
}
|
|
17083
|
-
async function completeInstallIntent(client, start, dependencies, browser) {
|
|
17084
|
-
process.stderr.write(`[prim] complete GitHub App installation at:
|
|
17085
|
-
${start.browserUrl}
|
|
17086
|
-
`);
|
|
17087
|
-
if (browser) dependencies.openBrowser(start.browserUrl);
|
|
17088
|
-
return dependencies.pollInstallIntent(client, start, {
|
|
17089
|
-
now: dependencies.now
|
|
17090
|
-
});
|
|
17091
|
-
}
|
|
17092
|
-
async function performGithubConnect(dependencies = defaultDependencies3, options = {}) {
|
|
17093
|
-
const root = dependencies.gitToplevel(dependencies.cwd());
|
|
17094
|
-
if (!root) {
|
|
17095
|
-
reportConnectFailure(
|
|
17096
|
-
new Error("not a git repository \u2014 run `prim github connect` inside a GitHub repository")
|
|
17097
|
-
);
|
|
17098
|
-
return;
|
|
17099
|
-
}
|
|
17100
|
-
try {
|
|
17101
|
-
const client = await dependencies.getPinnedClient({
|
|
17102
|
-
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
17103
|
-
});
|
|
17104
|
-
const existing = await dependencies.bindRepositoryWithClient(root, client, {
|
|
17105
|
-
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
17106
|
-
});
|
|
17107
|
-
if (existing.status === "connected") {
|
|
17108
|
-
reportBinding(existing);
|
|
17109
|
-
return;
|
|
17110
|
-
}
|
|
17111
|
-
const start = await dependencies.createInstallIntent(client, {
|
|
17112
|
-
signal: AbortSignal.timeout(START_TIMEOUT_MS),
|
|
17113
|
-
now: dependencies.now
|
|
17114
|
-
});
|
|
17115
|
-
const status = await completeInstallIntent(
|
|
17116
|
-
client,
|
|
17117
|
-
start,
|
|
17118
|
-
dependencies,
|
|
17119
|
-
options.browser ?? true
|
|
17120
|
-
);
|
|
17121
|
-
if (status.status !== "consumed") throw terminalInstallError(status);
|
|
17122
|
-
process.stderr.write(
|
|
17123
|
-
`[prim] GitHub installation verified: ${status.adminRepositoryCount} admin repositories (${status.repositoryCount} total)
|
|
17124
|
-
`
|
|
17125
|
-
);
|
|
17126
|
-
const binding = await dependencies.bindRepositoryWithClient(root, client, {
|
|
17127
|
-
signal: AbortSignal.timeout(BIND_TIMEOUT_MS)
|
|
17128
|
-
});
|
|
17129
|
-
reportBinding(binding, true);
|
|
17130
|
-
} catch (error) {
|
|
17131
|
-
reportConnectFailure(error);
|
|
17132
|
-
}
|
|
17133
|
-
}
|
|
17134
|
-
function registerGithubCommands(program2, dependencies = defaultDependencies3) {
|
|
17135
|
-
const github = program2.command("github").description("Manage GitHub repository bindings");
|
|
17136
|
-
github.command("connect").description("Install or reuse the Primitive GitHub App and bind the current GitHub origin").option("--no-browser", "Print the GitHub installation URL without opening a browser").action(async (options, command) => {
|
|
17137
|
-
const globals = command.optsWithGlobals();
|
|
17138
|
-
await performGithubConnect(dependencies, {
|
|
17139
|
-
browser: options.browser && globals.nonInteractive !== true
|
|
17140
|
-
});
|
|
17141
|
-
});
|
|
17142
|
-
}
|
|
17143
|
-
|
|
17144
17181
|
// src/commands/hooks.ts
|
|
17145
17182
|
import { execFileSync } from "child_process";
|
|
17146
17183
|
import {
|
|
@@ -17459,14 +17496,9 @@ function writeOwnHooks() {
|
|
|
17459
17496
|
mkdirSync4(PRIM_GIT_HOOKS_DIR, { recursive: true });
|
|
17460
17497
|
}
|
|
17461
17498
|
for (const spec of HOOKS) {
|
|
17462
|
-
|
|
17463
|
-
|
|
17464
|
-
|
|
17465
|
-
} else if (spec.hookName === POST_REWRITE.hookName) {
|
|
17466
|
-
writeFileSync3(path, globalHookScript(spec), { mode: 493 });
|
|
17467
|
-
} else {
|
|
17468
|
-
writeFileSync3(path, globalHookScript(spec), { mode: 493 });
|
|
17469
|
-
}
|
|
17499
|
+
writeFileSync3(resolve3(PRIM_GIT_HOOKS_DIR, spec.hookName), globalHookScript(spec), {
|
|
17500
|
+
mode: 493
|
|
17501
|
+
});
|
|
17470
17502
|
}
|
|
17471
17503
|
for (const name of PASSTHROUGH_HOOKS) {
|
|
17472
17504
|
writeFileSync3(resolve3(PRIM_GIT_HOOKS_DIR, name), passThroughScript(name), { mode: 493 });
|
|
@@ -18503,6 +18535,7 @@ function registerSetupCommand(program2, dependencies = {}) {
|
|
|
18503
18535
|
const agent = agentInput;
|
|
18504
18536
|
const scope = opts.scope;
|
|
18505
18537
|
const self = process.argv[1];
|
|
18538
|
+
const globals = program2.optsWithGlobals();
|
|
18506
18539
|
const run = dependencies.run ?? ((args, capture = false) => {
|
|
18507
18540
|
const r = spawnSync2(process.execPath, [self, ...args], {
|
|
18508
18541
|
stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
|
|
@@ -18572,7 +18605,12 @@ function registerSetupCommand(program2, dependencies = {}) {
|
|
|
18572
18605
|
const enableStep = setupSteps.find((candidate) => candidate.key === "enable");
|
|
18573
18606
|
if (enableStep) {
|
|
18574
18607
|
note(`${enableStep.label} \xB7 installing\u2026`);
|
|
18575
|
-
const
|
|
18608
|
+
const enableArgs = [
|
|
18609
|
+
...enableStep.args,
|
|
18610
|
+
...globals.yes ? ["--yes"] : [],
|
|
18611
|
+
...globals.nonInteractive ? ["--non-interactive"] : []
|
|
18612
|
+
];
|
|
18613
|
+
const { code } = run(enableArgs);
|
|
18576
18614
|
results[enableStep.key] = code === 0 ? "ok" : enableStep.required ? "failed" : "skipped";
|
|
18577
18615
|
}
|
|
18578
18616
|
const healthStep = setupSteps.find((candidate) => candidate.key === "health");
|