@wrongstack/cli 0.299.0 → 0.301.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 (82) hide show
  1. package/dist/{acp-S4LZQB66.js → acp-DATHHPNT.js} +36 -17
  2. package/dist/acp-server-agent.d.ts +1 -1
  3. package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
  4. package/dist/{auth-G5YKKSRH.js → auth-JASC4K3F.js} +151 -38
  5. package/dist/auth-menu/loopback-server.d.ts +41 -55
  6. package/dist/auth-menu/openai-codex-oauth.d.ts +36 -123
  7. package/dist/auto-discover-providers-DRW4OZHG.js +8 -0
  8. package/dist/boot/dispatch-webui.d.ts +3 -0
  9. package/dist/boot/launch-menu.d.ts +2 -4
  10. package/dist/boot/tool-registry.d.ts +3 -0
  11. package/dist/chimera-reviewer-policy.d.ts +10 -3
  12. package/dist/chimera-work-registry.d.ts +19 -0
  13. package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
  14. package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
  15. package/dist/{chunk-2SLSUDYS.js → chunk-2KDOFTTM.js} +312 -211
  16. package/dist/{chunk-TGDHN4LM.js → chunk-4GVH7V3S.js} +4 -24
  17. package/dist/chunk-CSAPOCBP.js +24 -0
  18. package/dist/{chunk-66T43Q4Y.js → chunk-ETOEGL3V.js} +6 -9
  19. package/dist/{chunk-PEKUKUCH.js → chunk-GUHQQG63.js} +32 -14
  20. package/dist/chunk-KE7E7DPX.js +38 -0
  21. package/dist/{chunk-ZNZJ34RF.js → chunk-MHF2IJDE.js} +21 -4
  22. package/dist/{chunk-4HH3YGG4.js → chunk-MRLYDHQ7.js} +8 -5
  23. package/dist/{chunk-L5CE52XW.js → chunk-O7XNGCRT.js} +317 -309
  24. package/dist/chunk-PYTFS4MC.js +238 -0
  25. package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
  26. package/dist/{chunk-4U3DMA2Y.js → chunk-ZECLGUTF.js} +28 -21
  27. package/dist/{cli-main-3MQDFSYK.js → cli-main-XWC57MGE.js} +849 -242
  28. package/dist/{execution-ANOYMWPO.js → execution-LHMSMDHB.js} +174 -55
  29. package/dist/execution-chimera-cascade.d.ts +3 -4
  30. package/dist/execution-chimera-review.d.ts +2 -3
  31. package/dist/execution-cleanup.d.ts +3 -2
  32. package/dist/execution.d.ts +1 -1
  33. package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
  34. package/dist/fleet/budget-source.d.ts +18 -0
  35. package/dist/fleet/host-context.d.ts +1 -1
  36. package/dist/fleet/host-helpers.d.ts +14 -0
  37. package/dist/fleet/host-status.d.ts +31 -0
  38. package/dist/fleet/host-types.d.ts +8 -0
  39. package/dist/fleet/host.d.ts +5 -0
  40. package/dist/fleet/status-broadcast.d.ts +1 -1
  41. package/dist/{hq-BDO56CX6.js → hq-LN5CJHCM.js} +22 -3
  42. package/dist/hq-server/auth-state.d.ts +29 -1
  43. package/dist/hq-server/auth.d.ts +16 -0
  44. package/dist/hq-server/client-address.d.ts +56 -0
  45. package/dist/hq-server/login-attempt-store.d.ts +30 -10
  46. package/dist/hq-server/routes/auth-handlers.d.ts +66 -26
  47. package/dist/hq-server/routes.d.ts +23 -1
  48. package/dist/hq-server/types.d.ts +10 -0
  49. package/dist/hq-server/ws.d.ts +17 -6
  50. package/dist/{hq-server-RJLJJZU7.js → hq-server-S7HZG3PA.js} +3 -2
  51. package/dist/hq-server.d.ts +19 -0
  52. package/dist/index.js +69 -104
  53. package/dist/live-settings-input.d.ts +5 -0
  54. package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
  55. package/dist/{modeldiag-HENYJ4N7.js → modeldiag-3DOK4BWW.js} +3 -3
  56. package/dist/{plugin-usage-VTL7ZTVC.js → plugin-usage-YEVLOR33.js} +7 -3
  57. package/dist/{plugins-ETY6W3DC.js → plugins-K3BAUP4D.js} +3 -3
  58. package/dist/profile-config-path.d.ts +0 -2
  59. package/dist/provider-helpers.d.ts +0 -5
  60. package/dist/{providers-models-GDELBHZK.js → providers-models-WEZ4EGLL.js} +38 -12
  61. package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
  62. package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
  63. package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
  64. package/dist/slash-commands/command-context.d.ts +19 -0
  65. package/dist/subcommands/flags.d.ts +23 -0
  66. package/dist/terminal-format.d.ts +15 -0
  67. package/dist/webui-server/setup-events.d.ts +17 -0
  68. package/dist/webui-server/terminal-log-view.d.ts +29 -12
  69. package/dist/{webui-server-PCLSXX6L.js → webui-server-DD35QFNO.js} +113 -50
  70. package/dist/webui-server-options.d.ts +16 -0
  71. package/dist/wiring/brain-and-orchestration.d.ts +4 -1
  72. package/dist/wiring/director-setup.d.ts +4 -0
  73. package/dist/wiring/fallback-gate.d.ts +31 -0
  74. package/dist/wiring/fleet-command-handlers.d.ts +1 -1
  75. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
  76. package/dist/wiring/management-tools.d.ts +9 -1
  77. package/dist/wiring/pipeline.d.ts +5 -5
  78. package/dist/wiring/plugins.d.ts +3 -10
  79. package/dist/wiring/provider-utility-tools.d.ts +27 -1
  80. package/dist/wiring/tools.d.ts +4 -5
  81. package/package.json +23 -23
  82. package/dist/chunk-GO3TJICK.js +0 -489
