@ulpi/cli 0.1.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.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/auth-PN7TMQHV-2W4ICG64.js +15 -0
  4. package/dist/chunk-247GVVKK.js +2259 -0
  5. package/dist/chunk-2CLNOKPA.js +793 -0
  6. package/dist/chunk-2HEE5OKX.js +79 -0
  7. package/dist/chunk-2MZER6ND.js +415 -0
  8. package/dist/chunk-3SBPZRB5.js +772 -0
  9. package/dist/chunk-4VNS5WPM.js +42 -0
  10. package/dist/chunk-6JCMYYBT.js +1546 -0
  11. package/dist/chunk-6OCEY7JY.js +422 -0
  12. package/dist/chunk-74WVVWJ4.js +375 -0
  13. package/dist/chunk-7AL4DOEJ.js +131 -0
  14. package/dist/chunk-7LXY5UVC.js +330 -0
  15. package/dist/chunk-DBMUNBNB.js +3048 -0
  16. package/dist/chunk-JWUUVXIV.js +13694 -0
  17. package/dist/chunk-KIKPIH6N.js +4048 -0
  18. package/dist/chunk-KLEASXUR.js +70 -0
  19. package/dist/chunk-MIAQVCFW.js +39 -0
  20. package/dist/chunk-NNUWU6CV.js +1610 -0
  21. package/dist/chunk-PKD4ASEM.js +115 -0
  22. package/dist/chunk-Q4HIY43N.js +4230 -0
  23. package/dist/chunk-QJ5GSMEC.js +146 -0
  24. package/dist/chunk-SIAQVRKG.js +2163 -0
  25. package/dist/chunk-SPOI23SB.js +197 -0
  26. package/dist/chunk-YM2HV4IA.js +505 -0
  27. package/dist/codemap-RRJIDBQ5.js +636 -0
  28. package/dist/config-EGAXXCGL.js +127 -0
  29. package/dist/dist-6G7JC2RA.js +90 -0
  30. package/dist/dist-7LHZ65GC.js +418 -0
  31. package/dist/dist-LZKZFPVX.js +140 -0
  32. package/dist/dist-R5F4MX3I.js +107 -0
  33. package/dist/dist-R5ZJ4LX5.js +56 -0
  34. package/dist/dist-RJGCUS3L.js +87 -0
  35. package/dist/dist-RKOGLK7R.js +151 -0
  36. package/dist/dist-W7K4WPAF.js +597 -0
  37. package/dist/export-import-4A5MWLIA.js +53 -0
  38. package/dist/history-ATTUKOHO.js +934 -0
  39. package/dist/index.js +2120 -0
  40. package/dist/init-AY5C2ZAS.js +393 -0
  41. package/dist/launchd-LF2QMSKZ.js +148 -0
  42. package/dist/log-TVTUXAYD.js +75 -0
  43. package/dist/mcp-installer-NQCGKQ23.js +124 -0
  44. package/dist/memory-J3G24QHS.js +406 -0
  45. package/dist/ollama-3XCUZMZT-FYKHW4TZ.js +7 -0
  46. package/dist/openai-E7G2YAHU-UYY4ZWON.js +8 -0
  47. package/dist/projects-ATHDD3D6.js +271 -0
  48. package/dist/review-ADUPV3PN.js +152 -0
  49. package/dist/rules-E427DKYJ.js +134 -0
  50. package/dist/server-MOYPE4SM-N7SE2AN7.js +18 -0
  51. package/dist/server-X5P6WH2M-7K2RY34N.js +11 -0
  52. package/dist/skills/ulpi-generate-guardian/SKILL.md +511 -0
  53. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +692 -0
  54. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +596 -0
  55. package/dist/skills-CX73O3IV.js +76 -0
  56. package/dist/status-4DFHDJMN.js +66 -0
  57. package/dist/templates/biome.yml +24 -0
  58. package/dist/templates/conventional-commits.yml +18 -0
  59. package/dist/templates/django.yml +30 -0
  60. package/dist/templates/docker.yml +30 -0
  61. package/dist/templates/eslint.yml +13 -0
  62. package/dist/templates/express.yml +20 -0
  63. package/dist/templates/fastapi.yml +23 -0
  64. package/dist/templates/git-flow.yml +26 -0
  65. package/dist/templates/github-flow.yml +27 -0
  66. package/dist/templates/go.yml +33 -0
  67. package/dist/templates/jest.yml +24 -0
  68. package/dist/templates/laravel.yml +30 -0
  69. package/dist/templates/monorepo.yml +26 -0
  70. package/dist/templates/nestjs.yml +21 -0
  71. package/dist/templates/nextjs.yml +31 -0
  72. package/dist/templates/nodejs.yml +33 -0
  73. package/dist/templates/npm.yml +15 -0
  74. package/dist/templates/php.yml +25 -0
  75. package/dist/templates/pnpm.yml +15 -0
  76. package/dist/templates/prettier.yml +23 -0
  77. package/dist/templates/prisma.yml +21 -0
  78. package/dist/templates/python.yml +33 -0
  79. package/dist/templates/quality-of-life.yml +111 -0
  80. package/dist/templates/ruby.yml +25 -0
  81. package/dist/templates/rust.yml +34 -0
  82. package/dist/templates/typescript.yml +14 -0
  83. package/dist/templates/vitest.yml +24 -0
  84. package/dist/templates/yarn.yml +15 -0
  85. package/dist/templates-U7T6MARD.js +156 -0
  86. package/dist/ui-L7UAWXDY.js +167 -0
  87. package/dist/ui.html +698 -0
  88. package/dist/ulpi-RMMCUAGP-JCJ273T6.js +161 -0
  89. package/dist/uninstall-6SW35IK4.js +25 -0
  90. package/dist/update-M2B4RLGH.js +61 -0
  91. package/dist/version-checker-ANCS3IHR.js +10 -0
  92. package/package.json +92 -0
