getprismo 0.1.37 → 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.
@@ -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 runOptions = { ...options, autoDetect: firstRun && options.autoDetect !== false };
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;
@@ -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,6 +340,7 @@ 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
  });
@@ -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,7 @@ 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");
422
427
  const limitIndex = rest.indexOf("--limit");
423
428
  const budgetIndex = rest.indexOf("--budget");
424
429
  const modeIndex = rest.indexOf("--mode");
@@ -426,7 +431,7 @@ function createCli(deps) {
426
431
  if (!AGENT_VALID_MODES.has(modeValue)) {
427
432
  throw new Error(`Invalid agent mode: ${modeValue}. Valid modes: observe, suggest, autopilot`);
428
433
  }
429
- const positional = getPositionals(rest, new Set(["--interval", "--limit", "--budget", "--mode"]));
434
+ const positional = getPositionals(rest, new Set(["--interval", "--sync-interval", "--limit", "--budget", "--mode"]));
430
435
  const target = positional[0] || process.cwd();
431
436
  const agentOptions = {
432
437
  json,
@@ -434,8 +439,11 @@ function createCli(deps) {
434
439
  watch: rest.includes("--watch") && !rest.includes("--once"),
435
440
  open: rest.includes("--open"),
436
441
  autoDetect: !rest.includes("--no-detect"),
442
+ noSync: rest.includes("--no-sync"),
437
443
  interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 15),
444
+ syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 60),
438
445
  limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
446
+ syncLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
439
447
  tokenBudget: parseTokenBudget(budgetIndex >= 0 ? rest[budgetIndex + 1] : null) || 600000,
440
448
  };
441
449
  const result = await runAgent(target, agentOptions);
@@ -79,9 +79,10 @@ 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));
82
83
  const mode = options.mode || "autopilot";
83
84
  fs.mkdirSync(connectorDir(), { recursive: true });
84
- const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
85
+ const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --sync-interval ${syncInterval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
85
86
  const contents = [
86
87
  "#!/bin/sh",
87
88
  "set -eu",
@@ -95,6 +96,7 @@ module.exports = function createConnector(deps) {
95
96
  installedAt: new Date().toISOString(),
96
97
  root,
97
98
  interval,
99
+ syncInterval,
98
100
  mode,
99
101
  command,
100
102
  platform: process.platform,
@@ -102,7 +104,7 @@ module.exports = function createConnector(deps) {
102
104
  logPath: logPath(),
103
105
  errorLogPath: errorLogPath(),
104
106
  });
105
- return { root, interval, mode, command };
107
+ return { root, interval, syncInterval, mode, command };
106
108
  }
107
109
 
108
110
  function writePlist() {
@@ -168,6 +170,7 @@ module.exports = function createConnector(deps) {
168
170
  root: runner.root,
169
171
  mode: runner.mode,
170
172
  interval: runner.interval,
173
+ syncInterval: runner.syncInterval,
171
174
  plistPath: plistPath(),
172
175
  statePath: statePath(),
173
176
  runner: scriptPath(),
@@ -261,10 +264,14 @@ module.exports = function createConnector(deps) {
261
264
  lines.push(`Installed: ${result.installed ? "yes" : "no"}`);
262
265
  lines.push(`Status: ${result.online ? "online" : "idle"}`);
263
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`);
264
269
  lines.push(`Logs: ${result.logPath}`);
265
270
  } else if (result.action === "install") {
266
271
  lines.push(`Status: ${result.started ? "started" : result.installed ? "installed" : "not installed"}`);
267
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`);
268
275
  if (result.reason) lines.push(`Note: ${result.reason}`);
269
276
  if (result.error) lines.push(`Error: ${result.error}`);
270
277
  } 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.38",
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",