@purix/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 (86) hide show
  1. package/LICENSE +202 -0
  2. package/dist/cli/commands/auth.d.ts +3 -0
  3. package/dist/cli/commands/auth.d.ts.map +1 -0
  4. package/dist/cli/commands/auth.js +130 -0
  5. package/dist/cli/commands/auth.js.map +1 -0
  6. package/dist/cli/commands/backup.d.ts +3 -0
  7. package/dist/cli/commands/backup.d.ts.map +1 -0
  8. package/dist/cli/commands/backup.js +54 -0
  9. package/dist/cli/commands/backup.js.map +1 -0
  10. package/dist/cli/commands/config.d.ts +3 -0
  11. package/dist/cli/commands/config.d.ts.map +1 -0
  12. package/dist/cli/commands/config.js +35 -0
  13. package/dist/cli/commands/config.js.map +1 -0
  14. package/dist/cli/commands/connect.d.ts +16 -0
  15. package/dist/cli/commands/connect.d.ts.map +1 -0
  16. package/dist/cli/commands/connect.js +45 -0
  17. package/dist/cli/commands/connect.js.map +1 -0
  18. package/dist/cli/commands/dev.d.ts +3 -0
  19. package/dist/cli/commands/dev.d.ts.map +1 -0
  20. package/dist/cli/commands/dev.js +58 -0
  21. package/dist/cli/commands/dev.js.map +1 -0
  22. package/dist/cli/commands/index.d.ts +3 -0
  23. package/dist/cli/commands/index.d.ts.map +1 -0
  24. package/dist/cli/commands/index.js +35 -0
  25. package/dist/cli/commands/index.js.map +1 -0
  26. package/dist/cli/commands/lang.d.ts +3 -0
  27. package/dist/cli/commands/lang.d.ts.map +1 -0
  28. package/dist/cli/commands/lang.js +187 -0
  29. package/dist/cli/commands/lang.js.map +1 -0
  30. package/dist/cli/commands/lifecycle.d.ts +3 -0
  31. package/dist/cli/commands/lifecycle.d.ts.map +1 -0
  32. package/dist/cli/commands/lifecycle.js +782 -0
  33. package/dist/cli/commands/lifecycle.js.map +1 -0
  34. package/dist/cli/commands/mcp.d.ts +3 -0
  35. package/dist/cli/commands/mcp.d.ts.map +1 -0
  36. package/dist/cli/commands/mcp.js +75 -0
  37. package/dist/cli/commands/mcp.js.map +1 -0
  38. package/dist/cli/commands/mcp_serve.d.ts +3 -0
  39. package/dist/cli/commands/mcp_serve.d.ts.map +1 -0
  40. package/dist/cli/commands/mcp_serve.js +16 -0
  41. package/dist/cli/commands/mcp_serve.js.map +1 -0
  42. package/dist/cli/commands/memory.d.ts +3 -0
  43. package/dist/cli/commands/memory.d.ts.map +1 -0
  44. package/dist/cli/commands/memory.js +12 -0
  45. package/dist/cli/commands/memory.js.map +1 -0
  46. package/dist/cli/commands/migrations.d.ts +3 -0
  47. package/dist/cli/commands/migrations.d.ts.map +1 -0
  48. package/dist/cli/commands/migrations.js +98 -0
  49. package/dist/cli/commands/migrations.js.map +1 -0
  50. package/dist/cli/commands/observability.d.ts +3 -0
  51. package/dist/cli/commands/observability.d.ts.map +1 -0
  52. package/dist/cli/commands/observability.js +141 -0
  53. package/dist/cli/commands/observability.js.map +1 -0
  54. package/dist/cli/commands/provider.d.ts +3 -0
  55. package/dist/cli/commands/provider.d.ts.map +1 -0
  56. package/dist/cli/commands/provider.js +82 -0
  57. package/dist/cli/commands/provider.js.map +1 -0
  58. package/dist/cli/commands/security.d.ts +3 -0
  59. package/dist/cli/commands/security.d.ts.map +1 -0
  60. package/dist/cli/commands/security.js +27 -0
  61. package/dist/cli/commands/security.js.map +1 -0
  62. package/dist/cli/commands/tier.d.ts +3 -0
  63. package/dist/cli/commands/tier.d.ts.map +1 -0
  64. package/dist/cli/commands/tier.js +27 -0
  65. package/dist/cli/commands/tier.js.map +1 -0
  66. package/dist/cli/commands/tools.d.ts +3 -0
  67. package/dist/cli/commands/tools.d.ts.map +1 -0
  68. package/dist/cli/commands/tools.js +18 -0
  69. package/dist/cli/commands/tools.js.map +1 -0
  70. package/dist/cli/output.d.ts +5 -0
  71. package/dist/cli/output.d.ts.map +1 -0
  72. package/dist/cli/output.js +22 -0
  73. package/dist/cli/output.js.map +1 -0
  74. package/dist/cli/savings_output.d.ts +9 -0
  75. package/dist/cli/savings_output.d.ts.map +1 -0
  76. package/dist/cli/savings_output.js +62 -0
  77. package/dist/cli/savings_output.js.map +1 -0
  78. package/dist/cli.d.ts +12 -0
  79. package/dist/cli.d.ts.map +1 -0
  80. package/dist/cli.js +155 -0
  81. package/dist/cli.js.map +1 -0
  82. package/dist/telemetry/log.d.ts +4 -0
  83. package/dist/telemetry/log.d.ts.map +1 -0
  84. package/dist/telemetry/log.js +92 -0
  85. package/dist/telemetry/log.js.map +1 -0
  86. package/package.json +47 -0
