@getfootgun/keys 0.2.0-rc.1 → 0.2.0-rc.2

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.
@@ -8,4 +8,5 @@ export function registerNativeHost(opts: {
8
8
  manifest: { name: string; [key: string]: unknown };
9
9
  }): Promise<void>;
10
10
  export function healthTaskXml(launcher: string, now?: Date): string;
11
- export function scheduleDaily(opts: { platform: string; exec: Exec; home: string; launcher: string; uid?: number; support?: string; now?: () => Date }): Promise<void>;
11
+ export function scheduleDaily(opts: { platform: string; exec: Exec; home: string; launcher: string; uid?: number; support?: string; now?: () => Date }): Promise<{ when: "daily" } | { when: "signin"; reason: string }>;
12
+ export function firstLine(e: unknown): string;
package/lib/schedule.mjs CHANGED
@@ -6,6 +6,8 @@ import { launchdPlist } from "./launchd.mjs";
6
6
  const LABEL = "com.getfootgun.keys.health";
7
7
  const WIN_HOST_KEY = "HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\com.getfootgun.keys";
8
8
  const TASK = "Footgun Keys health";
9
+ const WIN_RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
10
+ const WIN_RUN_VALUE = "FootgunKeysHealth";
9
11
 
10
12
  async function loadAgent(exec, uid, plist) {
11
13
  try {
@@ -79,19 +81,35 @@ export function healthTaskXml(launcher, now = new Date()) {
79
81
  ].join("\r\n");
80
82
  }
81
83
 
82
- // Daily 9:30 run of "<launcher> health". Throws if the OS scheduler refuses.
84
+ // Daily 9:30 run of "<launcher> health". Resolves { when: "daily" }, or on a Windows PC that blocks
85
+ // Task Scheduler { when: "signin", reason } after falling back to a per-user Run entry that checks
86
+ // at each sign-in. Throws if neither works.
83
87
  export async function scheduleDaily({ platform, exec, home, launcher, uid, support, now = () => new Date() }) {
84
88
  if (platform === "win32") {
85
89
  // schtasks reads /XML files as UTF-16LE with a byte order mark.
86
90
  const xmlPath = win32.join(support, "health-task.xml");
87
91
  const body = Buffer.from(healthTaskXml(launcher, now()), "utf16le");
88
92
  await writePrivate(xmlPath, Buffer.concat([Buffer.from([0xff, 0xfe]), body]));
89
- await exec("schtasks", ["/Create", "/F", "/TN", TASK, "/XML", xmlPath]);
90
- return;
93
+ try {
94
+ await exec("schtasks", ["/Create", "/F", "/TN", TASK, "/XML", xmlPath]);
95
+ } catch (e) {
96
+ // Some PCs (security software, policy) refuse every new task, even for an administrator.
97
+ await exec("reg", ["add", WIN_RUN_KEY, "/v", WIN_RUN_VALUE, "/t", "REG_SZ", "/d", `conhost.exe --headless "${launcher}" health`, "/f"]).catch(() => { throw e; });
98
+ return { when: "signin", reason: firstLine(e) };
99
+ }
100
+ // A sign-in entry from an earlier blocked run would now double the checks.
101
+ await exec("reg", ["delete", WIN_RUN_KEY, "/v", WIN_RUN_VALUE, "/f"]).catch(() => {});
102
+ return { when: "daily" };
91
103
  }
92
104
  const agents = join(home, "Library", "LaunchAgents");
93
105
  await mkdir(agents, { recursive: true });
94
106
  const plist = join(agents, `${LABEL}.plist`);
95
107
  await writePrivate(plist, launchdPlist(launcher));
96
108
  await loadAgent(exec, uid, plist);
109
+ return { when: "daily" };
110
+ }
111
+
112
+ // The OS tool's own message (schtasks/launchctl stderr), which names no key.
113
+ export function firstLine(e) {
114
+ return String(e?.stderr || (e instanceof Error ? e.message : "") || "").trim().split(/\r?\n/)[0];
97
115
  }
package/lib/setup.mjs CHANGED
@@ -8,7 +8,7 @@ import { Writable } from "node:stream";
8
8
  import { promisify } from "node:util";
9
9
  import { EXTENSION_IDS } from "../shared/extension-ids.mjs";
10
10
  import { SITE, ensureSupportDir, writePrivate } from "./paths.mjs";
11
- import { registerNativeHost, scheduleDaily } from "./schedule.mjs";
11
+ import { firstLine, registerNativeHost, scheduleDaily } from "./schedule.mjs";
12
12
  import { keychainStore } from "./vault.mjs";
13
13
  import { WIN_NOTIFY_SCRIPT } from "./platform.mjs";
14
14
  import { makeServer } from "./server.mjs";
@@ -134,11 +134,18 @@ export async function setup({ home, platform, prompt, exec, installApp, server,
134
134
 
135
135
  let scheduled = true;
136
136
  try {
137
- await scheduleDaily({ platform, exec, home, launcher, uid, support, now });
138
- print("Scheduled the daily key check.");
139
- } catch {
137
+ const result = await scheduleDaily({ platform, exec, home, launcher, uid, support, now });
138
+ if (result?.when === "signin") {
139
+ print(`Windows would not create a scheduled task on this PC${result.reason ? ` (${result.reason})` : ""}.`);
140
+ print("Footgun will check your keys each time you sign in to Windows instead.");
141
+ } else {
142
+ print("Scheduled the daily key check.");
143
+ }
144
+ } catch (e) {
140
145
  scheduled = false;
141
146
  print("Could not schedule the daily key check. Run setup again to retry.");
147
+ const reason = firstLine(e);
148
+ if (reason) print(`The scheduler said: ${reason}`);
142
149
  }
143
150
  // A failed schedule must not end in apparent success, so it is repeated last and fails the run.
144
151
  const finish = (code) => {
package/package.json CHANGED
@@ -1 +1 @@
1
- { "name": "@getfootgun/keys", "version": "0.2.0-rc.1", "description": "Footgun Keys: guided API key setup that saves keys to the Mac Keychain or Windows Credential Manager.", "bin": { "footgun-keys": "bin/footgun-keys.mjs" }, "type": "module", "license": "MIT", "files": ["bin", "lib", "shared", "README.md"], "dependencies": { "@modelcontextprotocol/server": "2.3.1", "@napi-rs/keyring": "2.1.2", "zod": "4.6.7" }, "engines": { "node": ">=20" } }
1
+ { "name": "@getfootgun/keys", "version": "0.2.0-rc.2", "description": "Footgun Keys: guided API key setup that saves keys to the Mac Keychain or Windows Credential Manager.", "bin": { "footgun-keys": "bin/footgun-keys.mjs" }, "type": "module", "license": "MIT", "files": ["bin", "lib", "shared", "README.md"], "dependencies": { "@modelcontextprotocol/server": "2.3.1", "@napi-rs/keyring": "2.1.2", "zod": "4.6.7" }, "engines": { "node": ">=20" } }