cleargate 0.14.0 → 0.15.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 (149) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/MANIFEST.json +71 -15
  3. package/dist/admin-api/index.cjs +0 -1
  4. package/dist/admin-api/index.js +1 -2
  5. package/dist/auth/factory.cjs +0 -1
  6. package/dist/auth/factory.js +2 -3
  7. package/dist/auth/require-token.cjs +0 -1
  8. package/dist/auth/require-token.js +1 -2
  9. package/dist/auth/token-store.cjs +0 -1
  10. package/dist/auth/token-store.js +1 -2
  11. package/dist/{bootstrap-root-QKSA5V75.js → bootstrap-root-2H5HVTCC.js} +1 -2
  12. package/dist/{chunk-PDE37WFQ.js → chunk-A7MSQUU7.js} +2 -3
  13. package/dist/{chunk-BTSZOEWC.js → chunk-P6KEDAK2.js} +0 -1
  14. package/dist/{chunk-E3X7IE5E.js → chunk-PY6FHGV5.js} +1 -2
  15. package/dist/{chunk-5DI2Z3C2.js → chunk-Y53ZZYYU.js} +1 -2
  16. package/dist/cli.cjs +1564 -1414
  17. package/dist/cli.js +1514 -1364
  18. package/dist/lib/ledger.cjs +0 -1
  19. package/dist/lib/ledger.js +1 -2
  20. package/dist/lib/lifecycle-reconcile.cjs +0 -1
  21. package/dist/lib/lifecycle-reconcile.js +2 -3
  22. package/dist/{whoami-EANGN46Z.js → whoami-JKQQPABQ.js} +3 -4
  23. package/package.json +4 -3
  24. package/templates/cleargate-planning/.claude/agents/architect.md +4 -2
  25. package/templates/cleargate-planning/.claude/agents/developer.md +4 -11
  26. package/templates/cleargate-planning/.claude/agents/qa.md +14 -6
  27. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +2 -2
  28. package/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +19 -1
  29. package/templates/cleargate-planning/.cleargate/config.example.yml +16 -0
  30. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.deferred-verify.red.node.test.ts +245 -0
  31. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +227 -0
  32. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +5 -4
  33. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +75 -2
  34. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +48 -0
  35. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +57 -1
  36. package/templates/cleargate-planning/.cleargate/scripts/provision_worktree_config.sh +155 -0
  37. package/templates/cleargate-planning/.cleargate/scripts/qa_red_lint.mjs +380 -0
  38. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +34 -1
  39. package/templates/cleargate-planning/.cleargate/scripts/test/cr077_eviction.red.sh +113 -0
  40. package/templates/cleargate-planning/.cleargate/scripts/test/cr078_init.test.sh +309 -0
  41. package/templates/cleargate-planning/.cleargate/scripts/test/cr079_provision.red.sh +262 -0
  42. package/templates/cleargate-planning/.cleargate/scripts/test/cr080_wrapper.test.sh +177 -0
  43. package/templates/cleargate-planning/.cleargate/scripts/test/cr081_qa_red_lint.red.sh +348 -0
  44. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/.session-totals.json +1 -0
  45. package/templates/cleargate-planning/.cleargate/sprint-runs/_off-sprint/token-ledger.jsonl +27 -0
  46. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +17 -0
  47. package/templates/cleargate-planning/.cleargate/templates/story.md +1 -0
  48. package/templates/cleargate-planning/MANIFEST.json +71 -15
  49. package/dist/admin-api/index.cjs.map +0 -1
  50. package/dist/admin-api/index.js.map +0 -1
  51. package/dist/auth/factory.cjs.map +0 -1
  52. package/dist/auth/factory.js.map +0 -1
  53. package/dist/auth/require-token.cjs.map +0 -1
  54. package/dist/auth/require-token.js.map +0 -1
  55. package/dist/auth/token-store.cjs.map +0 -1
  56. package/dist/auth/token-store.js.map +0 -1
  57. package/dist/bootstrap-root-QKSA5V75.js.map +0 -1
  58. package/dist/chunk-5DI2Z3C2.js.map +0 -1
  59. package/dist/chunk-BTSZOEWC.js.map +0 -1
  60. package/dist/chunk-E3X7IE5E.js.map +0 -1
  61. package/dist/chunk-PDE37WFQ.js.map +0 -1
  62. package/dist/cli.cjs.map +0 -1
  63. package/dist/cli.js.map +0 -1
  64. package/dist/lib/ledger.cjs.map +0 -1
  65. package/dist/lib/ledger.js.map +0 -1
  66. package/dist/lib/lifecycle-reconcile.cjs.map +0 -1
  67. package/dist/lib/lifecycle-reconcile.js.map +0 -1
  68. package/dist/templates/cleargate-planning/.claude/agents/architect-reader.md +0 -61
  69. package/dist/templates/cleargate-planning/.claude/agents/architect-synth.md +0 -124
  70. package/dist/templates/cleargate-planning/.claude/agents/architect.md +0 -230
  71. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-contradict.md +0 -108
  72. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-ingest.md +0 -194
  73. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-lint.md +0 -261
  74. package/dist/templates/cleargate-planning/.claude/agents/cleargate-wiki-query.md +0 -143
  75. package/dist/templates/cleargate-planning/.claude/agents/developer.md +0 -185
  76. package/dist/templates/cleargate-planning/.claude/agents/devops.md +0 -257
  77. package/dist/templates/cleargate-planning/.claude/agents/qa.md +0 -171
  78. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +0 -274
  79. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +0 -209
  80. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +0 -33
  81. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +0 -58
  82. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +0 -19
  83. package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +0 -162
  84. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-autonomy.sh +0 -58
  85. package/dist/templates/cleargate-planning/.claude/hooks/pre-tool-use-task.sh +0 -148
  86. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +0 -75
  87. package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +0 -43
  88. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +0 -590
  89. package/dist/templates/cleargate-planning/.claude/settings.json +0 -68
  90. package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +0 -102
  91. package/dist/templates/cleargate-planning/.claude/skills/sprint-execution/SKILL.md +0 -742
  92. package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +0 -7
  93. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +0 -67
  94. package/dist/templates/cleargate-planning/.cleargate/config.yml +0 -18
  95. package/dist/templates/cleargate-planning/.cleargate/delivery/archive/.gitkeep +0 -0
  96. package/dist/templates/cleargate-planning/.cleargate/delivery/pending-sync/.gitkeep +0 -0
  97. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-enforcement.md +0 -551
  98. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +0 -878
  99. package/dist/templates/cleargate-planning/.cleargate/knowledge/mid-sprint-triage-rubric.md +0 -160
  100. package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +0 -213
  101. package/dist/templates/cleargate-planning/.cleargate/knowledge/sprint-closeout-checklist.md +0 -71
  102. package/dist/templates/cleargate-planning/.cleargate/scripts/_migrate-schema-v3.mjs +0 -120
  103. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +0 -265
  104. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +0 -1012
  105. package/dist/templates/cleargate-planning/.cleargate/scripts/collision_surface.sh +0 -114
  106. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +0 -62
  107. package/dist/templates/cleargate-planning/.cleargate/scripts/dedupe_frontmatter.mjs +0 -219
  108. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +0 -320
  109. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +0 -15
  110. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +0 -38
  111. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +0 -240
  112. package/dist/templates/cleargate-planning/.cleargate/scripts/launch_wave.mjs +0 -341
  113. package/dist/templates/cleargate-planning/.cleargate/scripts/lib/report-filename.mjs +0 -54
  114. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +0 -206
  115. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +0 -371
  116. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +0 -280
  117. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_doc_refresh.mjs +0 -378
  118. package/dist/templates/cleargate-planning/.cleargate/scripts/prep_qa_context.mjs +0 -888
  119. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +0 -209
  120. package/dist/templates/cleargate-planning/.cleargate/scripts/sprint_trends.mjs +0 -71
  121. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +0 -127
  122. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +0 -717
  123. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +0 -27
  124. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +0 -261
  125. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +0 -210
  126. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +0 -190
  127. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_prep_qa_context.sh +0 -482
  128. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +0 -327
  129. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +0 -261
  130. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +0 -246
  131. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +0 -111
  132. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +0 -184
  133. package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +0 -172
  134. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +0 -126
  135. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +0 -130
  136. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +0 -137
  137. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +0 -166
  138. package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +0 -111
  139. package/dist/templates/cleargate-planning/.cleargate/templates/initiative.md +0 -122
  140. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +0 -50
  141. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +0 -224
  142. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +0 -213
  143. package/dist/templates/cleargate-planning/CLAUDE.md +0 -66
  144. package/dist/templates/cleargate-planning/MANIFEST.json +0 -503
  145. package/dist/templates/synthesis/active-sprint.md +0 -30
  146. package/dist/templates/synthesis/open-gates.md +0 -38
  147. package/dist/templates/synthesis/product-state.md +0 -31
  148. package/dist/templates/synthesis/roadmap.md +0 -63
  149. package/dist/whoami-EANGN46Z.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/auth/factory.ts","../src/auth/keychain-store.ts","../src/auth/file-store.ts"],"sourcesContent":["import * as os from 'node:os';\nimport * as path from 'node:path';\nimport { KeychainTokenStore } from './keychain-store.js';\nimport { FileTokenStore } from './file-store.js';\nimport type { TokenStore, TokenStoreFactoryOptions } from './token-store.js';\n\nconst DEFAULT_KEYCHAIN_SERVICE = 'cleargate';\n\nfunction resolveFilePath(opts: TokenStoreFactoryOptions): string {\n if (opts.filePath) return opts.filePath;\n const home = os.homedir();\n if (!home) {\n throw new Error(\n 'Cannot determine home directory. Set opts.filePath explicitly or ensure os.homedir() returns a non-empty string.',\n );\n }\n return path.join(home, '.cleargate', 'auth.json');\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\n/**\n * Creates a TokenStore, selecting the keychain backend when available and\n * falling back to file storage with a stderr warning when the OS keychain\n * cannot be accessed.\n */\nexport async function createTokenStore(\n opts: TokenStoreFactoryOptions = {},\n): Promise<TokenStore> {\n const filePath = resolveFilePath(opts);\n const service = opts.keychainService ?? DEFAULT_KEYCHAIN_SERVICE;\n const warn = opts.warn ?? defaultWarn;\n\n // Short-circuit if backend is forced (test seam, skips probe)\n if (opts.forceBackend === 'file') {\n return new FileTokenStore(filePath);\n }\n if (opts.forceBackend === 'keychain') {\n return new KeychainTokenStore(service);\n }\n\n // Probe the keychain to determine availability\n try {\n const { Entry } = await import('@napi-rs/keyring');\n new Entry(service, '__cleargate_probe__').getPassword();\n // Probe succeeded (returned string | null cleanly) — use keychain\n return new KeychainTokenStore(service);\n } catch {\n // Constructor threw (native module load failed, libsecret missing on Linux)\n // OR getPassword() threw (dbus not running, prompt cancelled)\n // Either way, keychain is unavailable for this CLI invocation\n warn(\n `cleargate: OS keychain unavailable, falling back to file storage at ${filePath}. Run with --log-level=debug for details.`,\n );\n return new FileTokenStore(filePath);\n }\n}\n","import { Entry } from '@napi-rs/keyring';\nimport type { TokenStore } from './token-store.js';\n\nexport class KeychainTokenStore implements TokenStore {\n readonly backend = 'keychain' as const;\n\n constructor(private readonly service: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n new Entry(this.service, profile).setPassword(token);\n }\n\n async load(profile: string): Promise<string | null> {\n try {\n const result = new Entry(this.service, profile).getPassword();\n // getPassword() returns string | null per @napi-rs/keyring@1.2.0 index.d.ts:124\n // Despite the docstring claiming it throws NoEntry, the return type wins.\n // Handle both: null return AND potential thrown NoEntry (platform-specific).\n return result ?? null;\n } catch {\n // NoEntry or other keychain error — treat as absent\n return null;\n }\n }\n\n async remove(profile: string): Promise<void> {\n try {\n new Entry(this.service, profile).deletePassword();\n } catch {\n // Entry didn't exist or other keychain error — idempotent, swallow\n }\n }\n}\n","import * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport type { TokenStore } from './token-store.js';\n\nconst ProfileEntrySchema = z.object({ refreshToken: z.string().min(1) }).strict();\n\nexport const AuthFileSchema = z\n .object({\n version: z.literal(1),\n profiles: z.record(z.string().min(1), ProfileEntrySchema),\n })\n .strict();\n\ntype AuthFile = z.infer<typeof AuthFileSchema>;\n\nconst EMPTY_AUTH_FILE: AuthFile = { version: 1, profiles: {} };\n\nexport class FileTokenStore implements TokenStore {\n readonly backend = 'file' as const;\n\n constructor(private readonly filePath: string) {}\n\n async save(profile: string, token: string): Promise<void> {\n const current = await this.readFile();\n const updated: AuthFile = {\n ...current,\n profiles: {\n ...current.profiles,\n [profile]: { refreshToken: token },\n },\n };\n await this.writeFile(updated);\n }\n\n async load(profile: string): Promise<string | null> {\n const data = await this.readFile();\n return data.profiles[profile]?.refreshToken ?? null;\n }\n\n async remove(profile: string): Promise<void> {\n let current: AuthFile;\n try {\n current = await this.readFile();\n } catch {\n // File doesn't exist or unreadable — no-op since there's nothing to remove\n return;\n }\n if (!(profile in current.profiles)) {\n return; // Profile doesn't exist — idempotent\n }\n const { [profile]: _removed, ...rest } = current.profiles;\n const updated: AuthFile = { ...current, profiles: rest };\n await this.writeFile(updated);\n }\n\n private async readFile(): Promise<AuthFile> {\n let raw: string;\n try {\n raw = await fs.readFile(this.filePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return EMPTY_AUTH_FILE;\n }\n throw err;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `Failed to parse auth file at ${this.filePath}: invalid JSON`,\n );\n }\n\n const result = AuthFileSchema.safeParse(parsed);\n if (!result.success) {\n // Check for version mismatch specifically\n const versionCheck = (parsed as Record<string, unknown>)?.['version'];\n if (versionCheck !== 1) {\n throw new Error(\n `Invalid auth file at ${this.filePath}: unsupported version ${String(versionCheck)}. Please upgrade \\`cleargate\\` to read this file.`,\n );\n }\n throw new Error(\n `Invalid auth file at ${this.filePath}: ${result.error.message}`,\n );\n }\n\n return result.data;\n }\n\n private async writeFile(data: AuthFile): Promise<void> {\n const dir = path.dirname(this.filePath);\n await fs.mkdir(dir, { recursive: true, mode: 0o700 });\n // Explicit chmod after mkdir — mkdir only sets mode on newly created dirs\n await fs.chmod(dir, 0o700).catch(() => {\n // If chmod fails on existing dir, that's acceptable — we don't want to\n // surprise users who have set custom modes on ~/.cleargate/\n });\n\n const json = JSON.stringify(data, null, 2);\n const tmpPath = path.join(dir, '.auth.json.tmp');\n\n // Atomic write: write to tmp then rename to avoid partial-write corruption\n await fs.writeFile(tmpPath, json, { mode: 0o600 });\n // Explicit chmod after writeFile — writeFile only sets mode on file creation\n await fs.chmod(tmpPath, 0o600);\n await fs.rename(tmpPath, this.filePath);\n // After rename, chmod the final path to ensure it stays 0600\n await fs.chmod(this.filePath, 0o600);\n }\n}\n"],"mappings":"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","names":["path","Entry"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config.ts","../src/lib/membership.ts","../src/auth/acquire.ts"],"sourcesContent":["import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const ConfigSchema = z\n .object({\n mcpUrl: z.string().url().optional(),\n profile: z.string().min(1).default('default'),\n logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n })\n .strict();\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n/** Partial raw config used for each layer before merge */\ntype RawConfig = Partial<{\n mcpUrl: string | undefined;\n profile: string | undefined;\n logLevel: string | undefined;\n}>;\n\nexport interface LoadConfigOptions {\n flags?: RawConfig;\n env?: NodeJS.ProcessEnv;\n configPath?: string;\n}\n\n/**\n * Synchronously loads and merges config from all layers:\n * flags > env > config file > zod defaults\n */\nexport function loadConfig(opts: LoadConfigOptions = {}): Config {\n const {\n flags = {},\n env = process.env,\n configPath,\n } = opts;\n\n // Resolve config file path\n const resolvedConfigPath =\n configPath ??\n (() => {\n const home = os.homedir();\n if (!home) return null;\n return path.join(home, '.cleargate', 'config.json');\n })();\n\n // Layer: file\n let fileLayer: RawConfig = {};\n if (resolvedConfigPath) {\n try {\n const raw = fs.readFileSync(resolvedConfigPath, 'utf8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\n `Failed to parse config file at ${resolvedConfigPath}: invalid JSON`,\n );\n }\n // Validate file contents strictly (unknown keys will throw here)\n const fileResult = ConfigSchema.safeParse(parsed);\n if (!fileResult.success) {\n throw new Error(\n `Invalid config file at ${resolvedConfigPath}: ${fileResult.error.message}`,\n );\n }\n fileLayer = fileResult.data;\n } catch (err) {\n // Re-throw parse/validation errors; silently skip only ENOENT\n if (\n err instanceof Error &&\n 'code' in err &&\n (err as NodeJS.ErrnoException).code === 'ENOENT'\n ) {\n // file doesn't exist — skip silently\n } else {\n throw err;\n }\n }\n }\n\n // Layer: env\n const envLayer: RawConfig = {};\n if (env['CLEARGATE_MCP_URL']) {\n envLayer.mcpUrl = env['CLEARGATE_MCP_URL'];\n }\n if (env['CLEARGATE_PROFILE']) {\n envLayer.profile = env['CLEARGATE_PROFILE'];\n }\n if (env['CLEARGATE_LOG_LEVEL']) {\n envLayer.logLevel = env['CLEARGATE_LOG_LEVEL'];\n }\n\n // Merge: flags > env > file (start from {} so zod defaults fill in missing fields)\n const merged: Record<string, unknown> = {\n ...fileLayer,\n ...envLayer,\n ...(flags.mcpUrl !== undefined ? { mcpUrl: flags.mcpUrl } : {}),\n ...(flags.profile !== undefined ? { profile: flags.profile } : {}),\n ...(flags.logLevel !== undefined ? { logLevel: flags.logLevel } : {}),\n };\n\n // Remove undefined values so zod defaults apply properly\n for (const key of Object.keys(merged)) {\n if (merged[key] === undefined) {\n delete merged[key];\n }\n }\n\n const result = ConfigSchema.safeParse(merged);\n if (!result.success) {\n throw new Error(`Config validation failed: ${result.error.message}`);\n }\n\n return result.data;\n}\n\n/**\n * Asserts mcpUrl is present, throws a user-friendly error if not.\n */\nexport function requireMcpUrl(cfg: Config): string {\n if (cfg.mcpUrl === undefined) {\n throw new Error(\n 'mcpUrl not configured. Run `cleargate join <invite-url>` first.',\n );\n }\n return cfg.mcpUrl;\n}\n\nexport interface SaveConfigOptions {\n configPath?: string;\n}\n\n/**\n * Persist a partial update into ~/.cleargate/config.json.\n *\n * Reads the existing raw JSON (if present), shallow-merges `updates` on top, and\n * writes atomically with mode 0600. Unknown keys already in the file (e.g.\n * project_id, written by other surfaces) are preserved — strict Zod validation\n * is intentionally skipped here because admin-url.ts and other readers store\n * fields outside the strict schema.\n */\nexport function saveConfig(\n updates: Partial<{ mcpUrl: string; profile: string; logLevel: string }>,\n opts: SaveConfigOptions = {},\n): void {\n const home = os.homedir();\n if (!home) {\n throw new Error('Cannot determine home directory.');\n }\n const configPath =\n opts.configPath ?? path.join(home, '.cleargate', 'config.json');\n const dir = path.dirname(configPath);\n\n let existing: Record<string, unknown> = {};\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n const parsed = JSON.parse(raw);\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n existing = parsed as Record<string, unknown>;\n }\n } catch (err) {\n if (\n !(\n err instanceof Error &&\n 'code' in err &&\n (err as NodeJS.ErrnoException).code === 'ENOENT'\n )\n ) {\n // Treat parse errors as recoverable — overwrite rather than fail join.\n }\n }\n\n const merged: Record<string, unknown> = { ...existing };\n for (const [k, v] of Object.entries(updates)) {\n if (v !== undefined) merged[k] = v;\n }\n\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n fs.chmodSync(dir, 0o700);\n } catch {\n // existing dir with custom mode — leave alone\n }\n\n const tmpPath = path.join(dir, '.config.json.tmp');\n const json = JSON.stringify(merged, null, 2) + '\\n';\n fs.writeFileSync(tmpPath, json, { mode: 0o600 });\n fs.chmodSync(tmpPath, 0o600);\n fs.renameSync(tmpPath, configPath);\n fs.chmodSync(configPath, 0o600);\n}\n","/**\n * membership.ts — CR-011\n *\n * Single source of truth for ClearGate membership state detection.\n * Cheap-path: reads ~/.cleargate/auth.json, decodes the stored refresh JWT\n * (introspection only — no signature verification), checks expiry.\n *\n * Returns 'pre-member' on: file missing | malformed JSON | malformed JWT | exp <= now.\n * Returns 'member' with decoded claims otherwise.\n *\n * No network call. Used by whoami --json, preAction gating hook, doctor --session-start banner.\n */\n\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { Buffer } from 'node:buffer';\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\nexport type MembershipState =\n | { state: 'member'; email: string; project_id: string; expires_at: string }\n | { state: 'pre-member' };\n\nexport interface GetMembershipStateOpts {\n /** Profile name (default: 'default'). */\n profile?: string;\n /** Test seam: override the ~/.cleargate home directory. */\n cleargateHome?: string;\n /** Test seam: clock for expiry comparison (ms epoch). */\n now?: () => number;\n /**\n * BUG-031: working directory for per-repo join evidence check.\n * If set, getMembershipState checks for .cleargate/.join.json in this\n * directory before returning 'member'. If absent, returns 'pre-member'\n * regardless of global auth.json validity.\n * Default: process.cwd().\n */\n projectRoot?: string;\n}\n\n// ─── JWT decode helper (extracted from whoami.ts) ─────────────────────────────\n\n/**\n * Decode a JWT payload without verifying the signature (introspection only).\n * Returns null if the token is malformed.\n */\nexport function decodeJwtPayload(jwt: string): Record<string, unknown> | null {\n const parts = jwt.split('.');\n if (parts.length !== 3) return null;\n try {\n const json = Buffer.from(parts[1]!, 'base64url').toString('utf8');\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n// ─── Auth file schema (mirrors src/auth/file-store.ts without Zod) ────────────\n\ninterface AuthFile {\n version: number;\n profiles: Record<string, { refreshToken: string }>;\n}\n\nfunction readAuthFile(authFilePath: string): AuthFile | null {\n let raw: string;\n try {\n raw = fs.readFileSync(authFilePath, 'utf8');\n } catch {\n // ENOENT or permission error → pre-member\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n (parsed as Record<string, unknown>)['version'] !== 1 ||\n typeof (parsed as Record<string, unknown>)['profiles'] !== 'object'\n ) {\n return null;\n }\n return parsed as AuthFile;\n } catch {\n return null;\n }\n}\n\n// ─── Main export ─────────────────────────────────────────────────────────────\n\n// ─── Per-repo join file schema ────────────────────────────────────────────────\n\ninterface JoinFile {\n project_id: string;\n joined_at?: string;\n}\n\n/**\n * Read .cleargate/.join.json from the given project root.\n * Returns null on ENOENT or malformed JSON.\n */\nfunction readJoinFile(projectRoot: string): JoinFile | null {\n const joinFilePath = path.join(projectRoot, '.cleargate', '.join.json');\n try {\n const raw = fs.readFileSync(joinFilePath, 'utf8');\n const parsed = JSON.parse(raw) as unknown;\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n typeof (parsed as Record<string, unknown>)['project_id'] !== 'string'\n ) {\n return null;\n }\n return parsed as JoinFile;\n } catch {\n return null;\n }\n}\n\n/**\n * Detect membership state locally.\n *\n * Algorithm (BUG-031 fix: per-repo isolation):\n * 1. Resolve auth file path: <cleargateHome>/auth.json (default: ~/.cleargate/auth.json)\n * 2. Read & parse the file as an AuthFile { version, profiles }.\n * 3. Look up the profile's refreshToken.\n * 4. Decode the refreshToken as a JWT (base64url, no sig verify).\n * 5. Extract exp, sub (= email proxy), project_id from global auth.\n * 6. If exp <= now (ms) → pre-member.\n * 7. BUG-031: Check per-repo .cleargate/.join.json in projectRoot (default: process.cwd()).\n * If absent → pre-member regardless of global auth.json validity.\n * If present → use its project_id (per-repo binding beats global auth project_id).\n * 8. Otherwise → member with per-repo project_id.\n *\n * Cross-Cutting Rule #3: Identity (email) is global; project_id is per-repo.\n */\nexport function getMembershipState(opts?: GetMembershipStateOpts): MembershipState {\n const profile = opts?.profile ?? 'default';\n const nowMs = (opts?.now ?? Date.now)();\n const projectRoot = opts?.projectRoot ?? process.cwd();\n\n // Resolve the auth file path\n const home = opts?.cleargateHome ?? path.join(os.homedir(), '.cleargate');\n const authFilePath = path.join(home, 'auth.json');\n\n // Read the auth file\n const authFile = readAuthFile(authFilePath);\n if (authFile === null) {\n return { state: 'pre-member' };\n }\n\n // Look up the profile\n const profileEntry = authFile.profiles[profile];\n if (!profileEntry || typeof profileEntry.refreshToken !== 'string') {\n return { state: 'pre-member' };\n }\n\n // Decode the stored refresh token as a JWT\n const claims = decodeJwtPayload(profileEntry.refreshToken);\n if (claims === null) {\n return { state: 'pre-member' };\n }\n\n // Check expiry (exp is in seconds per JWT spec)\n const exp = claims['exp'];\n if (typeof exp !== 'number' || !Number.isFinite(exp)) {\n return { state: 'pre-member' };\n }\n const expMs = exp * 1000;\n if (expMs <= nowMs) {\n return { state: 'pre-member' };\n }\n\n // Extract email from global auth (identity is global — Cross-Cutting Rule #3)\n // sub is the member UUID; we use it as the email proxy since the JWT\n // doesn't carry a separate email field (flashcard: sub = member UUID, not email).\n const sub = claims['sub'];\n const email = typeof sub === 'string' ? sub : '';\n const expiresAt = new Date(expMs).toISOString();\n\n // BUG-031: per-repo isolation — check for per-repo .join.json in projectRoot.\n // A valid global auth token alone is NOT sufficient to report 'member'.\n // The working directory MUST have a per-repo join marker (written by `cleargate join`).\n const joinFile = readJoinFile(projectRoot);\n if (joinFile === null) {\n // No per-repo join evidence → pre-member regardless of global auth.json validity.\n return { state: 'pre-member' };\n }\n\n // Per-repo project_id beats global auth project_id (per-repo binding is authoritative).\n const projectId = joinFile.project_id;\n\n return { state: 'member', email, project_id: projectId, expires_at: expiresAt };\n}\n","/**\n * acquireAccessToken — resolve a short-lived MCP access-token JWT.\n *\n * Resolution order (first success wins):\n * 1. CLEARGATE_MCP_TOKEN env var — CI / dev short-circuit (assumed JWT, not verified locally).\n * 2. In-memory single-flight cache (keyed by `${profile}::${mcpUrl}`) — returns cached token\n * if still valid (expires 60s before access token's `exp` claim).\n * 3. Stored refresh token (keychain/file) + POST /auth/refresh → rotates refresh token, returns access token.\n *\n * Errors surface to caller with a clear message so command handlers can exit cleanly.\n *\n * Lives here (not in mcp-client.ts) because the refresh flow needs TokenStore + mcpUrl and\n * mcp-client.ts is kept thin (just: host, bearer, JSON-RPC).\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { createTokenStore } from './factory.js';\nimport type { TokenStore } from './token-store.js';\n\n// ── In-memory + on-disk single-flight cache ──────────────────────────────────\n// In-memory: process-local; naturally cleared when the Node CLI exits.\n// On-disk: ~/.cleargate/access-token.json (mode 0600), survives across CLI\n// invocations. Critical because each `cleargate` call is a fresh\n// process — without a disk cache every call hits keychain to load\n// the refresh token, then rotates it via /auth/refresh, which\n// re-saves to keychain and resets the macOS ACL → re-prompt loop.\n// Key: `${profile}::${mcpUrl}` — two profiles in same process never collide.\n// Env-token path (CLEARGATE_MCP_TOKEN) bypasses both caches entirely.\n\nconst CACHE = new Map<string, { accessToken: string; expiresAtMs: number }>();\n\ninterface DiskCacheEntry {\n accessToken: string;\n expiresAtMs: number;\n}\ninterface DiskCacheFile {\n version: 1;\n entries: Record<string, DiskCacheEntry>;\n}\n\nfunction defaultDiskCachePath(env: NodeJS.ProcessEnv = process.env): string | null {\n // Test override: setting CLEARGATE_DISK_CACHE_PATH=off disables the disk\n // cache entirely; setting it to a path uses that file instead of the home dir.\n const override = env['CLEARGATE_DISK_CACHE_PATH'];\n if (override === 'off') return null;\n if (typeof override === 'string' && override.length > 0) return override;\n const home = os.homedir();\n if (!home) return null;\n return path.join(home, '.cleargate', 'access-token.json');\n}\n\nfunction readDiskCache(filePath: string): DiskCacheFile {\n try {\n const raw = fs.readFileSync(filePath, 'utf8');\n const parsed = JSON.parse(raw) as unknown;\n if (\n parsed !== null &&\n typeof parsed === 'object' &&\n (parsed as { version?: unknown }).version === 1 &&\n typeof (parsed as { entries?: unknown }).entries === 'object' &&\n (parsed as { entries?: unknown }).entries !== null\n ) {\n return parsed as DiskCacheFile;\n }\n } catch {\n // ENOENT, parse error, schema mismatch — treat as empty\n }\n return { version: 1, entries: {} };\n}\n\nfunction writeDiskCache(filePath: string, data: DiskCacheFile): void {\n const dir = path.dirname(filePath);\n try {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n try {\n fs.chmodSync(dir, 0o700);\n } catch {\n // existing dir with custom mode — leave alone\n }\n const tmpPath = path.join(dir, '.access-token.json.tmp');\n fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\\n', { mode: 0o600 });\n fs.chmodSync(tmpPath, 0o600);\n fs.renameSync(tmpPath, filePath);\n fs.chmodSync(filePath, 0o600);\n } catch {\n // Disk-cache failures are non-fatal — the next call just refreshes again.\n }\n}\n\n/** Test seam: clear the in-memory acquire cache between tests. */\nexport function __resetAcquireCache(): void {\n CACHE.clear();\n}\n\n/** Decode a JWT payload without verifying the signature (CLI-side only). */\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const padded = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const json = Buffer.from(padded, 'base64').toString('utf8');\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nexport interface AcquireOptions {\n mcpUrl: string;\n profile: string;\n /** Force a fresh /auth/refresh even if the cache has a valid entry. */\n forceRefresh?: boolean;\n /** Test seam: overrides globalThis.fetch */\n fetch?: typeof globalThis.fetch;\n /** Test seam: overrides createTokenStore */\n createStore?: () => Promise<TokenStore>;\n /** Test seam: overrides process.env lookup */\n env?: NodeJS.ProcessEnv;\n /** Test seam: overrides Date.now() for expiry calculations. */\n now?: () => number;\n /** Test seam: overrides ~/.cleargate/access-token.json path. */\n diskCachePath?: string | null;\n}\n\nexport class AcquireError extends Error {\n constructor(\n message: string,\n public readonly code:\n | 'env_token'\n | 'no_stored_token'\n | 'invalid_token'\n | 'token_revoked'\n | 'transport'\n | 'unexpected_status'\n | 'bad_response',\n ) {\n super(message);\n this.name = 'AcquireError';\n }\n}\n\n/**\n * Returns a bearer string suitable for Authorization headers against /mcp and\n * /admin-api. Rotates the stored refresh token on success.\n */\nexport async function acquireAccessToken(opts: AcquireOptions): Promise<string> {\n const env = opts.env ?? process.env;\n const nowFn = opts.now ?? Date.now;\n\n // 1. Env short-circuit — CI / dev / manual paste. Assumed to be a valid JWT.\n // Env tokens are NOT cached — they have no known exp without decoding + the\n // env is set per-invocation in CI anyway.\n const envToken = env['CLEARGATE_MCP_TOKEN'];\n if (envToken && envToken.length > 0) {\n return envToken;\n }\n\n // 2a. In-memory cache check (skip when forceRefresh is set).\n const cacheKey = `${opts.profile}::${opts.mcpUrl}`;\n if (!opts.forceRefresh) {\n const cached = CACHE.get(cacheKey);\n if (cached && nowFn() < cached.expiresAtMs) {\n return cached.accessToken;\n }\n }\n\n // 2b. On-disk cache check — survives across CLI invocations and avoids\n // the keychain re-prompt loop that comes from per-call refresh-token\n // rotation. Disabled by passing diskCachePath: null (tests) or\n // CLEARGATE_DISK_CACHE_PATH=off in the real process env.\n // Note: consults process.env (not opts.env) because tests deliberately\n // pass empty `env: {}` to suppress CLEARGATE_MCP_TOKEN, but still want\n // the disk-cache override picked up from the test runner's env.\n const diskCachePath =\n opts.diskCachePath === undefined ? defaultDiskCachePath() : opts.diskCachePath;\n if (!opts.forceRefresh && diskCachePath) {\n const file = readDiskCache(diskCachePath);\n const entry = file.entries[cacheKey];\n if (entry && nowFn() < entry.expiresAtMs) {\n // Promote into in-memory cache for the rest of this process's lifetime.\n CACHE.set(cacheKey, entry);\n return entry.accessToken;\n }\n }\n\n // 3. Stored refresh token → POST /auth/refresh.\n const store = await (opts.createStore ?? createTokenStore)();\n const stored = await store.load(opts.profile);\n if (!stored) {\n throw new AcquireError(\n `No stored credentials for profile '${opts.profile}'. Run \\`cleargate join <invite-url>\\` first, or export CLEARGATE_MCP_TOKEN.`,\n 'no_stored_token',\n );\n }\n\n const fetchFn = opts.fetch ?? globalThis.fetch;\n\n let response: Response;\n try {\n response = await fetchFn(`${opts.mcpUrl}/auth/refresh`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ refresh_token: stored }),\n });\n } catch (err) {\n throw new AcquireError(\n `cannot reach ${opts.mcpUrl} (${err instanceof Error ? err.message : String(err)})`,\n 'transport',\n );\n }\n\n if (response.status === 401) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n if (body.error === 'token_revoked') {\n throw new AcquireError(\n 'refresh token was revoked. Run `cleargate join <invite-url>` to re-authenticate.',\n 'token_revoked',\n );\n }\n throw new AcquireError(\n 'refresh token is invalid or expired. Run `cleargate join <invite-url>` to re-authenticate.',\n 'invalid_token',\n );\n }\n\n if (!response.ok) {\n throw new AcquireError(`unexpected status ${response.status} from /auth/refresh`, 'unexpected_status');\n }\n\n const body = (await response.json().catch(() => null)) as\n | { access_token?: unknown; refresh_token?: unknown }\n | null;\n if (\n !body ||\n typeof body.access_token !== 'string' ||\n typeof body.refresh_token !== 'string' ||\n body.access_token.length === 0 ||\n body.refresh_token.length === 0\n ) {\n throw new AcquireError('server returned unexpected /auth/refresh response shape', 'bad_response');\n }\n\n // Rotate — store the new refresh token so the next call uses a fresh jti.\n await store.save(opts.profile, body.refresh_token);\n\n const accessToken = body.access_token;\n\n // 4. Cache the new access token (expire 60s before the JWT exp claim) in\n // BOTH the in-memory map and on disk. The disk cache is what stops the\n // keychain re-prompt loop on subsequent CLI invocations.\n const payload = decodeJwtPayload(accessToken);\n const exp = payload?.exp;\n if (typeof exp === 'number' && Number.isFinite(exp)) {\n const expiresAtMs = (exp - 60) * 1000;\n const entry: DiskCacheEntry = { accessToken, expiresAtMs };\n CACHE.set(cacheKey, entry);\n if (diskCachePath) {\n const file = readDiskCache(diskCachePath);\n file.entries[cacheKey] = entry;\n writeDiskCache(diskCachePath, file);\n }\n }\n // If exp is missing or non-numeric, do NOT cache — next call will re-refresh.\n\n return accessToken;\n}\n"],"mappings":";;;;;;;;;;;AAAA;AAAA,YAAY,QAAQ;AACpB,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,SAAS,SAAS;AAEX,IAAM,eAAe,EACzB,OAAO;AAAA,EACN,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,EAClC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,SAAS;AAAA,EAC5C,UAAU,EAAE,KAAK,CAAC,SAAS,QAAQ,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;AACrE,CAAC,EACA,OAAO;AAqBH,SAAS,WAAW,OAA0B,CAAC,GAAW;AAC/D,QAAM;AAAA,IACJ,QAAQ,CAAC;AAAA,IACT,MAAM,QAAQ;AAAA,IACd;AAAA,EACF,IAAI;AAGJ,QAAM,qBACJ,eACC,MAAM;AACL,UAAM,OAAU,WAAQ;AACxB,QAAI,CAAC,KAAM,QAAO;AAClB,WAAY,UAAK,MAAM,cAAc,aAAa;AAAA,EACpD,GAAG;AAGL,MAAI,YAAuB,CAAC;AAC5B,MAAI,oBAAoB;AACtB,QAAI;AACF,YAAM,MAAS,gBAAa,oBAAoB,MAAM;AACtD,UAAI;AACJ,UAAI;AACF,iBAAS,KAAK,MAAM,GAAG;AAAA,MACzB,QAAQ;AACN,cAAM,IAAI;AAAA,UACR,kCAAkC,kBAAkB;AAAA,QACtD;AAAA,MACF;AAEA,YAAM,aAAa,aAAa,UAAU,MAAM;AAChD,UAAI,CAAC,WAAW,SAAS;AACvB,cAAM,IAAI;AAAA,UACR,0BAA0B,kBAAkB,KAAK,WAAW,MAAM,OAAO;AAAA,QAC3E;AAAA,MACF;AACA,kBAAY,WAAW;AAAA,IACzB,SAAS,KAAK;AAEZ,UACE,eAAe,SACf,UAAU,OACT,IAA8B,SAAS,UACxC;AAAA,MAEF,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,WAAsB,CAAC;AAC7B,MAAI,IAAI,mBAAmB,GAAG;AAC5B,aAAS,SAAS,IAAI,mBAAmB;AAAA,EAC3C;AACA,MAAI,IAAI,mBAAmB,GAAG;AAC5B,aAAS,UAAU,IAAI,mBAAmB;AAAA,EAC5C;AACA,MAAI,IAAI,qBAAqB,GAAG;AAC9B,aAAS,WAAW,IAAI,qBAAqB;AAAA,EAC/C;AAGA,QAAM,SAAkC;AAAA,IACtC,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,IAC7D,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChE,GAAI,MAAM,aAAa,SAAY,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,EACrE;AAGA,aAAW,OAAO,OAAO,KAAK,MAAM,GAAG;AACrC,QAAI,OAAO,GAAG,MAAM,QAAW;AAC7B,aAAO,OAAO,GAAG;AAAA,IACnB;AAAA,EACF;AAEA,QAAM,SAAS,aAAa,UAAU,MAAM;AAC5C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,6BAA6B,OAAO,MAAM,OAAO,EAAE;AAAA,EACrE;AAEA,SAAO,OAAO;AAChB;AAKO,SAAS,cAAc,KAAqB;AACjD,MAAI,IAAI,WAAW,QAAW;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI;AACb;AAeO,SAAS,WACd,SACA,OAA0B,CAAC,GACrB;AACN,QAAM,OAAU,WAAQ;AACxB,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,QAAM,aACJ,KAAK,cAAmB,UAAK,MAAM,cAAc,aAAa;AAChE,QAAM,MAAW,aAAQ,UAAU;AAEnC,MAAI,WAAoC,CAAC;AACzC,MAAI;AACF,UAAM,MAAS,gBAAa,YAAY,MAAM;AAC9C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC3E,iBAAW;AAAA,IACb;AAAA,EACF,SAAS,KAAK;AACZ,QACE,EACE,eAAe,SACf,UAAU,OACT,IAA8B,SAAS,WAE1C;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,SAAkC,EAAE,GAAG,SAAS;AACtD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC5C,QAAI,MAAM,OAAW,QAAO,CAAC,IAAI;AAAA,EACnC;AAEA,EAAG,aAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAClD,MAAI;AACF,IAAG,aAAU,KAAK,GAAK;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,QAAM,UAAe,UAAK,KAAK,kBAAkB;AACjD,QAAM,OAAO,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAC/C,EAAG,iBAAc,SAAS,MAAM,EAAE,MAAM,IAAM,CAAC;AAC/C,EAAG,aAAU,SAAS,GAAK;AAC3B,EAAG,cAAW,SAAS,UAAU;AACjC,EAAG,aAAU,YAAY,GAAK;AAChC;;;ACjMA;AAaA,YAAYA,SAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,SAAS,UAAAC,eAAc;AA+BhB,SAAS,iBAAiB,KAA6C;AAC5E,QAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI;AACF,UAAM,OAAOA,QAAO,KAAK,MAAM,CAAC,GAAI,WAAW,EAAE,SAAS,MAAM;AAChE,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,aAAa,cAAuC;AAC3D,MAAI;AACJ,MAAI;AACF,UAAS,iBAAa,cAAc,MAAM;AAAA,EAC5C,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACE,OAAO,WAAW,YAClB,WAAW,QACV,OAAmC,SAAS,MAAM,KACnD,OAAQ,OAAmC,UAAU,MAAM,UAC3D;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeA,SAAS,aAAa,aAAsC;AAC1D,QAAM,eAAoB,WAAK,aAAa,cAAc,YAAY;AACtE,MAAI;AACF,UAAM,MAAS,iBAAa,cAAc,MAAM;AAChD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACE,OAAO,WAAW,YAClB,WAAW,QACX,OAAQ,OAAmC,YAAY,MAAM,UAC7D;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAmBO,SAAS,mBAAmB,MAAgD;AACjF,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,SAAS,MAAM,OAAO,KAAK,KAAK;AACtC,QAAM,cAAc,MAAM,eAAe,QAAQ,IAAI;AAGrD,QAAM,OAAO,MAAM,iBAAsB,WAAQ,YAAQ,GAAG,YAAY;AACxE,QAAM,eAAoB,WAAK,MAAM,WAAW;AAGhD,QAAM,WAAW,aAAa,YAAY;AAC1C,MAAI,aAAa,MAAM;AACrB,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AAGA,QAAM,eAAe,SAAS,SAAS,OAAO;AAC9C,MAAI,CAAC,gBAAgB,OAAO,aAAa,iBAAiB,UAAU;AAClE,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AAGA,QAAM,SAAS,iBAAiB,aAAa,YAAY;AACzD,MAAI,WAAW,MAAM;AACnB,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AAGA,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,GAAG;AACpD,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AACA,QAAM,QAAQ,MAAM;AACpB,MAAI,SAAS,OAAO;AAClB,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AAKA,QAAM,MAAM,OAAO,KAAK;AACxB,QAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM;AAC9C,QAAM,YAAY,IAAI,KAAK,KAAK,EAAE,YAAY;AAK9C,QAAM,WAAW,aAAa,WAAW;AACzC,MAAI,aAAa,MAAM;AAErB,WAAO,EAAE,OAAO,aAAa;AAAA,EAC/B;AAGA,QAAM,YAAY,SAAS;AAE3B,SAAO,EAAE,OAAO,UAAU,OAAO,YAAY,WAAW,YAAY,UAAU;AAChF;;;AClMA;AAcA,YAAYC,SAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AActB,IAAM,QAAQ,oBAAI,IAA0D;AAW5E,SAAS,qBAAqB,MAAyB,QAAQ,KAAoB;AAGjF,QAAM,WAAW,IAAI,2BAA2B;AAChD,MAAI,aAAa,MAAO,QAAO;AAC/B,MAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,QAAO;AAChE,QAAM,OAAU,YAAQ;AACxB,MAAI,CAAC,KAAM,QAAO;AAClB,SAAY,WAAK,MAAM,cAAc,mBAAmB;AAC1D;AAEA,SAAS,cAAc,UAAiC;AACtD,MAAI;AACF,UAAM,MAAS,iBAAa,UAAU,MAAM;AAC5C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACE,WAAW,QACX,OAAO,WAAW,YACjB,OAAiC,YAAY,KAC9C,OAAQ,OAAiC,YAAY,YACpD,OAAiC,YAAY,MAC9C;AACA,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,EAAE,SAAS,GAAG,SAAS,CAAC,EAAE;AACnC;AAEA,SAAS,eAAe,UAAkB,MAA2B;AACnE,QAAM,MAAW,cAAQ,QAAQ;AACjC,MAAI;AACF,IAAG,cAAU,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAClD,QAAI;AACF,MAAG,cAAU,KAAK,GAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,UAAM,UAAe,WAAK,KAAK,wBAAwB;AACvD,IAAG,kBAAc,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,EAAE,MAAM,IAAM,CAAC;AAC/E,IAAG,cAAU,SAAS,GAAK;AAC3B,IAAG,eAAW,SAAS,QAAQ;AAC/B,IAAG,cAAU,UAAU,GAAK;AAAA,EAC9B,QAAQ;AAAA,EAER;AACF;AAQA,SAASC,kBAAiB,OAA+C;AACvE,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,UAAM,SAAS,MAAM,CAAC,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC5D,UAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,MAAM;AAC1D,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAmBO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YACE,SACgB,MAQhB;AACA,UAAM,OAAO;AATG;AAUhB,SAAK,OAAO;AAAA,EACd;AAAA,EAXkB;AAYpB;AAMA,eAAsB,mBAAmB,MAAuC;AAC9E,QAAM,MAAM,KAAK,OAAO,QAAQ;AAChC,QAAM,QAAQ,KAAK,OAAO,KAAK;AAK/B,QAAM,WAAW,IAAI,qBAAqB;AAC1C,MAAI,YAAY,SAAS,SAAS,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,GAAG,KAAK,OAAO,KAAK,KAAK,MAAM;AAChD,MAAI,CAAC,KAAK,cAAc;AACtB,UAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,QAAI,UAAU,MAAM,IAAI,OAAO,aAAa;AAC1C,aAAO,OAAO;AAAA,IAChB;AAAA,EACF;AASA,QAAM,gBACJ,KAAK,kBAAkB,SAAY,qBAAqB,IAAI,KAAK;AACnE,MAAI,CAAC,KAAK,gBAAgB,eAAe;AACvC,UAAM,OAAO,cAAc,aAAa;AACxC,UAAM,QAAQ,KAAK,QAAQ,QAAQ;AACnC,QAAI,SAAS,MAAM,IAAI,MAAM,aAAa;AAExC,YAAM,IAAI,UAAU,KAAK;AACzB,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AAGA,QAAM,QAAQ,OAAO,KAAK,eAAe,kBAAkB;AAC3D,QAAM,SAAS,MAAM,MAAM,KAAK,KAAK,OAAO;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR,sCAAsC,KAAK,OAAO;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,KAAK,SAAS,WAAW;AAEzC,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,QAAQ,GAAG,KAAK,MAAM,iBAAiB;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,eAAe,OAAO,CAAC;AAAA,IAChD,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,gBAAgB,KAAK,MAAM,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAMC,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,QAAIA,MAAK,UAAU,iBAAiB;AAClC,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,aAAa,qBAAqB,SAAS,MAAM,uBAAuB,mBAAmB;AAAA,EACvG;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,IAAI;AAGpD,MACE,CAAC,QACD,OAAO,KAAK,iBAAiB,YAC7B,OAAO,KAAK,kBAAkB,YAC9B,KAAK,aAAa,WAAW,KAC7B,KAAK,cAAc,WAAW,GAC9B;AACA,UAAM,IAAI,aAAa,2DAA2D,cAAc;AAAA,EAClG;AAGA,QAAM,MAAM,KAAK,KAAK,SAAS,KAAK,aAAa;AAEjD,QAAM,cAAc,KAAK;AAKzB,QAAM,UAAUD,kBAAiB,WAAW;AAC5C,QAAM,MAAM,SAAS;AACrB,MAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG,GAAG;AACnD,UAAM,eAAe,MAAM,MAAM;AACjC,UAAM,QAAwB,EAAE,aAAa,YAAY;AACzD,UAAM,IAAI,UAAU,KAAK;AACzB,QAAI,eAAe;AACjB,YAAM,OAAO,cAAc,aAAa;AACxC,WAAK,QAAQ,QAAQ,IAAI;AACzB,qBAAe,eAAe,IAAI;AAAA,IACpC;AAAA,EACF;AAGA,SAAO;AACT;","names":["fs","os","path","Buffer","fs","os","path","decodeJwtPayload","body"]}