@@ -1,138 +1,51 @@
1
1
  /**
2
- * OpenAI Codex — "Sign in with ChatGPT" OAuth (Authorization Code + PKCE).
2
+ * OpenAI Codex — "Sign in with ChatGPT", terminal flow.
3
3
  *
4
- * This is the ONLY login flow that actually works for using a ChatGPT
5
- * Plus/Pro/Team subscription programmatically. It mirrors the real Codex CLI:
4
+ * This module is now only the *terminal* half of the login: the rendered
5
+ * instructions, the SIGINT / TUI-signal cancellation contract, the
6
+ * manual-paste prompt, and writing the resulting tokens into the encrypted
7
+ * profile config.
6
8
  *
7
- * 1. Generate a PKCE verifier/challenge (S256) + CSRF state.
8
- * 2. Start a loopback HTTP server on http://localhost:1455/auth/callback.
9
- * 3. Open the browser to https://auth.openai.com/oauth/authorize.
10
- * 4. The browser redirects back to the loopback with `?code=...&state=...`.
11
- * 5. Exchange the code at https://auth.openai.com/oauth/token for an
12
- * access_token + refresh_token (and an id_token carrying the account id).
13
- * 6. Store the tokens in the vault as an `oauth` ProviderApiKey.
9
+ * The protocol itself — client id, endpoints, PKCE, the authorize URL, the
10
+ * token exchange and refresh, the JWT account-id claim, and model discovery —
11
+ * lives in `@wrongstack/providers/oauth`. It used to live here AND in
12
+ * `providers/src/oauth/chatgpt.ts` (the headless WebUI flow) AND in
13
+ * `providers/src/openai-codex.ts` (the runtime refresh path), whose header
14
+ * said outright that it was mirroring this file.
14
15
  *
15
- * The resulting access token is a JWT whose `https://api.openai.com/auth`
16
- * claim contains `chatgpt_account_id`. Requests then go to the ChatGPT
17
- * backend (`chatgpt.com/backend-api/codex/responses`) NOT api.openai.com
18
- * which is what the `openai-codex` wire family handles. The standard
19
- * API-key flow (`family: openai` api.openai.com/chat/completions) is left
20
- * completely untouched.
16
+ * Three copies is what let the OAuth abort-listener leak sit in two of them
17
+ * while a third flow in this same directory was already correct — nothing
18
+ * forced them to agree, so a fix applied to one silently left the others
19
+ * broken. A rotated client id or a changed token-endpoint contract carries the
20
+ * same risk, with the failure being "one surface stops authenticating" rather
21
+ * than a compile error.
21
22
  *
22
- * NOTE: a ChatGPT web *session cookie* is NOT an API token. The old
23
- * device-code + cookie-scraping attempts could never produce a usable
24
- * credential; this module replaces them.
23
+ * The protocol constants live in `providers` (not here) because the dependency
24
+ * edge runs `cli providers core`: the runtime provider needs them to
25
+ * refresh a token and must never import from `cli`.
26
+ *
27
+ * Everything the protocol layer defines is re-exported below, so existing
28
+ * importers (`auth-menu/index.ts`, `wiring/provider.ts`, the CLI tests) keep
29
+ * working against this module unchanged.
25
30
  */
