getprismo 0.1.36 → 0.1.38
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/lib/prismo-dev/agent.js +39 -1
- package/lib/prismo-dev/cli.js +67 -3
- package/lib/prismo-dev/cloud-sync.js +5 -0
- package/lib/prismo-dev/connector.js +306 -0
- package/lib/prismo-dev/help.js +43 -4
- package/lib/prismo-dev-scan.js +27 -1
- package/package.json +1 -1
package/lib/prismo-dev/agent.js
CHANGED
|
@@ -341,6 +341,24 @@ module.exports = function createAgent(deps) {
|
|
|
341
341
|
results.push({ id: action.id, label: action.label, ...result });
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
let syncResult = null;
|
|
345
|
+
if (options.syncTelemetry) {
|
|
346
|
+
try {
|
|
347
|
+
const result = await runSync(rootDir, { limit: options.syncLimit || 20 });
|
|
348
|
+
syncResult = {
|
|
349
|
+
synced: Boolean(result.synced),
|
|
350
|
+
sessions: Number(result.aggregate?.sessions || 0),
|
|
351
|
+
estimatedWastedTokens: Number(result.aggregate?.estimatedWastedTokens || 0),
|
|
352
|
+
wastePercent: Number(result.aggregate?.wastePercent || 0),
|
|
353
|
+
};
|
|
354
|
+
} catch (error) {
|
|
355
|
+
syncResult = {
|
|
356
|
+
synced: false,
|
|
357
|
+
error: error && error.message ? error.message : String(error),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
344
362
|
return {
|
|
345
363
|
schemaVersion: 1,
|
|
346
364
|
command: "agent",
|
|
@@ -352,6 +370,7 @@ module.exports = function createAgent(deps) {
|
|
|
352
370
|
actionsFailed: results.filter((item) => item.status === "failed").length,
|
|
353
371
|
actionsObserved: results.filter((item) => item.status === "observed" || item.status === "pending_approval").length,
|
|
354
372
|
autoDetect: autoDetectResult,
|
|
373
|
+
sync: syncResult,
|
|
355
374
|
results,
|
|
356
375
|
privacy: {
|
|
357
376
|
rawPrompts: false,
|
|
@@ -400,6 +419,16 @@ module.exports = function createAgent(deps) {
|
|
|
400
419
|
lines.push(` - ${f.message}`);
|
|
401
420
|
});
|
|
402
421
|
}
|
|
422
|
+
if (result.sync) {
|
|
423
|
+
lines.push("");
|
|
424
|
+
lines.push("Sync");
|
|
425
|
+
if (result.sync.synced) {
|
|
426
|
+
lines.push(` Sessions: ${result.sync.sessions}`);
|
|
427
|
+
lines.push(` Likely wasted: ${result.sync.estimatedWastedTokens.toLocaleString()} (${result.sync.wastePercent}%)`);
|
|
428
|
+
} else {
|
|
429
|
+
lines.push(` Status: not synced${result.sync.error ? ` (${result.sync.error})` : ""}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
403
432
|
if (result.results.length) {
|
|
404
433
|
lines.push("");
|
|
405
434
|
lines.push("Actions");
|
|
@@ -418,9 +447,11 @@ module.exports = function createAgent(deps) {
|
|
|
418
447
|
if (!options.watch) return runAgentOnce(rootDir, options);
|
|
419
448
|
|
|
420
449
|
const intervalMs = Math.max(5, Number(options.interval || 15)) * 1000;
|
|
450
|
+
const syncIntervalMs = Math.max(30, Number(options.syncInterval || 60)) * 1000;
|
|
421
451
|
let running = true;
|
|
422
452
|
let sleepResolve = null;
|
|
423
453
|
let firstRun = true;
|
|
454
|
+
let lastSyncAt = 0;
|
|
424
455
|
|
|
425
456
|
if (options.open) {
|
|
426
457
|
const config = loadConfig();
|
|
@@ -445,7 +476,14 @@ module.exports = function createAgent(deps) {
|
|
|
445
476
|
process.on("SIGTERM", shutdown);
|
|
446
477
|
|
|
447
478
|
while (running) {
|
|
448
|
-
const
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
const shouldSync = options.noSync !== true && (lastSyncAt === 0 || now - lastSyncAt >= syncIntervalMs);
|
|
481
|
+
if (shouldSync) lastSyncAt = now;
|
|
482
|
+
const runOptions = {
|
|
483
|
+
...options,
|
|
484
|
+
autoDetect: firstRun && options.autoDetect !== false,
|
|
485
|
+
syncTelemetry: shouldSync,
|
|
486
|
+
};
|
|
449
487
|
firstRun = false;
|
|
450
488
|
const result = await runAgentOnce(rootDir, runOptions);
|
|
451
489
|
if (!running) break;
|
package/lib/prismo-dev/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ const { printHelp, printCommandHelp } = require("./help");
|
|
|
3
3
|
|
|
4
4
|
const VALID_COMMANDS = new Set([
|
|
5
5
|
"dev", "init", "doctor", "firewall", "benchmark", "shield", "mcp",
|
|
6
|
-
"connect", "sync", "status", "disconnect", "agent", "setup", "scan",
|
|
6
|
+
"connect", "sync", "status", "disconnect", "agent", "connector", "setup", "scan",
|
|
7
7
|
"optimize", "context", "cc", "cursor", "receipt", "instructions",
|
|
8
8
|
"timeline", "replay", "boundaries", "usage", "guard", "watch", "demo",
|
|
9
9
|
]);
|
|
@@ -70,6 +70,12 @@ function createCli(deps) {
|
|
|
70
70
|
runGuard,
|
|
71
71
|
renderAgentTerminal,
|
|
72
72
|
runAgent,
|
|
73
|
+
renderConnectorTerminal,
|
|
74
|
+
runConnectorInstall,
|
|
75
|
+
runConnectorStart,
|
|
76
|
+
runConnectorStatus,
|
|
77
|
+
runConnectorStop,
|
|
78
|
+
runConnectorUninstall,
|
|
73
79
|
renderInstructionsAblationTerminal,
|
|
74
80
|
renderInstructionsApplyTerminal,
|
|
75
81
|
renderInstructionsAuditTerminal,
|
|
@@ -131,7 +137,7 @@ function createCli(deps) {
|
|
|
131
137
|
return;
|
|
132
138
|
}
|
|
133
139
|
if (!VALID_COMMANDS.has(command)) {
|
|
134
|
-
throw new Error(`Unknown command: ${command}. Try: prismo connect, prismo agent, prismo guard, prismo sync, prismo doctor, prismo watch, prismo receipt, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, prismo cursor, or prismo usage`);
|
|
140
|
+
throw new Error(`Unknown command: ${command}. Try: prismo connect, prismo connector, prismo agent, prismo guard, prismo sync, prismo doctor, prismo watch, prismo receipt, prismo benchmark, prismo shield, prismo mcp, prismo firewall, prismo init, prismo scan, prismo optimize, prismo context, prismo cc, prismo cursor, or prismo usage`);
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
if (command === "demo") {
|
|
@@ -314,6 +320,15 @@ function createCli(deps) {
|
|
|
314
320
|
const userIndex = rest.indexOf("--user");
|
|
315
321
|
const deviceIndex = rest.indexOf("--device");
|
|
316
322
|
const limitIndex = rest.indexOf("--limit");
|
|
323
|
+
const intervalIndex = rest.indexOf("--interval");
|
|
324
|
+
const syncIntervalIndex = rest.indexOf("--sync-interval");
|
|
325
|
+
const modeIndex = rest.indexOf("--mode");
|
|
326
|
+
const modeValue = modeIndex >= 0 ? rest[modeIndex + 1] : "autopilot";
|
|
327
|
+
if (!AGENT_VALID_MODES.has(modeValue)) {
|
|
328
|
+
throw new Error(`Invalid connector mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
|
|
329
|
+
}
|
|
330
|
+
const positional = getPositionals(rest, new Set(["--token", "--api-url", "--org", "--user", "--device", "--limit", "--interval", "--sync-interval", "--mode"]));
|
|
331
|
+
const target = positional[0] || process.cwd();
|
|
317
332
|
const result = runConnect({
|
|
318
333
|
token: tokenIndex >= 0 ? rest[tokenIndex + 1] : null,
|
|
319
334
|
apiUrl: apiUrlIndex >= 0 ? rest[apiUrlIndex + 1] : null,
|
|
@@ -322,6 +337,17 @@ function createCli(deps) {
|
|
|
322
337
|
device: deviceIndex >= 0 ? rest[deviceIndex + 1] : null,
|
|
323
338
|
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
324
339
|
});
|
|
340
|
+
if (result.connected && !rest.includes("--no-agent") && runConnectorInstall) {
|
|
341
|
+
result.connector = runConnectorInstall(target, {
|
|
342
|
+
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
|
|
343
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
|
|
344
|
+
mode: modeValue,
|
|
345
|
+
dryRun: rest.includes("--dry-run"),
|
|
346
|
+
});
|
|
347
|
+
result.next = result.connector?.started
|
|
348
|
+
? [`${NPX_COMMAND} status`, "Refresh Prismo Workspace"]
|
|
349
|
+
: [`${NPX_COMMAND} connector install`, `${NPX_COMMAND} status`];
|
|
350
|
+
}
|
|
325
351
|
if (result.connected && (rest.includes("--open") || !rest.includes("--no-open"))) {
|
|
326
352
|
openUrl("https://getprismo.dev/dashboard/dev");
|
|
327
353
|
}
|
|
@@ -330,6 +356,40 @@ function createCli(deps) {
|
|
|
330
356
|
return;
|
|
331
357
|
}
|
|
332
358
|
|
|
359
|
+
if (command === "connector") {
|
|
360
|
+
const json = rest.includes("--json");
|
|
361
|
+
const intervalIndex = rest.indexOf("--interval");
|
|
362
|
+
const syncIntervalIndex = rest.indexOf("--sync-interval");
|
|
363
|
+
const modeIndex = rest.indexOf("--mode");
|
|
364
|
+
const modeValue = modeIndex >= 0 ? rest[modeIndex + 1] : "autopilot";
|
|
365
|
+
if (!AGENT_VALID_MODES.has(modeValue)) {
|
|
366
|
+
throw new Error(`Invalid connector mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
|
|
367
|
+
}
|
|
368
|
+
const positional = getPositionals(rest, new Set(["--interval", "--sync-interval", "--mode"]));
|
|
369
|
+
const action = ["install", "start", "stop", "status", "uninstall"].includes(positional[0]) ? positional[0] : "status";
|
|
370
|
+
const target = ["install"].includes(action) ? positional[1] || process.cwd() : positional[0] || process.cwd();
|
|
371
|
+
let result;
|
|
372
|
+
if (action === "install") {
|
|
373
|
+
result = runConnectorInstall(target, {
|
|
374
|
+
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
|
|
375
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
|
|
376
|
+
mode: modeValue,
|
|
377
|
+
dryRun: rest.includes("--dry-run"),
|
|
378
|
+
});
|
|
379
|
+
} else if (action === "start") {
|
|
380
|
+
result = runConnectorStart({ dryRun: rest.includes("--dry-run") });
|
|
381
|
+
} else if (action === "stop") {
|
|
382
|
+
result = runConnectorStop({ dryRun: rest.includes("--dry-run") });
|
|
383
|
+
} else if (action === "uninstall") {
|
|
384
|
+
result = runConnectorUninstall({ dryRun: rest.includes("--dry-run") });
|
|
385
|
+
} else {
|
|
386
|
+
result = runConnectorStatus();
|
|
387
|
+
}
|
|
388
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
389
|
+
else console.log(renderConnectorTerminal(result));
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
333
393
|
if (command === "sync") {
|
|
334
394
|
const json = rest.includes("--json");
|
|
335
395
|
const limitIndex = rest.indexOf("--limit");
|
|
@@ -363,6 +423,7 @@ function createCli(deps) {
|
|
|
363
423
|
if (command === "agent") {
|
|
364
424
|
const json = rest.includes("--json");
|
|
365
425
|
const intervalIndex = rest.indexOf("--interval");
|
|
426
|
+
const syncIntervalIndex = rest.indexOf("--sync-interval");
|
|
366
427
|
const limitIndex = rest.indexOf("--limit");
|
|
367
428
|
const budgetIndex = rest.indexOf("--budget");
|
|
368
429
|
const modeIndex = rest.indexOf("--mode");
|
|
@@ -370,7 +431,7 @@ function createCli(deps) {
|
|
|
370
431
|
if (!AGENT_VALID_MODES.has(modeValue)) {
|
|
371
432
|
throw new Error(`Invalid agent mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
|
|
372
433
|
}
|
|
373
|
-
const positional = getPositionals(rest, new Set(["--interval", "--limit", "--budget", "--mode"]));
|
|
434
|
+
const positional = getPositionals(rest, new Set(["--interval", "--sync-interval", "--limit", "--budget", "--mode"]));
|
|
374
435
|
const target = positional[0] || process.cwd();
|
|
375
436
|
const agentOptions = {
|
|
376
437
|
json,
|
|
@@ -378,8 +439,11 @@ function createCli(deps) {
|
|
|
378
439
|
watch: rest.includes("--watch") && !rest.includes("--once"),
|
|
379
440
|
open: rest.includes("--open"),
|
|
380
441
|
autoDetect: !rest.includes("--no-detect"),
|
|
442
|
+
noSync: rest.includes("--no-sync"),
|
|
381
443
|
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
|
|
444
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
|
|
382
445
|
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
|
|
446
|
+
syncLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
383
447
|
tokenBudget: parseTokenBudget(budgetIndex >= 0 ? rest[budgetIndex + 1] : null) || 600000,
|
|
384
448
|
};
|
|
385
449
|
const result = await runAgent(target, agentOptions);
|
|
@@ -411,6 +411,11 @@ module.exports = function createCloudSync(deps) {
|
|
|
411
411
|
lines.push(`Config: ${result.configPath}`);
|
|
412
412
|
lines.push(`API: ${result.apiUrl}`);
|
|
413
413
|
lines.push(`Device: ${result.device.name}`);
|
|
414
|
+
if (result.connector) {
|
|
415
|
+
lines.push(`Connector: ${result.connector.started ? "started" : result.connector.installed ? "installed" : "not started"}`);
|
|
416
|
+
if (result.connector.reason) lines.push(`Connector note: ${result.connector.reason}`);
|
|
417
|
+
if (result.connector.error) lines.push(`Connector error: ${result.connector.error}`);
|
|
418
|
+
}
|
|
414
419
|
lines.push("");
|
|
415
420
|
lines.push("Next");
|
|
416
421
|
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
module.exports = function createConnector(deps) {
|
|
2
|
+
const {
|
|
3
|
+
fs,
|
|
4
|
+
os,
|
|
5
|
+
path,
|
|
6
|
+
spawnSync,
|
|
7
|
+
NPX_COMMAND,
|
|
8
|
+
} = deps;
|
|
9
|
+
|
|
10
|
+
const LABEL = "dev.getprismo.connector";
|
|
11
|
+
const BACKGROUND_COMMAND = String(NPX_COMMAND || "").includes(" -y ")
|
|
12
|
+
? NPX_COMMAND
|
|
13
|
+
: "npx -y getprismo@latest";
|
|
14
|
+
|
|
15
|
+
function prismoHome() {
|
|
16
|
+
return process.env.PRISMO_HOME || path.join(os.homedir(), ".prismo");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function connectorDir() {
|
|
20
|
+
return path.join(prismoHome(), "connector");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function statePath() {
|
|
24
|
+
return path.join(connectorDir(), "state.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function scriptPath() {
|
|
28
|
+
return path.join(connectorDir(), "run.sh");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function logPath() {
|
|
32
|
+
return path.join(connectorDir(), "connector.log");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function errorLogPath() {
|
|
36
|
+
return path.join(connectorDir(), "connector.err.log");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function plistPath() {
|
|
40
|
+
return path.join(os.homedir(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJson(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(filePath)) return null;
|
|
46
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeJson(filePath, payload) {
|
|
53
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
54
|
+
fs.writeFileSync(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function shellEscape(value) {
|
|
58
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function xmlEscape(value) {
|
|
62
|
+
return String(value)
|
|
63
|
+
.replace(/&/g, "&")
|
|
64
|
+
.replace(/</g, "<")
|
|
65
|
+
.replace(/>/g, ">")
|
|
66
|
+
.replace(/"/g, """);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function launchctl(args, options = {}) {
|
|
70
|
+
if (options.dryRun) return { status: 0, stdout: "", stderr: "" };
|
|
71
|
+
const result = spawnSync("launchctl", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
72
|
+
return {
|
|
73
|
+
status: result.status,
|
|
74
|
+
stdout: String(result.stdout || ""),
|
|
75
|
+
stderr: String(result.stderr || ""),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeRunner(rootDir, options = {}) {
|
|
80
|
+
const root = path.resolve(rootDir || process.cwd());
|
|
81
|
+
const interval = Math.max(5, Number(options.interval || 15));
|
|
82
|
+
const syncInterval = Math.max(30, Number(options.syncInterval || 60));
|
|
83
|
+
const mode = options.mode || "autopilot";
|
|
84
|
+
fs.mkdirSync(connectorDir(), { recursive: true });
|
|
85
|
+
const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --sync-interval ${syncInterval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
|
|
86
|
+
const contents = [
|
|
87
|
+
"#!/bin/sh",
|
|
88
|
+
"set -eu",
|
|
89
|
+
`cd ${shellEscape(root)}`,
|
|
90
|
+
`exec ${command}`,
|
|
91
|
+
"",
|
|
92
|
+
].join("\n");
|
|
93
|
+
fs.writeFileSync(scriptPath(), contents, { encoding: "utf8", mode: 0o700 });
|
|
94
|
+
writeJson(statePath(), {
|
|
95
|
+
schemaVersion: 1,
|
|
96
|
+
installedAt: new Date().toISOString(),
|
|
97
|
+
root,
|
|
98
|
+
interval,
|
|
99
|
+
syncInterval,
|
|
100
|
+
mode,
|
|
101
|
+
command,
|
|
102
|
+
platform: process.platform,
|
|
103
|
+
runner: scriptPath(),
|
|
104
|
+
logPath: logPath(),
|
|
105
|
+
errorLogPath: errorLogPath(),
|
|
106
|
+
});
|
|
107
|
+
return { root, interval, syncInterval, mode, command };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writePlist() {
|
|
111
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
112
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
113
|
+
<plist version="1.0">
|
|
114
|
+
<dict>
|
|
115
|
+
<key>Label</key>
|
|
116
|
+
<string>${LABEL}</string>
|
|
117
|
+
<key>ProgramArguments</key>
|
|
118
|
+
<array>
|
|
119
|
+
<string>/bin/sh</string>
|
|
120
|
+
<string>${xmlEscape(scriptPath())}</string>
|
|
121
|
+
</array>
|
|
122
|
+
<key>RunAtLoad</key>
|
|
123
|
+
<true/>
|
|
124
|
+
<key>KeepAlive</key>
|
|
125
|
+
<true/>
|
|
126
|
+
<key>StandardOutPath</key>
|
|
127
|
+
<string>${xmlEscape(logPath())}</string>
|
|
128
|
+
<key>StandardErrorPath</key>
|
|
129
|
+
<string>${xmlEscape(errorLogPath())}</string>
|
|
130
|
+
</dict>
|
|
131
|
+
</plist>
|
|
132
|
+
`;
|
|
133
|
+
fs.mkdirSync(path.dirname(plistPath()), { recursive: true });
|
|
134
|
+
fs.writeFileSync(plistPath(), plist, "utf8");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isMacLaunchdAvailable() {
|
|
138
|
+
return process.platform === "darwin";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function runConnectorInstall(rootDir = process.cwd(), options = {}) {
|
|
142
|
+
const runner = writeRunner(rootDir, options);
|
|
143
|
+
if (!isMacLaunchdAvailable()) {
|
|
144
|
+
return {
|
|
145
|
+
schemaVersion: 1,
|
|
146
|
+
command: "connector",
|
|
147
|
+
action: "install",
|
|
148
|
+
installed: false,
|
|
149
|
+
started: false,
|
|
150
|
+
platform: process.platform,
|
|
151
|
+
statePath: statePath(),
|
|
152
|
+
runner: scriptPath(),
|
|
153
|
+
reason: "background-service-not-supported",
|
|
154
|
+
next: [`${NPX_COMMAND} agent --watch`],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
writePlist();
|
|
159
|
+
launchctl(["bootout", `gui/${process.getuid()}`, plistPath()], options);
|
|
160
|
+
const bootstrap = launchctl(["bootstrap", `gui/${process.getuid()}`, plistPath()], options);
|
|
161
|
+
const kickstart = launchctl(["kickstart", "-k", `gui/${process.getuid()}/${LABEL}`], options);
|
|
162
|
+
const started = bootstrap.status === 0 || kickstart.status === 0 || options.dryRun;
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: 1,
|
|
165
|
+
command: "connector",
|
|
166
|
+
action: "install",
|
|
167
|
+
installed: true,
|
|
168
|
+
started,
|
|
169
|
+
label: LABEL,
|
|
170
|
+
root: runner.root,
|
|
171
|
+
mode: runner.mode,
|
|
172
|
+
interval: runner.interval,
|
|
173
|
+
syncInterval: runner.syncInterval,
|
|
174
|
+
plistPath: plistPath(),
|
|
175
|
+
statePath: statePath(),
|
|
176
|
+
runner: scriptPath(),
|
|
177
|
+
logPath: logPath(),
|
|
178
|
+
errorLogPath: errorLogPath(),
|
|
179
|
+
error: started ? null : (bootstrap.stderr || kickstart.stderr || "launchctl failed").trim(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function runConnectorStart(options = {}) {
|
|
184
|
+
if (!isMacLaunchdAvailable()) {
|
|
185
|
+
return { schemaVersion: 1, command: "connector", action: "start", started: false, reason: "background-service-not-supported" };
|
|
186
|
+
}
|
|
187
|
+
if (!fs.existsSync(plistPath())) {
|
|
188
|
+
return { schemaVersion: 1, command: "connector", action: "start", started: false, reason: "not-installed", next: [`${NPX_COMMAND} connector install`] };
|
|
189
|
+
}
|
|
190
|
+
const result = launchctl(["kickstart", "-k", `gui/${process.getuid()}/${LABEL}`], options);
|
|
191
|
+
return {
|
|
192
|
+
schemaVersion: 1,
|
|
193
|
+
command: "connector",
|
|
194
|
+
action: "start",
|
|
195
|
+
started: result.status === 0 || options.dryRun,
|
|
196
|
+
label: LABEL,
|
|
197
|
+
error: result.status === 0 || options.dryRun ? null : result.stderr.trim(),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function runConnectorStop(options = {}) {
|
|
202
|
+
if (!isMacLaunchdAvailable()) {
|
|
203
|
+
return { schemaVersion: 1, command: "connector", action: "stop", stopped: false, reason: "background-service-not-supported" };
|
|
204
|
+
}
|
|
205
|
+
const result = launchctl(["bootout", `gui/${process.getuid()}`, plistPath()], options);
|
|
206
|
+
return {
|
|
207
|
+
schemaVersion: 1,
|
|
208
|
+
command: "connector",
|
|
209
|
+
action: "stop",
|
|
210
|
+
stopped: result.status === 0 || options.dryRun,
|
|
211
|
+
label: LABEL,
|
|
212
|
+
error: result.status === 0 || options.dryRun ? null : result.stderr.trim(),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function runConnectorUninstall(options = {}) {
|
|
217
|
+
const stop = runConnectorStop(options);
|
|
218
|
+
if (!options.dryRun) {
|
|
219
|
+
if (fs.existsSync(plistPath())) fs.rmSync(plistPath(), { force: true });
|
|
220
|
+
if (fs.existsSync(scriptPath())) fs.rmSync(scriptPath(), { force: true });
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
schemaVersion: 1,
|
|
224
|
+
command: "connector",
|
|
225
|
+
action: "uninstall",
|
|
226
|
+
uninstalled: true,
|
|
227
|
+
stopped: stop.stopped,
|
|
228
|
+
removed: [plistPath(), scriptPath()],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function runConnectorStatus() {
|
|
233
|
+
const state = readJson(statePath());
|
|
234
|
+
const installed = fs.existsSync(plistPath()) || fs.existsSync(scriptPath());
|
|
235
|
+
let online = false;
|
|
236
|
+
let raw = "";
|
|
237
|
+
if (isMacLaunchdAvailable() && fs.existsSync(plistPath())) {
|
|
238
|
+
const result = launchctl(["print", `gui/${process.getuid()}/${LABEL}`]);
|
|
239
|
+
raw = `${result.stdout}${result.stderr}`;
|
|
240
|
+
online = result.status === 0 && /state = running|pid = \d+/i.test(raw);
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
schemaVersion: 1,
|
|
244
|
+
command: "connector",
|
|
245
|
+
action: "status",
|
|
246
|
+
installed,
|
|
247
|
+
online,
|
|
248
|
+
label: LABEL,
|
|
249
|
+
platform: process.platform,
|
|
250
|
+
state,
|
|
251
|
+
plistPath: plistPath(),
|
|
252
|
+
runner: scriptPath(),
|
|
253
|
+
logPath: logPath(),
|
|
254
|
+
errorLogPath: errorLogPath(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function renderConnectorTerminal(result) {
|
|
259
|
+
const lines = [];
|
|
260
|
+
lines.push("");
|
|
261
|
+
lines.push("PrismoDev Connector");
|
|
262
|
+
lines.push("");
|
|
263
|
+
if (result.action === "status") {
|
|
264
|
+
lines.push(`Installed: ${result.installed ? "yes" : "no"}`);
|
|
265
|
+
lines.push(`Status: ${result.online ? "online" : "idle"}`);
|
|
266
|
+
if (result.state?.root) lines.push(`Repo: ${result.state.root}`);
|
|
267
|
+
if (result.state?.interval) lines.push(`Poll: every ${result.state.interval}s`);
|
|
268
|
+
if (result.state?.syncInterval) lines.push(`Sync: every ${result.state.syncInterval}s`);
|
|
269
|
+
lines.push(`Logs: ${result.logPath}`);
|
|
270
|
+
} else if (result.action === "install") {
|
|
271
|
+
lines.push(`Status: ${result.started ? "started" : result.installed ? "installed" : "not installed"}`);
|
|
272
|
+
if (result.root) lines.push(`Repo: ${result.root}`);
|
|
273
|
+
if (result.interval) lines.push(`Poll: every ${result.interval}s`);
|
|
274
|
+
if (result.syncInterval) lines.push(`Sync: every ${result.syncInterval}s`);
|
|
275
|
+
if (result.reason) lines.push(`Note: ${result.reason}`);
|
|
276
|
+
if (result.error) lines.push(`Error: ${result.error}`);
|
|
277
|
+
} else if (result.action === "start") {
|
|
278
|
+
lines.push(`Status: ${result.started ? "started" : "not started"}`);
|
|
279
|
+
if (result.reason) lines.push(`Note: ${result.reason}`);
|
|
280
|
+
if (result.error) lines.push(`Error: ${result.error}`);
|
|
281
|
+
} else if (result.action === "stop") {
|
|
282
|
+
lines.push(`Status: ${result.stopped ? "stopped" : "not stopped"}`);
|
|
283
|
+
if (result.reason) lines.push(`Note: ${result.reason}`);
|
|
284
|
+
if (result.error) lines.push(`Error: ${result.error}`);
|
|
285
|
+
} else if (result.action === "uninstall") {
|
|
286
|
+
lines.push("Status: uninstalled");
|
|
287
|
+
}
|
|
288
|
+
if (result.next?.length) {
|
|
289
|
+
lines.push("");
|
|
290
|
+
lines.push("Next");
|
|
291
|
+
result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
|
|
292
|
+
}
|
|
293
|
+
return lines.join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
LABEL,
|
|
298
|
+
renderConnectorTerminal,
|
|
299
|
+
runConnectorInstall,
|
|
300
|
+
runConnectorStart,
|
|
301
|
+
runConnectorStatus,
|
|
302
|
+
runConnectorStop,
|
|
303
|
+
runConnectorUninstall,
|
|
304
|
+
writeRunner,
|
|
305
|
+
};
|
|
306
|
+
};
|
package/lib/prismo-dev/help.js
CHANGED
|
@@ -13,10 +13,11 @@ Usage:
|
|
|
13
13
|
prismo mcp [path]
|
|
14
14
|
prismo mcp doctor [--json] [path]
|
|
15
15
|
prismo connect [--json] [--token TOKEN] [--api-url URL] [--org ORG] [--user USER] [--device NAME]
|
|
16
|
+
prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
|
|
16
17
|
prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
17
18
|
prismo status [--json]
|
|
18
19
|
prismo disconnect [--json]
|
|
19
|
-
prismo agent [--json] [--once] [--watch] [--interval N] [--limit N] [--mode MODE] [path]
|
|
20
|
+
prismo agent [--json] [--once] [--watch] [--interval N] [--sync-interval N] [--limit N] [--mode MODE] [path]
|
|
20
21
|
prismo setup [--json] [--proxy-url URL] [path]
|
|
21
22
|
prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
|
|
22
23
|
prismo optimize [scope] [--json] [path]
|
|
@@ -44,6 +45,7 @@ Commands:
|
|
|
44
45
|
shield Run a noisy command, store full output locally, and return a compact summary.
|
|
45
46
|
mcp Start a local MCP server exposing Prismo tools over stdio.
|
|
46
47
|
connect Store a PrismoDev cloud connection for seamless dashboard sync.
|
|
48
|
+
connector Install or manage the background Prismo Workspace connector.
|
|
47
49
|
sync Send safe aggregate local agent telemetry to Prismo; use --watch for background-style sync.
|
|
48
50
|
status Show local PrismoDev connection and last sync state.
|
|
49
51
|
disconnect Remove the local PrismoDev cloud connection.
|
|
@@ -77,6 +79,7 @@ Options:
|
|
|
77
79
|
--firewall Generate cc timeline-derived firewall suggestion files.
|
|
78
80
|
--task TASK Name the task for timeline-derived firewall suggestions.
|
|
79
81
|
--interval N Refresh interval in seconds for watch mode.
|
|
82
|
+
--sync-interval N Telemetry sync interval in seconds for the background connector.
|
|
80
83
|
--dry-run Preview doctor/fix actions without writing files.
|
|
81
84
|
--apply-ignores-only Only create/suggest AI ignore files in doctor mode.
|
|
82
85
|
--apply-suggestions Append missing recommended ignore rules with backups.
|
|
@@ -98,6 +101,7 @@ Options:
|
|
|
98
101
|
--org ORG Organization identifier for connect mode.
|
|
99
102
|
--user USER User email or identifier for connect mode.
|
|
100
103
|
--device NAME Local device name for connect mode.
|
|
104
|
+
--no-agent Connect without installing the background workspace connector.
|
|
101
105
|
--tool TOOL Tool filter for sync mode: all, codex, claude, or cursor.
|
|
102
106
|
--watch Keep sync running and send updates on an interval.
|
|
103
107
|
--once Run watch mode once, useful for tests and scripts.
|
|
@@ -406,6 +410,7 @@ Output:
|
|
|
406
410
|
|
|
407
411
|
Usage:
|
|
408
412
|
prismo connect [--json] [--token TOKEN] [--api-url URL] [--org ORG] [--user USER] [--device NAME]
|
|
413
|
+
prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
|
|
409
414
|
prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
410
415
|
prismo status [--json]
|
|
411
416
|
prismo disconnect [--json]
|
|
@@ -413,6 +418,10 @@ Usage:
|
|
|
413
418
|
Examples:
|
|
414
419
|
prismo connect --token <token>
|
|
415
420
|
prismo connect --token <token> --api-url https://api.getprismo.dev
|
|
421
|
+
prismo connect --token <token> --no-agent
|
|
422
|
+
prismo connector status
|
|
423
|
+
prismo connector install
|
|
424
|
+
prismo connector install --sync-interval 120
|
|
416
425
|
prismo sync --dry-run
|
|
417
426
|
prismo sync
|
|
418
427
|
prismo sync --watch --interval 60
|
|
@@ -420,10 +429,38 @@ Examples:
|
|
|
420
429
|
prismo disconnect
|
|
421
430
|
|
|
422
431
|
Output:
|
|
423
|
-
connect stores a local PrismoDev device connection in ~/.prismo/config.json.
|
|
432
|
+
connect stores a local PrismoDev device connection in ~/.prismo/config.json and starts the background workspace connector by default.
|
|
433
|
+
connector keeps Prismo Workspace online so repairs queued in the dashboard run locally without copy/paste commands, and continuously syncs aggregate telemetry on a controlled interval.
|
|
424
434
|
sync reads local Codex, Claude Code, and Cursor session logs, builds aggregate agent-efficiency telemetry, and sends it to Prismo.
|
|
425
435
|
sync --watch keeps running so a local service manager can keep the Waste Scanner dashboard fresh.
|
|
426
436
|
Sync does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
|
|
437
|
+
connector: `PrismoDev Connector
|
|
438
|
+
|
|
439
|
+
Usage:
|
|
440
|
+
prismo connector status [--json]
|
|
441
|
+
prismo connector install [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
|
|
442
|
+
prismo connector start [--json]
|
|
443
|
+
prismo connector stop [--json]
|
|
444
|
+
prismo connector uninstall [--json]
|
|
445
|
+
|
|
446
|
+
Modes:
|
|
447
|
+
observe Keep the workspace connected but do not execute queued repairs.
|
|
448
|
+
suggest Stage repairs as pending approval.
|
|
449
|
+
autopilot Execute safe queued repairs automatically. (default)
|
|
450
|
+
|
|
451
|
+
Examples:
|
|
452
|
+
prismo connector status
|
|
453
|
+
prismo connector install
|
|
454
|
+
prismo connector install --mode suggest --interval 30
|
|
455
|
+
prismo connector install --sync-interval 120
|
|
456
|
+
prismo connector stop
|
|
457
|
+
prismo connector uninstall
|
|
458
|
+
|
|
459
|
+
Output:
|
|
460
|
+
Installs and manages the local Prismo Workspace connector.
|
|
461
|
+
On macOS this creates a LaunchAgent so Prismo stays online after the terminal closes.
|
|
462
|
+
The connector claims safe repairs queued from Prismo Cloud, runs them locally, continuously syncs aggregate telemetry, and reports status back.
|
|
463
|
+
It does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
|
|
427
464
|
sync: `PrismoDev Sync
|
|
428
465
|
|
|
429
466
|
Usage:
|
|
@@ -455,7 +492,7 @@ Output:
|
|
|
455
492
|
agent: `PrismoDev Agent
|
|
456
493
|
|
|
457
494
|
Usage:
|
|
458
|
-
prismo agent [--json] [--once] [--watch] [--open] [--no-detect] [--interval N] [--limit N] [--mode MODE] [path]
|
|
495
|
+
prismo agent [--json] [--once] [--watch] [--open] [--no-detect] [--no-sync] [--interval N] [--sync-interval N] [--limit N] [--mode MODE] [path]
|
|
459
496
|
|
|
460
497
|
Modes:
|
|
461
498
|
observe Watch and report actions without executing. No changes made.
|
|
@@ -465,6 +502,7 @@ Modes:
|
|
|
465
502
|
Examples:
|
|
466
503
|
prismo agent --once
|
|
467
504
|
prismo agent --watch --interval 15
|
|
505
|
+
prismo agent --watch --sync-interval 120
|
|
468
506
|
prismo agent --watch --mode observe
|
|
469
507
|
prismo agent --watch --mode suggest
|
|
470
508
|
prismo agent --watch --open
|
|
@@ -473,12 +511,13 @@ Examples:
|
|
|
473
511
|
Options:
|
|
474
512
|
--open Open the Prismo workspace in the browser on start.
|
|
475
513
|
--no-detect Skip the initial auto-detect scan on first poll.
|
|
514
|
+
--no-sync Keep watch mode from continuously syncing aggregate telemetry.
|
|
476
515
|
|
|
477
516
|
Output:
|
|
478
517
|
On first poll, the agent proactively runs doctor to detect context issues.
|
|
479
518
|
In autopilot mode it auto-fixes safe issues. In suggest mode it reports them as pending_approval in the workspace.
|
|
480
519
|
Claims safe workspace actions queued from Prismo Cloud, runs them locally, and reports status back.
|
|
481
|
-
Sends heartbeat to Prismo Cloud on each poll so the dashboard
|
|
520
|
+
Sends heartbeat to Prismo Cloud on each poll and syncs aggregate telemetry on a controlled interval so the dashboard stays fresh.
|
|
482
521
|
Handles SIGINT/SIGTERM gracefully and marks the agent offline before exiting.
|
|
483
522
|
Supported actions are doctor, sync, guard, context/optimize, and shield with a conservative command allowlist.
|
|
484
523
|
Agent does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -3,7 +3,7 @@ const http = require("http");
|
|
|
3
3
|
const https = require("https");
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const { execSync } = require("child_process");
|
|
6
|
+
const { execSync, spawnSync } = require("child_process");
|
|
7
7
|
const { version: PACKAGE_VERSION } = require("../package.json");
|
|
8
8
|
|
|
9
9
|
function openUrl(url) {
|
|
@@ -333,6 +333,21 @@ const {
|
|
|
333
333
|
openUrl,
|
|
334
334
|
});
|
|
335
335
|
|
|
336
|
+
const {
|
|
337
|
+
renderConnectorTerminal,
|
|
338
|
+
runConnectorInstall,
|
|
339
|
+
runConnectorStart,
|
|
340
|
+
runConnectorStatus,
|
|
341
|
+
runConnectorStop,
|
|
342
|
+
runConnectorUninstall,
|
|
343
|
+
} = require("./prismo-dev/connector")({
|
|
344
|
+
fs,
|
|
345
|
+
os,
|
|
346
|
+
path,
|
|
347
|
+
spawnSync,
|
|
348
|
+
NPX_COMMAND,
|
|
349
|
+
});
|
|
350
|
+
|
|
336
351
|
const {
|
|
337
352
|
buildInstructionsAblationPlan,
|
|
338
353
|
buildInstructionsApply,
|
|
@@ -439,6 +454,12 @@ const { runCli } = require("./prismo-dev/cli")({
|
|
|
439
454
|
runGuard,
|
|
440
455
|
renderAgentTerminal,
|
|
441
456
|
runAgent,
|
|
457
|
+
renderConnectorTerminal,
|
|
458
|
+
runConnectorInstall,
|
|
459
|
+
runConnectorStart,
|
|
460
|
+
runConnectorStatus,
|
|
461
|
+
runConnectorStop,
|
|
462
|
+
runConnectorUninstall,
|
|
442
463
|
renderInstructionsAblationTerminal,
|
|
443
464
|
renderInstructionsApplyTerminal,
|
|
444
465
|
renderInstructionsAuditTerminal,
|
|
@@ -531,6 +552,11 @@ module.exports = {
|
|
|
531
552
|
runConnect,
|
|
532
553
|
runDisconnect,
|
|
533
554
|
runGuard,
|
|
555
|
+
runConnectorInstall,
|
|
556
|
+
runConnectorStart,
|
|
557
|
+
runConnectorStatus,
|
|
558
|
+
runConnectorStop,
|
|
559
|
+
runConnectorUninstall,
|
|
534
560
|
runStatus,
|
|
535
561
|
runSync,
|
|
536
562
|
getUsageSummary,
|
package/package.json
CHANGED