@paradigma-inc/flywheel 0.1.93 → 0.1.99

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.
Files changed (34) hide show
  1. package/README.md +22 -4
  2. package/package.json +2 -1
  3. package/skills/flywheel-llm-proof-paper/SKILL.md +156 -0
  4. package/skills/flywheel-llm-proof-paper/agents/openai.yaml +4 -0
  5. package/skills/flywheel-llm-proof-paper/references/checks.md +60 -0
  6. package/skills/flywheel-llm-proof-paper/scripts/__pycache__/check_paper.cpython-311.pyc +0 -0
  7. package/skills/flywheel-llm-proof-paper/scripts/check_paper.py +1065 -0
  8. package/src/agents.mjs +5 -2
  9. package/src/cli.mjs +27 -3
  10. package/src/public-command-metadata.mjs +44 -0
  11. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.d.ts +21 -0
  12. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.js +68 -2
  13. package/src/runtime/vendor/flywheel-cli-dist/commands/_wait-polling.js.map +1 -1
  14. package/src/runtime/vendor/flywheel-cli-dist/commands/compute-acquire.js +2 -8
  15. package/src/runtime/vendor/flywheel-cli-dist/commands/compute-acquire.js.map +1 -1
  16. package/src/runtime/vendor/flywheel-cli-dist/commands/feedback-create.js +4 -0
  17. package/src/runtime/vendor/flywheel-cli-dist/commands/feedback-create.js.map +1 -1
  18. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/compute.js +1 -1
  19. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/compute.js.map +1 -1
  20. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js +16 -0
  21. package/src/runtime/vendor/flywheel-cli-dist/commands/registry/resources.js.map +1 -1
  22. package/src/runtime/vendor/flywheel-cli-dist/generatedProductTelemetryContract.d.ts +1 -1
  23. package/src/runtime/vendor/flywheel-cli-dist/generatedProductTelemetryContract.js +3 -3
  24. package/src/runtime/vendor/flywheel-cli-dist/generatedProductTelemetryContract.js.map +1 -1
  25. package/src/runtime/vendor/manifest.json +1 -1
  26. package/src/setup/shared/prior-mode-detect.mjs +76 -9
  27. package/src/unified-cli.mjs +54 -16
  28. package/src/update/cache.mjs +124 -0
  29. package/src/update/command.mjs +438 -0
  30. package/src/update/install-state.mjs +135 -0
  31. package/src/update/refresh.mjs +393 -0
  32. package/src/update/registry.mjs +35 -0
  33. package/src/update/version.mjs +59 -0
  34. package/src/update/warning.mjs +109 -0