31
+ import { buildCodexAuthorizeUrl, exchangeCodexAuthorizationCode, refreshCodexTokens } from '@wrongstack/providers/oauth';
26
32
  import { type LoopbackServer } from './loopback-server.js';
27
- import { type ModelsRegistry } from '@wrongstack/core/types';
28
33
  import type { AuthMenuDeps } from './types.js';
29
- /** Canonical provider id under which ChatGPT-login credentials are stored. */
30
- export declare const CODEX_PROVIDER_ID = "openai-codex";
31
- /** Default ChatGPT backend base. The wire family appends `/codex/responses`. */
32
- export declare const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
33
- /**
34
- * Codex-specific compatibility wrapper retained for callers that imported the
35
- * helper from this module before the shared OAuth loopback server extraction.
36
- */
37
- export declare function startLoopbackServer(expectedState: string, signal?: AbortSignal): Promise<LoopbackServer>;
38
- /**
39
- * Fallback model list used when BOTH the live backend AND the models.dev
40
- * catalog are unreachable. This should never happen in practice — the
41
- * catalog is cached locally and the only scenario where both fail is a
42
- * fresh install with no network on first login.
43
- *
44
- * The primary resolution order in `resolveCodexModels()` is:
45
- * 1. Live backend (`GET /models`)
46
- * 2. models.dev catalog (`family: gpt-codex*` under `openai` provider)
47
- * 3. This inline fallback
48
- */
49
- export interface CodexTokens {
50
- access: string;
51
- refresh: string;
52
- /** Absolute expiry in epoch milliseconds. */
53
- expires: number;
54
- idToken?: string | undefined;
55
- }
56
- export interface Pkce {
57
- verifier: string;
58
- challenge: string;
59
- }
60
- /** Generate a PKCE verifier + S256 challenge. */
61
- export declare function generatePkce(): Pkce;
62
- /** Build the full authorize URL with all Codex-required query params. */
63
- export declare function buildAuthorizeUrl(challenge: string, state: string, port?: number): string;
64
- /**
65
- * Extract `chatgpt_account_id` from an access/id token JWT. Returns null when
66
- * the token isn't a JWT or lacks the claim (the caller decides whether that
67
- * is fatal — for login it is; for a cached display value it isn't).
68
- */
69
- export declare function extractAccountId(token: string): string | null;
70
- /**
71
- * Recommended Codex models for ChatGPT sign-in. Live backend discovery
72
- * wins; these values are only used when the backend/catalog cannot
73
- * provide a list. Derived from `CODEX_MODELS` in core (the single source of
74
- * truth for Codex id/name/description) — used both by OAuth login
75
- * (resolveCodexModels → tier 3) and by the provider-boot synthesis
76
- * path (wiring/provider.ts when the catalog is unavailable).
77
- */
78
- export declare const FALLBACK_CODEX_MODELS: ReadonlyArray<{
79
- id: string;
80
- name: string;
81
- }>;
82
- /** Families in the models.dev catalog that indicate Codex / Responses API compatibility. */
83
- export declare const CODEX_CATALOG_FAMILIES: Set<string>;
84
- export declare function fallbackCodexModelIds(): string[];
85
- export declare function fallbackCodexProviderModels(): Array<{
86
- id: string;
87
- name: string;
88
- }>;
89
- /**
90
- * Filter a list of available model ids down to the ones that are still
91
- * current for ChatGPT sign-in (i.e. present in FALLBACK_CODEX_MODELS).
92
- * Used by both OAuth login (to drop deprecated ids from the live /models
93
- * response) and by the provider-boot synthesis (to pick current ids out
94
- * of the models.dev catalog).
95
- */
96
- export declare function filterCurrentCodexModelIds(ids: Iterable<string>): string[];
97
- export declare function isCodexCatalogModel(model: {
98
- family?: string | undefined;
99
- }): boolean;
100
- /**
101
- * Resolve the list of available Codex models using a 3-tier fallback chain:
102
- *
103
- * 1. **Live backend** — `fetchCodexModels()` hits `GET <baseUrl>/models`
104
- * and keeps only current Codex model ids.
105
- * 2. **models.dev catalog** — queries the `openai` provider in the cached
106
- * models.dev registry and picks models whose `family` is `gpt-codex` or
107
- * `gpt-codex-spark` and whose ids are current for ChatGPT sign-in.
108
- * 3. **Inline fallback** — a minimal list of documented mainstream models
109
- * (`gpt-5.6-sol`, `gpt-5.5`, `gpt-5.6-terra`, `gpt-5.6-luna`,
110
- * `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex-spark`).
111
- *
112
- * The live/catalog tiers gate account availability, while the shared current
113
- * model list filters out deprecated ChatGPT sign-in ids before config is saved.
114
- */
115
- export declare function resolveCodexModels(modelsRegistry: ModelsRegistry, accessToken: Promise<string> | string, baseUrl?: string | undefined, signal?: AbortSignal): Promise<string[]>;
34
+ export { CODEX_BASE_URL, CODEX_CATALOG_FAMILIES, CODEX_PROVIDER_ID, type CodexTokens, extractAccountId, FALLBACK_CODEX_MODELS, fallbackCodexModelIds, fallbackCodexProviderModels, fetchCodexModels, filterCurrentCodexModelIds, generatePkce, isCodexCatalogModel, parseAuthorizationInput, type Pkce, resolveCodexModels, } from '@wrongstack/providers/oauth';
35
+ /** Build the Codex authorize URL. Alias kept for the historical local name. */
36
+ export declare const buildAuthorizeUrl: typeof buildCodexAuthorizeUrl;
37
+ /** Exchange an authorization code (+ PKCE verifier) for tokens. */
38
+ export declare const exchangeAuthorizationCode: typeof exchangeCodexAuthorizationCode;
116
39
  /**
117
- * Fetch the account's available Codex model ids live from the ChatGPT backend.
118
- *
119
- * Hits `GET <baseUrl>/models` — the standard OpenAI-compatible model listing
120
- * endpoint, which the Codex backend may expose. Best-effort: returns an empty
121
- * array on any failure so login still succeeds with the hardcoded fallback.
40
+ * Refresh an expired access token. Alias kept for the historical local name;
41
+ * `wiring/provider.ts` and the CLI tests import it from here.
122
42
  */
