@coworker-jp/aidr 0.0.1
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/README.md +55 -0
- package/bin/aidr.js +2 -0
- package/package.json +29 -0
- package/src/agents/_stub.mjs +18 -0
- package/src/agents/aider.mjs +5 -0
- package/src/agents/amazonq.mjs +5 -0
- package/src/agents/amp.mjs +5 -0
- package/src/agents/antigravity.mjs +5 -0
- package/src/agents/claude.mjs +92 -0
- package/src/agents/cline.mjs +5 -0
- package/src/agents/codex.mjs +225 -0
- package/src/agents/continue.mjs +5 -0
- package/src/agents/copilot.mjs +5 -0
- package/src/agents/crush.mjs +5 -0
- package/src/agents/cursor.mjs +109 -0
- package/src/agents/gemini.mjs +66 -0
- package/src/agents/index.mjs +36 -0
- package/src/agents/jetbrains.mjs +5 -0
- package/src/agents/kiro.mjs +79 -0
- package/src/agents/opencode.mjs +5 -0
- package/src/agents/qwen.mjs +5 -0
- package/src/agents/roo.mjs +5 -0
- package/src/agents/standalone.mjs +43 -0
- package/src/agents/trae.mjs +5 -0
- package/src/agents/windsurf.mjs +105 -0
- package/src/binary-fetcher.mjs +157 -0
- package/src/browser-extension.mjs +83 -0
- package/src/cli.mjs +494 -0
- package/src/detect.mjs +39 -0
- package/src/fs-utils.mjs +247 -0
- package/src/merge.mjs +412 -0
- package/src/scheduled.mjs +219 -0
- package/src/templates.mjs +759 -0
- package/src/toml-merge.mjs +167 -0
- package/src/verify.mjs +51 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { execFile } from "child_process";
|
|
5
|
+
import { promisify } from "util";
|
|
6
|
+
|
|
7
|
+
const execFileP = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
function systemdDir(home) {
|
|
10
|
+
return path.join(home, ".config", "systemd", "user");
|
|
11
|
+
}
|
|
12
|
+
function systemdService(home) { return path.join(systemdDir(home), "aidr-scheduled.service"); }
|
|
13
|
+
function systemdTimer(home) { return path.join(systemdDir(home), "aidr-scheduled.timer"); }
|
|
14
|
+
function systemdEnv(home) { return path.join(systemdDir(home), "aidr-scheduled.env"); }
|
|
15
|
+
function launchdPlist(home) { return path.join(home, "Library", "LaunchAgents", "jp.coworker.aidr.scheduled.plist"); }
|
|
16
|
+
|
|
17
|
+
async function exists(p) {
|
|
18
|
+
try { await fs.access(p); return true; } catch { return false; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function isScheduledInstalled(home) {
|
|
22
|
+
return (
|
|
23
|
+
await exists(systemdTimer(home)) ||
|
|
24
|
+
await exists(launchdPlist(home))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function installScheduled(opts) {
|
|
29
|
+
const { home = os.homedir(), accessKey, binaryPath,
|
|
30
|
+
dryRun = false, interval = 4 } = opts;
|
|
31
|
+
const noActivate = opts.noActivate || process.env.AI_SCANNER_NO_SCHEDULED_ACTIVATE === "1";
|
|
32
|
+
|
|
33
|
+
// Schedule entry is agent-independent (cron points at ~/.aidr/bin/ai-scanner
|
|
34
|
+
// and the binary self-updates on each run). If a previous schedule entry
|
|
35
|
+
// exists, replace it unconditionally — the only thing that could differ is
|
|
36
|
+
// the binary path or interval, both of which the new install owns.
|
|
37
|
+
if (await isScheduledInstalled(home)) {
|
|
38
|
+
await uninstallScheduled({ home, dryRun, noActivate });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const platform = process.platform;
|
|
42
|
+
if (platform === "linux") {
|
|
43
|
+
await installSystemd({ home, accessKey, binaryPath, dryRun, noActivate, interval });
|
|
44
|
+
} else if (platform === "darwin") {
|
|
45
|
+
await installLaunchd({ home, accessKey, binaryPath, dryRun, noActivate, interval });
|
|
46
|
+
} else {
|
|
47
|
+
await installCrontab({ binaryPath, dryRun, interval });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function installSystemd({ home, accessKey, binaryPath, dryRun, noActivate, interval = 4 }) {
|
|
52
|
+
const dir = systemdDir(home);
|
|
53
|
+
|
|
54
|
+
const serviceContent = `[Unit]
|
|
55
|
+
Description=AIDR scheduled endpoint-info scan
|
|
56
|
+
|
|
57
|
+
[Service]
|
|
58
|
+
Type=oneshot
|
|
59
|
+
EnvironmentFile=${systemdEnv(home)}
|
|
60
|
+
ExecStart=${binaryPath} scan endpoint-info
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const timerContent = `[Unit]
|
|
64
|
+
Description=AIDR scheduled endpoint-info scan timer
|
|
65
|
+
|
|
66
|
+
[Timer]
|
|
67
|
+
OnBootSec=5min
|
|
68
|
+
OnUnitActiveSec=${interval}h
|
|
69
|
+
Persistent=true
|
|
70
|
+
|
|
71
|
+
[Install]
|
|
72
|
+
WantedBy=timers.target
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const envContent = `AI_SCANNER_ACCESS_KEY=${accessKey}\n`;
|
|
76
|
+
|
|
77
|
+
if (dryRun) {
|
|
78
|
+
console.log("[dry-run] Would create:", systemdService(home), systemdTimer(home), systemdEnv(home));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await fs.mkdir(dir, { recursive: true });
|
|
83
|
+
await fs.writeFile(systemdService(home), serviceContent, { mode: 0o644 });
|
|
84
|
+
await fs.writeFile(systemdTimer(home), timerContent, { mode: 0o644 });
|
|
85
|
+
await fs.writeFile(systemdEnv(home), envContent, { mode: 0o600 });
|
|
86
|
+
|
|
87
|
+
if (!noActivate) {
|
|
88
|
+
try {
|
|
89
|
+
await execFileP("systemctl", ["--user", "daemon-reload"]);
|
|
90
|
+
await execFileP("systemctl", ["--user", "enable", "--now", "aidr-scheduled.timer"]);
|
|
91
|
+
console.log(`Scheduled scan enabled via systemd user timer (every ${interval}h)`);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.warn("Warning: systemctl --user failed:", e.message);
|
|
94
|
+
console.warn("Timer files were written. Enable manually: systemctl --user enable --now aidr-scheduled.timer");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function installLaunchd({ home, accessKey, binaryPath, dryRun, noActivate, interval = 4 }) {
|
|
100
|
+
const dir = path.join(home, "Library", "LaunchAgents");
|
|
101
|
+
|
|
102
|
+
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
103
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
104
|
+
<plist version="1.0">
|
|
105
|
+
<dict>
|
|
106
|
+
<key>Label</key>
|
|
107
|
+
<string>jp.coworker.aidr.scheduled</string>
|
|
108
|
+
<key>ProgramArguments</key>
|
|
109
|
+
<array>
|
|
110
|
+
<string>${binaryPath}</string>
|
|
111
|
+
<string>scan</string>
|
|
112
|
+
<string>endpoint-info</string>
|
|
113
|
+
</array>
|
|
114
|
+
<key>EnvironmentVariables</key>
|
|
115
|
+
<dict>
|
|
116
|
+
<key>AI_SCANNER_ACCESS_KEY</key>
|
|
117
|
+
<string>${accessKey}</string>
|
|
118
|
+
</dict>
|
|
119
|
+
<key>StartInterval</key>
|
|
120
|
+
<integer>${interval * 3600}</integer>
|
|
121
|
+
<key>RunAtLoad</key>
|
|
122
|
+
<false/>
|
|
123
|
+
</dict>
|
|
124
|
+
</plist>
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
if (dryRun) {
|
|
128
|
+
console.log("[dry-run] Would create:", launchdPlist(home));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
await fs.mkdir(dir, { recursive: true });
|
|
133
|
+
await fs.writeFile(launchdPlist(home), plistContent, { mode: 0o644 });
|
|
134
|
+
|
|
135
|
+
if (!noActivate) {
|
|
136
|
+
try {
|
|
137
|
+
const uid = process.getuid?.() ?? (await execFileP("id", ["-u"])).stdout.trim();
|
|
138
|
+
await execFileP("launchctl", ["bootstrap", `gui/${uid}`, launchdPlist(home)]);
|
|
139
|
+
console.log(`Scheduled scan enabled via launchd (every ${interval}h)`);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.warn("Warning: launchctl bootstrap failed:", e.message);
|
|
142
|
+
console.warn("Plist written. Enable manually: launchctl load ~/Library/LaunchAgents/jp.coworker.aidr.scheduled.plist");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function installCrontab({ binaryPath, dryRun, interval = 4 }) {
|
|
148
|
+
const entry = `0 */${interval} * * * ${binaryPath} scan endpoint-info 2>/dev/null # aidr-scheduled\n`;
|
|
149
|
+
|
|
150
|
+
if (dryRun) {
|
|
151
|
+
console.log("[dry-run] Would add crontab entry:", entry.trim());
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let existing = "";
|
|
156
|
+
try {
|
|
157
|
+
const { stdout } = await execFileP("crontab", ["-l"]);
|
|
158
|
+
existing = stdout;
|
|
159
|
+
} catch { /* no crontab yet */ }
|
|
160
|
+
|
|
161
|
+
if (existing.includes("aidr-scheduled")) return;
|
|
162
|
+
|
|
163
|
+
const newCrontab = existing.endsWith("\n") || existing === ""
|
|
164
|
+
? existing + entry
|
|
165
|
+
: existing + "\n" + entry;
|
|
166
|
+
|
|
167
|
+
const child = execFile("crontab", ["-"]);
|
|
168
|
+
child.stdin.write(newCrontab);
|
|
169
|
+
child.stdin.end();
|
|
170
|
+
await new Promise((resolve, reject) => child.on("close", c => c === 0 ? resolve() : reject(new Error(`crontab exit ${c}`))));
|
|
171
|
+
console.log(`Scheduled scan added to user crontab (every ${interval}h)`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function uninstallScheduled({ home = os.homedir(), dryRun = false, noActivate = false } = {}) {
|
|
175
|
+
noActivate = noActivate || process.env.AI_SCANNER_NO_SCHEDULED_ACTIVATE === "1";
|
|
176
|
+
|
|
177
|
+
if (await exists(systemdTimer(home))) {
|
|
178
|
+
if (!dryRun && !noActivate) {
|
|
179
|
+
try { await execFileP("systemctl", ["--user", "disable", "--now", "aidr-scheduled.timer"]); } catch { /* ignore */ }
|
|
180
|
+
}
|
|
181
|
+
for (const f of [systemdService(home), systemdTimer(home), systemdEnv(home)]) {
|
|
182
|
+
if (!dryRun) { try { await fs.unlink(f); } catch { /* ignore */ } }
|
|
183
|
+
else { console.log("[dry-run] Would remove:", f); }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (await exists(launchdPlist(home))) {
|
|
188
|
+
if (!dryRun && !noActivate) {
|
|
189
|
+
try {
|
|
190
|
+
const uid = process.getuid?.() ?? (await execFileP("id", ["-u"])).stdout.trim();
|
|
191
|
+
await execFileP("launchctl", ["bootout", `gui/${uid}`, launchdPlist(home)]);
|
|
192
|
+
} catch { /* ignore */ }
|
|
193
|
+
}
|
|
194
|
+
if (!dryRun) { try { await fs.unlink(launchdPlist(home)); } catch { /* ignore */ } }
|
|
195
|
+
else { console.log("[dry-run] Would remove:", launchdPlist(home)); }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const { stdout } = await execFileP("crontab", ["-l"]);
|
|
200
|
+
const filtered = stdout.split("\n").filter(l => !l.includes("aidr-scheduled")).join("\n");
|
|
201
|
+
if (filtered !== stdout) {
|
|
202
|
+
if (!dryRun) {
|
|
203
|
+
const child = execFile("crontab", ["-"]);
|
|
204
|
+
child.stdin.write(filtered + (filtered.endsWith("\n") ? "" : "\n"));
|
|
205
|
+
child.stdin.end();
|
|
206
|
+
await new Promise(r => child.on("close", r));
|
|
207
|
+
} else {
|
|
208
|
+
console.log("[dry-run] Would remove aidr-scheduled from crontab");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch { /* no crontab */ }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function promptUser(question) {
|
|
215
|
+
return new Promise(resolve => {
|
|
216
|
+
process.stdout.write(question);
|
|
217
|
+
process.stdin.once("data", d => resolve(d.toString().trim()));
|
|
218
|
+
});
|
|
219
|
+
}
|