mitsupi 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +24 -0
- package/README.md +28 -18
- package/analyze-edits.py +232 -0
- package/distributions/README.md +8 -0
- package/distributions/mitsupi-common/README.md +11 -0
- package/distributions/mitsupi-common/package.json +95 -0
- package/distributions/mitsupi-loaded/README.md +13 -0
- package/distributions/mitsupi-loaded/package.json +38 -0
- package/{pi-extensions → extensions}/answer.ts +11 -11
- package/extensions/btw.ts +963 -0
- package/{pi-extensions → extensions}/context.ts +2 -2
- package/{pi-extensions → extensions}/control.ts +20 -13
- package/{pi-extensions → extensions}/files.ts +6 -8
- package/{pi-extensions → extensions}/go-to-bed.ts +2 -2
- package/{pi-extensions → extensions}/loop.ts +18 -11
- package/extensions/multi-edit.ts +871 -0
- package/{pi-extensions → extensions}/review.ts +254 -82
- package/{pi-extensions → extensions}/session-breakdown.ts +551 -74
- package/extensions/split-fork.ts +130 -0
- package/{pi-extensions → extensions}/todos.ts +41 -34
- package/extensions/uv.ts +123 -0
- package/intercepted-commands/poetry +8 -1
- package/intercepted-commands/python +42 -2
- package/intercepted-commands/python3 +42 -2
- package/package.json +6 -3
- package/skills/google-workspace/SKILL.md +53 -8
- package/skills/google-workspace/scripts/auth.js +106 -28
- package/skills/google-workspace/scripts/common.js +201 -32
- package/skills/google-workspace/scripts/workspace.js +64 -9
- package/skills/native-web-search/SKILL.md +2 -0
- package/skills/native-web-search/search.mjs +102 -15
- package/skills/pi-share/SKILL.md +5 -2
- package/skills/pi-share/fetch-session.mjs +46 -15
- package/skills/summarize/SKILL.md +4 -3
- package/skills/summarize/to-markdown.mjs +3 -1
- package/skills/web-browser/SKILL.md +6 -0
- package/skills/web-browser/scripts/start.js +75 -27
- package/pi-extensions/uv.ts +0 -33
- /package/{pi-extensions → extensions}/notify.ts +0 -0
- /package/{pi-extensions → extensions}/prompt-editor.ts +0 -0
- /package/{pi-extensions → extensions}/whimsical.ts +0 -0
- /package/{pi-themes → themes}/nightowl.json +0 -0
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
formatScopes,
|
|
10
10
|
getGoogleApis,
|
|
11
11
|
loadToken,
|
|
12
|
+
normalizeEmail,
|
|
12
13
|
resolveAuthMode,
|
|
13
14
|
} = require('./common');
|
|
14
15
|
|
|
@@ -16,10 +17,10 @@ function printHelp() {
|
|
|
16
17
|
console.log(`Google Workspace API helper (exec-only)
|
|
17
18
|
|
|
18
19
|
Usage:
|
|
19
|
-
node scripts/workspace.js exec [--script 'return 1'] [--timeout 30000] [--scopes s1,s2]
|
|
20
|
+
node scripts/workspace.js exec --email user@example.com [--script 'return 1'] [--timeout 30000] [--scopes s1,s2]
|
|
20
21
|
|
|
21
22
|
Example:
|
|
22
|
-
node scripts/workspace.js exec <<'JS'
|
|
23
|
+
node scripts/workspace.js exec --email user@example.com <<'JS'
|
|
23
24
|
const me = await workspace.whoAmI();
|
|
24
25
|
const files = await workspace.call('drive', 'files.list', {
|
|
25
26
|
pageSize: 3,
|
|
@@ -46,6 +47,7 @@ function parseTimeout(raw) {
|
|
|
46
47
|
function parseOptions(argv) {
|
|
47
48
|
const positional = [];
|
|
48
49
|
const options = {
|
|
50
|
+
email: undefined,
|
|
49
51
|
scopes: undefined,
|
|
50
52
|
timeout: undefined,
|
|
51
53
|
script: undefined,
|
|
@@ -54,6 +56,16 @@ function parseOptions(argv) {
|
|
|
54
56
|
for (let i = 0; i < argv.length; i += 1) {
|
|
55
57
|
const arg = argv[i];
|
|
56
58
|
|
|
59
|
+
if (arg === '--email') {
|
|
60
|
+
const value = argv[i + 1];
|
|
61
|
+
if (!value) {
|
|
62
|
+
throw new Error('--email requires a value');
|
|
63
|
+
}
|
|
64
|
+
options.email = normalizeEmail(value);
|
|
65
|
+
i += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
if (arg === '--scopes') {
|
|
58
70
|
const value = argv[i + 1];
|
|
59
71
|
if (!value) {
|
|
@@ -119,6 +131,7 @@ async function callApi({
|
|
|
119
131
|
version,
|
|
120
132
|
scopes,
|
|
121
133
|
authClient,
|
|
134
|
+
email,
|
|
122
135
|
}) {
|
|
123
136
|
const google = getGoogleApis();
|
|
124
137
|
const factory = google[service];
|
|
@@ -129,6 +142,7 @@ async function callApi({
|
|
|
129
142
|
const auth =
|
|
130
143
|
authClient ||
|
|
131
144
|
(await authorize({
|
|
145
|
+
email,
|
|
132
146
|
interactive: true,
|
|
133
147
|
scopes: scopes && scopes.length > 0 ? scopes : undefined,
|
|
134
148
|
}));
|
|
@@ -155,8 +169,9 @@ function getServiceClient({ google, auth, service, version }) {
|
|
|
155
169
|
});
|
|
156
170
|
}
|
|
157
171
|
|
|
158
|
-
function createWorkspaceHelper({ auth, google }) {
|
|
172
|
+
function createWorkspaceHelper({ auth, google, email }) {
|
|
159
173
|
return {
|
|
174
|
+
accountEmail: email,
|
|
160
175
|
versions: { ...DEFAULT_VERSIONS },
|
|
161
176
|
|
|
162
177
|
async call(service, methodPath, params = {}, options = {}) {
|
|
@@ -166,6 +181,7 @@ function createWorkspaceHelper({ auth, google }) {
|
|
|
166
181
|
params,
|
|
167
182
|
version: options.version,
|
|
168
183
|
authClient: auth,
|
|
184
|
+
email,
|
|
169
185
|
});
|
|
170
186
|
},
|
|
171
187
|
|
|
@@ -302,20 +318,51 @@ function readStdinText() {
|
|
|
302
318
|
});
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
function
|
|
321
|
+
function createUnauthorizedHint(message, email) {
|
|
322
|
+
const msg = String(message || '').toLowerCase();
|
|
323
|
+
const looksUnauthorized =
|
|
324
|
+
msg.includes('401') ||
|
|
325
|
+
msg.includes('unauthorized') ||
|
|
326
|
+
msg.includes('invalid credentials') ||
|
|
327
|
+
msg.includes('insufficient permissions') ||
|
|
328
|
+
msg.includes('forbidden');
|
|
329
|
+
|
|
330
|
+
if (!looksUnauthorized) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return [
|
|
335
|
+
`Auth/account hint: this request may not be authorized for ${email}.`,
|
|
336
|
+
'List known accounts with: node scripts/auth.js accounts',
|
|
337
|
+
`Sign in or refresh this account with: node scripts/auth.js login --email ${email}`,
|
|
338
|
+
'Then retry workspace.js exec with the intended --email.',
|
|
339
|
+
].join(' ');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function errorPayload(error, logs, timeoutMs, email) {
|
|
343
|
+
const baseMessage = error?.message || String(error);
|
|
344
|
+
const hint = createUnauthorizedHint(baseMessage, email);
|
|
345
|
+
|
|
306
346
|
return {
|
|
307
347
|
ok: false,
|
|
308
348
|
timeoutMs,
|
|
309
349
|
logs,
|
|
310
350
|
error: {
|
|
311
351
|
name: error?.name || 'Error',
|
|
312
|
-
message:
|
|
352
|
+
message: hint ? `${baseMessage}\n${hint}` : baseMessage,
|
|
313
353
|
stack: error?.stack || null,
|
|
314
354
|
},
|
|
315
355
|
};
|
|
316
356
|
}
|
|
317
357
|
|
|
318
358
|
async function cmdExec(args, options) {
|
|
359
|
+
if (!options.email) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
'Missing required --email <account@example.com>. ' +
|
|
362
|
+
'Use node scripts/auth.js accounts to list known accounts.',
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
319
366
|
let script = options.script;
|
|
320
367
|
|
|
321
368
|
if (!script && args.length > 0) {
|
|
@@ -330,7 +377,7 @@ async function cmdExec(args, options) {
|
|
|
330
377
|
|
|
331
378
|
if (!script) {
|
|
332
379
|
throw new Error(
|
|
333
|
-
'Usage: exec [--script "..."] (or pipe script via stdin / heredoc)',
|
|
380
|
+
'Usage: exec --email user@example.com [--script "..."] (or pipe script via stdin / heredoc)',
|
|
334
381
|
);
|
|
335
382
|
}
|
|
336
383
|
|
|
@@ -339,6 +386,7 @@ async function cmdExec(args, options) {
|
|
|
339
386
|
|
|
340
387
|
try {
|
|
341
388
|
const auth = await authorize({
|
|
389
|
+
email: options.email,
|
|
342
390
|
interactive: true,
|
|
343
391
|
scopes: options.scopes && options.scopes.length > 0
|
|
344
392
|
? options.scopes
|
|
@@ -346,7 +394,7 @@ async function cmdExec(args, options) {
|
|
|
346
394
|
});
|
|
347
395
|
|
|
348
396
|
const google = getGoogleApis();
|
|
349
|
-
const workspace = createWorkspaceHelper({ auth, google });
|
|
397
|
+
const workspace = createWorkspaceHelper({ auth, google, email: options.email });
|
|
350
398
|
|
|
351
399
|
const context = vm.createContext({
|
|
352
400
|
Buffer,
|
|
@@ -381,12 +429,13 @@ ${script}
|
|
|
381
429
|
);
|
|
382
430
|
|
|
383
431
|
const result = await withTimeout(resultPromise, timeoutMs);
|
|
384
|
-
const token = loadToken();
|
|
432
|
+
const token = loadToken(options.email);
|
|
385
433
|
|
|
386
434
|
console.log(
|
|
387
435
|
JSON.stringify(
|
|
388
436
|
{
|
|
389
437
|
ok: true,
|
|
438
|
+
email: options.email,
|
|
390
439
|
authMode: resolveAuthMode(token),
|
|
391
440
|
timeoutMs,
|
|
392
441
|
logs,
|
|
@@ -397,7 +446,13 @@ ${script}
|
|
|
397
446
|
),
|
|
398
447
|
);
|
|
399
448
|
} catch (error) {
|
|
400
|
-
console.log(
|
|
449
|
+
console.log(
|
|
450
|
+
JSON.stringify(
|
|
451
|
+
errorPayload(error, logs, timeoutMs, options.email),
|
|
452
|
+
null,
|
|
453
|
+
2,
|
|
454
|
+
),
|
|
455
|
+
);
|
|
401
456
|
process.exitCode = 1;
|
|
402
457
|
}
|
|
403
458
|
}
|
|
@@ -45,3 +45,5 @@ The script instructs the model to:
|
|
|
45
45
|
|
|
46
46
|
- No extra npm install is required.
|
|
47
47
|
- If module resolution fails, set `PI_AI_MODULE_PATH` to `@mariozechner/pi-ai`'s `dist/index.js` path.
|
|
48
|
+
- If OAuth helper resolution fails, set `PI_AI_OAUTH_MODULE_PATH` to `@mariozechner/pi-ai`'s `dist/oauth.js` path.
|
|
49
|
+
- For OAuth providers, the script can fall back to a still-valid cached `access` token from `~/.pi/agent/auth.json`.
|
|
@@ -159,7 +159,7 @@ function findPiExecutable() {
|
|
|
159
159
|
return first || undefined;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
function collectModuleCandidates() {
|
|
162
|
+
function collectModuleCandidates(fileName = "index.js", envVarName = "PI_AI_MODULE_PATH") {
|
|
163
163
|
const candidates = new Set();
|
|
164
164
|
|
|
165
165
|
const add = (p) => {
|
|
@@ -168,16 +168,16 @@ function collectModuleCandidates() {
|
|
|
168
168
|
candidates.add(abs);
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
-
if (process.env
|
|
171
|
+
if (envVarName && process.env[envVarName]) add(process.env[envVarName]);
|
|
172
172
|
|
|
173
173
|
const cwd = process.cwd();
|
|
174
174
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
175
175
|
for (const start of [cwd, scriptDir]) {
|
|
176
176
|
let dir = start;
|
|
177
177
|
for (let i = 0; i < 8; i++) {
|
|
178
|
-
add(join(dir, "node_modules", "@mariozechner", "pi-ai", "dist",
|
|
179
|
-
add(join(dir, "packages", "ai", "dist",
|
|
180
|
-
add(join(dir, "ai", "dist",
|
|
178
|
+
add(join(dir, "node_modules", "@mariozechner", "pi-ai", "dist", fileName));
|
|
179
|
+
add(join(dir, "packages", "ai", "dist", fileName));
|
|
180
|
+
add(join(dir, "ai", "dist", fileName));
|
|
181
181
|
const parent = dirname(dir);
|
|
182
182
|
if (parent === dir) break;
|
|
183
183
|
dir = parent;
|
|
@@ -189,16 +189,16 @@ function collectModuleCandidates() {
|
|
|
189
189
|
try {
|
|
190
190
|
const piReal = realpathSync(piExec);
|
|
191
191
|
const piDir = dirname(piReal);
|
|
192
|
-
add(join(piDir, "..", "..", "ai", "dist",
|
|
193
|
-
add(join(piDir, "..", "..", "pi-ai", "dist",
|
|
194
|
-
add(join(piDir, "..", "node_modules", "@mariozechner", "pi-ai", "dist",
|
|
195
|
-
add(join(piDir, "..", "..", "node_modules", "@mariozechner", "pi-ai", "dist",
|
|
192
|
+
add(join(piDir, "..", "..", "ai", "dist", fileName));
|
|
193
|
+
add(join(piDir, "..", "..", "pi-ai", "dist", fileName));
|
|
194
|
+
add(join(piDir, "..", "node_modules", "@mariozechner", "pi-ai", "dist", fileName));
|
|
195
|
+
add(join(piDir, "..", "..", "node_modules", "@mariozechner", "pi-ai", "dist", fileName));
|
|
196
196
|
} catch {
|
|
197
197
|
// ignore
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
add(join(homedir(), "Development", "pi-mono", "packages", "ai", "dist",
|
|
201
|
+
add(join(homedir(), "Development", "pi-mono", "packages", "ai", "dist", fileName));
|
|
202
202
|
|
|
203
203
|
return Array.from(candidates);
|
|
204
204
|
}
|
|
@@ -212,7 +212,7 @@ async function loadPiAi() {
|
|
|
212
212
|
tried.push(`@mariozechner/pi-ai (${err?.code || err?.message || "not found"})`);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
for (const candidate of collectModuleCandidates()) {
|
|
215
|
+
for (const candidate of collectModuleCandidates("index.js", "PI_AI_MODULE_PATH")) {
|
|
216
216
|
if (!existsSync(candidate)) continue;
|
|
217
217
|
try {
|
|
218
218
|
return await import(pathToFileURL(candidate).href);
|
|
@@ -226,6 +226,81 @@ async function loadPiAi() {
|
|
|
226
226
|
);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
async function loadPiAiOAuth(piAi) {
|
|
230
|
+
if (typeof piAi?.getOAuthApiKey === "function") {
|
|
231
|
+
return { getOAuthApiKey: piAi.getOAuthApiKey.bind(piAi) };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const tried = [];
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const oauth = await import("@mariozechner/pi-ai/oauth");
|
|
238
|
+
if (typeof oauth.getOAuthApiKey === "function") {
|
|
239
|
+
return { getOAuthApiKey: oauth.getOAuthApiKey.bind(oauth) };
|
|
240
|
+
}
|
|
241
|
+
tried.push("@mariozechner/pi-ai/oauth (missing getOAuthApiKey export)");
|
|
242
|
+
} catch (err) {
|
|
243
|
+
tried.push(`@mariozechner/pi-ai/oauth (${err?.code || err?.message || "not found"})`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
for (const candidate of collectModuleCandidates("oauth.js", "PI_AI_OAUTH_MODULE_PATH")) {
|
|
247
|
+
if (!existsSync(candidate)) continue;
|
|
248
|
+
try {
|
|
249
|
+
const oauth = await import(pathToFileURL(candidate).href);
|
|
250
|
+
if (typeof oauth.getOAuthApiKey === "function") {
|
|
251
|
+
return { getOAuthApiKey: oauth.getOAuthApiKey.bind(oauth) };
|
|
252
|
+
}
|
|
253
|
+
tried.push(`${candidate} (missing getOAuthApiKey export)`);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
tried.push(`${candidate} (${err?.code || err?.message || "failed"})`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
getOAuthApiKey: undefined,
|
|
261
|
+
error: `Could not load getOAuthApiKey. Set PI_AI_OAUTH_MODULE_PATH to pi-ai dist/oauth.js.\nTried:\n- ${tried.join("\n- ")}`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function parseExpiryTimestamp(expires) {
|
|
266
|
+
if (typeof expires === "number" && Number.isFinite(expires)) {
|
|
267
|
+
if (expires <= 0) return undefined;
|
|
268
|
+
return expires < 1_000_000_000_000 ? expires * 1000 : expires;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (typeof expires === "string") {
|
|
272
|
+
const trimmed = expires.trim();
|
|
273
|
+
if (!trimmed) return undefined;
|
|
274
|
+
|
|
275
|
+
const numeric = Number(trimmed);
|
|
276
|
+
if (Number.isFinite(numeric)) {
|
|
277
|
+
return parseExpiryTimestamp(numeric);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const parsed = Date.parse(trimmed);
|
|
281
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getCachedOAuthAccess(entry, now = Date.now()) {
|
|
288
|
+
if (!entry || typeof entry !== "object") return undefined;
|
|
289
|
+
|
|
290
|
+
const apiKey = resolveConfigValue(entry.access);
|
|
291
|
+
if (!apiKey) return undefined;
|
|
292
|
+
|
|
293
|
+
const expiresAt = parseExpiryTimestamp(entry.expires);
|
|
294
|
+
if (!expiresAt) return undefined;
|
|
295
|
+
|
|
296
|
+
if (now + 30_000 >= expiresAt) return undefined;
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
apiKey,
|
|
300
|
+
accountId: entry.accountId,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
229
304
|
function pickFastModel(provider, requestedModel, piAi) {
|
|
230
305
|
const models = typeof piAi.getModels === "function" ? piAi.getModels(provider) : [];
|
|
231
306
|
if (!Array.isArray(models) || models.length === 0) {
|
|
@@ -272,8 +347,12 @@ async function resolveApiKey(provider, auth, authPath, piAi) {
|
|
|
272
347
|
throw new Error(`Unsupported credential type for ${provider}: ${String(entry.type || "unknown")}`);
|
|
273
348
|
}
|
|
274
349
|
|
|
275
|
-
|
|
276
|
-
|
|
350
|
+
const fallbackToken = getCachedOAuthAccess(entry);
|
|
351
|
+
const oauth = await loadPiAiOAuth(piAi);
|
|
352
|
+
|
|
353
|
+
if (typeof oauth.getOAuthApiKey !== "function") {
|
|
354
|
+
if (fallbackToken) return fallbackToken;
|
|
355
|
+
throw new Error(oauth.error || "Loaded pi-ai module does not export getOAuthApiKey");
|
|
277
356
|
}
|
|
278
357
|
|
|
279
358
|
const oauthCreds = {};
|
|
@@ -283,8 +362,16 @@ async function resolveApiKey(provider, auth, authPath, piAi) {
|
|
|
283
362
|
}
|
|
284
363
|
}
|
|
285
364
|
|
|
286
|
-
|
|
287
|
-
|
|
365
|
+
let refreshed;
|
|
366
|
+
try {
|
|
367
|
+
refreshed = await oauth.getOAuthApiKey(provider, oauthCreds);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
if (fallbackToken) return fallbackToken;
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!refreshed?.apiKey) {
|
|
374
|
+
if (fallbackToken) return fallbackToken;
|
|
288
375
|
throw new Error(`No OAuth credentials available for provider '${provider}'`);
|
|
289
376
|
}
|
|
290
377
|
|
package/skills/pi-share/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pi-share
|
|
3
|
-
description: "Load and parse session transcripts from shittycodingagent.ai/buildwithpi.ai/buildwithpi.com (pi-share) URLs. Fetches gists, decodes embedded session data, and extracts conversation history."
|
|
3
|
+
description: "Load and parse session transcripts from shittycodingagent.ai/buildwithpi.ai/buildwithpi.com/pi.dev (pi-share) URLs. Fetches gists, decodes embedded session data, and extracts conversation history."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# pi-share / buildwithpi Session Loader
|
|
7
7
|
|
|
8
|
-
Load and parse session transcripts from pi-share URLs (shittycodingagent.ai, buildwithpi.ai, buildwithpi.com).
|
|
8
|
+
Load and parse session transcripts from pi-share URLs (shittycodingagent.ai, buildwithpi.ai, buildwithpi.com, pi.dev).
|
|
9
9
|
|
|
10
10
|
## When to Use
|
|
11
11
|
|
|
@@ -13,7 +13,10 @@ Load and parse session transcripts from pi-share URLs (shittycodingagent.ai, bui
|
|
|
13
13
|
- `https://shittycodingagent.ai/session/?<gist_id>`
|
|
14
14
|
- `https://buildwithpi.ai/session/?<gist_id>`
|
|
15
15
|
- `https://buildwithpi.com/session/?<gist_id>`
|
|
16
|
+
- `https://pi.dev/session/?<gist_id>`
|
|
17
|
+
- `https://pi.dev/session/#<gist_id>`
|
|
16
18
|
- Or just a gist ID like `46aee35206aefe99257bc5d5e60c6121`
|
|
19
|
+
- Or hash-prefixed shorthand like `#46aee35206aefe99257bc5d5e60c6121`
|
|
17
20
|
|
|
18
21
|
**Human summaries:** Use `--human-summary` when the user asks you to:
|
|
19
22
|
- Summarize what a human did in a pi/coding agent session
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Fetch and parse pi-share (shittycodingagent.ai/buildwithpi.ai/buildwithpi.com) session exports.
|
|
3
|
+
* Fetch and parse pi-share (shittycodingagent.ai/buildwithpi.ai/buildwithpi.com/pi.dev) session exports.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* node fetch-session.mjs <url-or-gist-id> [--header] [--entries] [--system] [--tools] [--human-summary] [--no-cache]
|
|
@@ -51,21 +51,52 @@ function writeCache(gistId, data) {
|
|
|
51
51
|
|
|
52
52
|
// Extract gist ID from URL or use directly
|
|
53
53
|
function extractGistId(input) {
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
54
|
+
const HEX_ID_RE = /^[a-f0-9]{8,40}$/i;
|
|
55
|
+
const normalize = (value = '') => value.trim().replace(/^#/, '');
|
|
56
|
+
|
|
57
|
+
// Handle direct gist ID and #<gist-id>
|
|
58
|
+
const normalizedInput = normalize(input);
|
|
59
|
+
if (HEX_ID_RE.test(normalizedInput)) return normalizedInput;
|
|
60
|
+
|
|
61
|
+
// Parse URL forms (supports shittycodingagent.ai, buildwithpi.ai/.com, pi.dev aliases)
|
|
62
|
+
try {
|
|
63
|
+
const url = new URL(input);
|
|
64
|
+
|
|
65
|
+
// Handle old query style: /session/?<gist-id>
|
|
66
|
+
const bareQuery = normalize(url.search.replace(/^\?/, ''));
|
|
67
|
+
if (HEX_ID_RE.test(bareQuery)) return bareQuery;
|
|
68
|
+
|
|
69
|
+
// Handle keyed query params: ?id=<gist-id>, ?gist=<gist-id>, etc.
|
|
70
|
+
for (const value of url.searchParams.values()) {
|
|
71
|
+
const normalizedValue = normalize(value);
|
|
72
|
+
if (HEX_ID_RE.test(normalizedValue)) return normalizedValue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle hash style: /session/#<gist-id>
|
|
76
|
+
const hashValue = normalize(url.hash);
|
|
77
|
+
if (HEX_ID_RE.test(hashValue)) return hashValue;
|
|
78
|
+
|
|
79
|
+
// Handle path-based URLs like /session/<gist-id>
|
|
80
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
81
|
+
for (let i = 0; i < segments.length; i++) {
|
|
82
|
+
if (segments[i] === 'session' && HEX_ID_RE.test(segments[i + 1] || '')) {
|
|
83
|
+
return segments[i + 1];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle gist.github.com URLs
|
|
88
|
+
if (url.hostname === 'gist.github.com') {
|
|
89
|
+
const gistCandidate = segments[segments.length - 1];
|
|
90
|
+
if (HEX_ID_RE.test(gistCandidate || '')) return gistCandidate;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Not a URL; continue with regex fallback below
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Regex fallback for unknown URL formats containing a gist ID
|
|
97
|
+
const fallbackMatch = input.match(/([a-f0-9]{8,40})/i);
|
|
98
|
+
if (fallbackMatch) return fallbackMatch[1];
|
|
57
99
|
|
|
58
|
-
// Handle path-based URLs like https://buildwithpi.ai/session/<id>
|
|
59
|
-
const pathMatch = input.match(/\/session\/?([a-f0-9]{32})/i);
|
|
60
|
-
if (pathMatch) return pathMatch[1];
|
|
61
|
-
|
|
62
|
-
// Handle direct gist ID
|
|
63
|
-
if (/^[a-f0-9]{32}$/i.test(input)) return input;
|
|
64
|
-
|
|
65
|
-
// Handle gist.github.com URLs
|
|
66
|
-
const gistMatch = input.match(/gist\.github\.com\/[^/]+\/([a-f0-9]+)/i);
|
|
67
|
-
if (gistMatch) return gistMatch[1];
|
|
68
|
-
|
|
69
100
|
throw new Error(`Cannot extract gist ID from: ${input}`);
|
|
70
101
|
}
|
|
71
102
|
|
|
@@ -6,6 +6,7 @@ description: "Fetch a URL or convert a local file (PDF/DOCX/HTML/etc.) into Mark
|
|
|
6
6
|
Turn “things” (URLs, PDFs, Word docs, PowerPoints, HTML pages, text files, etc.) into **Markdown** so they can be inspected/quoted/processed like normal text.
|
|
7
7
|
|
|
8
8
|
`markitdown` can fetch URLs by itself; this skill mainly wraps it to make saving + summarizing convenient.
|
|
9
|
+
For PDF inputs, use the `markitdown[pdf]` extra (or the wrapper below, which now does this automatically).
|
|
9
10
|
|
|
10
11
|
## When to use
|
|
11
12
|
|
|
@@ -21,7 +22,7 @@ Use this skill when you need to:
|
|
|
21
22
|
Run from **this skill folder** (the agent should `cd` here first):
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
uvx markitdown <url-or-path>
|
|
25
|
+
uvx --from 'markitdown[pdf]' markitdown <url-or-path>
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
To write Markdown to a temp file (prints the path) use the wrapper:
|
|
@@ -35,7 +36,7 @@ Tip: when summarizing, the script will **always** write the full converted Markd
|
|
|
35
36
|
Write Markdown to a specific file:
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
|
-
uvx markitdown <url-or-path> > /tmp/doc.md
|
|
39
|
+
uvx --from 'markitdown[pdf]' markitdown <url-or-path> > /tmp/doc.md
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
### Convert + summarize with haiku-4-5 (pass context!)
|
|
@@ -53,6 +54,6 @@ node to-markdown.mjs <url-or-path> --summary --prompt "Focus on security implica
|
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
This will:
|
|
56
|
-
1) convert to Markdown via `uvx markitdown`
|
|
57
|
+
1) convert to Markdown via `uvx --from 'markitdown[pdf]' markitdown`
|
|
57
58
|
2) write the full Markdown to a temp `.md` file and print its path as a "Hint" line
|
|
58
59
|
3) run `pi --model claude-haiku-4-5` (no-tools, no-session) to summarize using your extra prompt
|
|
@@ -132,7 +132,9 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
132
132
|
if (!input) usageAndExit(1);
|
|
133
133
|
|
|
134
134
|
function runMarkitdown(arg) {
|
|
135
|
-
|
|
135
|
+
// Include PDF support by default because many document URLs (for example arXiv PDFs)
|
|
136
|
+
// are detected as PDFs only after fetching, so extension-based switching is unreliable.
|
|
137
|
+
const result = spawnSync('uvx', ['--from', 'markitdown[pdf]', 'markitdown', arg], {
|
|
136
138
|
encoding: 'utf-8',
|
|
137
139
|
maxBuffer: 50 * 1024 * 1024
|
|
138
140
|
});
|
|
@@ -17,6 +17,12 @@ Minimal CDP tools for collaborative site exploration.
|
|
|
17
17
|
|
|
18
18
|
Start Chrome on `:9222` with remote debugging.
|
|
19
19
|
|
|
20
|
+
If Chrome is installed in a non-standard location, set:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
BROWSER_BIN=/path/to/chrome ./scripts/start.js
|
|
24
|
+
```
|
|
25
|
+
|
|
20
26
|
## Navigate
|
|
21
27
|
|
|
22
28
|
```bash
|
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn, execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, rmSync } from "node:fs";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const args = new Set(process.argv.slice(2));
|
|
9
|
+
const useProfile = args.has("--profile");
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const unknownArgs = [...args].filter((arg) => arg !== "--profile");
|
|
12
|
+
if (unknownArgs.length > 0) {
|
|
13
|
+
console.log("Usage: start.js [--profile]");
|
|
11
14
|
console.log("\nOptions:");
|
|
12
15
|
console.log(
|
|
13
16
|
" --profile Copy your default Chrome profile (cookies, logins)",
|
|
14
17
|
);
|
|
15
18
|
console.log("\nExamples:");
|
|
16
|
-
console.log(" start.
|
|
17
|
-
console.log(" start.
|
|
19
|
+
console.log(" start.js # Start with fresh profile");
|
|
20
|
+
console.log(" start.js --profile # Start with your Chrome profile");
|
|
18
21
|
process.exit(1);
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
async function isDebugEndpointUp() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch("http://localhost:9222/json/version");
|
|
27
|
+
return response.ok;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
//
|
|
27
|
-
|
|
33
|
+
// If something is already listening on :9222, reuse it instead of killing Chrome.
|
|
34
|
+
if (await isDebugEndpointUp()) {
|
|
35
|
+
console.log("✓ Chrome already running on :9222 (reusing existing instance)");
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
// Setup profile directory
|
|
30
40
|
execSync("mkdir -p ~/.cache/scraping", { stdio: "ignore" });
|
|
@@ -32,24 +42,60 @@ execSync("mkdir -p ~/.cache/scraping", { stdio: "ignore" });
|
|
|
32
42
|
if (useProfile) {
|
|
33
43
|
// Sync profile with rsync (much faster on subsequent runs)
|
|
34
44
|
execSync(
|
|
35
|
-
`rsync -a --delete "${process.env["HOME"]}/Library/Application Support/Google/Chrome/" ~/.cache/scraping/`,
|
|
45
|
+
`rsync -a --delete --exclude 'Singleton*' --exclude 'DevToolsActivePort*' "${process.env["HOME"]}/Library/Application Support/Google/Chrome/" ~/.cache/scraping/`,
|
|
36
46
|
{ stdio: "pipe" },
|
|
37
47
|
);
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Remove stale singleton/debug artifacts that can make Chrome forward to an
|
|
51
|
+
// already running browser instance (which drops our debug flags).
|
|
52
|
+
for (const staleFile of [
|
|
53
|
+
"SingletonCookie",
|
|
54
|
+
"SingletonLock",
|
|
55
|
+
"SingletonSocket",
|
|
56
|
+
"DevToolsActivePort",
|
|
57
|
+
"DevToolsActivePort.lock",
|
|
58
|
+
]) {
|
|
59
|
+
try {
|
|
60
|
+
rmSync(`${process.env["HOME"]}/.cache/scraping/${staleFile}`, { force: true });
|
|
61
|
+
} catch {
|
|
62
|
+
// ignore
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveChromeBinary() {
|
|
67
|
+
if (process.env.BROWSER_BIN && existsSync(process.env.BROWSER_BIN)) {
|
|
68
|
+
return process.env.BROWSER_BIN;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const candidates = [
|
|
72
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
73
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
74
|
+
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return candidates.find((path) => existsSync(path)) || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const chromeBinary = resolveChromeBinary();
|
|
81
|
+
|
|
82
|
+
if (!chromeBinary) {
|
|
83
|
+
console.error("✗ Could not find Chrome/Chromium binary");
|
|
84
|
+
console.error(" Set BROWSER_BIN=/path/to/chrome and retry");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const chromeArgs = [
|
|
89
|
+
"--remote-debugging-port=9222",
|
|
90
|
+
`--user-data-dir=${process.env["HOME"]}/.cache/scraping`,
|
|
91
|
+
"--profile-directory=Default",
|
|
92
|
+
"--disable-search-engine-choice-screen",
|
|
93
|
+
"--no-first-run",
|
|
94
|
+
"--disable-features=ProfilePicker",
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const chromeProc = spawn(chromeBinary, chromeArgs, { detached: true, stdio: "ignore" });
|
|
98
|
+
chromeProc.unref();
|
|
53
99
|
|
|
54
100
|
// Wait for Chrome to be ready by checking the debugging endpoint
|
|
55
101
|
let connected = false;
|
|
@@ -61,12 +107,14 @@ for (let i = 0; i < 30; i++) {
|
|
|
61
107
|
break;
|
|
62
108
|
}
|
|
63
109
|
} catch {
|
|
64
|
-
|
|
110
|
+
// ignore; wait below
|
|
65
111
|
}
|
|
112
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
66
113
|
}
|
|
67
114
|
|
|
68
115
|
if (!connected) {
|
|
69
|
-
console.error("✗ Failed to connect to Chrome");
|
|
116
|
+
console.error("✗ Failed to connect to Chrome on :9222");
|
|
117
|
+
console.error(` Attempted binary: ${chromeBinary}`);
|
|
70
118
|
process.exit(1);
|
|
71
119
|
}
|
|
72
120
|
|