123
- export declare function fetchCodexModels(accessToken: string, baseUrl?: string | undefined, signal?: AbortSignal): Promise<string[]>;
124
- /** Exchange an authorization code (+ PKCE verifier) for tokens. */
125
- export declare function exchangeAuthorizationCode(code: string, verifier: string, signal?: AbortSignal, port?: number): Promise<CodexTokens>;
43
+ export declare const refreshCodexToken: typeof refreshCodexTokens;
126
44
  /**
127
- * Refresh an expired access token using the stored refresh token. Exported for
128
- * the `openai-codex` provider, which calls it transparently before requests.
45
+ * Codex-specific loopback wrapper. Stays CLI-side because this flow pairs it
46
+ * with {@link openBrowser}, which is terminal-only.
129
47
  */
130
- export declare function refreshCodexToken(refreshToken: string, signal?: AbortSignal): Promise<CodexTokens>;
131
- /** Parse a pasted authorization code or full redirect URL into { code, state }. */
132
- export declare function parseAuthorizationInput(input: string): {
133
- code?: string | undefined;
134
- state?: string | undefined;
135
- };
48
+ export declare function startLoopbackServer(expectedState: string, signal?: AbortSignal): Promise<LoopbackServer>;
136
49
  export interface CodexLoginOptions {
137
50
  /** Storage provider id. Defaults to the canonical `openai-codex`. */
138
51
  providerId?: string;
@@ -0,0 +1,8 @@
1
+ import {
2
+ discoverAndMergeProviders
3
+ } from "./chunk-4GVH7V3S.js";
4
+ import "./chunk-7OCVIDC7.js";
5
+ export {
6
+ discoverAndMergeProviders
7
+ };
8
+ //# sourceMappingURL=auto-discover-providers-DRW4OZHG.js.map
@@ -22,6 +22,7 @@ import type { Config, MemoryPort, ModelsRegistry, ModeStore, SessionStore, Sessi
22
22
  import type { MCPRegistry } from '@wrongstack/mcp';
23
23
  import type { TerminalRenderer } from '../renderer.js';
24
24
  import type { AutonomyMode } from '../services/autonomy-mode.js';
25
+ import type { CliWebUIOptions } from '../webui-server-options.js';
25
26
  export interface WebUIDispatchContext {
26
27
  agent: Agent;
27
28
  events: EventBus;
@@ -96,6 +97,8 @@ export interface WebUIDispatchContext {
96
97
  error?: string | undefined;
97
98
  }) => void | Promise<void>) | undefined;
98
99
  }) => Promise<string>) | undefined;
