unfairly 0.8.2 → 0.8.4
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/CHANGELOG.md +10 -0
- package/LICENSE +15 -0
- package/README.md +3 -1
- package/dist/auth/browser-flow.d.ts +0 -1
- package/dist/auth/browser-flow.js +5 -81
- package/dist/cmd/connect.d.ts +0 -1
- package/dist/cmd/connect.js +1 -164
- package/dist/cmd/init.d.ts +0 -1
- package/dist/cmd/init.js +2 -138
- package/dist/cmd/invite.d.ts +0 -1
- package/dist/cmd/invite.js +4 -73
- package/dist/cmd/join.d.ts +0 -1
- package/dist/cmd/join.js +1 -91
- package/dist/cmd/login.d.ts +0 -1
- package/dist/cmd/login.js +3 -41
- package/dist/cmd/mcp.d.ts +0 -1
- package/dist/cmd/mcp.js +2 -86
- package/dist/cmd/pace.d.ts +23 -2
- package/dist/cmd/pace.js +4 -393
- package/dist/cmd/parse-share-input.d.ts +0 -1
- package/dist/cmd/parse-share-input.js +1 -56
- package/dist/cmd/pull.d.ts +0 -1
- package/dist/cmd/pull.js +7 -143
- package/dist/cmd/push.d.ts +0 -1
- package/dist/cmd/push.js +1 -85
- package/dist/cmd/resolve.d.ts +0 -1
- package/dist/cmd/resolve.js +1 -35
- package/dist/cmd/setup.d.ts +0 -1
- package/dist/cmd/setup.js +2 -345
- package/dist/cmd/status-card.d.ts +0 -1
- package/dist/cmd/status-card.js +2 -156
- package/dist/cmd/status.d.ts +0 -1
- package/dist/cmd/status.js +3 -29
- package/dist/cmd/watch.d.ts +0 -1
- package/dist/cmd/watch.js +1 -26
- package/dist/hosts/claude-code.d.ts +0 -1
- package/dist/hosts/claude-code.js +3 -180
- package/dist/hosts/claude-desktop.d.ts +0 -1
- package/dist/hosts/claude-desktop.js +3 -222
- package/dist/hosts/codex.d.ts +0 -1
- package/dist/hosts/codex.js +20 -234
- package/dist/hosts/shell-env.d.ts +0 -1
- package/dist/hosts/shell-env.js +9 -81
- package/dist/index.d.ts +0 -1
- package/dist/index.js +14 -206
- package/dist/lib/client.d.ts +0 -1
- package/dist/lib/client.js +1 -58
- package/dist/lib/events/emit.d.ts +0 -1
- package/dist/lib/events/emit.js +1 -28
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +1 -11
- package/dist/lib/transport.d.ts +0 -1
- package/dist/lib/transport.js +1 -85
- package/dist/lib/types/index.d.ts +0 -1
- package/dist/lib/types/index.js +1 -50
- package/dist/lib/vault/checkpoint.d.ts +0 -1
- package/dist/lib/vault/checkpoint.js +1 -18
- package/dist/lib/vault/read.d.ts +0 -1
- package/dist/lib/vault/read.js +1 -9
- package/dist/lib/vault/search.d.ts +0 -1
- package/dist/lib/vault/search.js +1 -10
- package/dist/pace/device.d.ts +3 -1
- package/dist/pace/device.js +2 -148
- package/dist/pace/enroll.d.ts +79 -0
- package/dist/pace/enroll.js +2 -0
- package/dist/pace/identity.d.ts +0 -1
- package/dist/pace/identity.js +1 -17
- package/dist/pace/install.d.ts +0 -1
- package/dist/pace/install.js +4 -127
- package/dist/pace/local-evidence.d.ts +0 -1
- package/dist/pace/local-evidence.js +2 -228
- package/dist/pace/local-sessions.d.ts +0 -1
- package/dist/pace/local-sessions.js +1 -254
- package/dist/pace/managed-config.d.ts +0 -1
- package/dist/pace/managed-config.js +8 -102
- package/dist/pace/normalize.d.ts +0 -1
- package/dist/pace/normalize.js +1 -293
- package/dist/pace/repository.d.ts +0 -1
- package/dist/pace/repository.js +2 -120
- package/dist/pace/schema.d.ts +0 -1
- package/dist/pace/schema.js +1 -134
- package/dist/pace/session-summary.d.ts +0 -1
- package/dist/pace/session-summary.js +2 -140
- package/dist/pace/spool.d.ts +0 -1
- package/dist/pace/spool.js +8 -87
- package/dist/pace-runtime.d.ts +1 -0
- package/dist/pace-runtime.js +2 -0
- package/dist/sync/api.d.ts +0 -1
- package/dist/sync/api.js +1 -55
- package/dist/sync/autoloader.d.ts +0 -1
- package/dist/sync/autoloader.js +5 -127
- package/dist/sync/conflict.d.ts +0 -1
- package/dist/sync/conflict.js +5 -15
- package/dist/sync/credentials.d.ts +0 -1
- package/dist/sync/credentials.js +1 -51
- package/dist/sync/files.d.ts +0 -1
- package/dist/sync/files.js +1 -71
- package/dist/sync/manifest.d.ts +0 -1
- package/dist/sync/manifest.js +1 -37
- package/dist/sync/repo-root.d.ts +0 -1
- package/dist/sync/repo-root.js +1 -24
- package/dist/sync/stitch-claude-md.d.ts +0 -1
- package/dist/sync/stitch-claude-md.js +8 -48
- package/dist/ui/colors.d.ts +0 -1
- package/dist/ui/colors.js +1 -28
- package/dist/ui/screens.d.ts +0 -1
- package/dist/ui/screens.js +5 -135
- package/package.json +8 -14
- package/plugin/.cursor-plugin/plugin.json +0 -2
- package/plugin/.mcp.json +1 -1
- package/plugin/LICENSE +15 -0
- package/plugin/README.md +3 -7
- package/plugin/gemini/gemini-extension.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/plugin.json +0 -2
- package/plugin/runtime/unfairly-intelligence.mjs +15 -0
- package/plugin/scripts/capture.cjs +30 -8
- package/plugin/skills/find-company-playbook/SKILL.md +1 -1
- package/dist/auth/browser-flow.d.ts.map +0 -1
- package/dist/auth/browser-flow.js.map +0 -1
- package/dist/cmd/connect.d.ts.map +0 -1
- package/dist/cmd/connect.js.map +0 -1
- package/dist/cmd/init.d.ts.map +0 -1
- package/dist/cmd/init.js.map +0 -1
- package/dist/cmd/invite.d.ts.map +0 -1
- package/dist/cmd/invite.js.map +0 -1
- package/dist/cmd/join.d.ts.map +0 -1
- package/dist/cmd/join.js.map +0 -1
- package/dist/cmd/login.d.ts.map +0 -1
- package/dist/cmd/login.js.map +0 -1
- package/dist/cmd/mcp.d.ts.map +0 -1
- package/dist/cmd/mcp.js.map +0 -1
- package/dist/cmd/pace.d.ts.map +0 -1
- package/dist/cmd/pace.js.map +0 -1
- package/dist/cmd/parse-share-input.d.ts.map +0 -1
- package/dist/cmd/parse-share-input.js.map +0 -1
- package/dist/cmd/pull.d.ts.map +0 -1
- package/dist/cmd/pull.js.map +0 -1
- package/dist/cmd/push.d.ts.map +0 -1
- package/dist/cmd/push.js.map +0 -1
- package/dist/cmd/resolve.d.ts.map +0 -1
- package/dist/cmd/resolve.js.map +0 -1
- package/dist/cmd/setup.d.ts.map +0 -1
- package/dist/cmd/setup.js.map +0 -1
- package/dist/cmd/status-card.d.ts.map +0 -1
- package/dist/cmd/status-card.js.map +0 -1
- package/dist/cmd/status.d.ts.map +0 -1
- package/dist/cmd/status.js.map +0 -1
- package/dist/cmd/watch.d.ts.map +0 -1
- package/dist/cmd/watch.js.map +0 -1
- package/dist/hosts/claude-code.d.ts.map +0 -1
- package/dist/hosts/claude-code.js.map +0 -1
- package/dist/hosts/claude-desktop.d.ts.map +0 -1
- package/dist/hosts/claude-desktop.js.map +0 -1
- package/dist/hosts/codex.d.ts.map +0 -1
- package/dist/hosts/codex.js.map +0 -1
- package/dist/hosts/shell-env.d.ts.map +0 -1
- package/dist/hosts/shell-env.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/events/emit.d.ts.map +0 -1
- package/dist/lib/events/emit.js.map +0 -1
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/transport.d.ts.map +0 -1
- package/dist/lib/transport.js.map +0 -1
- package/dist/lib/types/index.d.ts.map +0 -1
- package/dist/lib/types/index.js.map +0 -1
- package/dist/lib/vault/checkpoint.d.ts.map +0 -1
- package/dist/lib/vault/checkpoint.js.map +0 -1
- package/dist/lib/vault/read.d.ts.map +0 -1
- package/dist/lib/vault/read.js.map +0 -1
- package/dist/lib/vault/search.d.ts.map +0 -1
- package/dist/lib/vault/search.js.map +0 -1
- package/dist/pace/device.d.ts.map +0 -1
- package/dist/pace/device.js.map +0 -1
- package/dist/pace/identity.d.ts.map +0 -1
- package/dist/pace/identity.js.map +0 -1
- package/dist/pace/install.d.ts.map +0 -1
- package/dist/pace/install.js.map +0 -1
- package/dist/pace/local-evidence.d.ts.map +0 -1
- package/dist/pace/local-evidence.js.map +0 -1
- package/dist/pace/local-sessions.d.ts.map +0 -1
- package/dist/pace/local-sessions.js.map +0 -1
- package/dist/pace/managed-config.d.ts.map +0 -1
- package/dist/pace/managed-config.js.map +0 -1
- package/dist/pace/normalize.d.ts.map +0 -1
- package/dist/pace/normalize.js.map +0 -1
- package/dist/pace/repository.d.ts.map +0 -1
- package/dist/pace/repository.js.map +0 -1
- package/dist/pace/schema.d.ts.map +0 -1
- package/dist/pace/schema.js.map +0 -1
- package/dist/pace/session-summary.d.ts.map +0 -1
- package/dist/pace/session-summary.js.map +0 -1
- package/dist/pace/spool.d.ts.map +0 -1
- package/dist/pace/spool.js.map +0 -1
- package/dist/sync/api.d.ts.map +0 -1
- package/dist/sync/api.js.map +0 -1
- package/dist/sync/autoloader.d.ts.map +0 -1
- package/dist/sync/autoloader.js.map +0 -1
- package/dist/sync/conflict.d.ts.map +0 -1
- package/dist/sync/conflict.js.map +0 -1
- package/dist/sync/credentials.d.ts.map +0 -1
- package/dist/sync/credentials.js.map +0 -1
- package/dist/sync/files.d.ts.map +0 -1
- package/dist/sync/files.js.map +0 -1
- package/dist/sync/manifest.d.ts.map +0 -1
- package/dist/sync/manifest.js.map +0 -1
- package/dist/sync/repo-root.d.ts.map +0 -1
- package/dist/sync/repo-root.js.map +0 -1
- package/dist/sync/stitch-claude-md.d.ts.map +0 -1
- package/dist/sync/stitch-claude-md.js.map +0 -1
- package/dist/ui/colors.d.ts.map +0 -1
- package/dist/ui/colors.js.map +0 -1
- package/dist/ui/screens.d.ts.map +0 -1
- package/dist/ui/screens.js.map +0 -1
package/dist/cmd/pace.js
CHANGED
|
@@ -1,393 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { PaceEventV3Schema } from "../pace/schema.js";
|
|
7
|
-
import { normalizeHookEvent } from "../pace/normalize.js";
|
|
8
|
-
import { appendPaceEvent, paceSpoolPath, readPaceEvents, removePaceEvents } from "../pace/spool.js";
|
|
9
|
-
import { currentDevicePlatform, detectPaceClients, paceDevicePath, readOrCreatePaceDevice, writePaceDevice, } from "../pace/device.js";
|
|
10
|
-
const DEFAULT_API = "https://app.unfairly.ai";
|
|
11
|
-
export const RUNTIME_VERSION = "0.8.2";
|
|
12
|
-
const PLUGIN_VERSION = "0.1.0";
|
|
13
|
-
export const INTELLIGENCE_POLICY = {
|
|
14
|
-
version: "metadata-v3",
|
|
15
|
-
mode: "metadata_only",
|
|
16
|
-
permitted_fields: [
|
|
17
|
-
"event_id", "installation_id", "session_id", "workflow_id", "occurred_at",
|
|
18
|
-
"source", "event_type", "evidence_level", "model_family", "tool_category",
|
|
19
|
-
"blocker_category", "approval_mode", "permission_mode", "duration_bucket",
|
|
20
|
-
"exit_code_bucket", "token_counts", "playbook_id", "playbook_version",
|
|
21
|
-
"repository_id", "commit_sha", "artifact_type", "outcome_state",
|
|
22
|
-
],
|
|
23
|
-
prohibited_content: [
|
|
24
|
-
"prompts", "responses", "source code", "file contents", "screenshots",
|
|
25
|
-
"keystrokes", "clipboard", "raw commands", "arguments", "tool output",
|
|
26
|
-
"stdout", "stderr", "paths", "filenames", "repository names", "branches",
|
|
27
|
-
"URLs", "titles", "secrets", "arbitrary errors",
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
async function readStdin() {
|
|
31
|
-
if (process.stdin.isTTY)
|
|
32
|
-
return {};
|
|
33
|
-
let raw = "";
|
|
34
|
-
for await (const chunk of process.stdin)
|
|
35
|
-
raw += String(chunk);
|
|
36
|
-
if (!raw.trim())
|
|
37
|
-
return {};
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(raw);
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
export async function runPaceCapture(source, hookEvent) {
|
|
46
|
-
const device = await readOrCreatePaceDevice();
|
|
47
|
-
if (device.paused)
|
|
48
|
-
return;
|
|
49
|
-
const event = normalizeHookEvent({
|
|
50
|
-
source,
|
|
51
|
-
hookEvent,
|
|
52
|
-
payload: await readStdin(),
|
|
53
|
-
installationId: device.installation_id,
|
|
54
|
-
});
|
|
55
|
-
await appendPaceEvent(event);
|
|
56
|
-
const boundary = event.event_type === "session.started" || event.event_type === "session.ended" || event.event_type === "workflow.stopped";
|
|
57
|
-
if (boundary) {
|
|
58
|
-
// Session summaries (model, tokens, commits) come from the client's own
|
|
59
|
-
// transcript, which hook payloads do not carry. Summarize whatever changed
|
|
60
|
-
// since the last sweep; a failure here must never break the AI workflow.
|
|
61
|
-
await runPaceSweep().catch(() => undefined);
|
|
62
|
-
}
|
|
63
|
-
if (process.env.UNFAIRLY_PACE_NO_FLUSH !== "1" && boundary) {
|
|
64
|
-
// A telemetry outage must never break the employee's AI workflow. Keep the
|
|
65
|
-
// durable queue and retry on the next session boundary or explicit flush.
|
|
66
|
-
await runPaceFlush().catch(() => undefined);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const SWEEP_OVERLAP_MS = 15 * 60_000;
|
|
70
|
-
const FIRST_SWEEP_LOOKBACK_MS = 2 * 86_400_000;
|
|
71
|
-
/** Summarizes local sessions changed since the last sweep and queues them for upload. */
|
|
72
|
-
export async function runPaceSweep(options = {}) {
|
|
73
|
-
const { sweepLocalSessions } = await import("../pace/session-summary.js");
|
|
74
|
-
const device = await readOrCreatePaceDevice();
|
|
75
|
-
const startedAt = Date.now();
|
|
76
|
-
const last = device.last_session_sweep_at ? Date.parse(device.last_session_sweep_at) : startedAt - FIRST_SWEEP_LOOKBACK_MS;
|
|
77
|
-
const result = await sweepLocalSessions({
|
|
78
|
-
since: options.sinceMs ?? last - SWEEP_OVERLAP_MS,
|
|
79
|
-
installationId: device.installation_id,
|
|
80
|
-
collectedVia: options.collectedVia ?? "hook",
|
|
81
|
-
});
|
|
82
|
-
await writePaceDevice({ ...(await readOrCreatePaceDevice()), last_session_sweep_at: new Date(startedAt).toISOString() });
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
function apiBase() {
|
|
86
|
-
return (process.env.UNFAIRLY_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
87
|
-
}
|
|
88
|
-
/** Pulls the organization's approved repositories so sessions can carry repository IDs and commit SHAs. */
|
|
89
|
-
export async function refreshRepositoryPolicy(options = {}) {
|
|
90
|
-
const device = await readOrCreatePaceDevice();
|
|
91
|
-
const fresh = device.repository_policy_refreshed_at && Date.now() - Date.parse(device.repository_policy_refreshed_at) < 3_600_000;
|
|
92
|
-
if (fresh && !options.force)
|
|
93
|
-
return null;
|
|
94
|
-
const token = await getToken();
|
|
95
|
-
if (!token)
|
|
96
|
-
return null;
|
|
97
|
-
const response = await fetch(`${apiBase()}/api/ai-pace/repository-policy`, { headers: { Authorization: `Bearer ${token}` } });
|
|
98
|
-
if (!response.ok)
|
|
99
|
-
return null;
|
|
100
|
-
const { parseRepositoryPolicy, repositoryPolicyPath } = await import("../pace/repository.js");
|
|
101
|
-
const policy = parseRepositoryPolicy(await response.json());
|
|
102
|
-
const file = repositoryPolicyPath();
|
|
103
|
-
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
104
|
-
await fs.writeFile(file, `${JSON.stringify(policy, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
105
|
-
await writePaceDevice({ ...(await readOrCreatePaceDevice()), repository_policy_refreshed_at: new Date().toISOString() });
|
|
106
|
-
return { repositories: policy.mappings.length };
|
|
107
|
-
}
|
|
108
|
-
async function uploadSessionSummaries(token) {
|
|
109
|
-
const { readSessionSpool, removeSessionSummaries } = await import("../pace/session-summary.js");
|
|
110
|
-
let sent = 0;
|
|
111
|
-
for (let round = 0; round < 50; round += 1) {
|
|
112
|
-
const pending = Object.values(await readSessionSpool()).slice(0, 100);
|
|
113
|
-
if (!pending.length)
|
|
114
|
-
break;
|
|
115
|
-
const response = await fetch(`${apiBase()}/api/ai-pace/sessions`, {
|
|
116
|
-
method: "POST",
|
|
117
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
118
|
-
body: JSON.stringify({ sessions: pending }),
|
|
119
|
-
});
|
|
120
|
-
if (!response.ok)
|
|
121
|
-
throw new Error(`Unfairly Intelligence session upload failed (${response.status})`);
|
|
122
|
-
await removeSessionSummaries(pending);
|
|
123
|
-
sent += pending.length;
|
|
124
|
-
}
|
|
125
|
-
return { sent, remaining: Object.keys(await readSessionSpool()).length };
|
|
126
|
-
}
|
|
127
|
-
/** Summarizes and uploads the last `days` of local sessions so Intelligence has history from day one. */
|
|
128
|
-
export async function runPaceBackfill(days) {
|
|
129
|
-
const policy = await refreshRepositoryPolicy({ force: true }).catch(() => null);
|
|
130
|
-
// Uploads are only accepted from a registered, active installation.
|
|
131
|
-
await runPaceHeartbeat();
|
|
132
|
-
const sweep = await runPaceSweep({ sinceMs: Date.now() - days * 86_400_000, collectedVia: "backfill" });
|
|
133
|
-
const token = await getToken();
|
|
134
|
-
if (!token)
|
|
135
|
-
throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");
|
|
136
|
-
const upload = await uploadSessionSummaries(token);
|
|
137
|
-
return { summarized: sweep.summarized, uploaded: upload.sent, remaining: upload.remaining, approved_repositories: policy?.repositories ?? null };
|
|
138
|
-
}
|
|
139
|
-
export async function runPaceFlush() {
|
|
140
|
-
const token = await getToken();
|
|
141
|
-
if (!token)
|
|
142
|
-
throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");
|
|
143
|
-
await refreshRepositoryPolicy().catch(() => null);
|
|
144
|
-
const sessions = await uploadSessionSummaries(token);
|
|
145
|
-
const events = await readPaceEvents();
|
|
146
|
-
if (!events.length)
|
|
147
|
-
return { sent: 0, remaining: 0, sessions_sent: sessions.sent };
|
|
148
|
-
const apiUrl = (process.env.UNFAIRLY_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
149
|
-
const device = await readOrCreatePaceDevice();
|
|
150
|
-
const attributedEvents = events.map((event) => ({
|
|
151
|
-
...event,
|
|
152
|
-
installation_id: event.installation_id ?? device.installation_id,
|
|
153
|
-
}));
|
|
154
|
-
const response = await fetch(`${apiUrl}/api/ai-pace/events`, {
|
|
155
|
-
method: "POST",
|
|
156
|
-
headers: {
|
|
157
|
-
Authorization: `Bearer ${token}`,
|
|
158
|
-
"Content-Type": "application/json",
|
|
159
|
-
...(device.attestation ? { "X-AI-Pace-Attestation": device.attestation } : {}),
|
|
160
|
-
},
|
|
161
|
-
body: JSON.stringify({ events: attributedEvents }),
|
|
162
|
-
});
|
|
163
|
-
if (!response.ok)
|
|
164
|
-
throw new Error(`Unfairly Intelligence upload failed (${response.status})`);
|
|
165
|
-
await removePaceEvents(new Set(events.map((event) => event.event_id)));
|
|
166
|
-
const remaining = (await readPaceEvents()).length;
|
|
167
|
-
await writePaceDevice({ ...(await readOrCreatePaceDevice()), last_upload_at: new Date().toISOString(), collection_policy_version: "metadata-v3" });
|
|
168
|
-
return { sent: events.length, remaining, sessions_sent: sessions.sent };
|
|
169
|
-
}
|
|
170
|
-
export async function runPaceHeartbeat() {
|
|
171
|
-
const token = await getToken();
|
|
172
|
-
if (!token)
|
|
173
|
-
throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");
|
|
174
|
-
const device = await readOrCreatePaceDevice();
|
|
175
|
-
const clients = await detectPaceClients();
|
|
176
|
-
const apiUrl = (process.env.UNFAIRLY_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
177
|
-
const response = await fetch(`${apiUrl}/api/ai-pace/heartbeat`, {
|
|
178
|
-
method: "POST",
|
|
179
|
-
headers: {
|
|
180
|
-
Authorization: `Bearer ${token}`,
|
|
181
|
-
"Content-Type": "application/json",
|
|
182
|
-
},
|
|
183
|
-
body: JSON.stringify({
|
|
184
|
-
installation_id: device.installation_id,
|
|
185
|
-
agent_version: RUNTIME_VERSION,
|
|
186
|
-
runtime_version: RUNTIME_VERSION,
|
|
187
|
-
plugin_version: PLUGIN_VERSION,
|
|
188
|
-
collection_policy_version: "metadata-v3",
|
|
189
|
-
...currentDevicePlatform(),
|
|
190
|
-
clients,
|
|
191
|
-
}),
|
|
192
|
-
});
|
|
193
|
-
if (!response.ok)
|
|
194
|
-
throw new Error(`Unfairly Intelligence heartbeat failed (${response.status})`);
|
|
195
|
-
const body = await response.json();
|
|
196
|
-
if (!body.attestation || !body.expires_at)
|
|
197
|
-
throw new Error("Unfairly Intelligence heartbeat returned no attestation");
|
|
198
|
-
await writePaceDevice({
|
|
199
|
-
...device,
|
|
200
|
-
attestation: body.attestation,
|
|
201
|
-
attestation_expires_at: body.expires_at,
|
|
202
|
-
collection_policy_version: "metadata-v3",
|
|
203
|
-
});
|
|
204
|
-
return { installation_id: device.installation_id, clients };
|
|
205
|
-
}
|
|
206
|
-
export async function runPaceInstall() {
|
|
207
|
-
const clients = await detectPaceClients();
|
|
208
|
-
const { installIntelligence } = await import("../pace/install.js");
|
|
209
|
-
const installed = await installIntelligence({ version: RUNTIME_VERSION, clients });
|
|
210
|
-
const device = await readOrCreatePaceDevice();
|
|
211
|
-
await writePaceDevice({
|
|
212
|
-
...device,
|
|
213
|
-
collection_policy_version: "metadata-v3",
|
|
214
|
-
intelligence_install: {
|
|
215
|
-
version: RUNTIME_VERSION,
|
|
216
|
-
runtime_entrypoint: installed.runtime_entrypoint,
|
|
217
|
-
marketplace_root: installed.marketplace_root,
|
|
218
|
-
owned_paths: installed.owned_paths,
|
|
219
|
-
activated_clients: installed.activations.filter((item) => item.status === "activated").map((item) => item.client),
|
|
220
|
-
installed_at: new Date().toISOString(),
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
await runPaceHeartbeat();
|
|
224
|
-
return {
|
|
225
|
-
runtime_entrypoint: installed.runtime_entrypoint,
|
|
226
|
-
marketplace_root: installed.marketplace_root,
|
|
227
|
-
activations: installed.activations,
|
|
228
|
-
verification: { heartbeat: "ok", live_events: "pending_first_session" },
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
export async function runPaceStatus() {
|
|
232
|
-
const queued = (await readPaceEvents()).length;
|
|
233
|
-
const device = await readOrCreatePaceDevice();
|
|
234
|
-
const clients = await detectPaceClients();
|
|
235
|
-
process.stdout.write(JSON.stringify({
|
|
236
|
-
enabled: !device.paused,
|
|
237
|
-
paused: Boolean(device.paused),
|
|
238
|
-
installation_id: device.installation_id,
|
|
239
|
-
attestation_expires_at: device.attestation_expires_at ?? null,
|
|
240
|
-
last_upload_at: device.last_upload_at ?? null,
|
|
241
|
-
runtime_version: RUNTIME_VERSION,
|
|
242
|
-
plugin_version: PLUGIN_VERSION,
|
|
243
|
-
collection_policy_version: device.collection_policy_version ?? INTELLIGENCE_POLICY.version,
|
|
244
|
-
queued,
|
|
245
|
-
spool: paceSpoolPath(),
|
|
246
|
-
clients,
|
|
247
|
-
}, null, 2) + "\n");
|
|
248
|
-
}
|
|
249
|
-
export async function runPaceConfig(input) {
|
|
250
|
-
const { writeManagedConfigs } = await import("../pace/managed-config.js");
|
|
251
|
-
const files = await writeManagedConfigs(input);
|
|
252
|
-
const device = await readOrCreatePaceDevice();
|
|
253
|
-
await writePaceDevice({ ...device, managed_files: files, collection_policy_version: "metadata-v3" });
|
|
254
|
-
return files;
|
|
255
|
-
}
|
|
256
|
-
export function runPacePolicy() {
|
|
257
|
-
return INTELLIGENCE_POLICY;
|
|
258
|
-
}
|
|
259
|
-
export async function runPacePause(paused) {
|
|
260
|
-
const device = await readOrCreatePaceDevice();
|
|
261
|
-
await writePaceDevice({ ...device, paused });
|
|
262
|
-
return { paused };
|
|
263
|
-
}
|
|
264
|
-
export async function runPaceUninstall() {
|
|
265
|
-
const device = await readOrCreatePaceDevice();
|
|
266
|
-
const activated = new Set(device.intelligence_install?.activated_clients ?? []);
|
|
267
|
-
const { spawnSync } = await import("node:child_process");
|
|
268
|
-
if (activated.has("codex")) {
|
|
269
|
-
spawnSync("codex", ["plugin", "remove", "ai-pace@unfairly-intelligence", "--json"], { encoding: "utf8", timeout: 30_000 });
|
|
270
|
-
spawnSync("codex", ["plugin", "marketplace", "remove", "unfairly-intelligence"], { encoding: "utf8", timeout: 30_000 });
|
|
271
|
-
}
|
|
272
|
-
if (activated.has("claude_code")) {
|
|
273
|
-
spawnSync("claude", ["plugin", "uninstall", "ai-pace@unfairly-intelligence", "--scope", "user"], { encoding: "utf8", timeout: 30_000 });
|
|
274
|
-
spawnSync("claude", ["plugin", "marketplace", "remove", "unfairly-intelligence", "--scope", "user"], { encoding: "utf8", timeout: 30_000 });
|
|
275
|
-
}
|
|
276
|
-
const permitted = new Set([
|
|
277
|
-
"codex-requirements.toml", "claude-settings.json", "gemini-settings.json",
|
|
278
|
-
"evidence-policy.json", "repository-policy.json",
|
|
279
|
-
]);
|
|
280
|
-
let removed = 0;
|
|
281
|
-
for (const file of device.managed_files ?? []) {
|
|
282
|
-
const name = file.split(/[\\/]/).at(-1);
|
|
283
|
-
if (!name || !permitted.has(name))
|
|
284
|
-
continue;
|
|
285
|
-
await fs.unlink(file).then(() => { removed += 1; }).catch((error) => {
|
|
286
|
-
if (error.code !== "ENOENT")
|
|
287
|
-
throw error;
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
const safeOwnedRoots = [
|
|
291
|
-
path.join(os.homedir(), ".unfairly"),
|
|
292
|
-
path.join(os.homedir(), ".cursor", "plugins", "local", "ai-pace"),
|
|
293
|
-
path.join(os.homedir(), ".gemini", "extensions", "unfairly-intelligence"),
|
|
294
|
-
];
|
|
295
|
-
for (const ownedPath of device.intelligence_install?.owned_paths ?? []) {
|
|
296
|
-
const resolved = path.resolve(ownedPath);
|
|
297
|
-
if (!safeOwnedRoots.some((root) => resolved === root || resolved.startsWith(`${root}${path.sep}`)))
|
|
298
|
-
continue;
|
|
299
|
-
await fs.rm(resolved, { recursive: true, force: true });
|
|
300
|
-
removed += 1;
|
|
301
|
-
}
|
|
302
|
-
await fs.unlink(paceSpoolPath()).catch((error) => {
|
|
303
|
-
if (error.code !== "ENOENT")
|
|
304
|
-
throw error;
|
|
305
|
-
});
|
|
306
|
-
await fs.unlink(paceDevicePath()).catch((error) => {
|
|
307
|
-
if (error.code !== "ENOENT" && error.code !== "EISDIR")
|
|
308
|
-
throw error;
|
|
309
|
-
});
|
|
310
|
-
return { removed_files: removed, retained_global_credentials: true };
|
|
311
|
-
}
|
|
312
|
-
export async function runPaceEvidenceScan(policyPath) {
|
|
313
|
-
const { scanLocalEvidence } = await import("../pace/local-evidence.js");
|
|
314
|
-
const device = await readOrCreatePaceDevice();
|
|
315
|
-
const result = await scanLocalEvidence({ policyPath, installationId: device.installation_id });
|
|
316
|
-
await Promise.all(result.events.map((event) => appendPaceEvent(event)));
|
|
317
|
-
return { observed: result.events.length, baseline_roots: result.baselineRoots, queued: (await readPaceEvents()).length };
|
|
318
|
-
}
|
|
319
|
-
export async function runPaceEvidenceWatch(policyPath) {
|
|
320
|
-
const { readLocalEvidencePolicy } = await import("../pace/local-evidence.js");
|
|
321
|
-
const policy = await readLocalEvidencePolicy(policyPath);
|
|
322
|
-
process.stdout.write(`Unfairly Intelligence is watching ${policy.roots.length} approved local evidence root(s).\n`);
|
|
323
|
-
const scan = async () => {
|
|
324
|
-
const result = await runPaceEvidenceScan(policyPath);
|
|
325
|
-
if (result.observed)
|
|
326
|
-
await runPaceFlush().catch(() => undefined);
|
|
327
|
-
};
|
|
328
|
-
await scan();
|
|
329
|
-
await new Promise((resolve) => {
|
|
330
|
-
const timer = setInterval(() => { void scan(); }, policy.scan_interval_seconds * 1000);
|
|
331
|
-
const stop = () => { clearInterval(timer); resolve(); };
|
|
332
|
-
process.once("SIGINT", stop);
|
|
333
|
-
process.once("SIGTERM", stop);
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
export async function runPacePlaybooks(query) {
|
|
337
|
-
const token = await getToken();
|
|
338
|
-
if (!token)
|
|
339
|
-
throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");
|
|
340
|
-
const apiUrl = (process.env.UNFAIRLY_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
341
|
-
const params = query?.trim() ? `?q=${encodeURIComponent(query.trim())}` : "";
|
|
342
|
-
const response = await fetch(`${apiUrl}/api/ai-pace/playbooks${params}`, {
|
|
343
|
-
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
|
|
344
|
-
});
|
|
345
|
-
if (!response.ok)
|
|
346
|
-
throw new Error(`Unfairly Intelligence playbooks failed (${response.status})`);
|
|
347
|
-
const body = await response.json();
|
|
348
|
-
return body.playbooks ?? [];
|
|
349
|
-
}
|
|
350
|
-
export async function runPacePlaybookUse(playbookId, playbookVersion) {
|
|
351
|
-
const token = await getToken();
|
|
352
|
-
if (!token)
|
|
353
|
-
throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");
|
|
354
|
-
const device = await readOrCreatePaceDevice();
|
|
355
|
-
if (device.paused)
|
|
356
|
-
throw new Error("Unfairly Intelligence is paused. Run `unfairly intelligence resume` first.");
|
|
357
|
-
const workflowId = randomUUID();
|
|
358
|
-
const sourceRecordId = randomUUID();
|
|
359
|
-
const event = PaceEventV3Schema.parse({
|
|
360
|
-
schema_version: 3,
|
|
361
|
-
collection_policy_version: "metadata-v3",
|
|
362
|
-
event_id: randomUUID(),
|
|
363
|
-
installation_id: device.installation_id,
|
|
364
|
-
occurred_at: new Date().toISOString(),
|
|
365
|
-
source: "unknown",
|
|
366
|
-
event_type: "session.started",
|
|
367
|
-
evidence_level: "exact",
|
|
368
|
-
session_id: sourceRecordId,
|
|
369
|
-
workflow_id: workflowId,
|
|
370
|
-
playbook_id: playbookId,
|
|
371
|
-
playbook_version: playbookVersion,
|
|
372
|
-
attributes: { client_event: "session_start" },
|
|
373
|
-
});
|
|
374
|
-
await appendPaceEvent(event);
|
|
375
|
-
const apiUrl = (process.env.UNFAIRLY_API_URL ?? DEFAULT_API).replace(/\/$/, "");
|
|
376
|
-
const response = await fetch(`${apiUrl}/api/ai-pace/playbooks`, {
|
|
377
|
-
method: "POST",
|
|
378
|
-
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
379
|
-
body: JSON.stringify({
|
|
380
|
-
action: "record",
|
|
381
|
-
playbook_id: playbookId,
|
|
382
|
-
playbook_version: Number(playbookVersion),
|
|
383
|
-
event_type: "started",
|
|
384
|
-
source_record_id: sourceRecordId,
|
|
385
|
-
workflow_id: workflowId,
|
|
386
|
-
}),
|
|
387
|
-
});
|
|
388
|
-
// The event remains safely spooled when the marketplace endpoint is offline.
|
|
389
|
-
if (!response.ok && response.status < 500)
|
|
390
|
-
throw new Error(`Unfairly Intelligence playbook use failed (${response.status})`);
|
|
391
|
-
return { workflow_id: workflowId, queued: (await readPaceEvents()).length };
|
|
392
|
-
}
|
|
393
|
-
//# sourceMappingURL=pace.js.map
|
|
1
|
+
import{getToken as v}from"../sync/credentials.js";import u from"node:fs/promises";import{randomUUID as h}from"node:crypto";import g from"node:os";import d from"node:path";import{PaceEventV3Schema as D}from"../pace/schema.js";import{normalizeHookEvent as j}from"../pace/normalize.js";import{appendPaceEvent as k,paceSpoolPath as x,readPaceEvents as _,removePaceEvents as z}from"../pace/spool.js";import{currentDevicePlatform as C,detectPaceClients as I,paceDevicePath as F,readOrCreatePaceDevice as c,writePaceDevice as p}from"../pace/device.js";const f="https://app.unfairly.ai",w="0.8.4",O="0.1.0",R={version:"metadata-v3",mode:"metadata_only",permitted_fields:["event_id","installation_id","session_id","workflow_id","occurred_at","source","event_type","evidence_level","model_family","tool_category","blocker_category","approval_mode","permission_mode","duration_bucket","exit_code_bucket","token_counts","playbook_id","playbook_version","repository_id","commit_sha","artifact_type","outcome_state"],prohibited_content:["prompts","responses","source code","file contents","screenshots","keystrokes","clipboard","raw commands","arguments","tool output","stdout","stderr","paths","filenames","repository names","branches","URLs","titles","secrets","arbitrary errors"]};async function Y(){if(process.stdin.isTTY)return{};let n="";for await(const t of process.stdin)n+=String(t);if(!n.trim())return{};try{return JSON.parse(n)}catch{return{}}}async function ne(n,t){const e=await c();if(e.paused)return;const a=j({source:n,hookEvent:t,payload:await Y(),installationId:e.installation_id});await k(a),(a.event_type==="session.started"||a.event_type==="session.ended"||a.event_type==="workflow.stopped")&&await V({flush:process.env.UNFAIRLY_PACE_NO_FLUSH!=="1"}).catch(()=>{})}const B=15*6e4,J=2*864e5,M=90;async function V(n={}){if(!await m())return null;const t=await c(),e=await P({force:!t.history_backfilled_at}).catch(()=>null),a=!t.history_backfilled_at||!!e?.changed,i=a?await S({sinceMs:Date.now()-M*864e5,collectedVia:"backfill"}):await S();return a&&await p({...await c(),history_backfilled_at:new Date().toISOString()}),n.flush!==!1&&await $().catch(()=>{}),{summarized:i.summarized,backfilled:a}}async function S(n={}){const{sweepLocalSessions:t}=await import("../pace/session-summary.js"),e=await c(),a=Date.now(),i=e.last_session_sweep_at?Date.parse(e.last_session_sweep_at):a-J,o=await t({since:n.sinceMs??i-B,installationId:e.installation_id,collectedVia:n.collectedVia??"hook"});return await p({...await c(),last_session_sweep_at:new Date(a).toISOString()}),o}async function m(){const{collectorAuthorization:n,readCollectorCredential:t}=await import("../pace/enroll.js"),e=await t();if(e)return n(e);const a=await v();return a?`Bearer ${a}`:null}function A(){return(process.env.UNFAIRLY_API_URL??f).replace(/\/$/,"")}async function P(n={}){const t=await c();if(t.repository_policy_refreshed_at&&Date.now()-Date.parse(t.repository_policy_refreshed_at)<36e5&&!n.force)return null;const a=await m();if(!a)return null;const i=await fetch(`${A()}/api/ai-pace/repository-policy`,{headers:{Authorization:a}});if(!i.ok)return null;const{parseRepositoryPolicy:o,repositoryPolicyPath:s}=await import("../pace/repository.js"),l=o(await i.json()),r=s(),b=await u.readFile(r,"utf8").then(y=>o(JSON.parse(y)),()=>null),E=y=>(y?.mappings??[]).map(U=>`${U.repository_id}:${U.remote_fingerprint}`).sort().join(","),T=b!==null&&E(b)!==E(l);return await u.mkdir(d.dirname(r),{recursive:!0,mode:448}),await u.writeFile(r,`${JSON.stringify(l,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384}),await p({...await c(),repository_policy_refreshed_at:new Date().toISOString()}),{repositories:l.mappings.length,changed:T}}async function N(n){const{readSessionSpool:t,removeSessionSummaries:e}=await import("../pace/session-summary.js");let a=0;for(let i=0;i<50;i+=1){const o=Object.values(await t()).slice(0,100);if(!o.length)break;const s=await fetch(`${A()}/api/ai-pace/sessions`,{method:"POST",headers:{Authorization:n,"Content-Type":"application/json"},body:JSON.stringify({sessions:o})});if(!s.ok)throw new Error(`Unfairly Intelligence session upload failed (${s.status})`);await e(o),a+=o.length}return{sent:a,remaining:Object.keys(await t()).length}}async function ae(n){const t=await P({force:!0}).catch(()=>null);await L();const e=await S({sinceMs:Date.now()-n*864e5,collectedVia:"backfill"}),a=await m();if(!a)throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");const i=await N(a);return{summarized:e.summarized,uploaded:i.sent,remaining:i.remaining,approved_repositories:t?.repositories??null}}async function $(){const n=await m();if(!n)throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");await P().catch(()=>null);const t=await N(n),e=await _();if(!e.length)return{sent:0,remaining:0,sessions_sent:t.sent};const a=(process.env.UNFAIRLY_API_URL??f).replace(/\/$/,""),i=await c(),o=e.map(r=>({...r,installation_id:r.installation_id??i.installation_id})),s=await fetch(`${a}/api/ai-pace/events`,{method:"POST",headers:{Authorization:n,"Content-Type":"application/json",...i.attestation?{"X-AI-Pace-Attestation":i.attestation}:{}},body:JSON.stringify({events:o})});if(!s.ok)throw new Error(`Unfairly Intelligence upload failed (${s.status})`);await z(new Set(e.map(r=>r.event_id)));const l=(await _()).length;return await p({...await c(),last_upload_at:new Date().toISOString(),collection_policy_version:"metadata-v3"}),{sent:e.length,remaining:l,sessions_sent:t.sent}}async function L(){const n=await m();if(!n)throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");const t=await c(),e=await I(),a=(process.env.UNFAIRLY_API_URL??f).replace(/\/$/,""),i=await fetch(`${a}/api/ai-pace/heartbeat`,{method:"POST",headers:{Authorization:n,"Content-Type":"application/json"},body:JSON.stringify({installation_id:t.installation_id,agent_version:w,runtime_version:w,plugin_version:O,collection_policy_version:"metadata-v3",...C(),clients:e})});if(!i.ok)throw new Error(`Unfairly Intelligence heartbeat failed (${i.status})`);const o=await i.json();if(!o.attestation||!o.expires_at)throw new Error("Unfairly Intelligence heartbeat returned no attestation");return await p({...t,attestation:o.attestation,attestation_expires_at:o.expires_at,collection_policy_version:"metadata-v3"}),{installation_id:t.installation_id,clients:e}}async function ie(){const n=await I(),{installIntelligence:t}=await import("../pace/install.js"),e=await t({version:w,clients:n}),a=await c();return await p({...a,collection_policy_version:"metadata-v3",intelligence_install:{version:w,runtime_entrypoint:e.runtime_entrypoint,marketplace_root:e.marketplace_root,owned_paths:e.owned_paths,activated_clients:e.activations.filter(i=>i.status==="activated").map(i=>i.client),installed_at:new Date().toISOString()}}),await L(),{runtime_entrypoint:e.runtime_entrypoint,marketplace_root:e.marketplace_root,activations:e.activations,verification:{heartbeat:"ok",live_events:"pending_first_session"}}}async function oe(){const n=(await _()).length,t=await c(),e=await I();process.stdout.write(JSON.stringify({enabled:!t.paused,paused:!!t.paused,installation_id:t.installation_id,attestation_expires_at:t.attestation_expires_at??null,last_upload_at:t.last_upload_at??null,runtime_version:w,plugin_version:O,collection_policy_version:t.collection_policy_version??R.version,queued:n,spool:x(),clients:e},null,2)+`
|
|
3
|
+
`)}async function se(n){const{writeManagedConfigs:t}=await import("../pace/managed-config.js"),e=await t(n),a=await c();return await p({...a,managed_files:e,collection_policy_version:"metadata-v3"}),e}function re(){return R}async function ce(n){const t=await c();return await p({...t,paused:n}),{paused:n}}async function le(){const n=await c(),t=new Set(n.intelligence_install?.activated_clients??[]),{spawnSync:e}=await import("node:child_process");t.has("codex")&&(e("codex",["plugin","remove","ai-pace@unfairly-intelligence","--json"],{encoding:"utf8",timeout:3e4}),e("codex",["plugin","marketplace","remove","unfairly-intelligence"],{encoding:"utf8",timeout:3e4})),t.has("claude_code")&&(e("claude",["plugin","uninstall","ai-pace@unfairly-intelligence","--scope","user"],{encoding:"utf8",timeout:3e4}),e("claude",["plugin","marketplace","remove","unfairly-intelligence","--scope","user"],{encoding:"utf8",timeout:3e4}));const a=new Set(["codex-requirements.toml","claude-settings.json","gemini-settings.json","evidence-policy.json","repository-policy.json"]);let i=0;for(const s of n.managed_files??[]){const l=s.split(/[\\/]/).at(-1);!l||!a.has(l)||await u.unlink(s).then(()=>{i+=1}).catch(r=>{if(r.code!=="ENOENT")throw r})}const o=[d.join(g.homedir(),".unfairly"),d.join(g.homedir(),".cursor","plugins","local","ai-pace"),d.join(g.homedir(),".gemini","extensions","unfairly-intelligence")];for(const s of n.intelligence_install?.owned_paths??[]){const l=d.resolve(s);o.some(r=>l===r||l.startsWith(`${r}${d.sep}`))&&(await u.rm(l,{recursive:!0,force:!0}),i+=1)}return await u.unlink(x()).catch(s=>{if(s.code!=="ENOENT")throw s}),await u.unlink(F()).catch(s=>{if(s.code!=="ENOENT"&&s.code!=="EISDIR")throw s}),{removed_files:i,retained_global_credentials:!0}}async function q(n){const{scanLocalEvidence:t}=await import("../pace/local-evidence.js"),e=await c(),a=await t({policyPath:n,installationId:e.installation_id});return await Promise.all(a.events.map(i=>k(i))),{observed:a.events.length,baseline_roots:a.baselineRoots,queued:(await _()).length}}async function pe(n){const{readLocalEvidencePolicy:t}=await import("../pace/local-evidence.js"),e=await t(n);process.stdout.write(`Unfairly Intelligence is watching ${e.roots.length} approved local evidence root(s).
|
|
4
|
+
`);const a=async()=>{(await q(n)).observed&&await $().catch(()=>{})};await a(),await new Promise(i=>{const o=setInterval(()=>{a()},e.scan_interval_seconds*1e3),s=()=>{clearInterval(o),i()};process.once("SIGINT",s),process.once("SIGTERM",s)})}async function ue(n){const t=await v();if(!t)throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");const e=(process.env.UNFAIRLY_API_URL??f).replace(/\/$/,""),a=n?.trim()?`?q=${encodeURIComponent(n.trim())}`:"",i=await fetch(`${e}/api/ai-pace/playbooks${a}`,{headers:{Authorization:`Bearer ${t}`,Accept:"application/json"}});if(!i.ok)throw new Error(`Unfairly Intelligence playbooks failed (${i.status})`);return(await i.json()).playbooks??[]}async function de(n,t){const e=await v();if(!e)throw new Error("Unfairly Intelligence is not enrolled. Run `unfairly setup` first.");const a=await c();if(a.paused)throw new Error("Unfairly Intelligence is paused. Run `unfairly intelligence resume` first.");const i=h(),o=h(),s=D.parse({schema_version:3,collection_policy_version:"metadata-v3",event_id:h(),installation_id:a.installation_id,occurred_at:new Date().toISOString(),source:"unknown",event_type:"session.started",evidence_level:"exact",session_id:o,workflow_id:i,playbook_id:n,playbook_version:t,attributes:{client_event:"session_start"}});await k(s);const l=(process.env.UNFAIRLY_API_URL??f).replace(/\/$/,""),r=await fetch(`${l}/api/ai-pace/playbooks`,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},body:JSON.stringify({action:"record",playbook_id:n,playbook_version:Number(t),event_type:"started",source_record_id:o,workflow_id:i})});if(!r.ok&&r.status<500)throw new Error(`Unfairly Intelligence playbook use failed (${r.status})`);return{workflow_id:i,queued:(await _()).length}}export{M as HISTORY_BACKFILL_DAYS,R as INTELLIGENCE_POLICY,w as RUNTIME_VERSION,m as paceAuthorization,P as refreshRepositoryPolicy,ae as runPaceBackfill,ne as runPaceCapture,se as runPaceConfig,q as runPaceEvidenceScan,pe as runPaceEvidenceWatch,$ as runPaceFlush,L as runPaceHeartbeat,ie as runPaceInstall,ce as runPacePause,de as runPacePlaybookUse,ue as runPacePlaybooks,re as runPacePolicy,oe as runPaceStatus,S as runPaceSweep,V as runPaceSync,le as runPaceUninstall};
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Parse the positional arg to `unfairly connect`. Accepts:
|
|
4
|
-
// - Full URL: https://app.unfairly.ai/s/<uuid>
|
|
5
|
-
// - Short form: s/<uuid>
|
|
6
|
-
// - Bare UUID: <uuid>
|
|
7
|
-
//
|
|
8
|
-
// Returns the share_link UUID + the origin (when present in the input)
|
|
9
|
-
// so `connect` can POST to the same host the link came from. A bare UUID
|
|
10
|
-
// or short form yields `origin: null` — callers should fall back to the
|
|
11
|
-
// production app origin.
|
|
12
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
-
export function parseShareInput(input) {
|
|
14
|
-
if (typeof input !== "string") {
|
|
15
|
-
throw new Error("share link is required");
|
|
16
|
-
}
|
|
17
|
-
const trimmed = input.trim();
|
|
18
|
-
if (trimmed.length === 0) {
|
|
19
|
-
throw new Error("share link is required");
|
|
20
|
-
}
|
|
21
|
-
// Full URL form: https://host/s/<uuid> (with optional trailing slash / query).
|
|
22
|
-
if (/^https?:\/\//i.test(trimmed)) {
|
|
23
|
-
let url;
|
|
24
|
-
try {
|
|
25
|
-
url = new URL(trimmed);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
throw new Error(`not a valid URL: ${trimmed}`);
|
|
29
|
-
}
|
|
30
|
-
const segments = url.pathname.split("/").filter(Boolean);
|
|
31
|
-
// Expect [..., "s", "<uuid>"] — find the "s/" marker.
|
|
32
|
-
const sIdx = segments.indexOf("s");
|
|
33
|
-
if (sIdx === -1 || sIdx === segments.length - 1) {
|
|
34
|
-
throw new Error(`share link URL must include /s/<id>: ${trimmed}`);
|
|
35
|
-
}
|
|
36
|
-
const candidate = segments[sIdx + 1] ?? "";
|
|
37
|
-
if (!UUID_RE.test(candidate)) {
|
|
38
|
-
throw new Error(`share id must be a UUID, got "${candidate}"`);
|
|
39
|
-
}
|
|
40
|
-
return { shareId: candidate.toLowerCase(), origin: url.origin };
|
|
41
|
-
}
|
|
42
|
-
// Short form: s/<uuid>
|
|
43
|
-
if (/^s\//i.test(trimmed)) {
|
|
44
|
-
const candidate = trimmed.slice(2);
|
|
45
|
-
if (!UUID_RE.test(candidate)) {
|
|
46
|
-
throw new Error(`share id must be a UUID, got "${candidate}"`);
|
|
47
|
-
}
|
|
48
|
-
return { shareId: candidate.toLowerCase(), origin: null };
|
|
49
|
-
}
|
|
50
|
-
// Bare UUID
|
|
51
|
-
if (UUID_RE.test(trimmed)) {
|
|
52
|
-
return { shareId: trimmed.toLowerCase(), origin: null };
|
|
53
|
-
}
|
|
54
|
-
throw new Error(`not a recognized share link: "${input}" — expected a URL like https://app.unfairly.ai/s/<uuid>, "s/<uuid>", or a bare UUID`);
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=parse-share-input.js.map
|
|
1
|
+
const o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function a(t){if(typeof t!="string")throw new Error("share link is required");const r=t.trim();if(r.length===0)throw new Error("share link is required");if(/^https?:\/\//i.test(r)){let e;try{e=new URL(r)}catch{throw new Error(`not a valid URL: ${r}`)}const i=e.pathname.split("/").filter(Boolean),n=i.indexOf("s");if(n===-1||n===i.length-1)throw new Error(`share link URL must include /s/<id>: ${r}`);const s=i[n+1]??"";if(!o.test(s))throw new Error(`share id must be a UUID, got "${s}"`);return{shareId:s.toLowerCase(),origin:e.origin}}if(/^s\//i.test(r)){const e=r.slice(2);if(!o.test(e))throw new Error(`share id must be a UUID, got "${e}"`);return{shareId:e.toLowerCase(),origin:null}}if(o.test(r))return{shareId:r.toLowerCase(),origin:null};throw new Error(`not a recognized share link: "${t}" \u2014 expected a URL like https://app.unfairly.ai/s/<uuid>, "s/<uuid>", or a bare UUID`)}export{a as parseShareInput};
|
package/dist/cmd/pull.d.ts
CHANGED
package/dist/cmd/pull.js
CHANGED
|
@@ -1,143 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { getToken } from "../sync/credentials.js";
|
|
9
|
-
import { writeAutoloader } from "../sync/autoloader.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
|
-
async function gitMv(from, to) {
|
|
12
|
-
try {
|
|
13
|
-
await execFileAsync("git", ["mv", from, to], { cwd: process.cwd() });
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
async function tryUnlink(p) {
|
|
21
|
-
try {
|
|
22
|
-
await fs.unlink(p);
|
|
23
|
-
}
|
|
24
|
-
catch { }
|
|
25
|
-
}
|
|
26
|
-
export async function runPull(vaultArg) {
|
|
27
|
-
const cwd = process.cwd();
|
|
28
|
-
const manifest = await readManifest(cwd);
|
|
29
|
-
if (!manifest) {
|
|
30
|
-
console.error("No .unfairly/ found. Run `unfairly init` first.");
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
const token = await getToken();
|
|
34
|
-
if (!token) {
|
|
35
|
-
console.error("No token. Run `unfairly init` to authenticate.");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
const client = new SyncClient(manifest.api_url, token);
|
|
39
|
-
const targets = vaultArg ? [vaultArg] : Object.keys(manifest.vaults);
|
|
40
|
-
const failures = [];
|
|
41
|
-
for (const slug of targets) {
|
|
42
|
-
const v = manifest.vaults[slug];
|
|
43
|
-
if (!v) {
|
|
44
|
-
console.error(`No vault \`${slug}\` in manifest.`);
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
process.stdout.write(`pulling ${slug}... `);
|
|
48
|
-
let result;
|
|
49
|
-
try {
|
|
50
|
-
result = await client.pullAll(slug, v.last_pulled_at);
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
// One vault failing shouldn't kill the rest of the pull. For a single-
|
|
54
|
-
// vault target (`unfairly pull <vault>`) we still want to surface and
|
|
55
|
-
// exit non-zero — fall through to throw at the end.
|
|
56
|
-
const message = err.message.split("\n")[0];
|
|
57
|
-
console.log(`FAILED — ${message}`);
|
|
58
|
-
failures.push({ slug, message });
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
// Vault root: track_in_git → .unfairly/{slug}/, else .unfairly/cache/{slug}/
|
|
62
|
-
const vaultRoot = v.track_in_git ? slug : path.join("cache", slug);
|
|
63
|
-
const absVaultRoot = path.join(cwd, ".unfairly", vaultRoot);
|
|
64
|
-
await fs.mkdir(absVaultRoot, { recursive: true });
|
|
65
|
-
let upserts = 0;
|
|
66
|
-
let deletes = 0;
|
|
67
|
-
let renames = 0;
|
|
68
|
-
let skipped = 0;
|
|
69
|
-
for (const doc of result.docs) {
|
|
70
|
-
let file;
|
|
71
|
-
try {
|
|
72
|
-
file = buildFileFromServer(doc, vaultRoot);
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
// One bad doc shouldn't kill the whole pull. Most commonly this is
|
|
76
|
-
// gray-matter/js-yaml choking on body content that the YAML
|
|
77
|
-
// serializer mis-parses (e.g. `*Foo` interpreted as an alias). The
|
|
78
|
-
// doc stays on the server; the user can re-pull after the upstream
|
|
79
|
-
// doc is sanitized.
|
|
80
|
-
process.stderr.write(` · skipped ${doc.slug} — ${err.message.split("\n")[0]}\n`);
|
|
81
|
-
skipped++;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const relPath = file.path;
|
|
85
|
-
const absPath = path.join(cwd, ".unfairly", relPath);
|
|
86
|
-
// Detect rename: same id, different stored path
|
|
87
|
-
const prev = v.docs[doc.id];
|
|
88
|
-
if (prev && prev.path !== relPath) {
|
|
89
|
-
const prevAbs = path.join(cwd, ".unfairly", prev.path);
|
|
90
|
-
// Tracked vault → use git mv to preserve history
|
|
91
|
-
if (v.track_in_git) {
|
|
92
|
-
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
93
|
-
const ok = await gitMv(prevAbs, absPath);
|
|
94
|
-
if (!ok) {
|
|
95
|
-
await tryUnlink(prevAbs);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
await tryUnlink(prevAbs);
|
|
100
|
-
}
|
|
101
|
-
renames++;
|
|
102
|
-
}
|
|
103
|
-
await writeDocFile(absPath, file.content);
|
|
104
|
-
v.docs[doc.id] = {
|
|
105
|
-
slug: doc.slug,
|
|
106
|
-
path: relPath,
|
|
107
|
-
sha: file.sha,
|
|
108
|
-
updated_at: doc.updated_at,
|
|
109
|
-
source_type: doc.source_type ?? "markdown",
|
|
110
|
-
};
|
|
111
|
-
upserts++;
|
|
112
|
-
}
|
|
113
|
-
for (const tomb of result.deleted) {
|
|
114
|
-
const prev = v.docs[tomb.id];
|
|
115
|
-
if (!prev)
|
|
116
|
-
continue;
|
|
117
|
-
const absPath = path.join(cwd, ".unfairly", prev.path);
|
|
118
|
-
await tryUnlink(absPath);
|
|
119
|
-
delete v.docs[tomb.id];
|
|
120
|
-
deletes++;
|
|
121
|
-
}
|
|
122
|
-
v.last_pulled_at = new Date().toISOString();
|
|
123
|
-
console.log(`+${upserts} -${deletes}${renames ? ` ~${renames}` : ""}${skipped ? ` (skipped ${skipped})` : ""}`);
|
|
124
|
-
}
|
|
125
|
-
await writeManifest(cwd, manifest);
|
|
126
|
-
// Regenerate .unfairly/CLAUDE.md autoloader + .gitignore from the latest mirror.
|
|
127
|
-
// (init prints the @-import paste instruction; pull stays quiet.)
|
|
128
|
-
const refreshed = await readManifest(cwd);
|
|
129
|
-
if (refreshed) {
|
|
130
|
-
const target = path.join(cwd, ".unfairly");
|
|
131
|
-
await writeAutoloader(target, refreshed);
|
|
132
|
-
}
|
|
133
|
-
if (failures.length > 0) {
|
|
134
|
-
process.stderr.write(`\n${failures.length} vault${failures.length === 1 ? "" : "s"} failed:\n`);
|
|
135
|
-
for (const f of failures) {
|
|
136
|
-
process.stderr.write(` · ${f.slug} — ${f.message}\n`);
|
|
137
|
-
}
|
|
138
|
-
// Single-vault target → propagate so callers/CI see the failure.
|
|
139
|
-
// Multi-vault → keep what we got but signal partial failure via exit code.
|
|
140
|
-
process.exitCode = 1;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=pull.js.map
|
|
1
|
+
import{promises as g}from"node:fs";import r from"node:path";import{execFile as A}from"node:child_process";import{promisify as P}from"node:util";import{SyncClient as R}from"../sync/api.js";import{readManifest as b,writeManifest as S}from"../sync/manifest.js";import{buildFileFromServer as D,writeDocFile as M}from"../sync/files.js";import{getToken as N}from"../sync/credentials.js";import{writeAutoloader as C}from"../sync/autoloader.js";const I=P(A);async function O(n,o){try{return await I("git",["mv",n,o],{cwd:process.cwd()}),!0}catch{return!1}}async function h(n){try{await g.unlink(n)}catch{}}async function J(n){const o=process.cwd(),a=await b(o);a||(console.error("No .unfairly/ found. Run `unfairly init` first."),process.exit(1));const y=await N();y||(console.error("No token. Run `unfairly init` to authenticate."),process.exit(1));const j=new R(a.api_url,y),x=n?[n]:Object.keys(a.vaults),c=[];for(const t of x){const i=a.vaults[t];if(!i){console.error(`No vault \`${t}\` in manifest.`);continue}process.stdout.write(`pulling ${t}... `);let f;try{f=await j.pullAll(t,i.last_pulled_at)}catch(e){const s=e.message.split(`
|
|
2
|
+
`)[0];console.log(`FAILED \u2014 ${s}`),c.push({slug:t,message:s});continue}const $=i.track_in_git?t:r.join("cache",t),F=r.join(o,".unfairly",$);await g.mkdir(F,{recursive:!0});let v=0,_=0,p=0,d=0;for(const e of f.docs){let s;try{s=D(e,$)}catch(u){process.stderr.write(` \xB7 skipped ${e.slug} \u2014 ${u.message.split(`
|
|
3
|
+
`)[0]}
|
|
4
|
+
`),d++;continue}const l=s.path,m=r.join(o,".unfairly",l),w=i.docs[e.id];if(w&&w.path!==l){const u=r.join(o,".unfairly",w.path);i.track_in_git?(await g.mkdir(r.dirname(m),{recursive:!0}),await O(u,m)||await h(u)):await h(u),p++}await M(m,s.content),i.docs[e.id]={slug:e.slug,path:l,sha:s.sha,updated_at:e.updated_at,source_type:e.source_type??"markdown"},v++}for(const e of f.deleted){const s=i.docs[e.id];if(!s)continue;const l=r.join(o,".unfairly",s.path);await h(l),delete i.docs[e.id],_++}i.last_pulled_at=new Date().toISOString(),console.log(`+${v} -${_}${p?` ~${p}`:""}${d?` (skipped ${d})`:""}`)}await S(o,a);const k=await b(o);if(k){const t=r.join(o,".unfairly");await C(t,k)}if(c.length>0){process.stderr.write(`
|
|
5
|
+
${c.length} vault${c.length===1?"":"s"} failed:
|
|
6
|
+
`);for(const t of c)process.stderr.write(` \xB7 ${t.slug} \u2014 ${t.message}
|
|
7
|
+
`);process.exitCode=1}}export{J as runPull};
|
package/dist/cmd/push.d.ts
CHANGED