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/pace/schema.js
CHANGED
|
@@ -1,134 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const PaceSourceSchema = z.enum([
|
|
3
|
-
"codex", "claude_code", "cursor", "gemini_cli", "openrouter", "chatgpt",
|
|
4
|
-
"claude_desktop", "claude_web", "gemini_web", "local_filesystem", "unknown",
|
|
5
|
-
]);
|
|
6
|
-
export const PaceEventTypeSchema = z.enum([
|
|
7
|
-
"session.started", "session.ended", "model.completed", "tool.completed",
|
|
8
|
-
"tool.failed", "workflow.stopped", "artifact.created", "artifact.modified",
|
|
9
|
-
"desktop.activity", "unknown.observed",
|
|
10
|
-
]);
|
|
11
|
-
export const PaceEvidenceLevelSchema = z.enum(["exact", "correlated", "inferred"]);
|
|
12
|
-
export const PaceToolCategorySchema = z.enum([
|
|
13
|
-
"file_read", "file_write", "code_search", "shell", "version_control",
|
|
14
|
-
"web", "connector", "agent", "skill", "other", "unknown",
|
|
15
|
-
]);
|
|
16
|
-
export const PaceBlockerCategorySchema = z.enum([
|
|
17
|
-
"authentication", "authorization_permission", "approval_required",
|
|
18
|
-
"missing_dependency_tool", "configuration", "rate_limit_quota", "tool_failure",
|
|
19
|
-
"model_provider_unavailable", "workflow_stopped", "unknown",
|
|
20
|
-
]);
|
|
21
|
-
export const PaceModelFamilySchema = z.enum([
|
|
22
|
-
"openai_gpt", "anthropic_claude", "google_gemini", "meta_llama",
|
|
23
|
-
"mistral", "other", "unknown",
|
|
24
|
-
]);
|
|
25
|
-
export const PaceApprovalModeSchema = z.enum(["none", "manual", "automatic", "unknown"]);
|
|
26
|
-
export const PacePermissionModeSchema = z.enum([
|
|
27
|
-
"read_only", "workspace_write", "elevated", "unrestricted", "unknown",
|
|
28
|
-
]);
|
|
29
|
-
export const PaceDurationBucketSchema = z.enum([
|
|
30
|
-
"under_1s", "1s_to_10s", "10s_to_60s", "1m_to_5m", "over_5m", "unknown",
|
|
31
|
-
]);
|
|
32
|
-
export const PaceExitCodeBucketSchema = z.enum(["success", "failure", "signal", "unknown"]);
|
|
33
|
-
const OptionalOpaqueId = z.string().trim().regex(/^[A-Za-z0-9_-]{1,128}$/).optional();
|
|
34
|
-
// Legacy schemas exist only so an old local spool can be sanitized in place.
|
|
35
|
-
// New collection and network upload use v3 exclusively.
|
|
36
|
-
const LegacyAttributesSchema = z.object({
|
|
37
|
-
hook_event: z.string().max(128),
|
|
38
|
-
approval_mode: z.string().max(128).optional(),
|
|
39
|
-
permission_mode: z.string().max(128).optional(),
|
|
40
|
-
status: z.string().max(128).optional(),
|
|
41
|
-
success: z.boolean().optional(),
|
|
42
|
-
exit_code: z.number().int().optional(),
|
|
43
|
-
duration_ms: z.number().nonnegative().optional(),
|
|
44
|
-
input_tokens: z.number().int().nonnegative().optional(),
|
|
45
|
-
output_tokens: z.number().int().nonnegative().optional(),
|
|
46
|
-
cached_tokens: z.number().int().nonnegative().optional(),
|
|
47
|
-
error_type: z.string().max(128).optional(),
|
|
48
|
-
stop_reason: z.string().max(128).optional(),
|
|
49
|
-
root_label: z.string().max(128).optional(),
|
|
50
|
-
path_fingerprint: z.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
51
|
-
file_extension: z.string().max(32).optional(),
|
|
52
|
-
file_name: z.string().max(255).optional(),
|
|
53
|
-
size_bytes: z.number().int().nonnegative().optional(),
|
|
54
|
-
files_changed: z.number().int().nonnegative().optional(),
|
|
55
|
-
local_signal: z.enum(["approved_output", "app_state_change"]).optional(),
|
|
56
|
-
source_app: z.enum(["chatgpt", "claude_desktop", "other"]).optional(),
|
|
57
|
-
}).strict();
|
|
58
|
-
const LegacyEventBaseSchema = z.object({
|
|
59
|
-
event_id: z.string().uuid(),
|
|
60
|
-
installation_id: z.string().uuid().optional(),
|
|
61
|
-
occurred_at: z.string().datetime(),
|
|
62
|
-
source: PaceSourceSchema,
|
|
63
|
-
event_type: PaceEventTypeSchema,
|
|
64
|
-
evidence_level: PaceEvidenceLevelSchema,
|
|
65
|
-
session_id: z.string().trim().min(1).max(512).optional(),
|
|
66
|
-
model: z.string().trim().min(1).max(512).optional(),
|
|
67
|
-
tool: z.string().trim().min(1).max(512).optional(),
|
|
68
|
-
skill: z.string().trim().min(1).max(512).optional(),
|
|
69
|
-
cwd: z.string().trim().min(1).max(512).optional(),
|
|
70
|
-
repository: z.string().trim().min(1).max(512).optional(),
|
|
71
|
-
branch: z.string().trim().min(1).max(512).optional(),
|
|
72
|
-
commit_sha: z.string().regex(/^[a-f0-9]{7,64}$/i).optional(),
|
|
73
|
-
artifact_type: z.enum([
|
|
74
|
-
"code", "pull_request", "document", "deck", "spreadsheet", "analysis",
|
|
75
|
-
"workflow", "decision", "other",
|
|
76
|
-
]).optional(),
|
|
77
|
-
attributes: LegacyAttributesSchema,
|
|
78
|
-
});
|
|
79
|
-
export const PaceEventV1Schema = LegacyEventBaseSchema.extend({ schema_version: z.literal(1) }).strict();
|
|
80
|
-
export const PaceEventV2Schema = LegacyEventBaseSchema.extend({
|
|
81
|
-
schema_version: z.literal(2),
|
|
82
|
-
workflow_id: z.string().uuid().optional(),
|
|
83
|
-
initiative_id: z.string().trim().min(1).max(512).optional(),
|
|
84
|
-
playbook_id: z.string().trim().min(1).max(512).optional(),
|
|
85
|
-
playbook_version: z.string().trim().min(1).max(512).optional(),
|
|
86
|
-
}).strict();
|
|
87
|
-
export const PaceEventV3Schema = z.object({
|
|
88
|
-
schema_version: z.literal(3),
|
|
89
|
-
collection_policy_version: z.literal("metadata-v3"),
|
|
90
|
-
event_id: z.string().uuid(),
|
|
91
|
-
installation_id: z.string().uuid().optional(),
|
|
92
|
-
occurred_at: z.string().datetime(),
|
|
93
|
-
source: PaceSourceSchema,
|
|
94
|
-
event_type: PaceEventTypeSchema,
|
|
95
|
-
evidence_level: PaceEvidenceLevelSchema,
|
|
96
|
-
session_id: OptionalOpaqueId,
|
|
97
|
-
workflow_id: z.string().uuid().optional(),
|
|
98
|
-
initiative_id: OptionalOpaqueId,
|
|
99
|
-
playbook_id: OptionalOpaqueId,
|
|
100
|
-
playbook_version: OptionalOpaqueId,
|
|
101
|
-
repository_id: z.string().uuid().optional(),
|
|
102
|
-
commit_sha: z.string().regex(/^[a-f0-9]{40}$/i).optional(),
|
|
103
|
-
model_family: PaceModelFamilySchema.optional(),
|
|
104
|
-
tool_category: PaceToolCategorySchema.optional(),
|
|
105
|
-
blocker_category: PaceBlockerCategorySchema.optional(),
|
|
106
|
-
artifact_type: z.enum([
|
|
107
|
-
"code", "pull_request", "document", "deck", "spreadsheet", "analysis",
|
|
108
|
-
"workflow", "decision", "other",
|
|
109
|
-
]).optional(),
|
|
110
|
-
attributes: z.object({
|
|
111
|
-
client_event: z.enum([
|
|
112
|
-
"session_start", "session_end", "before_agent", "after_agent", "stop",
|
|
113
|
-
"tool_success", "tool_failure", "model_complete", "local_output", "local_activity", "unknown",
|
|
114
|
-
]),
|
|
115
|
-
approval_mode: PaceApprovalModeSchema.optional(),
|
|
116
|
-
permission_mode: PacePermissionModeSchema.optional(),
|
|
117
|
-
success: z.boolean().optional(),
|
|
118
|
-
exit_code_bucket: PaceExitCodeBucketSchema.optional(),
|
|
119
|
-
duration_bucket: PaceDurationBucketSchema.optional(),
|
|
120
|
-
input_tokens: z.number().int().nonnegative().optional(),
|
|
121
|
-
output_tokens: z.number().int().nonnegative().optional(),
|
|
122
|
-
cached_tokens: z.number().int().nonnegative().optional(),
|
|
123
|
-
files_changed_bucket: z.enum(["one", "two_to_five", "six_to_twenty", "over_twenty", "unknown"]).optional(),
|
|
124
|
-
local_signal: z.enum(["approved_output", "app_state_change"]).optional(),
|
|
125
|
-
}).strict(),
|
|
126
|
-
}).strict().superRefine((event, context) => {
|
|
127
|
-
if (event.commit_sha && !event.repository_id) {
|
|
128
|
-
context.addIssue({ code: z.ZodIssueCode.custom, path: ["commit_sha"], message: "commit_sha requires an approved repository_id" });
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
export const PaceLegacyEventSchema = z.discriminatedUnion("schema_version", [PaceEventV1Schema, PaceEventV2Schema]);
|
|
132
|
-
export const PaceEventSchema = PaceEventV3Schema;
|
|
133
|
-
export const PaceBatchSchema = z.object({ events: z.array(PaceEventV3Schema).min(1).max(250) }).strict();
|
|
134
|
-
//# sourceMappingURL=schema.js.map
|
|
1
|
+
import{z as e}from"zod";const n=e.enum(["codex","claude_code","cursor","gemini_cli","openrouter","chatgpt","claude_desktop","claude_web","gemini_web","local_filesystem","unknown"]),i=e.enum(["session.started","session.ended","model.completed","tool.completed","tool.failed","workflow.stopped","artifact.created","artifact.modified","desktop.activity","unknown.observed"]),a=e.enum(["exact","correlated","inferred"]),l=e.enum(["file_read","file_write","code_search","shell","version_control","web","connector","agent","skill","other","unknown"]),m=e.enum(["authentication","authorization_permission","approval_required","missing_dependency_tool","configuration","rate_limit_quota","tool_failure","model_provider_unavailable","workflow_stopped","unknown"]),p=e.enum(["openai_gpt","anthropic_claude","google_gemini","meta_llama","mistral","other","unknown"]),_=e.enum(["none","manual","automatic","unknown"]),u=e.enum(["read_only","workspace_write","elevated","unrestricted","unknown"]),d=e.enum(["under_1s","1s_to_10s","10s_to_60s","1m_to_5m","over_5m","unknown"]),g=e.enum(["success","failure","signal","unknown"]),o=e.string().trim().regex(/^[A-Za-z0-9_-]{1,128}$/).optional(),v=e.object({hook_event:e.string().max(128),approval_mode:e.string().max(128).optional(),permission_mode:e.string().max(128).optional(),status:e.string().max(128).optional(),success:e.boolean().optional(),exit_code:e.number().int().optional(),duration_ms:e.number().nonnegative().optional(),input_tokens:e.number().int().nonnegative().optional(),output_tokens:e.number().int().nonnegative().optional(),cached_tokens:e.number().int().nonnegative().optional(),error_type:e.string().max(128).optional(),stop_reason:e.string().max(128).optional(),root_label:e.string().max(128).optional(),path_fingerprint:e.string().regex(/^[a-f0-9]{64}$/).optional(),file_extension:e.string().max(32).optional(),file_name:e.string().max(255).optional(),size_bytes:e.number().int().nonnegative().optional(),files_changed:e.number().int().nonnegative().optional(),local_signal:e.enum(["approved_output","app_state_change"]).optional(),source_app:e.enum(["chatgpt","claude_desktop","other"]).optional()}).strict(),r=e.object({event_id:e.string().uuid(),installation_id:e.string().uuid().optional(),occurred_at:e.string().datetime(),source:n,event_type:i,evidence_level:a,session_id:e.string().trim().min(1).max(512).optional(),model:e.string().trim().min(1).max(512).optional(),tool:e.string().trim().min(1).max(512).optional(),skill:e.string().trim().min(1).max(512).optional(),cwd:e.string().trim().min(1).max(512).optional(),repository:e.string().trim().min(1).max(512).optional(),branch:e.string().trim().min(1).max(512).optional(),commit_sha:e.string().regex(/^[a-f0-9]{7,64}$/i).optional(),artifact_type:e.enum(["code","pull_request","document","deck","spreadsheet","analysis","workflow","decision","other"]).optional(),attributes:v}),h=r.extend({schema_version:e.literal(1)}).strict(),x=r.extend({schema_version:e.literal(2),workflow_id:e.string().uuid().optional(),initiative_id:e.string().trim().min(1).max(512).optional(),playbook_id:e.string().trim().min(1).max(512).optional(),playbook_version:e.string().trim().min(1).max(512).optional()}).strict(),s=e.object({schema_version:e.literal(3),collection_policy_version:e.literal("metadata-v3"),event_id:e.string().uuid(),installation_id:e.string().uuid().optional(),occurred_at:e.string().datetime(),source:n,event_type:i,evidence_level:a,session_id:o,workflow_id:e.string().uuid().optional(),initiative_id:o,playbook_id:o,playbook_version:o,repository_id:e.string().uuid().optional(),commit_sha:e.string().regex(/^[a-f0-9]{40}$/i).optional(),model_family:p.optional(),tool_category:l.optional(),blocker_category:m.optional(),artifact_type:e.enum(["code","pull_request","document","deck","spreadsheet","analysis","workflow","decision","other"]).optional(),attributes:e.object({client_event:e.enum(["session_start","session_end","before_agent","after_agent","stop","tool_success","tool_failure","model_complete","local_output","local_activity","unknown"]),approval_mode:_.optional(),permission_mode:u.optional(),success:e.boolean().optional(),exit_code_bucket:g.optional(),duration_bucket:d.optional(),input_tokens:e.number().int().nonnegative().optional(),output_tokens:e.number().int().nonnegative().optional(),cached_tokens:e.number().int().nonnegative().optional(),files_changed_bucket:e.enum(["one","two_to_five","six_to_twenty","over_twenty","unknown"]).optional(),local_signal:e.enum(["approved_output","app_state_change"]).optional()}).strict()}).strict().superRefine((t,c)=>{t.commit_sha&&!t.repository_id&&c.addIssue({code:e.ZodIssueCode.custom,path:["commit_sha"],message:"commit_sha requires an approved repository_id"})}),b=e.discriminatedUnion("schema_version",[h,x]),f=s,y=e.object({events:e.array(s).min(1).max(250)}).strict();export{_ as PaceApprovalModeSchema,y as PaceBatchSchema,m as PaceBlockerCategorySchema,d as PaceDurationBucketSchema,f as PaceEventSchema,i as PaceEventTypeSchema,h as PaceEventV1Schema,x as PaceEventV2Schema,s as PaceEventV3Schema,a as PaceEvidenceLevelSchema,g as PaceExitCodeBucketSchema,b as PaceLegacyEventSchema,p as PaceModelFamilySchema,u as PacePermissionModeSchema,n as PaceSourceSchema,l as PaceToolCategorySchema};
|
|
@@ -1,140 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
import { collectLocalSessions } from "./local-sessions.js";
|
|
6
|
-
import { approvedRepositoryId, readRepositoryPolicy, sessionCommitShas } from "./repository.js";
|
|
7
|
-
function readPolicySkills() {
|
|
8
|
-
return readRepositoryPolicy()?.skills ?? [];
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* One content-free summary per AI coding session, uploaded to Unfairly
|
|
12
|
-
* Intelligence. This is the unit the server joins to GitHub pull requests and
|
|
13
|
-
* prices into spend. Keep in sync with src/lib/ai-pace/sessions.ts.
|
|
14
|
-
*/
|
|
15
|
-
const MODEL_ID = /^(claude-[a-z0-9.-]{1,48}|gpt-[a-z0-9.-]{1,32}|o[1-9][a-z0-9-]{0,20}|gemini-[a-z0-9.-]{1,40}|codex-[a-z0-9.-]{1,32})$/;
|
|
16
|
-
const Count = z.number().int().nonnegative();
|
|
17
|
-
const SKILL_ID = /^[a-z0-9][a-z0-9:_.-]{0,119}$/;
|
|
18
|
-
/** Published plugin skills are namespaced (`superpowers:brainstorming`); a public name, not content. */
|
|
19
|
-
const PUBLIC_SKILL = /^[a-z0-9][a-z0-9-]{0,40}:[a-z0-9][a-z0-9-]{0,78}$/;
|
|
20
|
-
/**
|
|
21
|
-
* Splits a session's skills into names that may be reported (published plugin
|
|
22
|
-
* skills and the organization's own catalog) and a count of everything else,
|
|
23
|
-
* such as personal or project skills, whose names never leave the device.
|
|
24
|
-
*/
|
|
25
|
-
export function reportableSkills(skills, orgCatalog = []) {
|
|
26
|
-
const catalog = new Set(orgCatalog);
|
|
27
|
-
const reportable = [...new Set(skills)].filter((skill) => SKILL_ID.test(skill) && (PUBLIC_SKILL.test(skill) || catalog.has(skill)));
|
|
28
|
-
return { skills: reportable.slice(0, 20), unlisted: new Set(skills).size - reportable.length };
|
|
29
|
-
}
|
|
30
|
-
export const SessionSummaryUploadSchema = z.object({
|
|
31
|
-
schema_version: z.literal(1),
|
|
32
|
-
collection_policy_version: z.literal("metadata-v3"),
|
|
33
|
-
installation_id: z.string().uuid(),
|
|
34
|
-
session_id: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/),
|
|
35
|
-
source: z.enum(["claude_code", "codex"]),
|
|
36
|
-
collected_via: z.enum(["hook", "backfill"]),
|
|
37
|
-
started_at: z.string().datetime(),
|
|
38
|
-
ended_at: z.string().datetime(),
|
|
39
|
-
model_id: z.string().regex(MODEL_ID).nullable(),
|
|
40
|
-
tokens: z.object({ input: Count, output: Count, cache_read: Count, cache_write: Count }).strict(),
|
|
41
|
-
user_turns: Count,
|
|
42
|
-
tool_calls: Count,
|
|
43
|
-
tool_errors: Count,
|
|
44
|
-
approval_denials: Count,
|
|
45
|
-
delegated: z.boolean(),
|
|
46
|
-
launched_by: z.enum(["claude_code"]).nullable(),
|
|
47
|
-
repository_id: z.string().uuid().nullable(),
|
|
48
|
-
commit_shas: z.array(z.string().regex(/^[a-f0-9]{40}$/)).max(50),
|
|
49
|
-
skills: z.array(z.string().regex(SKILL_ID)).max(20),
|
|
50
|
-
unlisted_skills: Count,
|
|
51
|
-
}).strict().superRefine((summary, context) => {
|
|
52
|
-
if (summary.commit_shas.length && !summary.repository_id) {
|
|
53
|
-
context.addIssue({ code: z.ZodIssueCode.custom, path: ["commit_shas"], message: "commit_shas require an approved repository_id" });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
/** Only recognizable model identifiers leave the device; anything else is reported as unknown. */
|
|
57
|
-
export function allowlistedModelId(model) {
|
|
58
|
-
if (!model)
|
|
59
|
-
return null;
|
|
60
|
-
const normalized = model.trim().toLowerCase();
|
|
61
|
-
return MODEL_ID.test(normalized) ? normalized : null;
|
|
62
|
-
}
|
|
63
|
-
export function toSessionUpload(session, context) {
|
|
64
|
-
const repositoryId = approvedRepositoryId(session.cwd, context.policy) ?? null;
|
|
65
|
-
const commits = repositoryId && session.cwd ? sessionCommitShas(session.cwd, session).map((sha) => sha.toLowerCase()) : [];
|
|
66
|
-
const skillReport = reportableSkills(session.skills ?? [], context.policy?.skills ?? readPolicySkills());
|
|
67
|
-
return SessionSummaryUploadSchema.parse({
|
|
68
|
-
schema_version: 1,
|
|
69
|
-
collection_policy_version: "metadata-v3",
|
|
70
|
-
installation_id: context.installationId,
|
|
71
|
-
session_id: session.sessionId.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 128) || "unknown",
|
|
72
|
-
source: session.client,
|
|
73
|
-
collected_via: context.collectedVia,
|
|
74
|
-
started_at: new Date(session.startedAt).toISOString(),
|
|
75
|
-
ended_at: new Date(session.endedAt).toISOString(),
|
|
76
|
-
model_id: allowlistedModelId(session.primaryModel),
|
|
77
|
-
tokens: { input: session.tokens.input, output: session.tokens.output, cache_read: session.tokens.cacheRead, cache_write: session.tokens.cacheWrite },
|
|
78
|
-
user_turns: session.userTurns,
|
|
79
|
-
tool_calls: session.toolCalls,
|
|
80
|
-
tool_errors: session.toolErrors,
|
|
81
|
-
approval_denials: session.approvalDenials,
|
|
82
|
-
delegated: session.delegated,
|
|
83
|
-
launched_by: session.launchedBy,
|
|
84
|
-
repository_id: repositoryId,
|
|
85
|
-
commit_shas: commits,
|
|
86
|
-
skills: skillReport.skills,
|
|
87
|
-
unlisted_skills: skillReport.unlisted,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
// Pending uploads, keyed by source and session so a later, fuller summary of a
|
|
91
|
-
// still-running session replaces the earlier one.
|
|
92
|
-
export function sessionSpoolPath() {
|
|
93
|
-
return process.env.UNFAIRLY_PACE_SESSION_SPOOL ?? path.join(os.homedir(), ".unfairly", "pace", "sessions.json");
|
|
94
|
-
}
|
|
95
|
-
const spoolKey = (summary) => `${summary.source}:${summary.session_id}`;
|
|
96
|
-
export async function readSessionSpool() {
|
|
97
|
-
try {
|
|
98
|
-
const parsed = JSON.parse(await fs.readFile(sessionSpoolPath(), "utf8"));
|
|
99
|
-
const valid = {};
|
|
100
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
101
|
-
const summary = SessionSummaryUploadSchema.safeParse(value);
|
|
102
|
-
if (summary.success)
|
|
103
|
-
valid[key] = summary.data;
|
|
104
|
-
}
|
|
105
|
-
return valid;
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
return {};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async function writeSessionSpool(spool) {
|
|
112
|
-
const file = sessionSpoolPath();
|
|
113
|
-
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
114
|
-
await fs.writeFile(file, `${JSON.stringify(spool)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
115
|
-
}
|
|
116
|
-
export async function queueSessionSummaries(summaries) {
|
|
117
|
-
const spool = await readSessionSpool();
|
|
118
|
-
for (const summary of summaries)
|
|
119
|
-
spool[spoolKey(summary)] = summary;
|
|
120
|
-
await writeSessionSpool(spool);
|
|
121
|
-
return Object.keys(spool).length;
|
|
122
|
-
}
|
|
123
|
-
export async function removeSessionSummaries(sent) {
|
|
124
|
-
const spool = await readSessionSpool();
|
|
125
|
-
for (const summary of sent) {
|
|
126
|
-
const current = spool[spoolKey(summary)];
|
|
127
|
-
// Keep a newer summary that arrived while this batch was uploading.
|
|
128
|
-
if (current && current.ended_at === summary.ended_at)
|
|
129
|
-
delete spool[spoolKey(summary)];
|
|
130
|
-
}
|
|
131
|
-
await writeSessionSpool(spool);
|
|
132
|
-
}
|
|
133
|
-
/** Summarizes every local session whose transcript changed since `since` and queues it for upload. */
|
|
134
|
-
export async function sweepLocalSessions(input) {
|
|
135
|
-
const sessions = await collectLocalSessions({ since: input.since, home: input.home });
|
|
136
|
-
const summaries = sessions.map((session) => toSessionUpload(session, input));
|
|
137
|
-
const queued = summaries.length ? await queueSessionSummaries(summaries) : (Object.keys(await readSessionSpool()).length);
|
|
138
|
-
return { summarized: summaries.length, queued };
|
|
139
|
-
}
|
|
140
|
-
//# sourceMappingURL=session-summary.js.map
|
|
1
|
+
import n from"node:fs/promises";import y from"node:os";import d from"node:path";import{z as o}from"zod";import{collectLocalSessions as S}from"./local-sessions.js";import{approvedRepositoryId as g,readRepositoryPolicy as w,sessionCommitShas as k}from"./repository.js";function v(){return w()?.skills??[]}const u=/^(claude-[a-z0-9.-]{1,48}|gpt-[a-z0-9.-]{1,32}|o[1-9][a-z0-9-]{0,20}|gemini-[a-z0-9.-]{1,40}|codex-[a-z0-9.-]{1,32})$/,i=o.number().int().nonnegative(),m=/^[a-z0-9][a-z0-9:_.-]{0,119}$/,z=/^[a-z0-9][a-z0-9-]{0,40}:[a-z0-9][a-z0-9-]{0,78}$/;function I(e,t=[]){const a=new Set(t),r=[...new Set(e)].filter(s=>m.test(s)&&(z.test(s)||a.has(s)));return{skills:r.slice(0,20),unlisted:new Set(e).size-r.length}}const p=o.object({schema_version:o.literal(1),collection_policy_version:o.literal("metadata-v3"),installation_id:o.string().uuid(),session_id:o.string().regex(/^[A-Za-z0-9_-]{1,128}$/),source:o.enum(["claude_code","codex"]),collected_via:o.enum(["hook","backfill"]),started_at:o.string().datetime(),ended_at:o.string().datetime(),model_id:o.string().regex(u).nullable(),tokens:o.object({input:i,output:i,cache_read:i,cache_write:i}).strict(),user_turns:i,tool_calls:i,tool_errors:i,approval_denials:i,delegated:o.boolean(),launched_by:o.enum(["claude_code"]).nullable(),repository_id:o.string().uuid().nullable(),commit_shas:o.array(o.string().regex(/^[a-f0-9]{40}$/)).max(50),skills:o.array(o.string().regex(m)).max(20),unlisted_skills:i}).strict().superRefine((e,t)=>{e.commit_shas.length&&!e.repository_id&&t.addIssue({code:o.ZodIssueCode.custom,path:["commit_shas"],message:"commit_shas require an approved repository_id"})});function b(e){if(!e)return null;const t=e.trim().toLowerCase();return u.test(t)?t:null}function x(e,t){const a=g(e.cwd,t.policy)??null,r=a&&e.cwd?k(e.cwd,e).map(h=>h.toLowerCase()):[],s=I(e.skills??[],t.policy?.skills??v());return p.parse({schema_version:1,collection_policy_version:"metadata-v3",installation_id:t.installationId,session_id:e.sessionId.replace(/[^A-Za-z0-9_-]/g,"").slice(0,128)||"unknown",source:e.client,collected_via:t.collectedVia,started_at:new Date(e.startedAt).toISOString(),ended_at:new Date(e.endedAt).toISOString(),model_id:b(e.primaryModel),tokens:{input:e.tokens.input,output:e.tokens.output,cache_read:e.tokens.cacheRead,cache_write:e.tokens.cacheWrite},user_turns:e.userTurns,tool_calls:e.toolCalls,tool_errors:e.toolErrors,approval_denials:e.approvalDenials,delegated:e.delegated,launched_by:e.launchedBy,repository_id:a,commit_shas:r,skills:s.skills,unlisted_skills:s.unlisted})}function _(){return process.env.UNFAIRLY_PACE_SESSION_SPOOL??d.join(y.homedir(),".unfairly","pace","sessions.json")}const l=e=>`${e.source}:${e.session_id}`;async function c(){try{const e=JSON.parse(await n.readFile(_(),"utf8")),t={};for(const[a,r]of Object.entries(e)){const s=p.safeParse(r);s.success&&(t[a]=s.data)}return t}catch{return{}}}async function f(e){const t=_();await n.mkdir(d.dirname(t),{recursive:!0,mode:448}),await n.writeFile(t,`${JSON.stringify(e)}
|
|
2
|
+
`,{encoding:"utf8",mode:384})}async function L(e){const t=await c();for(const a of e)t[l(a)]=a;return await f(t),Object.keys(t).length}async function D(e){const t=await c();for(const a of e){const r=t[l(a)];r&&r.ended_at===a.ended_at&&delete t[l(a)]}await f(t)}async function R(e){const a=(await S({since:e.since,home:e.home})).map(s=>x(s,e)),r=a.length?await L(a):Object.keys(await c()).length;return{summarized:a.length,queued:r}}export{p as SessionSummaryUploadSchema,b as allowlistedModelId,L as queueSessionSummaries,c as readSessionSpool,D as removeSessionSummaries,I as reportableSkills,_ as sessionSpoolPath,R as sweepLocalSessions,x as toSessionUpload};
|
package/dist/pace/spool.d.ts
CHANGED
|
@@ -3,4 +3,3 @@ export declare function paceSpoolPath(): string;
|
|
|
3
3
|
export declare function appendPaceEvent(event: PaceEvent): Promise<string>;
|
|
4
4
|
export declare function readPaceEvents(limit?: number): Promise<PaceEvent[]>;
|
|
5
5
|
export declare function removePaceEvents(ids: Set<string>): Promise<void>;
|
|
6
|
-
//# sourceMappingURL=spool.d.ts.map
|
package/dist/pace/spool.js
CHANGED
|
@@ -1,87 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
export async function appendPaceEvent(event) {
|
|
10
|
-
const parsed = PaceEventSchema.parse(event);
|
|
11
|
-
const spool = paceSpoolPath();
|
|
12
|
-
await fs.mkdir(path.dirname(spool), { recursive: true, mode: 0o700 });
|
|
13
|
-
await fs.appendFile(spool, `${JSON.stringify(parsed)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
14
|
-
await fs.chmod(spool, 0o600).catch(() => undefined);
|
|
15
|
-
return spool;
|
|
16
|
-
}
|
|
17
|
-
export async function readPaceEvents(limit = 250) {
|
|
18
|
-
let raw;
|
|
19
|
-
try {
|
|
20
|
-
raw = await fs.readFile(paceSpoolPath(), "utf8");
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
if (error.code === "ENOENT")
|
|
24
|
-
return [];
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
const events = [];
|
|
28
|
-
let needsMigration = false;
|
|
29
|
-
for (const line of raw.split("\n")) {
|
|
30
|
-
if (!line.trim())
|
|
31
|
-
continue;
|
|
32
|
-
let candidate;
|
|
33
|
-
try {
|
|
34
|
-
candidate = JSON.parse(line);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
needsMigration = true;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
const parsed = PaceEventSchema.safeParse(candidate);
|
|
41
|
-
if (parsed.success) {
|
|
42
|
-
events.push(parsed.data);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
const legacy = PaceLegacyEventSchema.safeParse(candidate);
|
|
46
|
-
if (legacy.success)
|
|
47
|
-
events.push(migrateLegacyEvent(legacy.data));
|
|
48
|
-
needsMigration = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (needsMigration) {
|
|
52
|
-
const spool = paceSpoolPath();
|
|
53
|
-
await fs.writeFile(spool, events.length ? `${events.map((event) => JSON.stringify(event)).join("\n")}\n` : "", {
|
|
54
|
-
encoding: "utf8",
|
|
55
|
-
mode: 0o600,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
return events.slice(0, limit);
|
|
59
|
-
}
|
|
60
|
-
export async function removePaceEvents(ids) {
|
|
61
|
-
const spool = paceSpoolPath();
|
|
62
|
-
let raw;
|
|
63
|
-
try {
|
|
64
|
-
raw = await fs.readFile(spool, "utf8");
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (error.code === "ENOENT")
|
|
68
|
-
return;
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
const remaining = raw.split("\n").filter((line) => {
|
|
72
|
-
if (!line.trim())
|
|
73
|
-
return false;
|
|
74
|
-
try {
|
|
75
|
-
const event = JSON.parse(line);
|
|
76
|
-
return !event.event_id || !ids.has(event.event_id);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
await fs.writeFile(spool, remaining.length ? `${remaining.join("\n")}\n` : "", {
|
|
83
|
-
encoding: "utf8",
|
|
84
|
-
mode: 0o600,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
//# sourceMappingURL=spool.js.map
|
|
1
|
+
import o from"node:fs/promises";import d from"node:os";import p from"node:path";import{migrateLegacyEvent as m}from"./normalize.js";import{PaceEventSchema as u,PaceLegacyEventSchema as l}from"./schema.js";function c(){return process.env.UNFAIRLY_PACE_SPOOL??p.join(d.homedir(),".unfairly","pace","events.jsonl")}async function E(i){const r=u.parse(i),e=c();return await o.mkdir(p.dirname(e),{recursive:!0,mode:448}),await o.appendFile(e,`${JSON.stringify(r)}
|
|
2
|
+
`,{encoding:"utf8",mode:384}),await o.chmod(e,384).catch(()=>{}),e}async function P(i=250){let r;try{r=await o.readFile(c(),"utf8")}catch(t){if(t.code==="ENOENT")return[];throw t}const e=[];let a=!1;for(const t of r.split(`
|
|
3
|
+
`)){if(!t.trim())continue;let n;try{n=JSON.parse(t)}catch{a=!0;continue}const s=u.safeParse(n);if(s.success)e.push(s.data);else{const f=l.safeParse(n);f.success&&e.push(m(f.data)),a=!0}}if(a){const t=c();await o.writeFile(t,e.length?`${e.map(n=>JSON.stringify(n)).join(`
|
|
4
|
+
`)}
|
|
5
|
+
`:"",{encoding:"utf8",mode:384})}return e.slice(0,i)}async function N(i){const r=c();let e;try{e=await o.readFile(r,"utf8")}catch(t){if(t.code==="ENOENT")return;throw t}const a=e.split(`
|
|
6
|
+
`).filter(t=>{if(!t.trim())return!1;try{const n=JSON.parse(t);return!n.event_id||!i.has(n.event_id)}catch{return!0}});await o.writeFile(r,a.length?`${a.join(`
|
|
7
|
+
`)}
|
|
8
|
+
`:"",{encoding:"utf8",mode:384})}export{E as appendPaceEvent,c as paceSpoolPath,P as readPaceEvents,N as removePaceEvents};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{spawn as l}from"node:child_process";import c from"node:os";import{runPaceCapture as u,runPaceSync as f}from"./cmd/pace.js";import{readOrCreatePaceDevice as w}from"./pace/device.js";import{acquireApprovalLock as d,awaitApproval as m,ensureEnrolled as v,releaseApprovalLock as A,sessionStartOutput as y}from"./pace/enroll.js";const U="https://app.unfairly.ai",p=()=>(process.env.UNFAIRLY_API_URL??U).replace(/\/$/,"");async function h(a,n){const t=await w();if(t.paused)return;const r=c.platform(),e=c.arch();if(r!=="darwin"&&r!=="linux"&&r!=="win32"||e!=="x64"&&e!=="arm64")return;const i=await v({apiUrl:p(),installationId:t.installation_id,platform:r,arch:e,clients:[a],orgHint:process.env.UNFAIRLY_ORG}),o=y(i,n==="sessionStart"?"sessionStart":"SessionStart");if(o&&process.stdout.write(`${o}
|
|
2
|
+
`),i.status==="waiting"){const s=l(process.execPath,[process.argv[1],"await-approval"],{detached:!0,stdio:"ignore",env:process.env});s.on("error",()=>{}),s.unref()}}async function S(){if(await d())try{await m({apiUrl:p()})&&await f()}finally{await A()}}async function L(){const[a,n="unknown",t="Unknown"]=process.argv.slice(2);a==="connect"?await h(n,t):a==="await-approval"?await S():a==="capture"&&await u(n,t)}L().catch(()=>{}).finally(()=>process.exit(0));
|
package/dist/sync/api.d.ts
CHANGED
package/dist/sync/api.js
CHANGED
|
@@ -1,55 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
apiUrl;
|
|
3
|
-
token;
|
|
4
|
-
constructor(apiUrl, token) {
|
|
5
|
-
this.apiUrl = apiUrl;
|
|
6
|
-
this.token = token;
|
|
7
|
-
}
|
|
8
|
-
headers() {
|
|
9
|
-
return {
|
|
10
|
-
Authorization: `Bearer ${this.token}`,
|
|
11
|
-
"Content-Type": "application/json",
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
async pull(vault, since) {
|
|
15
|
-
const url = new URL(`${this.apiUrl}/api/sync/pull`);
|
|
16
|
-
url.searchParams.set("vault", vault);
|
|
17
|
-
if (since)
|
|
18
|
-
url.searchParams.set("since", since);
|
|
19
|
-
const res = await fetch(url, { headers: this.headers() });
|
|
20
|
-
if (!res.ok) {
|
|
21
|
-
throw new Error(`pull failed: ${res.status} ${await res.text()}`);
|
|
22
|
-
}
|
|
23
|
-
return res.json();
|
|
24
|
-
}
|
|
25
|
-
/** Loop pull until has_more=false, merging pages. */
|
|
26
|
-
async pullAll(vault, since) {
|
|
27
|
-
const merged = await this.pull(vault, since);
|
|
28
|
-
while (merged.has_more && merged.next_cursor) {
|
|
29
|
-
const next = await this.pull(vault, merged.next_cursor);
|
|
30
|
-
merged.docs.push(...next.docs);
|
|
31
|
-
merged.deleted.push(...next.deleted);
|
|
32
|
-
merged.next_cursor = next.next_cursor;
|
|
33
|
-
merged.has_more = next.has_more;
|
|
34
|
-
}
|
|
35
|
-
return merged;
|
|
36
|
-
}
|
|
37
|
-
async push(args) {
|
|
38
|
-
const res = await fetch(`${this.apiUrl}/api/sync/push`, {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: this.headers(),
|
|
41
|
-
body: JSON.stringify(args),
|
|
42
|
-
});
|
|
43
|
-
return { status: res.status, data: (await res.json()) };
|
|
44
|
-
}
|
|
45
|
-
async status() {
|
|
46
|
-
const res = await fetch(`${this.apiUrl}/api/sync/status`, {
|
|
47
|
-
headers: this.headers(),
|
|
48
|
-
});
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
throw new Error(`status failed: ${res.status} ${await res.text()}`);
|
|
51
|
-
}
|
|
52
|
-
return res.json();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
//# sourceMappingURL=api.js.map
|
|
1
|
+
class n{apiUrl;token;constructor(t,e){this.apiUrl=t,this.token=e}headers(){return{Authorization:`Bearer ${this.token}`,"Content-Type":"application/json"}}async pull(t,e){const s=new URL(`${this.apiUrl}/api/sync/pull`);s.searchParams.set("vault",t),e&&s.searchParams.set("since",e);const a=await fetch(s,{headers:this.headers()});if(!a.ok)throw new Error(`pull failed: ${a.status} ${await a.text()}`);return a.json()}async pullAll(t,e){const s=await this.pull(t,e);for(;s.has_more&&s.next_cursor;){const a=await this.pull(t,s.next_cursor);s.docs.push(...a.docs),s.deleted.push(...a.deleted),s.next_cursor=a.next_cursor,s.has_more=a.has_more}return s}async push(t){const e=await fetch(`${this.apiUrl}/api/sync/push`,{method:"POST",headers:this.headers(),body:JSON.stringify(t)});return{status:e.status,data:await e.json()}}async status(){const t=await fetch(`${this.apiUrl}/api/sync/status`,{headers:this.headers()});if(!t.ok)throw new Error(`status failed: ${t.status} ${await t.text()}`);return t.json()}}export{n as SyncClient};
|
package/dist/sync/autoloader.js
CHANGED
|
@@ -1,128 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const SENTINEL_HEADER = "# Unfairly Vault Context (auto-generated by `unfairly`)";
|
|
5
|
-
const GENERATED_CLAUDE_MD = "CLAUDE.md";
|
|
6
|
-
/**
|
|
7
|
-
* After `unfairly pull` populates .unfairly/, scan local files, parse
|
|
8
|
-
* frontmatter, build the autoloader index, and write CLAUDE.md + .gitignore.
|
|
9
|
-
*/
|
|
10
|
-
export async function writeAutoloader(unfairlyRoot, manifest) {
|
|
11
|
-
const docs = await scanLocalDocs(unfairlyRoot, manifest);
|
|
12
|
-
const goldPrimitives = docs
|
|
13
|
-
.filter((d) => d.trust_tier === "gold" && d.role === "primitive")
|
|
14
|
-
.sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""))
|
|
15
|
-
.slice(0, 8);
|
|
16
|
-
const recentSilver = docs
|
|
17
|
-
.filter((d) => d.trust_tier === "silver")
|
|
18
|
-
.sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""))
|
|
19
|
-
.slice(0, 5);
|
|
20
|
-
const claudeMd = renderClaudeMd(goldPrimitives, recentSilver);
|
|
21
|
-
await fs.writeFile(path.join(unfairlyRoot, GENERATED_CLAUDE_MD), claudeMd, "utf8");
|
|
22
|
-
// .gitignore: write only if missing. The generated file invites users to
|
|
23
|
-
// uncomment !vault/ re-includes — regenerating on every pull would wipe them.
|
|
24
|
-
const gitignorePath = path.join(unfairlyRoot, ".gitignore");
|
|
25
|
-
try {
|
|
26
|
-
await fs.access(gitignorePath);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
await fs.writeFile(gitignorePath, renderGitignore(), "utf8");
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
async function scanLocalDocs(root, manifest) {
|
|
33
|
-
const entries = [];
|
|
34
|
-
// manifest.vaults is Record<string, ManifestVault> keyed by slug.
|
|
35
|
-
for (const vault of Object.values(manifest.vaults)) {
|
|
36
|
-
// Repo-bound vaults live at the root; non-tracked vaults live under cache/.
|
|
37
|
-
const vaultRoot = vault.track_in_git
|
|
38
|
-
? path.join(root, vault.slug)
|
|
39
|
-
: path.join(root, "cache", vault.slug);
|
|
40
|
-
try {
|
|
41
|
-
await walkAndParse(vaultRoot, root, vault.slug, entries);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
// Vault dir doesn't exist yet (e.g., empty vault) — fine, skip.
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return entries;
|
|
48
|
-
}
|
|
49
|
-
async function walkAndParse(dir, unfairlyRoot, vaultSlug, out) {
|
|
50
|
-
const generatedRootPath = path.join(unfairlyRoot, GENERATED_CLAUDE_MD);
|
|
51
|
-
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
52
|
-
for (const item of items) {
|
|
53
|
-
const full = path.join(dir, item.name);
|
|
54
|
-
if (item.isDirectory()) {
|
|
55
|
-
await walkAndParse(full, unfairlyRoot, vaultSlug, out);
|
|
56
|
-
}
|
|
57
|
-
else if (item.name.endsWith(".md")) {
|
|
58
|
-
// Skip ONLY our own generated file (not user docs that happen to be named CLAUDE.md).
|
|
59
|
-
if (path.resolve(full) === path.resolve(generatedRootPath))
|
|
60
|
-
continue;
|
|
61
|
-
try {
|
|
62
|
-
const raw = await fs.readFile(full, "utf8");
|
|
63
|
-
const { data } = matter(raw);
|
|
64
|
-
// Normalize to forward slashes — @-import processors expect POSIX-style paths.
|
|
65
|
-
const relative = path.relative(unfairlyRoot, full).split(path.sep).join("/");
|
|
66
|
-
// gray-matter / js-yaml auto-coerces ISO 8601 timestamps to Date — normalize to string.
|
|
67
|
-
const updatedAtRaw = data.updated_at;
|
|
68
|
-
const updated_at = updatedAtRaw instanceof Date
|
|
69
|
-
? updatedAtRaw.toISOString()
|
|
70
|
-
: typeof updatedAtRaw === "string"
|
|
71
|
-
? updatedAtRaw
|
|
72
|
-
: undefined;
|
|
73
|
-
out.push({
|
|
74
|
-
relativePath: relative,
|
|
75
|
-
trust_tier: typeof data.trust_tier === "string" ? data.trust_tier : undefined,
|
|
76
|
-
role: typeof data.role === "string" ? data.role : undefined,
|
|
77
|
-
updated_at,
|
|
78
|
-
vaultSlug,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// Unparseable (malformed YAML, encoding) — skip.
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function renderClaudeMd(gold, silver) {
|
|
88
|
-
const lines = [];
|
|
89
|
-
lines.push(SENTINEL_HEADER);
|
|
90
|
-
lines.push("");
|
|
91
|
-
lines.push("This directory mirrors your accessible Unfairly vaults. Files below are");
|
|
92
|
-
lines.push("the active canon — read them before answering questions about this team's");
|
|
93
|
-
lines.push("architecture, decisions, brand, or in-flight work.");
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push("## Canon (gold, top primitives)");
|
|
96
|
-
if (gold.length === 0) {
|
|
97
|
-
lines.push("(No gold primitives in your accessible vaults yet. Run `unfairly pull` after the team seeds canon docs.)");
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
for (const d of gold)
|
|
101
|
-
lines.push(`@${d.relativePath}`);
|
|
102
|
-
}
|
|
103
|
-
lines.push("");
|
|
104
|
-
lines.push("## Working knowledge (silver, recently touched)");
|
|
105
|
-
if (silver.length === 0) {
|
|
106
|
-
lines.push("(No recent silver docs.)");
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
for (const d of silver)
|
|
110
|
-
lines.push(`@${d.relativePath}`);
|
|
111
|
-
}
|
|
112
|
-
lines.push("");
|
|
113
|
-
lines.push("## Conventions");
|
|
114
|
-
lines.push("- Trust tier = audience signal, not visibility filter. All tiers are working memory.");
|
|
115
|
-
lines.push("- This file is regenerated on every `unfairly init` or `unfairly pull`.");
|
|
116
|
-
lines.push(" Don't hand-edit — your changes will be overwritten.");
|
|
117
|
-
lines.push("- For real-time multiplayer state (current checkpoints, applied edits,");
|
|
118
|
-
lines.push(" pending proposals, online teammates), call `enter_org` via MCP.");
|
|
119
|
-
lines.push(" This filesystem mirror is static-as-of-last-pull.");
|
|
120
|
-
return lines.join("\n") + "\n";
|
|
121
|
-
}
|
|
122
|
-
function renderGitignore() {
|
|
123
|
-
return `# Default: ignore everything in this directory.
|
|
1
|
+
import{promises as c}from"node:fs";import n from"node:path";import y from"gray-matter";const v="# Unfairly Vault Context (auto-generated by `unfairly`)",h="CLAUDE.md";async function E(t,r){const e=await w(t,r),i=e.filter(s=>s.trust_tier==="gold"&&s.role==="primitive").sort((s,l)=>(l.updated_at??"").localeCompare(s.updated_at??"")).slice(0,8),o=e.filter(s=>s.trust_tier==="silver").sort((s,l)=>(l.updated_at??"").localeCompare(s.updated_at??"")).slice(0,5),p=_(i,o);await c.writeFile(n.join(t,h),p,"utf8");const a=n.join(t,".gitignore");try{await c.access(a)}catch{await c.writeFile(a,b(),"utf8")}}async function w(t,r){const e=[];for(const i of Object.values(r.vaults)){const o=i.track_in_git?n.join(t,i.slug):n.join(t,"cache",i.slug);try{await f(o,t,i.slug,e)}catch{}}return e}async function f(t,r,e,i){const o=n.join(r,h),p=await c.readdir(t,{withFileTypes:!0});for(const a of p){const s=n.join(t,a.name);if(a.isDirectory())await f(s,r,e,i);else if(a.name.endsWith(".md")){if(n.resolve(s)===n.resolve(o))continue;try{const l=await c.readFile(s,"utf8"),{data:u}=y(l),g=n.relative(r,s).split(n.sep).join("/"),d=u.updated_at,m=d instanceof Date?d.toISOString():typeof d=="string"?d:void 0;i.push({relativePath:g,trust_tier:typeof u.trust_tier=="string"?u.trust_tier:void 0,role:typeof u.role=="string"?u.role:void 0,updated_at:m,vaultSlug:e})}catch{}}}}function _(t,r){const e=[];if(e.push(v),e.push(""),e.push("This directory mirrors your accessible Unfairly vaults. Files below are"),e.push("the active canon \u2014 read them before answering questions about this team's"),e.push("architecture, decisions, brand, or in-flight work."),e.push(""),e.push("## Canon (gold, top primitives)"),t.length===0)e.push("(No gold primitives in your accessible vaults yet. Run `unfairly pull` after the team seeds canon docs.)");else for(const i of t)e.push(`@${i.relativePath}`);if(e.push(""),e.push("## Working knowledge (silver, recently touched)"),r.length===0)e.push("(No recent silver docs.)");else for(const i of r)e.push(`@${i.relativePath}`);return e.push(""),e.push("## Conventions"),e.push("- Trust tier = audience signal, not visibility filter. All tiers are working memory."),e.push("- This file is regenerated on every `unfairly init` or `unfairly pull`."),e.push(" Don't hand-edit \u2014 your changes will be overwritten."),e.push("- For real-time multiplayer state (current checkpoints, applied edits,"),e.push(" pending proposals, online teammates), call `enter_org` via MCP."),e.push(" This filesystem mirror is static-as-of-last-pull."),e.join(`
|
|
2
|
+
`)+`
|
|
3
|
+
`}function b(){return`# Default: ignore everything in this directory.
|
|
124
4
|
# .unfairly/ is a local cache of vault content. By default, none of it is
|
|
125
|
-
# committed
|
|
5
|
+
# committed \u2014 agents read from disk; the source of truth is in cloud vaults.
|
|
126
6
|
*
|
|
127
7
|
|
|
128
8
|
# Re-include the autoloader and this gitignore so Claude Code finds them:
|
|
@@ -133,6 +13,4 @@ function renderGitignore() {
|
|
|
133
13
|
# whose docs should be versioned alongside code), add explicit re-includes:
|
|
134
14
|
# !unfairly-app/
|
|
135
15
|
# !unfairly-app/**
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
//# sourceMappingURL=autoloader.js.map
|
|
16
|
+
`}export{E as writeAutoloader};
|
package/dist/sync/conflict.d.ts
CHANGED