100
+ /** Live fleet budget for WebUI concurrency/spawn gauges (issue #323). */
101
+ getFleetBudget?: CliWebUIOptions['getFleetBudget'];
99
102
  }
100
103
  /**
101
104
  * Run the WebUI server and block until it shuts down.
@@ -50,13 +50,11 @@ declare const DEFAULT_PORTS: Record<Exclude<LaunchMenuMode, 'tui-repl'>, number>
50
50
  * - `--webui`, `--simpleui`, `--hq`, or `--desktop` flag is set
51
51
  * (the user already chose a surface)
52
52
  * - stdin is not a TTY (CI, pipes, redirects)
53
- * - the first positional looks like a known subcommand (auth,
54
- * init, mcp, plugin, doctor, version, help, …)
55
53
  * - `--no-interactive` or `--skip` (existing "skip all prompts"
56
54
  * contract from boot.ts)
57
55
  * - a one-shot prompt (`--prompt <x>`) is supplied
58
- * - any positional argument is present (treats it like the user
59
- * is forwarding a one-shot query, not launching a surface)
56
+ * - ANY positional argument is present a subcommand or a one-shot
57
+ * query. The user already said what they wanted.
60
58
  */
61
59
  export declare function shouldSkipMenu(argv: string[], flags: Record<string, string | boolean>, positional: string[]): boolean;