@@ -0,0 +1,27 @@
1
+ import { setSecret, deleteSecret, listSecretStatus } from "@purix/core/security/secrets_manager";
2
+ export function registerSecurityCommands(program) {
3
+ // ---------------------------------------------------------------------------
4
+ // Secrets Manager
5
+ // ---------------------------------------------------------------------------
6
+ program.command("secret-set <n> <value>").description("Store a credential, encrypted at rest").action((name, value) => {
7
+ setSecret(name, value);
8
+ console.log(`✅ Stored "${name}" encrypted. Remove it from any .env once you've confirmed this works.`);
9
+ });
10
+ program.command("secret-rotate <n> <newValue>").description("Rotate a stored credential").action((name, newValue) => {
11
+ setSecret(name, newValue);
12
+ console.log(`✅ Rotated "${name}".`);
13
+ });
14
+ program.command("secret-remove <n>").description("Delete a stored credential").action((name) => {
15
+ console.log(deleteSecret(name) ? `✅ Removed "${name}".` : `No secret stored as "${name}".`);
16
+ });
17
+ program.command("secrets-status").description("Show credential age / rotation status").action(() => {
18
+ const statuses = listSecretStatus();
19
+ if (statuses.length === 0) {
20
+ console.log("No secrets stored yet.");
21
+ return;
22
+ }
23
+ for (const s of statuses)
24
+ console.log(`${s.name} set ${s.ageDays}d ago${s.rotationDue ? " ⚠ ROTATION DUE" : ""} (rotated ${s.rotationCount}x before)`);
25
+ });
26
+ }
27
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/cli/commands/security.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAEjG,MAAM,UAAU,wBAAwB,CAAC,OAAgB;IACvD,8EAA8E;IAC9E,kBAAkB;IAClB,8EAA8E;IAC9E,OAAO,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC,WAAW,CAAC,uCAAuC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE;QACpI,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,wEAAwE,CAAC,CAAC;IACzG,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAE,QAAgB,EAAE,EAAE;QAClI,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAE,EAAE;QACrG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,wBAAwB,IAAI,IAAI,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,WAAW,CAAC,uCAAuC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE;QACjG,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;QACpC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YAAC,OAAO;QAAC,CAAC;QAC7E,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,aAAa,WAAW,CAAC,CAAC;IAC5J,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerTierCommands(program: Command): void;
3
+ //# sourceMappingURL=tier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tier.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/tier.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAuBpD"}
@@ -0,0 +1,27 @@
1
+ import { getEntitlements, refreshEntitlements } from "@purix/core/licensing/tier";
2
+ export function registerTierCommands(program) {
3
+ program
4
+ .command("tier-status")
5
+ .description("Show current tier and what it unlocks (refreshes from the server if logged in)")
6
+ .action(async () => {
7
+ try {
8
+ await refreshEntitlements();
9
+ }
10
+ catch (err) {
11
+ // A real rejection (e.g. an expired/invalid session) still falls
12
+ // through to printing whatever's cached below, rather than
13
+ // aborting the whole command — tier-status should always show
14
+ // *something*, even a stale answer with a warning.
15
+ console.warn(` (couldn't refresh from the server: ${err instanceof Error ? err.message : err})`);
16
+ }
17
+ const ent = getEntitlements();
18
+ console.log(`Tier: ${ent.tier}`);
19
+ console.log(` Tracked components: ${ent.componentLimit === null ? "unlimited" : ent.componentLimit}`);
20
+ console.log(` Audit export: ${ent.auditExport ? "✅" : "❌"}`);
21
+ console.log(` Webhooks: ${ent.webhooks ? "✅" : "❌"}`);
22
+ if (ent.tier === "free") {
23
+ console.log(`\nRun "purix login" to upgrade.`);
24
+ }
25
+ });
26
+ }
27
+ //# sourceMappingURL=tier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tier.js","sourceRoot":"","sources":["../../../src/cli/commands/tier.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAElF,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,gFAAgF,CAAC;SAC7F,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,IAAI,CAAC;YACH,MAAM,mBAAmB,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iEAAiE;YACjE,2DAA2D;YAC3D,8DAA8D;YAC9D,mDAAmD;YACnD,OAAO,CAAC,IAAI,CAAC,wCAAwC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QACpG,CAAC;QACD,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,6BAA6B,GAAG,CAAC,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;QAC3G,OAAO,CAAC,GAAG,CAAC,6BAA6B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,6BAA6B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACrE,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerToolsCommands(program: Command): void;
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QAcrD"}
@@ -0,0 +1,18 @@
1
+ import { suggestTools, formatSuggestions } from "@purix/core/tools/matchmaker";
2
+ export function registerToolsCommands(program) {
3
+ program
4
+ .command("tools <purpose>")
5
+ .description("Tool Matchmaker: suggest vetted npm packages for a purpose (advisory only, never installs)")
6
+ .action(async (purpose) => {
7
+ try {
8
+ console.log(`Searching npm for vetted packages: "${purpose}"...`);
9
+ const suggestions = await suggestTools(purpose);
10
+ console.log(formatSuggestions(suggestions));
11
+ }
12
+ catch (err) {
13
+ console.error(`\n🛑 Tool search failed: ${err instanceof Error ? err.message : err}`);
14
+ process.exitCode = 1;
15
+ }
16
+ });
17
+ }
18
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/cli/commands/tools.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAE/E,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,4FAA4F,CAAC;SACzG,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAChC,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,uCAAuC,OAAO,MAAM,CAAC,CAAC;YAClE,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;YACtF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,5 @@
1
+ /** Called once from cli.ts's preAction hook after Commander parses argv. */
2
+ export declare function setQuiet(value: boolean): void;
3
+ /** Checked by lifecycle.ts and any other command's narrative console.log calls. */
4
+ export declare function isQuiet(): boolean;
5
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAcA,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE7C;AAED,mFAAmF;AACnF,wBAAgB,OAAO,IAAI,OAAO,CAEjC"}
@@ -0,0 +1,22 @@
1
+ // packages/cli/src/cli/output.ts
2
+ //
3
+ // Part 2: "read it once in that same [preAction] hook and store it
4
+ // somewhere the output layer can check (a module-level flag or a small
5
+ // context object), rather than checking program.opts() inside every
6
+ // individual command." This is that module-level flag.
7
+ //
8
+ // Two ways quiet can be turned on, checked in this priority order:
9
+ // 1. PURIX_QUIET=1 in the environment — checked first so it's never
10
+ // silently overridden by option-parsing order.
11
+ // 2. -q/--quiet on the command line, read once in cli.ts's preAction
12
+ // hook via setQuiet().
13
+ let quiet = process.env.PURIX_QUIET === "1";
14
+ /** Called once from cli.ts's preAction hook after Commander parses argv. */
15
+ export function setQuiet(value) {
16
+ quiet = quiet || value;
17
+ }
18
+ /** Checked by lifecycle.ts and any other command's narrative console.log calls. */
19
+ export function isQuiet() {
20
+ return quiet;
21
+ }
22
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,mEAAmE;AACnE,uEAAuE;AACvE,oEAAoE;AACpE,uDAAuD;AACvD,EAAE;AACF,mEAAmE;AACnE,sEAAsE;AACtE,oDAAoD;AACpD,uEAAuE;AACvE,4BAA4B;AAC5B,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,GAAG,CAAC;AAE5C,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,KAAK,GAAG,KAAK,IAAI,KAAK,CAAC;AACzB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,OAAO;IACrB,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Captures the all-time savings total/count BEFORE a command's action
3
+ * runs. Diffing against the total AFTER is how "Savings This Run" is
4
+ * derived — there's no other reliable way to scope a query to "just this
5
+ * process's LLM calls" against a shared, cumulative SQLite ledger.
6
+ */
7
+ export declare function snapshotSavings(): import("@purix/core/llm/budget").SavingsSummary;
8
+ export declare function printRunSummary(before: ReturnType<typeof snapshotSavings>): void;
9
+ //# sourceMappingURL=savings_output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savings_output.d.ts","sourceRoot":"","sources":["../../src/cli/savings_output.ts"],"names":[],"mappings":"AAeA;;;;;GAKG;AACH,wBAAgB,eAAe,oDAE9B;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,GAAG,IAAI,CAyBhF"}
@@ -0,0 +1,62 @@
1
+ // packages/cli/src/cli/savings_output.ts
2
+ //
3
+ // Part 2: the "Savings This Run" / "Total Savings This Week" terminal
4
+ // output and the milestone-upsell line. Shared by lifecycle.ts's command
5
+ // actions (create, modify) rather than duplicated in each — both are
6
+ // where an intent_refinement/escalation call could actually happen.
7
+ import { getSavingsSummary } from "@purix/core/llm/budget";
8
+ import { config } from "@purix/core/state/config";
9
+ import { isLoggedIn } from "@purix/core/security/session";
10
+ import { isQuiet } from "./output.js";
11
+ const DEFAULT_MILESTONE_THRESHOLD_USD = 50;
12
+ const MILESTONE_LAST_SHOWN_KEY = "milestone-upsell-last-shown";
13
+ const MILESTONE_THRESHOLD_KEY = "milestone-threshold";
14
+ /**
15
+ * Captures the all-time savings total/count BEFORE a command's action
16
+ * runs. Diffing against the total AFTER is how "Savings This Run" is
17
+ * derived — there's no other reliable way to scope a query to "just this
18
+ * process's LLM calls" against a shared, cumulative SQLite ledger.
19
+ */
20
+ export function snapshotSavings() {
21
+ return getSavingsSummary(3650); // ~10 years ≈ "all time" for this project
22
+ }
23
+ export function printRunSummary(before) {
24
+ if (isQuiet() || config.get("show-savings") === "off")
25
+ return;
26
+ const after = getSavingsSummary(3650);
27
+ const thisRunDelta = after.totalSavingsUsd - before.totalSavingsUsd;
28
+ const thisRunCalls = after.callCount - before.callCount;
29
+ const weekly = getSavingsSummary(7);
30
+ // Per Part 2: on a run with no high-tier-eligible calls, omit the
31
+ // "Savings This Run" line entirely rather than printing a misleading
32
+ // $0.00 — a run that never touched intent_refinement/escalation, or
33
+ // touched them without a genuine downgrade, has nothing to report.
34
+ const lines = [];
35
+ if (thisRunCalls > 0) {
36
+ lines.push(`Savings This Run: $${thisRunDelta.toFixed(2)}`);
37
+ }
38
+ if (weekly.callCount > 0) {
39
+ lines.push(`Total Savings This Week: $${weekly.totalSavingsUsd.toFixed(2)}`);
40
+ }
41
+ if (lines.length === 0)
42
+ return; // nothing genuine to show at all
43
+ console.log(`\nPurix Verification Complete ✅`);
44
+ for (const line of lines)
45
+ console.log(line);
46
+ maybePrintMilestoneUpsell();
47
+ }
48
+ function maybePrintMilestoneUpsell() {
49
+ if (isLoggedIn())
50
+ return; // suppressed permanently once logged in
51
+ const threshold = Number(config.get(MILESTONE_THRESHOLD_KEY) ?? DEFAULT_MILESTONE_THRESHOLD_USD);
52
+ const allTime = getSavingsSummary(3650);
53
+ if (allTime.totalSavingsUsd < threshold)
54
+ return;
55
+ const lastShown = config.get(MILESTONE_LAST_SHOWN_KEY);
56
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
57
+ if (lastShown === today)
58
+ return; // rate-limited to once per day
59
+ console.log(`\nWant this data saved? Run \`purix login\` to unlock your web dashboard and get daily compute receipts sent to your inbox.`);
60
+ config.set(MILESTONE_LAST_SHOWN_KEY, today);
61
+ }
62
+ //# sourceMappingURL=savings_output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"savings_output.js","sourceRoot":"","sources":["../../src/cli/savings_output.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,sEAAsE;AACtE,yEAAyE;AACzE,qEAAqE;AACrE,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,+BAA+B,GAAG,EAAE,CAAC;AAC3C,MAAM,wBAAwB,GAAG,6BAA6B,CAAC;AAC/D,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,0CAA0C;AAC5E,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAA0C;IACxE,IAAI,OAAO,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,KAAK;QAAE,OAAO;IAE9D,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,KAAK,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;IACpE,MAAM,YAAY,GAAG,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACxD,MAAM,MAAM,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAEpC,kEAAkE;IAClE,qEAAqE;IACrE,oEAAoE;IACpE,mEAAmE;IACnE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,sBAAsB,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,6BAA6B,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,iCAAiC;IAEjE,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5C,yBAAyB,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,yBAAyB;IAChC,IAAI,UAAU,EAAE;QAAE,OAAO,CAAC,wCAAwC;IAElE,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,+BAA+B,CAAC,CAAC;IACjG,MAAM,OAAO,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS;QAAE,OAAO;IAEhD,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa;IAClE,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,CAAC,+BAA+B;IAEhE,OAAO,CAAC,GAAG,CACT,6HAA6H,CAC9H,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { Command } from "commander";
4
+ /**
5
+ * Builds the configured Commander program without running it. Split out
6
+ * from the parseAsync call below so this file is importable in a test —
7
+ * `program.parseAsync(process.argv)` as a bare top-level side effect
8
+ * would otherwise run (and try to interpret the test runner's own argv)
9
+ * the moment anything imported this module.
10
+ */
11
+ export declare function buildProgram(): Command;
12
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAQA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyDpC;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAoDtC"}
package/dist/cli.js ADDED
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ // packages/cli/src/cli.ts>COM
3
+ // Must be the first import: loads .env from process.cwd() (the user's
4
+ // project, where they'd put a real .env per .env.example) into
5
+ // process.env before anything else runs. Several modules read
6
+ // process.env.* at module-evaluation time (e.g. cli/output.ts's `quiet`
7
+ // flag), so anything imported after this line sees the loaded values —
8
+ // anything imported before it would not.
9
+ import "dotenv/config";
10
+ import { Command } from "commander";
11
+ import { reconcilePendingOperations } from "@purix/core/state/reconcile";
12
+ import { acquireRepoLock, releaseRepoLock } from "@purix/core/state/repo_lock";
13
+ import { startScheduler } from "@purix/core/state/scheduler";
14
+ import { setQuiet } from "./cli/output.js";
15
+ import { registerLifecycleCommands } from "./cli/commands/lifecycle.js";
16
+ import { registerMigrationsCommands } from "./cli/commands/migrations.js";
17
+ import { registerObservabilityCommands } from "./cli/commands/observability.js";
18
+ import { registerSecurityCommands } from "./cli/commands/security.js";
19
+ import { registerProviderCommands } from "./cli/commands/provider.js";
20
+ import { registerTierCommands } from "./cli/commands/tier.js";
21
+ // registerMcpCommands intentionally not wired in for v1.0 launch — see
22
+ // ADR-057 ("MCP Client Commands Governance Deferral"). The gateway/registry
23
+ // code is intact and untouched; re-enable this import + the
24
+ // registerMcpCommands(program) call below once the governance hardening
25
+ // (identity-backed provenance, DLP scrub, escalation budget caps) ships.
26
+ // import { registerMcpCommands } from "./cli/commands/mcp";
27
+ import { registerBackupCommands } from "./cli/commands/backup.js";
28
+ import { registerMemoryCommands } from "./cli/commands/memory.js";
29
+ import { registerToolsCommands } from "./cli/commands/tools.js";
30
+ import { registerConfigCommands } from "./cli/commands/config.js";
31
+ import { registerAuthCommands } from "./cli/commands/auth.js";
32
+ import { registerLangCommands } from "./cli/commands/lang.js";
33
+ // registerDevCommands intentionally not wired in for beta — `purix dev
34
+ // scaffold-language` is an internal codegen tool for Purix's own
35
+ // contributors (generates provider/pack/fixture stubs for a new
36
+ // language), not an end-user-facing command. It was previously
37
+ // unconditionally registered with no gate, unlike every other
38
+ // internal-only surface in this file (see registerMcpCommands above and
39
+ // PURIX_INTERNAL_LANGUAGES in language/registry.ts for the established
40
+ // pattern). Re-enable via an explicit internal/dev-mode flag if
41
+ // contributor tooling needs it exposed through the published binary;
42
+ // until then this is dead weight in the public `purix --help` output.
43
+ // import { registerDevCommands } from "./cli/commands/dev";
44
+ import { registerIndexCommands } from "./cli/commands/index.js";
45
+ import { registerMcpServeCommand } from "./cli/commands/mcp_serve.js";
46
+ import { fileURLToPath } from "node:url";
47
+ import { realpathSync } from "node:fs";
48
+ import { logError } from "./telemetry/log.js";
49
+ import { registerConnectCommand } from "./cli/commands/connect.js";
50
+ process.on("uncaughtException", (err) => {
51
+ logError(err, "uncaughtException");
52
+ console.error("\n🛑 Something went wrong. Details saved to ~/.purix/logs/. Run `purix diagnostics` to review or share them.");
53
+ releaseRepoLock();
54
+ process.exit(1);
55
+ });
56
+ process.on("unhandledRejection", (err) => {
57
+ logError(err, "unhandledRejection");
58
+ console.error("\n🛑 Something went wrong. Details saved to ~/.purix/logs/. Run `purix diagnostics` to review or share them.");
59
+ releaseRepoLock();
60
+ process.exit(1);
61
+ });
62
+ /**
63
+ * Builds the configured Commander program without running it. Split out
64
+ * from the parseAsync call below so this file is importable in a test —
65
+ * `program.parseAsync(process.argv)` as a bare top-level side effect
66
+ * would otherwise run (and try to interpret the test runner's own argv)
67
+ * the moment anything imported this module.
68
+ */
69
+ export function buildProgram() {
70
+ const program = new Command();
71
+ program
72
+ .name("purix")
73
+ .description("Verification & Governance Layer for AI-Generated Code (Purix v0.1.0)")
74
+ .version("0.1.0");
75
+ // Part 2: global quiet flag, alongside the existing preAction hook.
76
+ program.option("-q, --quiet", "suppress non-essential output");
77
+ // §7.3: startup reconciliation runs before ANY command touches the
78
+ // manifest — a crash-interrupted operation from a prior run must be
79
+ // resolved before new work starts, or state drifts further. Reading
80
+ // --quiet here too, once, rather than in every individual command.
81
+ program.hook("preAction", async (thisCommand) => {
82
+ setQuiet(Boolean(thisCommand.opts().quiet));
83
+ await reconcilePendingOperations();
84
+ acquireRepoLock();
85
+ startScheduler();
86
+ });
87
+ program.hook("postAction", async () => {
88
+ releaseRepoLock();
89
+ });
90
+ process.on("SIGINT", () => {
91
+ releaseRepoLock();
92
+ process.exit(130);
93
+ });
94
+ process.on("SIGTERM", () => {
95
+ releaseRepoLock();
96
+ process.exit(143);
97
+ });
98
+ registerLifecycleCommands(program);
99
+ registerMigrationsCommands(program);
100
+ registerObservabilityCommands(program);
101
+ registerSecurityCommands(program);
102
+ registerProviderCommands(program);
103
+ registerTierCommands(program);
104
+ // registerMcpCommands(program); — gated off for v1.0, see note above import
105
+ registerBackupCommands(program);
106
+ registerMemoryCommands(program);
107
+ registerToolsCommands(program);
108
+ registerConfigCommands(program);
109
+ registerAuthCommands(program);
110
+ registerLangCommands(program);
111
+ // registerDevCommands(program); — gated off for beta, see note above import
112
+ registerIndexCommands(program);
113
+ registerMcpServeCommand(program);
114
+ registerConnectCommand(program);
115
+ return program;
116
+ }
117
+ // Real bug, found while verifying the published-package install path (not
118
+ // hypothetical): `process.argv[1] === fileURLToPath(import.meta.url)` looks
119
+ // like the standard "am I the directly-executed entry point, or just
120
+ // imported for buildProgram() in tests" guard, but Node does NOT resolve
121
+ // symlinks for argv[1] while it DOES resolve them for import.meta.url when
122
+ // loading the module. Every real invocation of this CLI goes through a
123
+ // symlink: `npm install -g purix` creates one in the global bin dir, `npx
124
+ // purix` creates one in its cache, and even local `node_modules/.bin/purix`
125
+ // is one. Under all three, argv[1] stays the symlink path while
126
+ // import.meta.url resolves to the real target — they never match, the
127
+ // guard silently evaluates false, and the CLI exits 0 with zero output no
128
+ // matter what was asked for. realpath-ing argv[1] first makes the
129
+ // comparison symlink-agnostic on both sides; buildProgram() itself is
130
+ // untouched, so cli.test.ts / cli.integration.test.ts importing it directly
131
+ // still don't trigger this block (there's no argv[1] file to realpath to
132
+ // a match in that context).
133
+ function isDirectlyExecuted() {
134
+ if (!process.argv[1])
135
+ return false;
136
+ try {
137
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ if (isDirectlyExecuted()) {
144
+ const program = buildProgram();
145
+ if (process.argv.length <= 2) {
146
+ program.help();
147
+ }
148
+ else {
149
+ program.parseAsync(process.argv).catch((err) => {
150
+ console.error(`\n🛑 Unhandled error: ${err instanceof Error ? err.message : err}`);
151
+ process.exitCode = 1;
152
+ });
153
+ }
154
+ }
155
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function logError(err: unknown, context?: string): void;
2
+ export declare function getRecentLogs(maxLines?: number): string[];
3
+ export declare function redactLogContent(line: string): string;
4
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/telemetry/log.ts"],"names":[],"mappings":"AAoBA,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAgB7D;AA0BD,wBAAgB,aAAa,CAAC,QAAQ,SAAK,GAAG,MAAM,EAAE,CAqBrD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQrD"}
@@ -0,0 +1,92 @@
1
+ // packages/cli/src/telemetry/log.ts
2
+ import { appendFileSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ function getLogDir() {
6
+ const dir = join(homedir(), ".purix", "logs");
7
+ if (!existsSync(dir)) {
8
+ try {
9
+ mkdirSync(dir, { recursive: true });
10
+ }
11
+ catch { }
12
+ }
13
+ return dir;
14
+ }
15
+ function getLogFilePath() {
16
+ const dateStr = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
17
+ return join(getLogDir(), `purix-${dateStr}.log`);
18
+ }
19
+ export function logError(err, context) {
20
+ try {
21
+ const dir = getLogDir();
22
+ const p = getLogFilePath();
23
+ const entry = {
24
+ timestamp: new Date().toISOString(),
25
+ context: context ?? "cli",
26
+ message: err instanceof Error ? err.message : String(err),
27
+ stack: err instanceof Error ? err.stack : undefined,
28
+ version: "0.1.0",
29
+ };
30
+ appendFileSync(p, JSON.stringify(entry) + "\n", "utf-8");
31
+ // Clean up logs older than 7 days
32
+ cleanupOldLogs(dir);
33
+ }
34
+ catch { }
35
+ }
36
+ function cleanupOldLogs(dir) {
37
+ try {
38
+ const files = readdirNames(dir);
39
+ const now = Date.now();
40
+ for (const file of files) {
41
+ if (file.startsWith("purix-") && file.endsWith(".log")) {
42
+ const fp = join(dir, file);
43
+ const stats = statSync(fp);
44
+ if (now - stats.mtimeMs > 7 * 24 * 60 * 60 * 1000) {
45
+ rmSync(fp, { force: true });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ catch { }
51
+ }
52
+ function readdirNames(dir) {
53
+ try {
54
+ return readdirSync(dir);
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ }
60
+ export function getRecentLogs(maxLines = 50) {
61
+ try {
62
+ const dir = getLogDir();
63
+ const files = readdirNames(dir)
64
+ .filter((f) => f.startsWith("purix-") && f.endsWith(".log"))
65
+ .sort()
66
+ .reverse();
67
+ const lines = [];
68
+ for (const file of files) {
69
+ const content = readFileSync(join(dir, file), "utf-8");
70
+ const fileLines = content.trim().split("\n").filter(Boolean);
71
+ for (const line of fileLines.reverse()) {
72
+ lines.push(line);
73
+ if (lines.length >= maxLines)
74
+ return lines;
75
+ }
76
+ }
77
+ return lines;
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ export function redactLogContent(line) {
84
+ // Redact potential absolute paths, secrets, or api keys
85
+ return line
86
+ .replace(/[C-Z]:\\[^\s"']+/gi, "[REDACTED_PATH]")
87
+ .replace(/\/home\/[^\s"']+/gi, "[REDACTED_PATH]")
88
+ .replace(/\/Users\/[^\s"']+/gi, "[REDACTED_PATH]")
89
+ .replace(/AKIA[0-9A-Z]{16}/g, "[REDACTED_SECRET]")
90
+ .replace(/bearer\s+[a-zA-Z0-9_\-\.]+/gi, "bearer [REDACTED_TOKEN]");
91
+ }
92
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/telemetry/log.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@purix/cli",
3
+ "version": "0.1.0",
4
+ "description": "Governance-first AI coding agent CLI: classifies and verifies AI-driven code changes in a sandbox, against your test suite, before they land. Also runs as an MCP server for other coding agents.",
5
+ "keywords": [
6
+ "ai",
7
+ "cli",
8
+ "code-verification",
9
+ "governance",
10
+ "mcp",
11
+ "model-context-protocol",
12
+ "sandbox",
13
+ "typescript"
14
+ ],
15
+ "type": "module",
16
+ "license": "Apache-2.0",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/purix-ai/purix.git",
20
+ "directory": "packages/cli"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "bin": {
26
+ "purix": "./dist/cli.js"
27
+ },
28
+ "dependencies": {
29
+ "@purix/core": "^0.1.0",
30
+ "@purix/mcp-server": "^0.1.0",
31
+ "add-mcp": "^2.4.0",
32
+ "commander": "^15.0.0",
33
+ "dotenv": "^17.4.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "22.13.0",
37
+ "typescript": "5.7.2",
38
+ "expect": "^29.7.0",
39
+ "tsx": "^4.19.2"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "purix": "tsx src/cli.ts",
44
+ "test": "tsx --test \"src/**/*.test.ts\"",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }