getprismo 0.1.37 → 0.1.39

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.
@@ -174,13 +174,32 @@ module.exports = function createAgent(deps) {
174
174
  return url;
175
175
  }
176
176
 
177
+ async function reportProgress(config, actionId, step, detail, options = {}) {
178
+ const endpoint = options.progressEndpoint || `${apiBase(config)}/v1/dev/workspace/actions/${actionId}/progress`;
179
+ try {
180
+ await requestJson("POST", endpoint, config.token, {
181
+ step,
182
+ detail: detail || null,
183
+ timestamp: new Date().toISOString(),
184
+ }, options.timeoutMs || 5000);
185
+ } catch (_) {}
186
+ }
187
+
177
188
  async function executeAction(action, rootDir, options = {}) {
178
189
  const root = repoRoot(path.resolve(rootDir || process.cwd()), action);
179
190
  const parsed = parseCommand(action.command);
180
191
  const startedAt = new Date().toISOString();
192
+ const config = options._config || null;
193
+ const progress = config ? (step, detail) => reportProgress(config, action.id, step, detail, options) : async () => {};
181
194
 
182
195
  if (parsed.command === "doctor" || action.actionType === "doctor") {
196
+ await progress("scanning", "Scanning repo for context issues");
183
197
  const result = runDoctor(root, { limit: options.limit || 3, applySuggestions: true, json: true });
198
+ const score = result.after?.score ?? result.scan?.score ?? null;
199
+ const issueCount = result.scan?.issues?.length || 0;
200
+ const generatedFiles = result.generatedFiles || result.optimize?.generatedFiles || [];
201
+ if (issueCount > 0) await progress("fixing", `Found ${issueCount} issue${issueCount === 1 ? "" : "s"}, applying fixes`);
202
+ await progress("done", `Score: ${score}/100, generated ${generatedFiles.length} file${generatedFiles.length === 1 ? "" : "s"}`);
184
203
  return {
185
204
  status: "completed",
186
205
  statusMessage: "Doctor completed and applied safe ignore/context fixes.",
@@ -188,14 +207,16 @@ module.exports = function createAgent(deps) {
188
207
  command: "doctor",
189
208
  startedAt,
190
209
  completedAt: new Date().toISOString(),
191
- score: result.after?.score ?? result.scan?.score ?? null,
192
- generatedFiles: result.generatedFiles || result.optimize?.generatedFiles || [],
210
+ score,
211
+ generatedFiles,
193
212
  },
194
213
  };
195
214
  }
196
215
 
197
216
  if (parsed.command === "sync" || action.actionType === "sync") {
217
+ await progress("syncing", "Syncing local telemetry to workspace");
198
218
  const result = await runSync(root, { limit: options.limit || 20 });
219
+ await progress("done", result.synced ? "Sync completed" : "Sync failed");
199
220
  return {
200
221
  status: result.synced ? "completed" : "failed",
201
222
  statusMessage: result.synced ? "Sync completed." : "Sync could not run because this machine is not connected.",
@@ -211,6 +232,7 @@ module.exports = function createAgent(deps) {
211
232
  }
212
233
 
213
234
  if (parsed.command === "guard" || action.actionType === "guard") {
235
+ await progress("guarding", "Running guardrail snapshot across sessions");
214
236
  const result = await runGuard(root, {
215
237
  tool: "all",
216
238
  limit: options.limit || 5,
@@ -218,6 +240,8 @@ module.exports = function createAgent(deps) {
218
240
  noSync: false,
219
241
  watch: false,
220
242
  });
243
+ const eventCount = result.events?.length || 0;
244
+ await progress("done", `Guard complete, ${eventCount} event${eventCount === 1 ? "" : "s"} recorded`);
221
245
  return {
222
246
  status: "completed",
223
247
  statusMessage: "Guard snapshot completed. Start agent watch mode for continuous protection.",
@@ -226,14 +250,17 @@ module.exports = function createAgent(deps) {
226
250
  startedAt,
227
251
  completedAt: new Date().toISOString(),
228
252
  guardRunning: Boolean(result.guardRunning),
229
- events: result.events?.length || 0,
253
+ events: eventCount,
230
254
  },
231
255
  };
232
256
  }
233
257
 
234
258
  if (parsed.command === "context" || parsed.command === "optimize" || action.actionType === "context") {
235
259
  const scope = parsed.args.find((arg) => !arg.startsWith("-")) || null;
260
+ await progress("generating", `Generating context pack${scope ? ` for ${scope}` : ""}`);
236
261
  const result = runOptimize(root, { scope });
262
+ const generatedFiles = result.generatedFiles || [];
263
+ await progress("done", `Generated ${generatedFiles.length} file${generatedFiles.length === 1 ? "" : "s"}`);
237
264
  return {
238
265
  status: "completed",
239
266
  statusMessage: "Context pack generated.",
@@ -242,7 +269,7 @@ module.exports = function createAgent(deps) {
242
269
  startedAt,
243
270
  completedAt: new Date().toISOString(),
244
271
  scope,
245
- generatedFiles: result.generatedFiles || [],
272
+ generatedFiles,
246
273
  },
247
274
  };
248
275
  }
@@ -250,13 +277,16 @@ module.exports = function createAgent(deps) {
250
277
  if (parsed.command === "shield" || action.actionType === "shield") {
251
278
  const commandArgs = parseShieldArgs(parsed.args);
252
279
  if (!commandArgs) {
280
+ await progress("rejected", "Command not on safe allowlist");
253
281
  return {
254
282
  status: "failed",
255
283
  statusMessage: "Shield action was rejected because the command is not on the safe allowlist.",
256
284
  result: { command: "shield", rejected: true, reason: "unsafe-shield-command" },
257
285
  };
258
286
  }
287
+ await progress("shielding", `Running shielded command: ${commandArgs.join(" ")}`);
259
288
  const result = runShield(root, commandArgs);
289
+ await progress("done", result.exitCode === 0 ? "Command completed" : "Command failed");
260
290
  return {
261
291
  status: result.exitCode === 0 ? "completed" : "failed",
262
292
  statusMessage: result.exitCode === 0 ? "Shielded command completed." : "Shielded command exited with an error.",
@@ -336,11 +366,29 @@ module.exports = function createAgent(deps) {
336
366
  status: "running",
337
367
  statusMessage: "Running locally through PrismoDev agent.",
338
368
  }, options);
339
- const result = await executeAction(action, rootDir, options);
369
+ const result = await executeAction(action, rootDir, { ...options, _config: config });
340
370
  await updateAction(config, action.id, result, options);
341
371
  results.push({ id: action.id, label: action.label, ...result });
342
372
  }
343
373
 
374
+ let syncResult = null;
375
+ if (options.syncTelemetry) {
376
+ try {
377
+ const result = await runSync(rootDir, { limit: options.syncLimit || 20 });
378
+ syncResult = {
379
+ synced: Boolean(result.synced),
380
+ sessions: Number(result.aggregate?.sessions || 0),
381
+ estimatedWastedTokens: Number(result.aggregate?.estimatedWastedTokens || 0),
382
+ wastePercent: Number(result.aggregate?.wastePercent || 0),
383
+ };
384
+ } catch (error) {
385
+ syncResult = {
386
+ synced: false,
387
+ error: error && error.message ? error.message : String(error),
388
+ };
389
+ }
390
+ }
391
+
344
392
  return {
345
393
  schemaVersion: 1,
346
394
  command: "agent",
@@ -352,6 +400,7 @@ module.exports = function createAgent(deps) {
352
400
  actionsFailed: results.filter((item) => item.status === "failed").length,
353
401
  actionsObserved: results.filter((item) => item.status === "observed" || item.status === "pending_approval").length,
354
402
  autoDetect: autoDetectResult,
403
+ sync: syncResult,
355
404
  results,
356
405
  privacy: {
357
406
  rawPrompts: false,
@@ -400,6 +449,16 @@ module.exports = function createAgent(deps) {
400
449
  lines.push(` - ${f.message}`);
401
450
  });
402
451
  }
452
+ if (result.sync) {
453
+ lines.push("");
454
+ lines.push("Sync");
455
+ if (result.sync.synced) {
456
+ lines.push(` Sessions: ${result.sync.sessions}`);
457
+ lines.push(` Likely wasted: ${result.sync.estimatedWastedTokens.toLocaleString()} (${result.sync.wastePercent}%)`);
458
+ } else {
459
+ lines.push(` Status: not synced${result.sync.error ? ` (${result.sync.error})` : ""}`);
460
+ }
461
+ }
403
462
  if (result.results.length) {
404
463
  lines.push("");
405
464
  lines.push("Actions");
@@ -418,9 +477,13 @@ module.exports = function createAgent(deps) {
418
477
  if (!options.watch) return runAgentOnce(rootDir, options);
419
478
 
420
479
  const intervalMs = Math.max(5, Number(options.interval || 15)) * 1000;
480
+ const syncIntervalMs = Math.max(30, Number(options.syncInterval || 60)) * 1000;
481
+ const detectIntervalMs = Math.max(60, Number(options.detectInterval || 300)) * 1000;
421
482
  let running = true;
422
483
  let sleepResolve = null;
423
484
  let firstRun = true;
485
+ let lastSyncAt = 0;
486
+ let lastDetectAt = 0;
424
487
 
425
488
  if (options.open) {
426
489
  const config = loadConfig();
@@ -445,7 +508,16 @@ module.exports = function createAgent(deps) {
445
508
  process.on("SIGTERM", shutdown);
446
509
 
447
510
  while (running) {
448
- const runOptions = { ...options, autoDetect: firstRun && options.autoDetect !== false };
511
+ const now = Date.now();
512
+ const shouldSync = options.noSync !== true && (lastSyncAt === 0 || now - lastSyncAt >= syncIntervalMs);
513
+ if (shouldSync) lastSyncAt = now;
514
+ const shouldDetect = options.autoDetect !== false && (firstRun || now - lastDetectAt >= detectIntervalMs);
515
+ if (shouldDetect) lastDetectAt = now;
516
+ const runOptions = {
517
+ ...options,
518
+ autoDetect: shouldDetect,
519
+ syncTelemetry: shouldSync,
520
+ };
449
521
  firstRun = false;
450
522
  const result = await runAgentOnce(rootDir, runOptions);
451
523
  if (!running) break;
@@ -468,6 +540,7 @@ module.exports = function createAgent(deps) {
468
540
  parseCommand,
469
541
  renderAgentTerminal,
470
542
  reportAutoDetect,
543
+ reportProgress,
471
544
  runAgent,
472
545
  runAgentOnce,
473
546
  runAutoDetect,
@@ -321,12 +321,13 @@ function createCli(deps) {
321
321
  const deviceIndex = rest.indexOf("--device");
322
322
  const limitIndex = rest.indexOf("--limit");
323
323
  const intervalIndex = rest.indexOf("--interval");
324
+ const syncIntervalIndex = rest.indexOf("--sync-interval");
324
325
  const modeIndex = rest.indexOf("--mode");
325
326
  const modeValue = modeIndex >= 0 ? rest[modeIndex + 1] : "autopilot";
326
327
  if (!AGENT_VALID_MODES.has(modeValue)) {
327
328
  throw new Error(`Invalid connector mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
328
329
  }
329
- const positional = getPositionals(rest, new Set(["--token", "--api-url", "--org", "--user", "--device", "--limit", "--interval", "--mode"]));
330
+ const positional = getPositionals(rest, new Set(["--token", "--api-url", "--org", "--user", "--device", "--limit", "--interval", "--sync-interval", "--mode"]));
330
331
  const target = positional[0] || process.cwd();
331
332
  const result = runConnect({
332
333
  token: tokenIndex >= 0 ? rest[tokenIndex + 1] : null,
@@ -339,11 +340,12 @@ function createCli(deps) {
339
340
  if (result.connected && !rest.includes("--no-agent") && runConnectorInstall) {
340
341
  result.connector = runConnectorInstall(target, {
341
342
  interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
343
+ syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
342
344
  mode: modeValue,
343
345
  dryRun: rest.includes("--dry-run"),
344
346
  });
345
347
  result.next = result.connector?.started
346
- ? [`${NPX_COMMAND} status`, "Refresh Prismo Workspace"]
348
+ ? [`${NPX_COMMAND} status`]
347
349
  : [`${NPX_COMMAND} connector install`, `${NPX_COMMAND} status`];
348
350
  }
349
351
  if (result.connected && (rest.includes("--open") || !rest.includes("--no-open"))) {
@@ -357,18 +359,20 @@ function createCli(deps) {
357
359
  if (command === "connector") {
358
360
  const json = rest.includes("--json");
359
361
  const intervalIndex = rest.indexOf("--interval");
362
+ const syncIntervalIndex = rest.indexOf("--sync-interval");
360
363
  const modeIndex = rest.indexOf("--mode");
361
364
  const modeValue = modeIndex >= 0 ? rest[modeIndex + 1] : "autopilot";
362
365
  if (!AGENT_VALID_MODES.has(modeValue)) {
363
366
  throw new Error(`Invalid connector mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
364
367
  }
365
- const positional = getPositionals(rest, new Set(["--interval", "--mode"]));
368
+ const positional = getPositionals(rest, new Set(["--interval", "--sync-interval", "--mode"]));
366
369
  const action = ["install", "start", "stop", "status", "uninstall"].includes(positional[0]) ? positional[0] : "status";
367
370
  const target = ["install"].includes(action) ? positional[1] || process.cwd() : positional[0] || process.cwd();
368
371
  let result;
369
372
  if (action === "install") {
370
373
  result = runConnectorInstall(target, {
371
374
  interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
375
+ syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
372
376
  mode: modeValue,
373
377
  dryRun: rest.includes("--dry-run"),
374
378
  });
@@ -419,6 +423,8 @@ function createCli(deps) {
419
423
  if (command === "agent") {
420
424
  const json = rest.includes("--json");
421
425
  const intervalIndex = rest.indexOf("--interval");
426
+ const syncIntervalIndex = rest.indexOf("--sync-interval");
427
+ const detectIntervalIndex = rest.indexOf("--detect-interval");
422
428
  const limitIndex = rest.indexOf("--limit");
423
429
  const budgetIndex = rest.indexOf("--budget");
424
430
  const modeIndex = rest.indexOf("--mode");
@@ -426,7 +432,7 @@ function createCli(deps) {
426
432
  if (!AGENT_VALID_MODES.has(modeValue)) {
427
433
  throw new Error(`Invalid agent mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
428
434
  }
429
- const positional = getPositionals(rest, new Set(["--interval", "--limit", "--budget", "--mode"]));
435
+ const positional = getPositionals(rest, new Set(["--interval", "--sync-interval", "--detect-interval", "--limit", "--budget", "--mode"]));
430
436
  const target = positional[0] || process.cwd();
431
437
  const agentOptions = {
432
438
  json,
@@ -434,8 +440,12 @@ function createCli(deps) {
434
440
  watch: rest.includes("--watch") && !rest.includes("--once"),
435
441
  open: rest.includes("--open"),
436
442
  autoDetect: !rest.includes("--no-detect"),
443
+ noSync: rest.includes("--no-sync"),
437
444
  interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
445
+ syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
446
+ detectInterval: parsePositiveInt(detectIntervalIndex >= 0 ? rest[detectIntervalIndex + 1] : null, 300),
438
447
  limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
448
+ syncLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
439
449
  tokenBudget: parseTokenBudget(budgetIndex >= 0 ? rest[budgetIndex + 1] : null) || 600000,
440
450
  };
441
451
  const result = await runAgent(target, agentOptions);
@@ -405,20 +405,32 @@ module.exports = function createCloudSync(deps) {
405
405
  function renderConnectTerminal(result) {
406
406
  const lines = [];
407
407
  lines.push("");
408
- lines.push("PrismoDev Connect");
409
- lines.push("");
410
- lines.push(`Status: ${result.connected ? "connected" : "token needed"}`);
411
- lines.push(`Config: ${result.configPath}`);
412
- lines.push(`API: ${result.apiUrl}`);
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}`);
408
+ if (result.connected && result.connector?.started) {
409
+ lines.push("Prismo agent is running.");
410
+ lines.push("");
411
+ lines.push(`Device: ${result.device.name}`);
412
+ lines.push(`Mode: ${result.connector.mode || "autopilot"}`);
413
+ lines.push(`Poll: every ${result.connector.interval || 15}s`);
414
+ lines.push(`Sync: every ${result.connector.syncInterval || 60}s`);
415
+ lines.push("");
416
+ lines.push("Your agent will continuously scan, repair, and guard this repo.");
417
+ lines.push("Open the dashboard to see what it's doing.");
418
+ } else {
419
+ lines.push("PrismoDev Connect");
420
+ lines.push("");
421
+ lines.push(`Status: ${result.connected ? "connected" : "token needed"}`);
422
+ lines.push(`Config: ${result.configPath}`);
423
+ lines.push(`API: ${result.apiUrl}`);
424
+ lines.push(`Device: ${result.device.name}`);
425
+ if (result.connector) {
426
+ lines.push(`Connector: ${result.connector.started ? "started" : result.connector.installed ? "installed" : "not started"}`);
427
+ if (result.connector.reason) lines.push(`Note: ${result.connector.reason}`);
428
+ if (result.connector.error) lines.push(`Error: ${result.connector.error}`);
429
+ }
430
+ lines.push("");
431
+ lines.push("Next");
432
+ result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
418
433
  }
419
- lines.push("");
420
- lines.push("Next");
421
- result.next.forEach((item, index) => lines.push(`${index + 1}. ${item}`));
422
434
  return lines.join("\n");
423
435
  }
424
436
 
@@ -79,9 +79,11 @@ module.exports = function createConnector(deps) {
79
79
  function writeRunner(rootDir, options = {}) {
80
80
  const root = path.resolve(rootDir || process.cwd());
81
81
  const interval = Math.max(5, Number(options.interval || 15));
82
+ const syncInterval = Math.max(30, Number(options.syncInterval || 60));
83
+ const detectInterval = Math.max(60, Number(options.detectInterval || 300));
82
84
  const mode = options.mode || "autopilot";
83
85
  fs.mkdirSync(connectorDir(), { recursive: true });
84
- const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
86
+ const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --sync-interval ${syncInterval} --detect-interval ${detectInterval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
85
87
  const contents = [
86
88
  "#!/bin/sh",
87
89
  "set -eu",
@@ -95,6 +97,8 @@ module.exports = function createConnector(deps) {
95
97
  installedAt: new Date().toISOString(),
96
98
  root,
97
99
  interval,
100
+ syncInterval,
101
+ detectInterval,
98
102
  mode,
99
103
  command,
100
104
  platform: process.platform,
@@ -102,7 +106,7 @@ module.exports = function createConnector(deps) {
102
106
  logPath: logPath(),
103
107
  errorLogPath: errorLogPath(),
104
108
  });
105
- return { root, interval, mode, command };
109
+ return { root, interval, syncInterval, detectInterval, mode, command };
106
110
  }
107
111
 
108
112
  function writePlist() {
@@ -168,6 +172,7 @@ module.exports = function createConnector(deps) {
168
172
  root: runner.root,
169
173
  mode: runner.mode,
170
174
  interval: runner.interval,
175
+ syncInterval: runner.syncInterval,
171
176
  plistPath: plistPath(),
172
177
  statePath: statePath(),
173
178
  runner: scriptPath(),
@@ -261,10 +266,14 @@ module.exports = function createConnector(deps) {
261
266
  lines.push(`Installed: ${result.installed ? "yes" : "no"}`);
262
267
  lines.push(`Status: ${result.online ? "online" : "idle"}`);
263
268
  if (result.state?.root) lines.push(`Repo: ${result.state.root}`);
269
+ if (result.state?.interval) lines.push(`Poll: every ${result.state.interval}s`);
270
+ if (result.state?.syncInterval) lines.push(`Sync: every ${result.state.syncInterval}s`);
264
271
  lines.push(`Logs: ${result.logPath}`);
265
272
  } else if (result.action === "install") {
266
273
  lines.push(`Status: ${result.started ? "started" : result.installed ? "installed" : "not installed"}`);
267
274
  if (result.root) lines.push(`Repo: ${result.root}`);
275
+ if (result.interval) lines.push(`Poll: every ${result.interval}s`);
276
+ if (result.syncInterval) lines.push(`Sync: every ${result.syncInterval}s`);
268
277
  if (result.reason) lines.push(`Note: ${result.reason}`);
269
278
  if (result.error) lines.push(`Error: ${result.error}`);
270
279
  } else if (result.action === "start") {
@@ -13,11 +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] [--mode observe|suggest|autopilot] [path]
16
+ prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
17
17
  prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
18
18
  prismo status [--json]
19
19
  prismo disconnect [--json]
20
- 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]
21
21
  prismo setup [--json] [--proxy-url URL] [path]
22
22
  prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
23
23
  prismo optimize [scope] [--json] [path]
@@ -79,6 +79,7 @@ Options:
79
79
  --firewall Generate cc timeline-derived firewall suggestion files.
80
80
  --task TASK Name the task for timeline-derived firewall suggestions.
81
81
  --interval N Refresh interval in seconds for watch mode.
82
+ --sync-interval N Telemetry sync interval in seconds for the background connector.
82
83
  --dry-run Preview doctor/fix actions without writing files.
83
84
  --apply-ignores-only Only create/suggest AI ignore files in doctor mode.
84
85
  --apply-suggestions Append missing recommended ignore rules with backups.
@@ -409,7 +410,7 @@ Output:
409
410
 
410
411
  Usage:
411
412
  prismo connect [--json] [--token TOKEN] [--api-url URL] [--org ORG] [--user USER] [--device NAME]
412
- prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--mode observe|suggest|autopilot] [path]
413
+ prismo connector [status|install|start|stop|uninstall] [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
413
414
  prismo sync [--json] [--dry-run] [--watch] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
414
415
  prismo status [--json]
415
416
  prismo disconnect [--json]
@@ -420,6 +421,7 @@ Examples:
420
421
  prismo connect --token <token> --no-agent
421
422
  prismo connector status
422
423
  prismo connector install
424
+ prismo connector install --sync-interval 120
423
425
  prismo sync --dry-run
424
426
  prismo sync
425
427
  prismo sync --watch --interval 60
@@ -428,7 +430,7 @@ Examples:
428
430
 
429
431
  Output:
430
432
  connect stores a local PrismoDev device connection in ~/.prismo/config.json and starts the background workspace connector by default.
431
- connector keeps Prismo Workspace online so repairs queued in the dashboard run locally without copy/paste commands.
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.
432
434
  sync reads local Codex, Claude Code, and Cursor session logs, builds aggregate agent-efficiency telemetry, and sends it to Prismo.
433
435
  sync --watch keeps running so a local service manager can keep the Waste Scanner dashboard fresh.
434
436
  Sync does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
@@ -436,7 +438,7 @@ Output:
436
438
 
437
439
  Usage:
438
440
  prismo connector status [--json]
439
- prismo connector install [--json] [--interval N] [--mode observe|suggest|autopilot] [path]
441
+ prismo connector install [--json] [--interval N] [--sync-interval N] [--mode observe|suggest|autopilot] [path]
440
442
  prismo connector start [--json]
441
443
  prismo connector stop [--json]
442
444
  prismo connector uninstall [--json]
@@ -450,13 +452,14 @@ Examples:
450
452
  prismo connector status
451
453
  prismo connector install
452
454
  prismo connector install --mode suggest --interval 30
455
+ prismo connector install --sync-interval 120
453
456
  prismo connector stop
454
457
  prismo connector uninstall
455
458
 
456
459
  Output:
457
460
  Installs and manages the local Prismo Workspace connector.
458
461
  On macOS this creates a LaunchAgent so Prismo stays online after the terminal closes.
459
- The connector claims safe repairs queued from Prismo Cloud, runs them locally, and reports status back.
462
+ The connector claims safe repairs queued from Prismo Cloud, runs them locally, continuously syncs aggregate telemetry, and reports status back.
460
463
  It does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
461
464
  sync: `PrismoDev Sync
462
465
 
@@ -489,7 +492,7 @@ Output:
489
492
  agent: `PrismoDev Agent
490
493
 
491
494
  Usage:
492
- 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]
493
496
 
494
497
  Modes:
495
498
  observe Watch and report actions without executing. No changes made.
@@ -499,6 +502,7 @@ Modes:
499
502
  Examples:
500
503
  prismo agent --once
501
504
  prismo agent --watch --interval 15
505
+ prismo agent --watch --sync-interval 120
502
506
  prismo agent --watch --mode observe
503
507
  prismo agent --watch --mode suggest
504
508
  prismo agent --watch --open
@@ -507,12 +511,13 @@ Examples:
507
511
  Options:
508
512
  --open Open the Prismo workspace in the browser on start.
509
513
  --no-detect Skip the initial auto-detect scan on first poll.
514
+ --no-sync Keep watch mode from continuously syncing aggregate telemetry.
510
515
 
511
516
  Output:
512
517
  On first poll, the agent proactively runs doctor to detect context issues.
513
518
  In autopilot mode it auto-fixes safe issues. In suggest mode it reports them as pending_approval in the workspace.
514
519
  Claims safe workspace actions queued from Prismo Cloud, runs them locally, and reports status back.
515
- Sends heartbeat to Prismo Cloud on each poll so the dashboard shows agent online/offline status.
520
+ Sends heartbeat to Prismo Cloud on each poll and syncs aggregate telemetry on a controlled interval so the dashboard stays fresh.
516
521
  Handles SIGINT/SIGTERM gracefully and marks the agent offline before exiting.
517
522
  Supported actions are doctor, sync, guard, context/optimize, and shield with a conservative command allowlist.
518
523
  Agent does not upload prompts, source code, file contents, stdout, stderr, or full command logs.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",