62
60
  interface RunLaunchMenuDeps {
@@ -15,6 +15,9 @@ interface RegisterBuiltinToolsDeps {
15
15
  descriptionMode?: ToolDescriptionModeConfig | undefined;
16
16
  resultRenderMode?: ToolResultRenderModeConfig | undefined;
17
17
  disabledTools?: string[] | undefined;
18
+ nextsteps?: {
19
+ enabled?: boolean | undefined;
20
+ } | undefined;
18
21
  exec?: {
19
22
  allow?: string[] | undefined;
20
23
  deny?: string[] | undefined;
@@ -17,9 +17,10 @@ export declare function __resetReviewerRoundRobinCursor(value?: number): void;
17
17
  /**
18
18
  * Assign provider/model/fallbackModels for a Chimera reviewer spawn.
19
19
  *
20
- * Builds the pool from the configured primary + fallback chain, then picks
21
- * the next entry via round-robin. When the pool has <=1 usable entry the
22
- * original primary/fallbacks are returned unchanged.
20
+ * Builds the pool from the configured primary + fallback chain, then chooses
21
+ * either the next round-robin entry or a random starting entry. The injected
22
+ * random source keeps the random policy deterministic in tests. When the pool
23
+ * has <=1 usable entry the original primary/fallbacks are returned unchanged.
23
24
  *
24
25
  * When a {@link ProviderModelStatusTracker} is supplied, blocked entries
25
26
  * (waiting-room / token-reset-limit room) are filtered from both the pool
@@ -27,6 +28,12 @@ export declare function __resetReviewerRoundRobinCursor(value?: number): void;
27
28
  * a concurrent reviewer turn. Without the tracker, the legacy pre-waiting-
28
29
  * room behavior is preserved.
29
30
  */
31
+ export declare function assignReviewerModels(provider: string, model: string, fallbackModels: readonly string[], selection?: 'round-robin' | 'random', statusTracker?: ProviderModelStatusTracker | undefined, random?: () => number): {
32
+ provider: string;
33
+ model: string;
34
+ fallbackModels: string[];
35
+ };
36
+ /** Backward-compatible round-robin entry point for existing callers. */
30
37
  export declare function assignReviewerModelsRoundRobin(provider: string, model: string, fallbackModels: readonly string[], statusTracker?: ProviderModelStatusTracker | undefined): {
31
38
  provider: string;
32
39
  model: string;
@@ -0,0 +1,19 @@
1
+ export interface ChimeraWorkRegistry {
2
+ /** Track one review/cascade promise until it settles. */
3
+ track(work: Promise<void>): boolean;
4
+ /** Snapshot currently pending work for parent-before-child ordering. */
5
+ pending(): Promise<void> | undefined;
6
+ /** Stop accepting new roots after draining all accepted work to a fixed point. */
7
+ drainAndClose(): Promise<void>;
8
+ readonly size: number;
9
+ }
10
+ /**
11
+ * Session-scoped registry for Chimera review and cascade work.
12
+ *
13
+ * A single Promise slot loses overlapping reviews. A single Promise.all snapshot
14
+ * can also miss cascades or re-reviews registered while an earlier batch is
15
+ * settling. This registry retains every accepted promise and drains repeatedly
16
+ * until no accepted work remains, then seals admission.
17
+ */
18
+ export declare function createChimeraWorkRegistry(): ChimeraWorkRegistry;
19
+ //# sourceMappingURL=chimera-work-registry.d.ts.map
@@ -2,8 +2,12 @@ import "./chunk-7OCVIDC7.js";
2
2
 
3
3
  // src/subcommands/handlers/chronicle.ts
4
4
  import { statSync } from "node:fs";
5
+ import path from "node:path";
5
6
  import {
6
- createChronicleProjectAccess
7
+ chronicleProjectServerMetadataPath,
8
+ compactChronicleSqlite,
9
+ createChronicleProjectAccess,
10
+ resolveChronicleProjectServerOptions
7
11
  } from "@wrongstack/core/chronicle";
8
12
  var facets = /* @__PURE__ */ new Set([
9
13
  "eventType",
@@ -26,6 +30,22 @@ var chronicleCmd = async (args, deps) => {
26
30
  }
27
31
  let access;
28
32
  try {
33
+ if (operation === "compact") {
34
+ if (args.length !== 1) {
35
+ throw new Error("Usage: wstack chronicle compact (stop the Chronicle daemon first)");
36
+ }
37
+ const resolved = resolveChronicleProjectServerOptions({
38
+ projectRoot: deps.projectRoot,
39
+ userHome: deps.userHome
40
+ });
41
+ const result2 = compactChronicleSqlite({
42
+ dbPath: path.join(resolved.projectDir, "chronicle", "chronicle.sqlite"),
43
+ endpointMetadataPath: chronicleProjectServerMetadataPath(resolved.projectDir)
44
+ });
45
+ deps.renderer.write(`${JSON.stringify(result2, null, 2)}
46
+ `);
47
+ return 0;
48
+ }
29
49
  access = createChronicleProjectAccess({
30
50
  projectRoot: deps.projectRoot,
31
51
  userHome: deps.userHome
@@ -60,9 +80,10 @@ var chronicleCmd = async (args, deps) => {
60
80
  return 0;
61
81
  }
62
82
  if (operation === "prune") {
63
- const dryRun = args.includes("--dry-run") || args.includes("-n");
83
+ const flags = deps.flags ?? {};
84
+ const dryRun = args.includes("--dry-run") || args.includes("-n") || flags["dry-run"] === true || flags["dry-run"] === "true" || flags["n"] === true;
64
85
  const daysIndex = args.indexOf("--days");
65
- const rawDays = daysIndex >= 0 && daysIndex + 1 < args.length ? args[daysIndex + 1] : void 0;
86
+ const rawDays = daysIndex >= 0 && daysIndex + 1 < args.length ? args[daysIndex + 1] : typeof flags["days"] === "string" ? flags["days"] : void 0;
66
87
  const days = rawDays ? parseInt(rawDays, 10) : 30;
67
88
  if (!Number.isFinite(days) || days < 1) {
68
89
  deps.renderer.writeError(
@@ -235,7 +256,7 @@ function parseValue(value) {
235
256
  }
236
257
  }
237
258
  function usage() {
238
- return "Usage:\n wstack chronicle query [field=value ...]\n wstack chronicle status\n wstack chronicle facet <field> [field=value ...]\n wstack chronicle metrics [providers|tasks|files|summary] [field=value ...]\n wstack chronicle prune [--days N] [--dry-run]\n\nExamples:\n wstack chronicle status\n wstack chronicle query path=src/app.ts line=42\n wstack chronicle query providerId=openai outcome=failure\n wstack chronicle query eventType=tool.executed sessionId=<id> limit=50\n wstack chronicle query attr.tool.name=read\n wstack chronicle facet modelId from=2026-07-01T00:00:00Z\n wstack chronicle metrics providers from=2026-07-01\n wstack chronicle metrics files path=src/app.ts\n wstack chronicle metrics tasks limit=20\n wstack chronicle prune --days 7 --dry-run\n wstack chronicle prune --days 30\n";
259
+ return "Usage:\n wstack chronicle query [field=value ...]\n wstack chronicle status\n wstack chronicle facet <field> [field=value ...]\n wstack chronicle metrics [providers|tasks|files|summary] [field=value ...]\n wstack chronicle prune [--days N] [--dry-run]\n wstack chronicle compact\n\nExamples:\n wstack chronicle status\n wstack chronicle query path=src/app.ts line=42\n wstack chronicle query providerId=openai outcome=failure\n wstack chronicle query eventType=tool.executed sessionId=<id> limit=50\n wstack chronicle query attr.tool.name=read\n wstack chronicle facet modelId from=2026-07-01T00:00:00Z\n wstack chronicle metrics providers from=2026-07-01\n wstack chronicle metrics files path=src/app.ts\n wstack chronicle metrics tasks limit=20\n wstack chronicle prune --days 7 --dry-run\n wstack chronicle prune --days 30\n wstack chronicle compact\n";
239
260
  }
240
261
  function formatBytes(bytes) {
241
262
  if (bytes < 1024) return `${bytes} B`;
@@ -253,4 +274,4 @@ function tryStat(file) {
253
274
  export {
254
275
  chronicleCmd
255
276
  };
256
- //# sourceMappingURL=chronicle-QQEQFSSE.js.map
277
+ //# sourceMappingURL=chronicle-63MFCZN6.js.map
@@ -80,20 +80,40 @@ async function safeDelete(filePath) {
80
80
  }
81
81
  }
82
82
  }
83
+ var REDACTED = "[REDACTED]";
84
+ function maskValue(v) {
85
+ if (Array.isArray(v)) return v.map(maskValue);
86
+ if (typeof v === "object" && v !== null) return maskConfigSecrets(v);
87
+ return v;
88
+ }
83
89
  function maskConfigSecrets(cfg) {
84
90
  if (typeof cfg !== "object" || cfg === null) return {};
85
91
  const out = {};
86
92
  for (const [k, v] of Object.entries(cfg)) {
87
93
  if (isSecretField(k)) {
88
- out[k] = "[REDACTED]";
89
- } else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
90
- out[k] = maskConfigSecrets(v);
94
+ out[k] = REDACTED;
91
95
  } else {
92
- out[k] = v;
96
+ out[k] = maskValue(v);
93
97
  }
94
98
  }
95
99
  return out;
96
100
  }
101
+ function reviveSecrets(masked, current) {
102
+ if (masked === REDACTED) return current;
103
+ if (Array.isArray(masked)) {
104
+ const cur = Array.isArray(current) ? current : [];
105
+ return masked.map((item, i) => reviveSecrets(item, cur[i]));
106
+ }
107
+ if (typeof masked !== "object" || masked === null) return masked;
108
+ const curObj = typeof current === "object" && current !== null && !Array.isArray(current) ? current : {};
109
+ const out = {};
110
+ for (const [k, v] of Object.entries(masked)) {
111
+ const revived = reviveSecrets(v, curObj[k]);
112
+ if (revived === void 0) continue;
113
+ out[k] = revived;
114
+ }
115
+ return out;
116
+ }
97
117
  function diffSummary(oldCfg, newCfg) {
98
118
  const changes = [];
99
119
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);
@@ -199,7 +219,7 @@ async function backupCurrent(homeFn = defaultHomeDir, targetConfigPath) {
199
219
  }
200
220
  if (content !== void 0) {
201
221
  try {
202
- await atomicWrite(last, content);
222
+ await atomicWrite(last, content, { mode: 384 });
203
223
  } catch (err) {
204
224
  writeErr(
205
225
  `[config-history] .last backup failed: ${toErrorMessage(err)}`
@@ -209,7 +229,7 @@ async function backupCurrent(homeFn = defaultHomeDir, targetConfigPath) {
209
229
  if (content !== void 0) {
210
230
  try {
211
231
  const bakPath = `${cfg}.${ts}.bak`;
212
- await atomicWrite(bakPath, content);
232
+ await atomicWrite(bakPath, content, { mode: 384 });
213
233
  } catch (err) {
214
234
  writeErr(
215
235
  `[config-history] timestamped backup failed: ${toErrorMessage(err)}`
@@ -244,7 +264,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
244
264
  await fs.writeFile(
245
265
  path.join(historyDir(homeFn, targetConfigPath), `${id}.json`),
246
266
  JSON.stringify(entry, null, 2),
247
- "utf8"
267
+ { encoding: "utf8", mode: 384 }
248
268
  );
249
269
  } catch (err) {
250
270
  throw new FsError({
@@ -292,14 +312,17 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir, targetConfigPath)
292
312
  oldCfg = JSON.parse(raw);
293
313
  } catch {
294
314
  }
315
+ const restored = reviveSecrets(entry.snapshotMasked, oldCfg);
295
316
  try {
296
- await atomicWrite(configPath(homeFn, targetConfigPath), JSON.stringify(entry.snapshotMasked, null, 2));
317
+ await atomicWrite(configPath(homeFn, targetConfigPath), JSON.stringify(restored, null, 2), {
318
+ mode: 384
319
+ });
297
320
  } catch (err) {
298
321
  return { ok: false, backupId: null, error: String(err) };
299
322
  }
300
323
  const backupId = await appendHistory(
301
324
  oldCfg,
302
- entry.snapshotMasked,
325
+ restored,
303
326
  `Restored from history ${id}`,
304
327
  homeFn,
305
328
  targetConfigPath
@@ -343,4 +366,4 @@ export {
343
366
  restoreFromHistory,
344
367
  restoreLast
345
368
  };
346
- //# sourceMappingURL=chunk-YWUPHRGB.js.map
369
+ //# sourceMappingURL=chunk-263G3FMB.js.map