@@ -0,0 +1,393 @@
1
+ import {
2
+ installGlobalSkill,
3
+ installHooks,
4
+ installLocalSkill
5
+ } from "./chunk-QJ5GSMEC.js";
6
+ import {
7
+ detectStack
8
+ } from "./chunk-2CLNOKPA.js";
9
+ import {
10
+ registerProject
11
+ } from "./chunk-SPOI23SB.js";
12
+ import {
13
+ composeTemplates,
14
+ loadBundledTemplates,
15
+ resolveTemplate
16
+ } from "./chunk-6OCEY7JY.js";
17
+ import {
18
+ DEFAULT_AI_MODEL,
19
+ GUARDS_FILENAME,
20
+ getApiPort,
21
+ projectConfigDir
22
+ } from "./chunk-7LXY5UVC.js";
23
+ import "./chunk-4VNS5WPM.js";
24
+
25
+ // src/commands/init.ts
26
+ import * as fs from "fs";
27
+ import * as path from "path";
28
+ import { execFileSync, spawn } from "child_process";
29
+ import chalk from "chalk";
30
+
31
+ // src/ai-scan/response-parser.ts
32
+ function parseAIScanResponse(response) {
33
+ const yamlMatch = response.match(/```ya?ml\n([\s\S]*?)```/);
34
+ if (yamlMatch) {
35
+ return yamlMatch[1].trim();
36
+ }
37
+ const codeMatch = response.match(/```\n([\s\S]*?)```/);
38
+ if (codeMatch) {
39
+ return codeMatch[1].trim();
40
+ }
41
+ if (response.trim().match(/^[a-z_]+:/m)) {
42
+ return response.trim();
43
+ }
44
+ return null;
45
+ }
46
+ function validateScanResult(yaml) {
47
+ const warnings = [];
48
+ const sections = ["preconditions", "postconditions", "permissions", "pipelines"];
49
+ let foundSections = 0;
50
+ for (const section of sections) {
51
+ if (yaml.includes(`${section}:`)) {
52
+ foundSections++;
53
+ } else {
54
+ warnings.push(`Missing section: ${section}`);
55
+ }
56
+ }
57
+ return {
58
+ valid: foundSections > 0,
59
+ warnings
60
+ };
61
+ }
62
+
63
+ // src/commands/init.ts
64
+ async function runInit(args) {
65
+ const projectDir = path.resolve(args[0] ?? process.cwd());
66
+ const noAi = args.includes("--no-ai");
67
+ const modelFlag = args.find((a) => a.startsWith("--model="));
68
+ const model = modelFlag ? modelFlag.split("=")[1] : DEFAULT_AI_MODEL;
69
+ if (!fs.existsSync(projectDir) || !fs.statSync(projectDir).isDirectory()) {
70
+ console.log(chalk.red(`Error: Project directory does not exist or is not a directory: ${projectDir}`));
71
+ return;
72
+ }
73
+ if (projectDir.includes("..")) {
74
+ console.log(chalk.red("Error: Invalid project directory path."));
75
+ return;
76
+ }
77
+ console.log(chalk.bold("\nULPI \u2014 Project Setup\n"));
78
+ const rulesDir = projectConfigDir(projectDir);
79
+ const rulesPath = path.join(rulesDir, GUARDS_FILENAME);
80
+ fs.mkdirSync(rulesDir, { recursive: true });
81
+ console.log(chalk.cyan("Detecting project stack..."));
82
+ const stack = detectStack(projectDir);
83
+ printDetectedStack(stack);
84
+ console.log(chalk.cyan("\nInstalling Claude Code hooks..."));
85
+ installHooks(projectDir);
86
+ console.log(chalk.green("\u2713 Hooks installed in .claude/settings.local.json"));
87
+ const localResult = installLocalSkill(projectDir);
88
+ if (localResult.installed) {
89
+ console.log(chalk.green(`\u2713 ${localResult.message}`));
90
+ }
91
+ const globalResult = installGlobalSkill();
92
+ if (globalResult.installed) {
93
+ console.log(chalk.green(`\u2713 ${globalResult.message}`));
94
+ } else if (!globalResult.message.includes("already installed") && !globalResult.message.includes("not found")) {
95
+ console.log(chalk.yellow(`\u26A0 ${globalResult.message}`));
96
+ }
97
+ try {
98
+ const { isSupported, isLaunchAgentInstalled, needsLegacyMigration, installLaunchAgent } = await import("./launchd-LF2QMSKZ.js");
99
+ if (isSupported() && (!isLaunchAgentInstalled() || needsLegacyMigration())) {
100
+ const migrating = needsLegacyMigration();
101
+ installLaunchAgent();
102
+ if (migrating) {
103
+ console.log(chalk.green("\u2713 Migrated LaunchAgent from com.ulpi.guardian.ui \u2192 com.ulpi.ui"));
104
+ } else {
105
+ console.log(chalk.green("\u2713 Installed LaunchAgent for UI server (auto-starts on login)"));
106
+ }
107
+ console.log(chalk.dim(` Web UI: http://localhost:${getApiPort()}`));
108
+ }
109
+ } catch {
110
+ }
111
+ try {
112
+ const { installMcpServer, installMemoryMcpServer } = await import("./mcp-installer-NQCGKQ23.js");
113
+ const codemapMcp = installMcpServer(projectDir);
114
+ if (codemapMcp.installed) {
115
+ console.log(chalk.green("\u2713 CodeMap MCP server registered"));
116
+ }
117
+ const memoryMcp = installMemoryMcpServer(projectDir);
118
+ if (memoryMcp.installed) {
119
+ console.log(chalk.green("\u2713 Memory MCP server registered"));
120
+ }
121
+ } catch {
122
+ }
123
+ try {
124
+ const { runInitPipeline } = await import("./dist-LZKZFPVX.js");
125
+ const { initMemoryBranch } = await import("./dist-R5F4MX3I.js");
126
+ console.log(chalk.cyan("\nInitializing code index..."));
127
+ try {
128
+ await runInitPipeline(projectDir, (progress) => {
129
+ if (progress.message) {
130
+ process.stdout.write(`\r ${chalk.dim(progress.message)}${"".padEnd(20)}`);
131
+ }
132
+ });
133
+ process.stdout.write("\r");
134
+ console.log(chalk.green("\u2713 Code index initialized"));
135
+ } catch {
136
+ console.log(chalk.dim(" Skipped code indexing (embedding API unavailable)"));
137
+ }
138
+ try {
139
+ initMemoryBranch(projectDir);
140
+ console.log(chalk.green("\u2713 Memory branch initialized"));
141
+ } catch {
142
+ console.log(chalk.dim(" Skipped memory init"));
143
+ }
144
+ } catch {
145
+ }
146
+ const config = buildStackConfig(stack, projectDir);
147
+ let generated = false;
148
+ if (!noAi) {
149
+ generated = await attemptAiGeneration(projectDir, rulesPath, model);
150
+ }
151
+ if (!generated) {
152
+ if (!noAi) {
153
+ console.log(chalk.yellow("\nFalling back to template-based generation..."));
154
+ }
155
+ generateFromTemplates(projectDir, rulesPath, stack, config);
156
+ }
157
+ try {
158
+ registerProject(projectDir, {
159
+ name: config.name,
160
+ hooksInstalled: true,
161
+ configStatus: "configured",
162
+ stack: {
163
+ runtime: stack.runtime?.name,
164
+ framework: stack.framework?.name,
165
+ packageManager: stack.packageManager?.name
166
+ }
167
+ });
168
+ console.log(chalk.green("\u2713 Project registered in global registry"));
169
+ } catch {
170
+ }
171
+ console.log(chalk.bold.green("\nSetup complete! ULPI is now active.\n"));
172
+ }
173
+ function isClaudeCliAvailable() {
174
+ try {
175
+ execFileSync("claude", ["--version"], { stdio: "pipe", timeout: 5e3 });
176
+ return true;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ function runClaudeCli(projectDir, model) {
182
+ return new Promise((resolve2, reject) => {
183
+ const timeout = 3 * 60 * 1e3;
184
+ const proc = spawn("claude", [
185
+ "--print",
186
+ "--model",
187
+ model,
188
+ "--output-format",
189
+ "text",
190
+ "--permission-mode",
191
+ "bypassPermissions",
192
+ `/ulpi-generate-guardian ${projectDir}`
193
+ ], {
194
+ cwd: projectDir,
195
+ stdio: ["pipe", "pipe", "pipe"]
196
+ });
197
+ let stdout = "";
198
+ let stderr = "";
199
+ proc.stdout.on("data", (data) => {
200
+ stdout += data.toString();
201
+ });
202
+ proc.stderr.on("data", (data) => {
203
+ stderr += data.toString();
204
+ });
205
+ const timer = setTimeout(() => {
206
+ proc.kill("SIGTERM");
207
+ reject(new Error("AI generation timed out after 3 minutes"));
208
+ }, timeout);
209
+ proc.on("close", (code) => {
210
+ clearTimeout(timer);
211
+ if (code !== 0) {
212
+ reject(new Error(`claude CLI exited with code ${code}: ${stderr.slice(0, 500)}`));
213
+ } else {
214
+ resolve2({ stdout, stderr });
215
+ }
216
+ });
217
+ proc.on("error", (err) => {
218
+ clearTimeout(timer);
219
+ reject(new Error(`Failed to run claude CLI: ${err.message}`));
220
+ });
221
+ });
222
+ }
223
+ async function attemptAiGeneration(projectDir, rulesPath, model) {
224
+ if (!isClaudeCliAvailable()) {
225
+ console.log(chalk.dim("Claude CLI not found, skipping AI generation."));
226
+ return false;
227
+ }
228
+ console.log(chalk.cyan(`
229
+ Generating guards with AI (${model})...`));
230
+ console.log(chalk.dim("This may take a few minutes."));
231
+ try {
232
+ const { stdout } = await runClaudeCli(projectDir, model);
233
+ const yaml = parseAIScanResponse(stdout);
234
+ if (!yaml) {
235
+ console.log(chalk.yellow("\u26A0 Could not parse AI response as YAML."));
236
+ return false;
237
+ }
238
+ const validation = validateScanResult(yaml);
239
+ if (!validation.valid) {
240
+ console.log(chalk.yellow(`\u26A0 AI response missing required sections: ${validation.warnings.join(", ")}`));
241
+ return false;
242
+ }
243
+ const header = [
244
+ "# ULPI \u2014 AI-Generated Rules",
245
+ `# Generated by AI on ${(/* @__PURE__ */ new Date()).toISOString()}`,
246
+ `# Model: ${model}`,
247
+ "# Review these rules and adjust as needed.",
248
+ ""
249
+ ].join("\n");
250
+ fs.writeFileSync(rulesPath, header + yaml + "\n", "utf-8");
251
+ console.log(chalk.green(`\u2713 AI-generated guards.yml`));
252
+ if (validation.warnings.length > 0) {
253
+ for (const w of validation.warnings) {
254
+ console.log(chalk.yellow(` \u26A0 ${w}`));
255
+ }
256
+ }
257
+ return true;
258
+ } catch (err) {
259
+ const message = err instanceof Error ? err.message : String(err);
260
+ console.log(chalk.yellow(`\u26A0 AI generation failed: ${message}`));
261
+ return false;
262
+ }
263
+ }
264
+ function generateFromTemplates(projectDir, rulesPath, stack, config) {
265
+ console.log(chalk.cyan("\nSelecting templates..."));
266
+ const templates = selectTemplates(stack);
267
+ if (templates.length === 0) {
268
+ console.log(chalk.yellow("No matching templates found. Using quality-of-life defaults."));
269
+ } else {
270
+ console.log(`Selected ${templates.length} template(s):`);
271
+ for (const t of templates) {
272
+ console.log(` ${chalk.green("\u2713")} ${t.name} (${t.category})`);
273
+ }
274
+ }
275
+ const composed = composeTemplates(templates);
276
+ const resolved = resolveTemplate(composed, config);
277
+ const rulesYaml = generateRulesYaml(resolved, config);
278
+ fs.writeFileSync(rulesPath, rulesYaml, "utf-8");
279
+ console.log(chalk.green(`\u2713 Generated ${rulesPath}`));
280
+ }
281
+ function printDetectedStack(stack) {
282
+ const items = [
283
+ ["Runtime", stack.runtime],
284
+ ["Language", stack.language],
285
+ ["Framework", stack.framework],
286
+ ["Package Manager", stack.packageManager],
287
+ ["Formatter", stack.formatter],
288
+ ["Linter", stack.linter],
289
+ ["Test Runner", stack.testRunner],
290
+ ["ORM", stack.orm],
291
+ ["Git Workflow", stack.gitWorkflow]
292
+ ];
293
+ for (const [label, item] of items) {
294
+ if (item) {
295
+ console.log(` ${chalk.green("\u2713")} ${label}: ${chalk.bold(item.name)} (${item.confidence})`);
296
+ }
297
+ }
298
+ if (stack.features.length > 0) {
299
+ console.log(` Features: ${stack.features.join(", ")}`);
300
+ }
301
+ }
302
+ function selectTemplates(stack) {
303
+ const all = loadBundledTemplates();
304
+ const selected = [];
305
+ const qol = all.find((t) => t.id === "quality-of-life");
306
+ if (qol) selected.push(qol);
307
+ const detectors = [
308
+ stack.runtime,
309
+ stack.language,
310
+ stack.packageManager,
311
+ stack.framework,
312
+ stack.formatter,
313
+ stack.linter,
314
+ stack.testRunner,
315
+ stack.orm,
316
+ stack.gitWorkflow
317
+ ];
318
+ for (const det of detectors) {
319
+ if (det) {
320
+ const t = all.find((l) => l.id === det.id);
321
+ if (t && !selected.includes(t)) selected.push(t);
322
+ }
323
+ }
324
+ return selected;
325
+ }
326
+ function buildStackConfig(stack, projectDir) {
327
+ return {
328
+ name: path.basename(projectDir),
329
+ runtime: stack.runtime?.id ?? "unknown",
330
+ language: stack.language?.id,
331
+ framework: stack.framework?.id,
332
+ package_manager: stack.packageManager?.id ?? "npm",
333
+ orm: stack.orm?.id,
334
+ test_runner: stack.testRunner?.id,
335
+ formatter: stack.formatter?.id,
336
+ linter: stack.linter?.id,
337
+ git_workflow: stack.gitWorkflow?.id
338
+ };
339
+ }
340
+ function generateRulesYaml(resolved, config) {
341
+ const lines = [
342
+ "# ULPI \u2014 Rules Configuration",
343
+ "# Generated automatically based on detected stack.",
344
+ `# Project: ${config.name}`,
345
+ `# Package Manager: ${config.package_manager}`,
346
+ "",
347
+ "project:",
348
+ ` name: "${config.name}"`,
349
+ ` runtime: "${config.runtime}"`,
350
+ ` package_manager: "${config.package_manager}"`,
351
+ ""
352
+ ];
353
+ const sections = ["preconditions", "postconditions", "permissions", "pipelines"];
354
+ for (const section of sections) {
355
+ const data = resolved[section];
356
+ if (data && Object.keys(data).length > 0) {
357
+ lines.push(`${section}:`);
358
+ for (const [key, rule] of Object.entries(data)) {
359
+ lines.push(` ${key}:`);
360
+ writeYamlObject(rule, lines, 4);
361
+ }
362
+ lines.push("");
363
+ }
364
+ }
365
+ return lines.join("\n") + "\n";
366
+ }
367
+ function writeYamlObject(obj, lines, indent) {
368
+ const pad = " ".repeat(indent);
369
+ for (const [k, v] of Object.entries(obj)) {
370
+ if (k === "id" || k === "type" || k === "source") continue;
371
+ if (typeof v === "string") {
372
+ lines.push(`${pad}${k}: "${v}"`);
373
+ } else if (typeof v === "boolean" || typeof v === "number") {
374
+ lines.push(`${pad}${k}: ${v}`);
375
+ } else if (Array.isArray(v)) {
376
+ lines.push(`${pad}${k}:`);
377
+ for (const item of v) {
378
+ if (typeof item === "string") {
379
+ lines.push(`${pad} - "${item}"`);
380
+ } else if (typeof item === "object" && item !== null) {
381
+ lines.push(`${pad} -`);
382
+ writeYamlObject(item, lines, indent + 4);
383
+ }
384
+ }
385
+ } else if (v !== null && typeof v === "object") {
386
+ lines.push(`${pad}${k}:`);
387
+ writeYamlObject(v, lines, indent + 2);
388
+ }
389
+ }
390
+ }
391
+ export {
392
+ runInit
393
+ };
@@ -0,0 +1,148 @@
1
+ import "./chunk-QJ5GSMEC.js";
2
+ import {
3
+ LAUNCHD_LABEL,
4
+ LOGS_DIR,
5
+ getApiPort,
6
+ getBinaryPath
7
+ } from "./chunk-7LXY5UVC.js";
8
+ import "./chunk-4VNS5WPM.js";
9
+
10
+ // src/launchd.ts
11
+ import * as fs from "fs";
12
+ import * as path from "path";
13
+ import * as os from "os";
14
+ import { execFileSync } from "child_process";
15
+ var PLIST_LABEL = LAUNCHD_LABEL;
16
+ var PLIST_DIR = path.join(os.homedir(), "Library", "LaunchAgents");
17
+ var PLIST_PATH = path.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
18
+ var LOG_DIR = LOGS_DIR;
19
+ var LEGACY_PLIST_LABEL = "com.ulpi.guardian.ui";
20
+ var LEGACY_PLIST_PATH = path.join(PLIST_DIR, `${LEGACY_PLIST_LABEL}.plist`);
21
+ function isSupported() {
22
+ return process.platform === "darwin";
23
+ }
24
+ function isLaunchAgentInstalled() {
25
+ return fs.existsSync(PLIST_PATH);
26
+ }
27
+ function needsLegacyMigration() {
28
+ return fs.existsSync(LEGACY_PLIST_PATH);
29
+ }
30
+ function generatePlist(options = {}) {
31
+ const port = options.port ?? getApiPort();
32
+ const binary = getBinaryPath();
33
+ const firstSpace = binary.indexOf(" ");
34
+ const parts = firstSpace === -1 ? [binary] : [binary.slice(0, firstSpace), binary.slice(firstSpace + 1)];
35
+ for (let i = 0; i < parts.length; i++) {
36
+ const p = parts[i].replace(/^"|"$/g, "");
37
+ if (p && !p.startsWith("/")) {
38
+ try {
39
+ const resolved = execFileSync("which", [p], { encoding: "utf-8", timeout: 3e3 }).trim();
40
+ if (resolved) parts[i] = resolved;
41
+ else parts[i] = p;
42
+ } catch {
43
+ parts[i] = p;
44
+ }
45
+ } else {
46
+ parts[i] = p;
47
+ }
48
+ }
49
+ const programArgs = parts.map((p) => ` <string>${escapeXml(p)}</string>`).join("\n");
50
+ return `<?xml version="1.0" encoding="UTF-8"?>
51
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
52
+ <plist version="1.0">
53
+ <dict>
54
+ <key>Label</key>
55
+ <string>${PLIST_LABEL}</string>
56
+ <key>ProgramArguments</key>
57
+ <array>
58
+ ${programArgs}
59
+ <string>ui</string>
60
+ <string>--port</string>
61
+ <string>${port}</string>
62
+ </array>
63
+ <key>EnvironmentVariables</key>
64
+ <dict>
65
+ <key>ULPI_API_PORT</key>
66
+ <string>${port}</string>
67
+ </dict>
68
+ <key>RunAtLoad</key>
69
+ <true/>
70
+ <key>KeepAlive</key>
71
+ <true/>
72
+ <key>StandardOutPath</key>
73
+ <string>${escapeXml(path.join(LOG_DIR, "ulpi.stdout.log"))}</string>
74
+ <key>StandardErrorPath</key>
75
+ <string>${escapeXml(path.join(LOG_DIR, "ulpi.stderr.log"))}</string>
76
+ </dict>
77
+ </plist>
78
+ `;
79
+ }
80
+ function cleanupLegacyAgent() {
81
+ try {
82
+ if (fs.existsSync(LEGACY_PLIST_PATH)) {
83
+ try {
84
+ execFileSync("launchctl", ["unload", LEGACY_PLIST_PATH], { stdio: "pipe" });
85
+ } catch {
86
+ }
87
+ fs.unlinkSync(LEGACY_PLIST_PATH);
88
+ }
89
+ } catch {
90
+ }
91
+ }
92
+ function installLaunchAgent(options = {}) {
93
+ if (!isSupported()) return;
94
+ try {
95
+ cleanupLegacyAgent();
96
+ fs.mkdirSync(PLIST_DIR, { recursive: true });
97
+ fs.mkdirSync(LOG_DIR, { recursive: true });
98
+ const plist = generatePlist(options);
99
+ fs.writeFileSync(PLIST_PATH, plist, "utf-8");
100
+ try {
101
+ execFileSync("launchctl", ["load", "-w", PLIST_PATH], { stdio: "pipe" });
102
+ } catch {
103
+ try {
104
+ execFileSync("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
105
+ execFileSync("launchctl", ["load", "-w", PLIST_PATH], { stdio: "pipe" });
106
+ } catch {
107
+ }
108
+ }
109
+ } catch {
110
+ }
111
+ }
112
+ function uninstallLaunchAgent() {
113
+ if (!isSupported()) return;
114
+ cleanupLegacyAgent();
115
+ try {
116
+ if (fs.existsSync(PLIST_PATH)) {
117
+ try {
118
+ execFileSync("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
119
+ } catch {
120
+ }
121
+ fs.unlinkSync(PLIST_PATH);
122
+ }
123
+ } catch {
124
+ }
125
+ }
126
+ function restartLaunchAgent() {
127
+ if (!isSupported() || !fs.existsSync(PLIST_PATH)) return;
128
+ try {
129
+ execFileSync("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
130
+ } catch {
131
+ }
132
+ try {
133
+ execFileSync("launchctl", ["load", "-w", PLIST_PATH], { stdio: "pipe" });
134
+ } catch {
135
+ }
136
+ }
137
+ function escapeXml(str) {
138
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
139
+ }
140
+ export {
141
+ generatePlist,
142
+ installLaunchAgent,
143
+ isLaunchAgentInstalled,
144
+ isSupported,
145
+ needsLegacyMigration,
146
+ restartLaunchAgent,
147
+ uninstallLaunchAgent
148
+ };
@@ -0,0 +1,75 @@
1
+ import "./chunk-4VNS5WPM.js";
2
+
3
+ // src/commands/log.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import * as os from "os";
7
+ import chalk from "chalk";
8
+ async function runLog(args, _projectDir) {
9
+ const count = parseInt(args[0] ?? "20", 10);
10
+ const logsDir = path.join(os.homedir(), ".ulpi", "events");
11
+ if (!fs.existsSync(logsDir)) {
12
+ console.log(chalk.yellow("No activity logs found."));
13
+ return;
14
+ }
15
+ const files = fs.readdirSync(logsDir).filter((f) => f.endsWith(".jsonl")).sort().reverse();
16
+ if (files.length === 0) {
17
+ console.log(chalk.yellow("No activity logs found."));
18
+ return;
19
+ }
20
+ console.log(chalk.bold("\nRecent Activity\n"));
21
+ let shown = 0;
22
+ for (const file of files) {
23
+ if (shown >= count) break;
24
+ const content = fs.readFileSync(path.join(logsDir, file), "utf-8");
25
+ const lines = content.trim().split("\n").reverse();
26
+ for (const line of lines) {
27
+ if (shown >= count) break;
28
+ try {
29
+ const event = JSON.parse(line);
30
+ printEvent(event);
31
+ shown++;
32
+ } catch {
33
+ }
34
+ }
35
+ }
36
+ console.log("");
37
+ }
38
+ function printEvent(event) {
39
+ const time = chalk.dim(event.ts.slice(11, 19));
40
+ const eventType = colorEvent(event.event);
41
+ const details = [];
42
+ if (event.toolName) details.push(chalk.cyan(event.toolName));
43
+ if (event.filePath) details.push(event.filePath);
44
+ if (event.command) details.push(chalk.dim(truncate(event.command, 50)));
45
+ if (event.ruleName) details.push(chalk.yellow(event.ruleName));
46
+ if (event.message) details.push(event.message);
47
+ console.log(` ${time} ${eventType} ${details.join(" \u2014 ")}`);
48
+ }
49
+ function colorEvent(event) {
50
+ switch (event) {
51
+ case "tool_blocked":
52
+ case "postcondition_failed":
53
+ case "pipeline_failed":
54
+ case "permission_deny":
55
+ return chalk.red(padRight(event, 22));
56
+ case "tool_allowed":
57
+ case "permission_allow":
58
+ case "pipeline_complete":
59
+ return chalk.green(padRight(event, 22));
60
+ case "session_start":
61
+ case "session_end":
62
+ return chalk.blue(padRight(event, 22));
63
+ default:
64
+ return chalk.white(padRight(event, 22));
65
+ }
66
+ }
67
+ function padRight(str, len) {
68
+ return str.length >= len ? str : str + " ".repeat(len - str.length);
69
+ }
70
+ function truncate(str, maxLen) {
71
+ return str.length > maxLen ? str.slice(0, maxLen - 1) + "\u2026" : str;
72
+ }
73
+ export {
74
+ runLog
75
+ };