@@ -1 +1 @@
1
- {"version":3,"file":"generatedProductTelemetryContract.js","sourceRoot":"","sources":["../src/generatedProductTelemetryContract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generatedProductTelemetryContract.js","sourceRoot":"","sources":["../src/generatedProductTelemetryContract.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "sourceCommit": "ae7d207c5a0e787e36307094ce25ad2bf18700ff",
2
+ "sourceCommit": "8c6b8edcadb0b2513c57e53ddcfbceca76c1c2c4",
3
3
  "files": [
4
4
  "auth/active-key-id.d.ts",
5
5
  "auth/active-key-id.js",
@@ -123,7 +123,34 @@ function isFlywheelMcpUrl(url, targetOrigin) {
123
123
  return targetOrigin !== null && parsed.origin.toLowerCase() === targetOrigin;
124
124
  }
125
125
 
126
- async function findFlywheelTomlEntryNames(filePath, targetOrigin) {
126
+ function isMcpServerUrl(url) {
127
+ if (typeof url !== "string" || url.trim().length === 0) {
128
+ return false;
129
+ }
130
+ try {
131
+ return new URL(url).pathname === "/mcp-server";
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ function shouldTreatAsFlywheelMcpEntry({
138
+ serverName,
139
+ serverUrl,
140
+ targetOrigin,
141
+ trustedServerNames,
142
+ }) {
143
+ return (
144
+ isFlywheelMcpUrl(serverUrl, targetOrigin) ||
145
+ (trustedServerNames.has(serverName) && isMcpServerUrl(serverUrl))
146
+ );
147
+ }
148
+
149
+ async function findFlywheelTomlEntries(
150
+ filePath,
151
+ targetOrigin,
152
+ trustedServerNames,
153
+ ) {
127
154
  let raw;
128
155
  try {
129
156
  raw = await readFile(filePath, "utf8");
@@ -139,9 +166,14 @@ async function findFlywheelTomlEntryNames(filePath, targetOrigin) {
139
166
  if (
140
167
  currentServerName !== null &&
141
168
  currentUrl !== null &&
142
- isFlywheelMcpUrl(currentUrl, targetOrigin)
169
+ shouldTreatAsFlywheelMcpEntry({
170
+ serverName: currentServerName,
171
+ serverUrl: currentUrl,
172
+ targetOrigin,
173
+ trustedServerNames,
174
+ })
143
175
  ) {
144
- matches.push(currentServerName);
176
+ matches.push({ serverName: currentServerName, serverUrl: currentUrl });
145
177
  }
146
178
  currentServerName = null;
147
179
  currentUrl = null;
@@ -176,26 +208,48 @@ async function findFlywheelTomlEntryNames(filePath, targetOrigin) {
176
208
  }
177
209
  commitCurrent();
178
210
 
179
- return unique(matches);
211
+ return uniqueEntries(matches);
212
+ }
213
+
214
+ function uniqueEntries(entries = []) {
215
+ const seen = new Set();
216
+ const out = [];
217
+ for (const entry of entries) {
218
+ const key = `${entry.serverName}\0${entry.serverUrl}`;
219
+ if (seen.has(key)) continue;
220
+ seen.add(key);
221
+ out.push(entry);
222
+ }
223
+ return out.sort((left, right) => {
224
+ const byName = left.serverName.localeCompare(right.serverName);
225
+ if (byName !== 0) return byName;
226
+ return left.serverUrl.localeCompare(right.serverUrl);
227
+ });
180
228
  }
181
229
 
182
- export async function findFlywheelMcpEntryNames({
230
+ export async function findFlywheelMcpEntries({
183
231
  hostName,
184
232
  scope,
185
233
  cwd = process.cwd(),
186
234
  targetOrigin = null,
235
+ trustedServerNames = [],
187
236
  getHost = defaultGetHost,
188
237
  resolveMcpPath = defaultResolveMcpPath,
189
238
  readJsonConfig = defaultReadJsonConfig,
190
239
  readYamlConfig = defaultReadYamlConfig,
191
240
  }) {
241
+ const trustedServerNameSet = new Set(trustedServerNames);
192
242
  try {
193
243
  const host = getHost(hostName);
194
244
  const candidates = resolveCandidatesForScope(host, scope, cwd);
195
245
  const filePath = await resolveMcpPath(candidates);
196
246
 
197
247
  if (host.mcp.configType === "toml") {
198
- return await findFlywheelTomlEntryNames(filePath, targetOrigin);
248
+ return await findFlywheelTomlEntries(
249
+ filePath,
250
+ targetOrigin,
251
+ trustedServerNameSet,
252
+ );
199
253
  }
200
254
 
201
255
  const config =
@@ -223,16 +277,29 @@ export async function findFlywheelMcpEntryNames({
223
277
  const matches = [];
224
278
  for (const [entryName, entry] of Object.entries(section)) {
225
279
  const entryUrl = readEntryServerUrl(entry);
226
- if (entryUrl !== null && isFlywheelMcpUrl(entryUrl, targetOrigin)) {
227
- matches.push(entryName);
280
+ if (
281
+ entryUrl !== null &&
282
+ shouldTreatAsFlywheelMcpEntry({
283
+ serverName: entryName,
284
+ serverUrl: entryUrl,
285
+ targetOrigin,
286
+ trustedServerNames: trustedServerNameSet,
287
+ })
288
+ ) {
289
+ matches.push({ serverName: entryName, serverUrl: entryUrl });
228
290
  }
229
291
  }
230
- return unique(matches);
292
+ return uniqueEntries(matches);
231
293
  } catch {
232
294
  return [];
233
295
  }
234
296
  }
235
297
 
298
+ export async function findFlywheelMcpEntryNames(options) {
299
+ const entries = await findFlywheelMcpEntries(options);
300
+ return entries.map((entry) => entry.serverName);
301
+ }
302
+
236
303
  /**
237
304
  * Determine whether a truly skill-only host's ownership marker is present at
238
305
  * the skills-root that `hostName` would write into. The two destination
@@ -7,9 +7,12 @@ import {
7
7
  } from "./public-command-metadata.mjs";
8
8
  import { runRuntimeCli } from "./runtime/delegate.mjs";
9
9
  import { RUNTIME_ALIASES } from "./runtime/vendor/flywheel-cli-dist/commands/aliases.js";
10
+ import { runUpdateCommand } from "./update/command.mjs";
11
+ import { runCommandWithDeferredUpdateWarning } from "./update/warning.mjs";
10
12
 
11
13
  const ALIAS_RESERVED_COMMANDS = new Set([
12
14
  "setup",
15
+ "update",
13
16
  "uninstall",
14
17
  "completion",
15
18
  "help",
@@ -100,7 +103,14 @@ async function routeExplicitHelp(argv) {
100
103
  }
101
104
  const setupHelpArgv = setupHelpArgvFor(target, subcommand);
102
105
  if (setupHelpArgv) {
103
- await runSetupCli(["node", "flywheel", ...setupHelpArgv]);
106
+ process.exitCode = await runSetupCli(
107
+ ["node", "flywheel", ...setupHelpArgv],
108
+ { exitOverride: true },
109
+ );
110
+ return;
111
+ }
112
+ if (target === "update") {
113
+ process.exitCode = await runUpdateCommand(["--help"]);
104
114
  return;
105
115
  }
106
116
  if (target === undefined) {
@@ -120,6 +130,17 @@ async function routeExplicitHelp(argv) {
120
130
  process.exitCode = exitCode;
121
131
  }
122
132
 
133
+ async function runWithDeferredUpdateWarning(argv, runCommand) {
134
+ const exitCode = await runCommandWithDeferredUpdateWarning({
135
+ argv,
136
+ currentVersion: await loadPackageVersion(),
137
+ runCommand,
138
+ });
139
+ if (Number.isInteger(exitCode)) {
140
+ process.exitCode = exitCode;
141
+ }
142
+ }
143
+
123
144
  export async function runUnifiedCli(argv = process.argv.slice(2)) {
124
145
  if (!Array.isArray(argv)) {
125
146
  throw new Error("runUnifiedCli expects an argv array.");
@@ -132,13 +153,14 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
132
153
  }
133
154
 
134
155
  if (argv.length === 1 && USAGE_REQUEST_TOKENS.has(argv[0])) {
135
- const publicPackageVersion = await loadPackageVersion();
136
- const exitCode = await runRuntimeCli([
137
- argv[0],
138
- usageExtrasArg(),
139
- usageVersionArg(publicPackageVersion),
140
- ]);
141
- process.exitCode = exitCode;
156
+ await runWithDeferredUpdateWarning(argv, async () => {
157
+ const publicPackageVersion = await loadPackageVersion();
158
+ return await runRuntimeCli([
159
+ argv[0],
160
+ usageExtrasArg(),
161
+ usageVersionArg(publicPackageVersion),
162
+ ]);
163
+ });
142
164
  return;
143
165
  }
144
166
 
@@ -164,8 +186,9 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
164
186
  }
165
187
  }
166
188
  const passthrough = ["help", helpExtrasArg(), ...formatArgs];
167
- const exitCode = await runRuntimeCli(passthrough);
168
- process.exitCode = exitCode;
189
+ await runWithDeferredUpdateWarning(argv, async () =>
190
+ await runRuntimeCli(passthrough),
191
+ );
169
192
  return;
170
193
  }
171
194
 
@@ -178,8 +201,16 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
178
201
  }
179
202
 
180
203
  const [command] = argv;
204
+ if (command === "update") {
205
+ process.exitCode = await runUpdateCommand(argv.slice(1));
206
+ return;
207
+ }
208
+
181
209
  if (command === "help") {
182
- await routeExplicitHelp(argv);
210
+ await runWithDeferredUpdateWarning(argv, async () => {
211
+ await routeExplicitHelp(argv);
212
+ return process.exitCode ?? 0;
213
+ });
183
214
  return;
184
215
  }
185
216
 
@@ -188,16 +219,23 @@ export async function runUnifiedCli(argv = process.argv.slice(2)) {
188
219
  command === "uninstall" ||
189
220
  command === "completion"
190
221
  ) {
191
- await runSetupCli(["node", "flywheel", ...argv]);
222
+ await runWithDeferredUpdateWarning(argv, async () => {
223
+ return await runSetupCli(["node", "flywheel", ...argv], {
224
+ exitOverride: true,
225
+ });
226
+ });
192
227
  return;
193
228
  }
194
229
 
195
230
  if (command.includes(":")) {
196
- const exitCode = await runRuntimeCli(argv);
197
- process.exitCode = exitCode;
231
+ await runWithDeferredUpdateWarning(argv, async () =>
232
+ await runRuntimeCli(argv),
233
+ );
198
234
  return;
199
235
  }
200
236
 
201
- process.stderr.write(`unknown command: ${command}\n`);
202
- process.exitCode = 1;
237
+ await runWithDeferredUpdateWarning(argv, async () => {
238
+ process.stderr.write(`unknown command: ${command}\n`);
239
+ return 1;
240
+ });
203
241
  }
@@ -0,0 +1,124 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ import { FLYWHEEL_PACKAGE_NAME } from "./version.mjs";
7
+
8
+ export const UPDATE_CHECK_CACHE_INTERVAL_MS = 24 * 60 * 60 * 1000;
9
+
10
+ export function resolveUpdateCheckCachePath({
11
+ env = process.env,
12
+ homeDir = os.homedir(),
13
+ } = {}) {
14
+ const override = String(env.FLYWHEEL_UPDATE_CHECK_CACHE_PATH || "").trim();
15
+ if (override) {
16
+ return override;
17
+ }
18
+ return path.join(homeDir, ".flywheel", "update-check.json");
19
+ }
20
+
21
+ function parseCache(raw) {
22
+ const parsed = JSON.parse(raw);
23
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
24
+ return null;
25
+ }
26
+ if (parsed.packageName !== FLYWHEEL_PACKAGE_NAME) {
27
+ return null;
28
+ }
29
+ if (typeof parsed.latestVersion !== "string" || !parsed.latestVersion.trim()) {
30
+ return null;
31
+ }
32
+ if (typeof parsed.nextCheckAt !== "string" || !parsed.nextCheckAt.trim()) {
33
+ return null;
34
+ }
35
+ const nextCheckAt = new Date(parsed.nextCheckAt);
36
+ if (Number.isNaN(nextCheckAt.getTime())) {
37
+ return null;
38
+ }
39
+ return parsed;
40
+ }
41
+
42
+ export async function readFreshUpdateCache({
43
+ cachePath = resolveUpdateCheckCachePath(),
44
+ now = () => new Date(),
45
+ } = {}) {
46
+ let raw;
47
+ try {
48
+ raw = await readFile(cachePath, "utf8");
49
+ } catch {
50
+ return null;
51
+ }
52
+
53
+ try {
54
+ const parsed = parseCache(raw);
55
+ if (!parsed) return null;
56
+ if (new Date(parsed.nextCheckAt).getTime() <= now().getTime()) {
57
+ return null;
58
+ }
59
+ return parsed;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ async function finalCacheIsValid(cachePath) {
66
+ try {
67
+ return parseCache(await readFile(cachePath, "utf8")) !== null;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ export async function writeUpdateCacheAtomic({
74
+ cachePath = resolveUpdateCheckCachePath(),
75
+ latestVersion,
76
+ now = () => new Date(),
77
+ intervalMs = UPDATE_CHECK_CACHE_INTERVAL_MS,
78
+ pid = process.pid,
79
+ randomId = randomUUID,
80
+ } = {}) {
81
+ const checkedAt = now();
82
+ const payload = {
83
+ packageName: FLYWHEEL_PACKAGE_NAME,
84
+ latestVersion,
85
+ checkedAt: checkedAt.toISOString(),
86
+ nextCheckAt: new Date(checkedAt.getTime() + intervalMs).toISOString(),
87
+ };
88
+ const tmpPath = `${cachePath}.${pid}.${randomId()}.tmp`;
89
+ await mkdir(path.dirname(cachePath), { recursive: true });
90
+ await writeFile(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, {
91
+ encoding: "utf8",
92
+ mode: 0o600,
93
+ });
94
+ try {
95
+ await rename(tmpPath, cachePath);
96
+ } catch (error) {
97
+ await rm(tmpPath, { force: true }).catch(() => {});
98
+ if (await finalCacheIsValid(cachePath)) {
99
+ return payload;
100
+ }
101
+ throw error;
102
+ }
103
+ return payload;
104
+ }
105
+
106
+ export async function getLatestVersionWithCache({
107
+ cachePath = resolveUpdateCheckCachePath(),
108
+ now = () => new Date(),
109
+ fetchLatestVersion,
110
+ intervalMs = UPDATE_CHECK_CACHE_INTERVAL_MS,
111
+ } = {}) {
112
+ const cached = await readFreshUpdateCache({ cachePath, now });
113
+ if (cached) {
114
+ return cached.latestVersion;
115
+ }
116
+ const latestVersion = await fetchLatestVersion();
117
+ await writeUpdateCacheAtomic({
118
+ cachePath,
119
+ latestVersion,
120
+ now,
121
+ intervalMs,
122
+ });
123
+ return latestVersion;
124
+ }