@primitive.ai/prim 0.1.0-alpha.45 → 0.1.0-alpha.47
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/SKILL.md +91 -6
- package/dist/{chunk-ZWTZMTV5.js → chunk-5K725K35.js} +1 -1
- package/dist/{chunk-6TRRGGLD.js → chunk-CK75QBCB.js} +1 -1
- package/dist/chunk-TPROV45L.js +502 -0
- package/dist/{chunk-6MKLCOEH.js → chunk-WRPKAPVE.js} +1 -1
- package/dist/daemon/server.js +460 -221
- package/dist/hooks/post-commit.js +2 -2
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/prim-hook.js +3 -3
- package/dist/hooks/session-start.js +2 -2
- package/dist/index.js +332 -286
- package/dist/statusline-main.js +45 -37
- package/package.json +1 -1
- package/dist/chunk-PI6LDYNJ.js +0 -220
package/dist/statusline-main.js
CHANGED
|
@@ -1,54 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands/statusline.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as
|
|
4
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
+
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/client.ts
|
|
9
|
-
import {
|
|
9
|
+
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
10
|
+
import {
|
|
11
|
+
chmodSync,
|
|
12
|
+
existsSync as existsSync2,
|
|
13
|
+
mkdirSync as mkdirSync2,
|
|
14
|
+
readFileSync as readFileSync2,
|
|
15
|
+
renameSync,
|
|
16
|
+
rmSync as rmSync2,
|
|
17
|
+
writeFileSync as writeFileSync2
|
|
18
|
+
} from "fs";
|
|
10
19
|
import { homedir } from "os";
|
|
11
|
-
import { join, resolve } from "path";
|
|
20
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
21
|
+
|
|
22
|
+
// src/lib/file-lock.ts
|
|
23
|
+
import { randomBytes } from "crypto";
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
25
|
+
import { dirname, join } from "path";
|
|
26
|
+
|
|
27
|
+
// src/client.ts
|
|
28
|
+
var DEFAULT_API_URL = "https://api.getprimitive.ai";
|
|
12
29
|
function loadEnvFile() {
|
|
13
30
|
const envVars = {};
|
|
14
|
-
const
|
|
15
|
-
for (const file of candidates) {
|
|
31
|
+
for (const file of [".env.local", ".env"]) {
|
|
16
32
|
const filePath = resolve(process.cwd(), file);
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
26
|
-
envVars[key] = value;
|
|
27
|
-
}
|
|
33
|
+
if (!existsSync2(filePath)) continue;
|
|
34
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
35
|
+
for (const line of content.split("\n")) {
|
|
36
|
+
const trimmed = line.trim();
|
|
37
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
38
|
+
const eqIdx = trimmed.indexOf("=");
|
|
39
|
+
if (eqIdx === -1) continue;
|
|
40
|
+
envVars[trimmed.slice(0, eqIdx).trim()] = trimmed.slice(eqIdx + 1).trim();
|
|
28
41
|
}
|
|
29
42
|
}
|
|
30
43
|
return envVars;
|
|
31
44
|
}
|
|
32
|
-
var TOKEN_FILE_PATH =
|
|
33
|
-
var REFRESH_TOKEN_PATH = TOKEN_FILE_PATH
|
|
34
|
-
var TOKEN_EXPIRES_PATH =
|
|
35
|
-
var
|
|
45
|
+
var TOKEN_FILE_PATH = join2(homedir(), ".config", "prim", "token");
|
|
46
|
+
var REFRESH_TOKEN_PATH = join2(dirname2(TOKEN_FILE_PATH), "refresh_token");
|
|
47
|
+
var TOKEN_EXPIRES_PATH = join2(dirname2(TOKEN_FILE_PATH), "token_expires_at");
|
|
48
|
+
var TERMINAL_REFRESH_PATH = join2(dirname2(TOKEN_FILE_PATH), "refresh_terminal");
|
|
49
|
+
var CREDENTIAL_LOCK_PATH = join2(dirname2(TOKEN_FILE_PATH), "credentials.lock");
|
|
36
50
|
function getSiteUrl() {
|
|
37
|
-
if (process.env.PRIM_API_URL)
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
const envVars = loadEnvFile();
|
|
41
|
-
if (envVars.PRIM_API_URL) {
|
|
42
|
-
return envVars.PRIM_API_URL;
|
|
43
|
-
}
|
|
44
|
-
return DEFAULT_API_URL;
|
|
51
|
+
if (process.env.PRIM_API_URL) return process.env.PRIM_API_URL;
|
|
52
|
+
return loadEnvFile().PRIM_API_URL ?? DEFAULT_API_URL;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
// src/daemon/client.ts
|
|
48
56
|
import { createConnection } from "net";
|
|
49
57
|
import { homedir as homedir2 } from "os";
|
|
50
|
-
import { join as
|
|
51
|
-
var SOCK_PATH =
|
|
58
|
+
import { join as join3 } from "path";
|
|
59
|
+
var SOCK_PATH = join3(homedir2(), ".config", "prim", "sock");
|
|
52
60
|
var DEFAULT_TIMEOUT_MS = 250;
|
|
53
61
|
var nextRequestId = 1;
|
|
54
62
|
function daemonRequest(method, params = {}, opts = {}) {
|
|
@@ -125,13 +133,13 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
// src/lib/bin-cache.ts
|
|
128
|
-
import { chmodSync, mkdirSync, renameSync, writeFileSync as
|
|
136
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
129
137
|
import { homedir as homedir3 } from "os";
|
|
130
|
-
import { join as
|
|
138
|
+
import { join as join5 } from "path";
|
|
131
139
|
|
|
132
140
|
// src/lib/bin-path.ts
|
|
133
|
-
import { existsSync as
|
|
134
|
-
import { dirname, isAbsolute, join as
|
|
141
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
142
|
+
import { dirname as dirname3, isAbsolute, join as join4 } from "path";
|
|
135
143
|
import { fileURLToPath } from "url";
|
|
136
144
|
var PKG_NAME = "@primitive.ai/prim";
|
|
137
145
|
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
@@ -200,7 +208,7 @@ var STATUSLINE_TIMEOUT_MS = 200;
|
|
|
200
208
|
var STATUSLINE_NAME_CAP = 3;
|
|
201
209
|
function readPackageVersion() {
|
|
202
210
|
try {
|
|
203
|
-
const here =
|
|
211
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
204
212
|
const candidates = [
|
|
205
213
|
// The supervised runtime stages a tiny manifest beside the standalone
|
|
206
214
|
// statusline bundle, so it remains versioned without loading the package.
|
|
@@ -210,7 +218,7 @@ function readPackageVersion() {
|
|
|
210
218
|
];
|
|
211
219
|
for (const path of candidates) {
|
|
212
220
|
try {
|
|
213
|
-
const pkg = JSON.parse(
|
|
221
|
+
const pkg = JSON.parse(readFileSync4(path, "utf-8"));
|
|
214
222
|
if (pkg.version) {
|
|
215
223
|
return pkg.version;
|
|
216
224
|
}
|
package/package.json
CHANGED
package/dist/chunk-PI6LDYNJ.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// src/client.ts
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join, resolve } from "path";
|
|
5
|
-
function loadEnvFile() {
|
|
6
|
-
const envVars = {};
|
|
7
|
-
const candidates = [".env.local", ".env"];
|
|
8
|
-
for (const file of candidates) {
|
|
9
|
-
const filePath = resolve(process.cwd(), file);
|
|
10
|
-
if (existsSync(filePath)) {
|
|
11
|
-
const content = readFileSync(filePath, "utf-8");
|
|
12
|
-
for (const line of content.split("\n")) {
|
|
13
|
-
const trimmed = line.trim();
|
|
14
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
15
|
-
const eqIdx = trimmed.indexOf("=");
|
|
16
|
-
if (eqIdx === -1) continue;
|
|
17
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
18
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
19
|
-
envVars[key] = value;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return envVars;
|
|
24
|
-
}
|
|
25
|
-
var TOKEN_FILE_PATH = join(homedir(), ".config", "prim", "token");
|
|
26
|
-
var REFRESH_TOKEN_PATH = TOKEN_FILE_PATH.replace("/token", "/refresh_token");
|
|
27
|
-
var TOKEN_EXPIRES_PATH = join(homedir(), ".config", "prim", "token_expires_at");
|
|
28
|
-
var REFRESH_THRESHOLD_MS = 6e4;
|
|
29
|
-
function isTokenExpiringSoon() {
|
|
30
|
-
if (!existsSync(TOKEN_EXPIRES_PATH)) return false;
|
|
31
|
-
const expiresAt = Number(readFileSync(TOKEN_EXPIRES_PATH, "utf-8").trim());
|
|
32
|
-
return !Number.isNaN(expiresAt) && Date.now() >= expiresAt - REFRESH_THRESHOLD_MS;
|
|
33
|
-
}
|
|
34
|
-
function getJwtExpiry(token) {
|
|
35
|
-
const parts = token.split(".");
|
|
36
|
-
if (parts.length !== 3) return void 0;
|
|
37
|
-
try {
|
|
38
|
-
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
39
|
-
return payload.exp ? payload.exp * 1e3 : void 0;
|
|
40
|
-
} catch {
|
|
41
|
-
return void 0;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function saveTokenExpiry(token, expiresIn) {
|
|
45
|
-
const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : getJwtExpiry(token);
|
|
46
|
-
if (expiresAt) {
|
|
47
|
-
writeFileSync(TOKEN_EXPIRES_PATH, String(expiresAt), { mode: 384 });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function getTokenExpiresAt() {
|
|
51
|
-
if (!existsSync(TOKEN_EXPIRES_PATH)) return void 0;
|
|
52
|
-
const val = Number(readFileSync(TOKEN_EXPIRES_PATH, "utf-8").trim());
|
|
53
|
-
return Number.isNaN(val) ? void 0 : val;
|
|
54
|
-
}
|
|
55
|
-
function getAuthToken() {
|
|
56
|
-
if (process.env.PRIM_TOKEN) {
|
|
57
|
-
return process.env.PRIM_TOKEN;
|
|
58
|
-
}
|
|
59
|
-
if (existsSync(TOKEN_FILE_PATH)) {
|
|
60
|
-
const token = readFileSync(TOKEN_FILE_PATH, "utf-8").trim();
|
|
61
|
-
if (token) {
|
|
62
|
-
return token;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const envVars = loadEnvFile();
|
|
66
|
-
if (envVars.PRIM_TOKEN) {
|
|
67
|
-
return envVars.PRIM_TOKEN;
|
|
68
|
-
}
|
|
69
|
-
return void 0;
|
|
70
|
-
}
|
|
71
|
-
var DEFAULT_API_URL = "https://api.getprimitive.ai";
|
|
72
|
-
function getSiteUrl() {
|
|
73
|
-
if (process.env.PRIM_API_URL) {
|
|
74
|
-
return process.env.PRIM_API_URL;
|
|
75
|
-
}
|
|
76
|
-
const envVars = loadEnvFile();
|
|
77
|
-
if (envVars.PRIM_API_URL) {
|
|
78
|
-
return envVars.PRIM_API_URL;
|
|
79
|
-
}
|
|
80
|
-
return DEFAULT_API_URL;
|
|
81
|
-
}
|
|
82
|
-
var _endedRefreshToken;
|
|
83
|
-
async function performTokenRefresh(options = {}) {
|
|
84
|
-
if (!existsSync(REFRESH_TOKEN_PATH)) {
|
|
85
|
-
return void 0;
|
|
86
|
-
}
|
|
87
|
-
const refreshTokenValue = readFileSync(REFRESH_TOKEN_PATH, "utf-8").trim();
|
|
88
|
-
if (!refreshTokenValue) {
|
|
89
|
-
return void 0;
|
|
90
|
-
}
|
|
91
|
-
if (_endedRefreshToken !== void 0 && refreshTokenValue === _endedRefreshToken) {
|
|
92
|
-
return void 0;
|
|
93
|
-
}
|
|
94
|
-
const siteUrl = getSiteUrl();
|
|
95
|
-
const response = await fetch(`${siteUrl}/mcp/broker/refresh`, {
|
|
96
|
-
method: "POST",
|
|
97
|
-
headers: { "Content-Type": "application/json" },
|
|
98
|
-
body: JSON.stringify({ refresh_token: refreshTokenValue }),
|
|
99
|
-
signal: options.signal
|
|
100
|
-
});
|
|
101
|
-
if (!response.ok) {
|
|
102
|
-
const detail = (await response.text().catch(() => "")).slice(0, 200);
|
|
103
|
-
if (detail.includes("invalid_grant") || detail.includes("Session has already ended")) {
|
|
104
|
-
_endedRefreshToken = refreshTokenValue;
|
|
105
|
-
}
|
|
106
|
-
if (!options.quiet) {
|
|
107
|
-
process.stderr.write(
|
|
108
|
-
`[prim] token refresh rejected by broker: ${response.status} ${response.statusText}${detail ? ` \u2014 ${detail}` : ""}
|
|
109
|
-
`
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
return void 0;
|
|
113
|
-
}
|
|
114
|
-
const data = await response.json();
|
|
115
|
-
if (!data.access_token) {
|
|
116
|
-
return void 0;
|
|
117
|
-
}
|
|
118
|
-
_endedRefreshToken = void 0;
|
|
119
|
-
writeFileSync(TOKEN_FILE_PATH, data.access_token, { mode: 384 });
|
|
120
|
-
if (data.refresh_token) {
|
|
121
|
-
writeFileSync(REFRESH_TOKEN_PATH, data.refresh_token, { mode: 384 });
|
|
122
|
-
}
|
|
123
|
-
saveTokenExpiry(data.access_token, data.expires_in);
|
|
124
|
-
return data.access_token;
|
|
125
|
-
}
|
|
126
|
-
var _refreshInFlight;
|
|
127
|
-
function refreshToken(options = {}) {
|
|
128
|
-
if (_refreshInFlight) {
|
|
129
|
-
return _refreshInFlight;
|
|
130
|
-
}
|
|
131
|
-
const attempt = performTokenRefresh(options).then((token) => {
|
|
132
|
-
if (token) {
|
|
133
|
-
_cachedToken = token;
|
|
134
|
-
}
|
|
135
|
-
return token;
|
|
136
|
-
}).finally(() => {
|
|
137
|
-
if (_refreshInFlight === attempt) {
|
|
138
|
-
_refreshInFlight = void 0;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
_refreshInFlight = attempt;
|
|
142
|
-
return attempt;
|
|
143
|
-
}
|
|
144
|
-
var HttpError = class extends Error {
|
|
145
|
-
status;
|
|
146
|
-
constructor(status, message) {
|
|
147
|
-
super(message);
|
|
148
|
-
this.name = "HttpError";
|
|
149
|
-
this.status = status;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
var _cachedToken;
|
|
153
|
-
async function request(method, path, body, options) {
|
|
154
|
-
const siteUrl = getSiteUrl();
|
|
155
|
-
const url = `${siteUrl}${path}`;
|
|
156
|
-
if (!_cachedToken) {
|
|
157
|
-
_cachedToken = getAuthToken();
|
|
158
|
-
}
|
|
159
|
-
if (_cachedToken && isTokenExpiringSoon()) {
|
|
160
|
-
const newToken = await refreshToken({
|
|
161
|
-
signal: options?.signal,
|
|
162
|
-
quiet: options?.quietRefresh
|
|
163
|
-
});
|
|
164
|
-
if (newToken) {
|
|
165
|
-
_cachedToken = newToken;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const doFetch = async (token) => {
|
|
169
|
-
const headers = {
|
|
170
|
-
"Content-Type": "application/json"
|
|
171
|
-
};
|
|
172
|
-
if (token) {
|
|
173
|
-
headers.Authorization = `Bearer ${token}`;
|
|
174
|
-
}
|
|
175
|
-
return fetch(url, {
|
|
176
|
-
method,
|
|
177
|
-
headers,
|
|
178
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
179
|
-
signal: options?.signal
|
|
180
|
-
});
|
|
181
|
-
};
|
|
182
|
-
const tokenUsed = _cachedToken;
|
|
183
|
-
let res = await doFetch(tokenUsed);
|
|
184
|
-
if (res.status === 401) {
|
|
185
|
-
const newToken = _cachedToken && _cachedToken !== tokenUsed ? _cachedToken : await refreshToken({
|
|
186
|
-
signal: options?.signal,
|
|
187
|
-
quiet: options?.quietRefresh
|
|
188
|
-
});
|
|
189
|
-
if (newToken) {
|
|
190
|
-
_cachedToken = newToken;
|
|
191
|
-
res = await doFetch(newToken);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (!res.ok) {
|
|
195
|
-
if (res.status === 401) {
|
|
196
|
-
throw new HttpError(401, "Authentication expired. Run `prim auth login` to re-authenticate.");
|
|
197
|
-
}
|
|
198
|
-
const errorBody = await res.json().catch(() => null);
|
|
199
|
-
throw new HttpError(res.status, errorBody?.error ?? `HTTP ${res.status}`);
|
|
200
|
-
}
|
|
201
|
-
return res.json();
|
|
202
|
-
}
|
|
203
|
-
function getClient() {
|
|
204
|
-
return {
|
|
205
|
-
get: (path, options) => request("GET", path, void 0, options),
|
|
206
|
-
post: (path, body, options) => request("POST", path, body, options)
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export {
|
|
211
|
-
TOKEN_FILE_PATH,
|
|
212
|
-
REFRESH_TOKEN_PATH,
|
|
213
|
-
TOKEN_EXPIRES_PATH,
|
|
214
|
-
saveTokenExpiry,
|
|
215
|
-
getTokenExpiresAt,
|
|
216
|
-
getAuthToken,
|
|
217
|
-
getSiteUrl,
|
|
218
|
-
HttpError,
|
|
219
|
-
getClient
|
|
220
|
-
};
|