cleargate 0.2.1 → 0.3.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/MANIFEST.json +58 -16
  3. package/dist/admin-api/index.cjs +88 -1
  4. package/dist/admin-api/index.cjs.map +1 -1
  5. package/dist/admin-api/index.d.cts +105 -1
  6. package/dist/admin-api/index.d.ts +105 -1
  7. package/dist/admin-api/index.js +77 -1
  8. package/dist/admin-api/index.js.map +1 -1
  9. package/dist/bootstrap-root-FGWDICDT.js +130 -0
  10. package/dist/bootstrap-root-FGWDICDT.js.map +1 -0
  11. package/dist/chunk-OM4FAEA7.js +184 -0
  12. package/dist/chunk-OM4FAEA7.js.map +1 -0
  13. package/dist/cli.cjs +7980 -3956
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +3980 -466
  16. package/dist/cli.js.map +1 -1
  17. package/dist/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  18. package/dist/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  19. package/dist/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  20. package/dist/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  21. package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  22. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  23. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  24. package/dist/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  25. package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  26. package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  27. package/dist/templates/cleargate-planning/.claude/settings.json +11 -0
  28. package/dist/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  29. package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  30. package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  31. package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  32. package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  33. package/dist/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  34. package/dist/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  35. package/dist/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  36. package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  37. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  38. package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  39. package/dist/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  40. package/dist/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  41. package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  42. package/dist/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  43. package/dist/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  44. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  45. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  46. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  47. package/dist/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  48. package/dist/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  49. package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  50. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  51. package/dist/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  52. package/dist/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  53. package/dist/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  54. package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  55. package/dist/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  56. package/dist/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  57. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  58. package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  59. package/dist/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  60. package/dist/templates/cleargate-planning/CLAUDE.md +2 -0
  61. package/dist/templates/cleargate-planning/MANIFEST.json +58 -16
  62. package/dist/whoami-CX7CXJD5.js +76 -0
  63. package/dist/whoami-CX7CXJD5.js.map +1 -0
  64. package/package.json +6 -2
  65. package/templates/cleargate-planning/.claude/agents/architect.md +72 -0
  66. package/templates/cleargate-planning/.claude/agents/developer.md +45 -3
  67. package/templates/cleargate-planning/.claude/agents/qa.md +7 -3
  68. package/templates/cleargate-planning/.claude/agents/reporter.md +71 -89
  69. package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +204 -0
  70. package/templates/cleargate-planning/.claude/hooks/pre-commit-surface-gate.sh +10 -0
  71. package/templates/cleargate-planning/.claude/hooks/pre-commit-test-ratchet.sh +58 -0
  72. package/templates/cleargate-planning/.claude/hooks/pre-commit.sh +19 -0
  73. package/templates/cleargate-planning/.claude/hooks/session-start.sh +51 -0
  74. package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +1 -1
  75. package/templates/cleargate-planning/.claude/settings.json +11 -0
  76. package/templates/cleargate-planning/.cleargate/config.example.yml +37 -0
  77. package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +407 -0
  78. package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +146 -0
  79. package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +250 -0
  80. package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +57 -0
  81. package/templates/cleargate-planning/.cleargate/scripts/file_surface_diff.sh +320 -0
  82. package/templates/cleargate-planning/.cleargate/scripts/gate-checks.json +15 -0
  83. package/templates/cleargate-planning/.cleargate/scripts/init_gate_config.sh +38 -0
  84. package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +187 -0
  85. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +132 -0
  86. package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +307 -0
  87. package/templates/cleargate-planning/.cleargate/scripts/prefill_report.mjs +280 -0
  88. package/templates/cleargate-planning/.cleargate/scripts/run_script.sh +123 -0
  89. package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +110 -0
  90. package/templates/cleargate-planning/.cleargate/scripts/suggest_improvements.mjs +247 -0
  91. package/templates/cleargate-planning/.cleargate/scripts/surface-whitelist.txt +27 -0
  92. package/templates/cleargate-planning/.cleargate/scripts/test/test_assert_story_files.sh +261 -0
  93. package/templates/cleargate-planning/.cleargate/scripts/test/test_file_surface.sh +210 -0
  94. package/templates/cleargate-planning/.cleargate/scripts/test/test_flashcard_gate.sh +190 -0
  95. package/templates/cleargate-planning/.cleargate/scripts/test/test_test_ratchet.sh +327 -0
  96. package/templates/cleargate-planning/.cleargate/scripts/test_ratchet.mjs +261 -0
  97. package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +154 -0
  98. package/templates/cleargate-planning/.cleargate/scripts/validate_bounce_readiness.mjs +111 -0
  99. package/templates/cleargate-planning/.cleargate/scripts/validate_state.mjs +164 -0
  100. package/templates/cleargate-planning/.cleargate/templates/Bug.md +9 -0
  101. package/templates/cleargate-planning/.cleargate/templates/CR.md +9 -0
  102. package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +29 -3
  103. package/templates/cleargate-planning/.cleargate/templates/epic.md +9 -0
  104. package/templates/cleargate-planning/.cleargate/templates/proposal.md +9 -0
  105. package/templates/cleargate-planning/.cleargate/templates/sprint_context.md +42 -0
  106. package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +175 -0
  107. package/templates/cleargate-planning/.cleargate/templates/story.md +19 -0
  108. package/templates/cleargate-planning/CLAUDE.md +2 -0
  109. package/templates/cleargate-planning/MANIFEST.json +58 -16
@@ -26,6 +26,7 @@ declare const MemberSchema: z.ZodObject<{
26
26
  status: z.ZodEnum<{
27
27
  pending: "pending";
28
28
  active: "active";
29
+ expired: "expired";
29
30
  }>;
30
31
  }, z.core.$strict>;
31
32
  type Member = z.infer<typeof MemberSchema>;
@@ -40,6 +41,7 @@ declare const InviteCreatedSchema: z.ZodObject<{
40
41
  status: z.ZodEnum<{
41
42
  pending: "pending";
42
43
  active: "active";
44
+ expired: "expired";
43
45
  }>;
44
46
  }, z.core.$strict>;
45
47
  invite_url: z.ZodString;
@@ -68,11 +70,113 @@ declare const TokenIssuedSchema: z.ZodObject<{
68
70
  token: z.ZodString;
69
71
  }, z.core.$strict>;
70
72
  type TokenIssued = z.infer<typeof TokenIssuedSchema>;
73
+ declare const AuthExchangeResponseSchema: z.ZodObject<{
74
+ admin_token: z.ZodString;
75
+ expires_at: z.ZodString;
76
+ }, z.core.$strict>;
77
+ type AuthExchangeResponse = z.infer<typeof AuthExchangeResponseSchema>;
71
78
  declare const ErrorBodySchema: z.ZodObject<{
72
79
  error: z.ZodString;
73
80
  details: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
74
81
  }, z.core.$strict>;
75
82
  type ErrorBody = z.infer<typeof ErrorBodySchema>;
83
+ declare const ItemSummarySchema: z.ZodObject<{
84
+ id: z.ZodString;
85
+ cleargate_id: z.ZodString;
86
+ type: z.ZodString;
87
+ title: z.ZodString;
88
+ status: z.ZodString;
89
+ remote_id: z.ZodNullable<z.ZodString>;
90
+ last_pushed_at: z.ZodNullable<z.ZodString>;
91
+ pushed_by_member_id: z.ZodNullable<z.ZodString>;
92
+ version: z.ZodNumber;
93
+ updated_at: z.ZodString;
94
+ current_payload: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
95
+ }, z.core.$strict>;
96
+ type ItemSummary = z.infer<typeof ItemSummarySchema>;
97
+ declare const ItemsListResponseSchema: z.ZodObject<{
98
+ items: z.ZodArray<z.ZodObject<{
99
+ id: z.ZodString;
100
+ cleargate_id: z.ZodString;
101
+ type: z.ZodString;
102
+ title: z.ZodString;
103
+ status: z.ZodString;
104
+ remote_id: z.ZodNullable<z.ZodString>;
105
+ last_pushed_at: z.ZodNullable<z.ZodString>;
106
+ pushed_by_member_id: z.ZodNullable<z.ZodString>;
107
+ version: z.ZodNumber;
108
+ updated_at: z.ZodString;
109
+ current_payload: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
110
+ }, z.core.$strict>>;
111
+ next_cursor: z.ZodNullable<z.ZodString>;
112
+ }, z.core.$strict>;
113
+ type ItemsListResponse = z.infer<typeof ItemsListResponseSchema>;
114
+ declare const ItemVersionSchema: z.ZodObject<{
115
+ version: z.ZodNumber;
116
+ pushed_by_member_id: z.ZodNullable<z.ZodString>;
117
+ pushed_at: z.ZodString;
118
+ status: z.ZodString;
119
+ diff_summary: z.ZodNullable<z.ZodString>;
120
+ }, z.core.$strict>;
121
+ type ItemVersion = z.infer<typeof ItemVersionSchema>;
122
+ declare const ItemVersionsResponseSchema: z.ZodObject<{
123
+ versions: z.ZodArray<z.ZodObject<{
124
+ version: z.ZodNumber;
125
+ pushed_by_member_id: z.ZodNullable<z.ZodString>;
126
+ pushed_at: z.ZodString;
127
+ status: z.ZodString;
128
+ diff_summary: z.ZodNullable<z.ZodString>;
129
+ }, z.core.$strict>>;
130
+ }, z.core.$strict>;
131
+ type ItemVersionsResponse = z.infer<typeof ItemVersionsResponseSchema>;
132
+ declare const DeviceStartResponseSchema: z.ZodObject<{
133
+ device_code: z.ZodString;
134
+ user_code: z.ZodString;
135
+ verification_uri: z.ZodString;
136
+ expires_in: z.ZodNumber;
137
+ interval: z.ZodNumber;
138
+ }, z.core.$strict>;
139
+ type DeviceStartResponse = z.infer<typeof DeviceStartResponseSchema>;
140
+ declare const DevicePollPendingResponseSchema: z.ZodObject<{
141
+ pending: z.ZodLiteral<true>;
142
+ retry_after: z.ZodOptional<z.ZodNumber>;
143
+ }, z.core.$strict>;
144
+ type DevicePollPendingResponse = z.infer<typeof DevicePollPendingResponseSchema>;
145
+ declare const DevicePollSuccessResponseSchema: z.ZodObject<{
146
+ pending: z.ZodLiteral<false>;
147
+ admin_token: z.ZodString;
148
+ expires_at: z.ZodString;
149
+ admin_user_id: z.ZodString;
150
+ }, z.core.$strict>;
151
+ type DevicePollSuccessResponse = z.infer<typeof DevicePollSuccessResponseSchema>;
152
+ declare const AdminUserSchema: z.ZodObject<{
153
+ id: z.ZodString;
154
+ github_handle: z.ZodString;
155
+ github_user_id: z.ZodNullable<z.ZodString>;
156
+ is_root: z.ZodBoolean;
157
+ disabled_at: z.ZodNullable<z.ZodString>;
158
+ created_at: z.ZodString;
159
+ created_by: z.ZodNullable<z.ZodString>;
160
+ }, z.core.$strict>;
161
+ type AdminUser = z.infer<typeof AdminUserSchema>;
162
+ declare const AdminUsersListResponseSchema: z.ZodObject<{
163
+ admin_users: z.ZodArray<z.ZodObject<{
164
+ id: z.ZodString;
165
+ github_handle: z.ZodString;
166
+ github_user_id: z.ZodNullable<z.ZodString>;
167
+ is_root: z.ZodBoolean;
168
+ disabled_at: z.ZodNullable<z.ZodString>;
169
+ created_at: z.ZodString;
170
+ created_by: z.ZodNullable<z.ZodString>;
171
+ }, z.core.$strict>>;
172
+ }, z.core.$strict>;
173
+ type AdminUsersListResponse = z.infer<typeof AdminUsersListResponseSchema>;
174
+ declare const UsersMeResponseSchema: z.ZodObject<{
175
+ id: z.ZodString;
176
+ github_handle: z.ZodString;
177
+ is_root: z.ZodBoolean;
178
+ }, z.core.$strict>;
179
+ type UsersMeResponse = z.infer<typeof UsersMeResponseSchema>;
76
180
 
77
181
  interface AdminApiClientOptions {
78
182
  baseUrl: string;
@@ -132,4 +236,4 @@ interface AdminAuth {
132
236
  }
133
237
  declare function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth;
134
238
 
135
- export { type AdminApiClient, type AdminApiClientOptions, AdminApiError, type AdminAuth, AdminAuthFileSchema, type ErrorBody, ErrorBodySchema, type InviteCreated, InviteCreatedSchema, type LoadAdminAuthOptions, type Member, MemberSchema, type Project, ProjectSchema, type TokenIssued, TokenIssuedSchema, type TokenMeta, TokenMetaSchema, createAdminApiClient, loadAdminAuth, redactSensitive };
239
+ export { type AdminApiClient, type AdminApiClientOptions, AdminApiError, type AdminAuth, AdminAuthFileSchema, type AdminUser, AdminUserSchema, type AdminUsersListResponse, AdminUsersListResponseSchema, type AuthExchangeResponse, AuthExchangeResponseSchema, type DevicePollPendingResponse, DevicePollPendingResponseSchema, type DevicePollSuccessResponse, DevicePollSuccessResponseSchema, type DeviceStartResponse, DeviceStartResponseSchema, type ErrorBody, ErrorBodySchema, type InviteCreated, InviteCreatedSchema, type ItemSummary, ItemSummarySchema, type ItemVersion, ItemVersionSchema, type ItemVersionsResponse, ItemVersionsResponseSchema, type ItemsListResponse, ItemsListResponseSchema, type LoadAdminAuthOptions, type Member, MemberSchema, type Project, ProjectSchema, type TokenIssued, TokenIssuedSchema, type TokenMeta, TokenMetaSchema, type UsersMeResponse, UsersMeResponseSchema, createAdminApiClient, loadAdminAuth, redactSensitive };
@@ -30,7 +30,7 @@ var MemberSchema = z.object({
30
30
  role: z.string(),
31
31
  display_name: z.string().nullable().optional(),
32
32
  created_at: z.string(),
33
- status: z.enum(["pending", "active"])
33
+ status: z.enum(["pending", "active", "expired"])
34
34
  }).strict();
35
35
  var InviteCreatedSchema = z.object({
36
36
  member: MemberSchema,
@@ -57,10 +57,75 @@ var TokenIssuedSchema = z.object({
57
57
  revoked_at: z.string().nullable().optional(),
58
58
  token: z.string()
59
59
  }).strict();
60
+ var AuthExchangeResponseSchema = z.object({
61
+ admin_token: z.string(),
62
+ expires_at: z.string()
63
+ }).strict();
60
64
  var ErrorBodySchema = z.object({
61
65
  error: z.string(),
62
66
  details: z.record(z.string(), z.unknown()).optional()
63
67
  }).strict();
68
+ var ItemSummarySchema = z.object({
69
+ id: z.string(),
70
+ cleargate_id: z.string(),
71
+ type: z.string(),
72
+ title: z.string(),
73
+ status: z.string(),
74
+ remote_id: z.string().nullable(),
75
+ last_pushed_at: z.string().nullable(),
76
+ pushed_by_member_id: z.string().nullable(),
77
+ version: z.number().int(),
78
+ updated_at: z.string(),
79
+ current_payload: z.record(z.string(), z.unknown()).default({})
80
+ }).strict();
81
+ var ItemsListResponseSchema = z.object({
82
+ items: z.array(ItemSummarySchema),
83
+ next_cursor: z.string().nullable()
84
+ }).strict();
85
+ var ItemVersionSchema = z.object({
86
+ version: z.number().int(),
87
+ pushed_by_member_id: z.string().nullable(),
88
+ pushed_at: z.string(),
89
+ status: z.string(),
90
+ diff_summary: z.string().nullable()
91
+ }).strict();
92
+ var ItemVersionsResponseSchema = z.object({
93
+ versions: z.array(ItemVersionSchema)
94
+ }).strict();
95
+ var DeviceStartResponseSchema = z.object({
96
+ device_code: z.string(),
97
+ user_code: z.string(),
98
+ verification_uri: z.string(),
99
+ expires_in: z.number().int(),
100
+ interval: z.number().int()
101
+ }).strict();
102
+ var DevicePollPendingResponseSchema = z.object({
103
+ pending: z.literal(true),
104
+ retry_after: z.number().int().optional()
105
+ }).strict();
106
+ var DevicePollSuccessResponseSchema = z.object({
107
+ pending: z.literal(false),
108
+ admin_token: z.string(),
109
+ expires_at: z.string(),
110
+ admin_user_id: z.string()
111
+ }).strict();
112
+ var AdminUserSchema = z.object({
113
+ id: z.string(),
114
+ github_handle: z.string(),
115
+ github_user_id: z.string().nullable(),
116
+ is_root: z.boolean(),
117
+ disabled_at: z.string().nullable(),
118
+ created_at: z.string(),
119
+ created_by: z.string().nullable()
120
+ }).strict();
121
+ var AdminUsersListResponseSchema = z.object({
122
+ admin_users: z.array(AdminUserSchema)
123
+ }).strict();
124
+ var UsersMeResponseSchema = z.object({
125
+ id: z.string(),
126
+ github_handle: z.string(),
127
+ is_root: z.boolean()
128
+ }).strict();
64
129
 
65
130
  // src/admin-api/redact.ts
66
131
  var SENSITIVE_KEYS = /* @__PURE__ */ new Set(["token", "refresh_token", "invite_token"]);
@@ -300,12 +365,23 @@ function loadAdminAuth(opts) {
300
365
  export {
301
366
  AdminApiError,
302
367
  AdminAuthFileSchema,
368
+ AdminUserSchema,
369
+ AdminUsersListResponseSchema,
370
+ AuthExchangeResponseSchema,
371
+ DevicePollPendingResponseSchema,
372
+ DevicePollSuccessResponseSchema,
373
+ DeviceStartResponseSchema,
303
374
  ErrorBodySchema,
304
375
  InviteCreatedSchema,
376
+ ItemSummarySchema,
377
+ ItemVersionSchema,
378
+ ItemVersionsResponseSchema,
379
+ ItemsListResponseSchema,
305
380
  MemberSchema,
306
381
  ProjectSchema,
307
382
  TokenIssuedSchema,
308
383
  TokenMetaSchema,
384
+ UsersMeResponseSchema,
309
385
  createAdminApiClient,
310
386
  loadAdminAuth,
311
387
  redactSensitive
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/admin-api/errors.ts","../../src/admin-api/responses.ts","../../src/admin-api/redact.ts","../../src/admin-api/client.ts","../../src/admin-api/admin-auth.ts"],"sourcesContent":["/**\n * Typed error class for all admin API failures.\n * kind → exit code mapping lives in mcp/scripts/commands/_render-error.ts\n */\nexport class AdminApiError extends Error {\n constructor(\n public readonly kind:\n | 'network'\n | 'auth'\n | 'forbidden'\n | 'not_found'\n | 'invalid_request'\n | 'server'\n | 'response_shape',\n public readonly status: number | null,\n public readonly details: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'AdminApiError';\n }\n}\n","/**\n * Vendored Zod response schemas — hand-authored from\n * mcp/src/admin-api/__snapshots__/openapi.test.ts.snap\n *\n * Snapshot drift is detected by cleargate-cli/test/admin-api/snapshot-drift.test.ts,\n * which reads the snapshot file at runtime and asserts field-set equality.\n */\nimport { z } from 'zod';\n\nexport const ProjectSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n created_by: z.string(),\n created_at: z.string(),\n deleted_at: z.string().nullable(),\n })\n .strict();\n\nexport type Project = z.infer<typeof ProjectSchema>;\n\nexport const MemberSchema = z\n .object({\n id: z.string(),\n project_id: z.string(),\n email: z.string(),\n role: z.string(),\n display_name: z.string().nullable().optional(),\n created_at: z.string(),\n status: z.enum(['pending', 'active']),\n })\n .strict();\n\nexport type Member = z.infer<typeof MemberSchema>;\n\nexport const InviteCreatedSchema = z\n .object({\n member: MemberSchema,\n invite_url: z.string(),\n invite_token: z.string(),\n invite_expires_in: z.number().int(),\n })\n .strict();\n\nexport type InviteCreated = z.infer<typeof InviteCreatedSchema>;\n\nexport const TokenMetaSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n })\n .strict();\n\nexport type TokenMeta = z.infer<typeof TokenMetaSchema>;\n\n// TokenIssued = TokenMeta + plaintext token field (returned exactly once)\nexport const TokenIssuedSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n token: z.string(),\n })\n .strict();\n\nexport type TokenIssued = z.infer<typeof TokenIssuedSchema>;\n\nexport const ErrorBodySchema = z\n .object({\n error: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n })\n .strict();\n\nexport type ErrorBody = z.infer<typeof ErrorBodySchema>;\n","/**\n * Recursively replaces sensitive key values with '<redacted>'.\n * Used in debug log paths — never in success output.\n * Keys stripped: token, refresh_token, invite_token\n */\nconst SENSITIVE_KEYS = new Set(['token', 'refresh_token', 'invite_token']);\n\nexport function redactSensitive(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => redactSensitive(item));\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key)) {\n result[key] = '<redacted>';\n } else {\n result[key] = redactSensitive(value);\n }\n }\n return result;\n }\n return obj;\n}\n","/**\n * AdminApiClient — typed HTTP client for the ClearGate admin API.\n *\n * Key implementation notes:\n * - CLI method args are camelCase; wire bodies are snake_case (converted internally)\n * - DELETE requests MUST omit Content-Type (Fastify 5 FST_ERR_CTP_EMPTY_JSON_BODY)\n * - All 2xx responses are validated through vendored Zod schemas\n * - Errors map to AdminApiError with kind → exit code table in D6\n */\nimport { AdminApiError } from './errors.js';\nimport {\n ProjectSchema,\n InviteCreatedSchema,\n TokenIssuedSchema,\n type Project,\n type InviteCreated,\n type TokenIssued,\n} from './responses.js';\nimport { redactSensitive } from './redact.js';\n\nexport interface AdminApiClientOptions {\n baseUrl: string;\n token: string;\n fetch?: typeof globalThis.fetch;\n warn?: (msg: string) => void;\n userAgent?: string;\n}\n\nexport interface AdminApiClient {\n createProject(input: { name: string }): Promise<Project>;\n inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated>;\n issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued>;\n revokeToken(input: { tokenId: string }): Promise<void>;\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\nclass AdminApiClientImpl implements AdminApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly warn: (msg: string) => void;\n private readonly userAgent: string;\n\n constructor(opts: AdminApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.token = opts.token;\n this.fetchFn = opts.fetch ?? globalThis.fetch;\n this.warn = opts.warn ?? defaultWarn;\n this.userAgent = opts.userAgent ?? `cleargate`;\n }\n\n private debugLog(method: string, path: string, status: number, body: unknown): void {\n if (process.env['CLEARGATE_LOG_LEVEL'] === 'debug') {\n const redacted = redactSensitive(body);\n this.warn(`[admin-api] ${method} ${path} → ${status} ${JSON.stringify(redacted)}`);\n }\n }\n\n private async request<T>(\n method: string,\n urlPath: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n const url = `${this.baseUrl}/admin-api/v1${urlPath}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': this.userAgent,\n Accept: 'application/json',\n };\n\n // CRITICAL: omit Content-Type on requests without body (DELETE)\n // Fastify 5 throws FST_ERR_CTP_EMPTY_JSON_BODY if Content-Type is set with empty body\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n throw new AdminApiError(\n 'network',\n null,\n err,\n `cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n\n // Parse response body when present\n let responseBody: unknown = null;\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n responseBody = await response.json();\n } catch {\n responseBody = null;\n }\n }\n\n this.debugLog(method, urlPath, response.status, responseBody);\n\n // Map HTTP status to AdminApiError kinds\n if (!response.ok) {\n const errBody = responseBody as { error?: string; details?: unknown } | null;\n if (response.status === 401) {\n throw new AdminApiError('auth', 401, responseBody, 'Admin token rejected.');\n }\n if (response.status === 403) {\n throw new AdminApiError('forbidden', 403, responseBody, 'Token is not admin-role.');\n }\n if (response.status === 404) {\n throw new AdminApiError('not_found', 404, responseBody, 'Not found.');\n }\n if (response.status === 400 || response.status === 409) {\n throw new AdminApiError(\n 'invalid_request',\n response.status,\n errBody?.details ?? responseBody,\n `Invalid request: ${errBody?.error ?? 'unknown'}`,\n );\n }\n if (response.status >= 500) {\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Server error ${response.status}.`,\n );\n }\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Unexpected status ${response.status}.`,\n );\n }\n\n return responseBody as T;\n }\n\n async createProject(input: { name: string }): Promise<Project> {\n const raw = await this.request<unknown>('POST', '/projects', { name: input.name });\n const parsed = ProjectSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated> {\n const body: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.displayName !== undefined) {\n body['display_name'] = input.displayName;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/members`,\n body,\n );\n const parsed = InviteCreatedSchema.safeParse(raw);\n if (!parsed.success) {\n // Try MemberSchema in case the server returned a member-only response\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued> {\n const body: Record<string, unknown> = {\n member_id: input.memberId,\n name: input.name,\n };\n if (input.expiresAt !== undefined) {\n body['expires_at'] = input.expiresAt;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/tokens`,\n body,\n );\n const parsed = TokenIssuedSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async revokeToken(input: { tokenId: string }): Promise<void> {\n // No body — Content-Type must be omitted (Fastify 5 CTP quirk)\n await this.request<unknown>('DELETE', `/tokens/${input.tokenId}`, undefined);\n }\n}\n\nexport function createAdminApiClient(opts: AdminApiClientOptions): AdminApiClient {\n return new AdminApiClientImpl(opts);\n}\n","/**\n * Loads an admin JWT for use with cleargate-admin CLI commands.\n *\n * Load order:\n * 1. CLEARGATE_ADMIN_TOKEN env var (wins immediately — file is not read)\n * 2. ~/.cleargate/admin-auth.json (shape: { version: 1, token: string })\n *\n * DISTINCT from FileTokenStore: that file holds user profile → refresh-token maps.\n * This file holds a single admin JWT acquired out-of-band via dev-issue-token.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const AdminAuthFileSchema = z\n .object({\n version: z.literal(1),\n token: z.string().min(1),\n })\n .strict();\n\nexport interface LoadAdminAuthOptions {\n env?: NodeJS.ProcessEnv;\n filePath?: string;\n homedir?: () => string;\n warn?: (msg: string) => void;\n}\n\nexport interface AdminAuth {\n token: string;\n source: 'env' | 'file';\n}\n\nconst MISSING_TOKEN_ERROR =\n 'No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README §admin-jwt.';\n\nexport function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth {\n const env = opts?.env ?? process.env;\n const warn = opts?.warn ?? ((msg: string) => process.stderr.write(msg + '\\n'));\n\n // Env wins — file is not read at all when env is set\n const envToken = env['CLEARGATE_ADMIN_TOKEN'];\n if (envToken) {\n return { token: envToken, source: 'env' };\n }\n\n // Resolve file path\n const homedirFn = opts?.homedir ?? os.homedir;\n const filePath =\n opts?.filePath ?? path.join(homedirFn(), '.cleargate', 'admin-auth.json');\n\n // Try file\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(MISSING_TOKEN_ERROR);\n }\n throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);\n }\n\n // Check file permissions (warn if too permissive)\n try {\n const stat = fs.statSync(filePath);\n const mode = stat.mode & 0o777;\n if (mode & 0o077) {\n warn(\n `cleargate-admin: warning: ${filePath} is group/world readable (mode ${(mode).toString(8).padStart(3, '0')}). Run: chmod 600 ${filePath}`,\n );\n }\n } catch {\n // If we can't stat the file, ignore — the read already succeeded\n }\n\n // Parse JSON\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);\n }\n\n // Validate with strict schema\n const result = AdminAuthFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Invalid admin-auth file at ${filePath}: ${result.error.message}`,\n );\n }\n\n return { token: result.data.token, source: 'file' };\n}\n"],"mappings":"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","names":["path","z"]}
1
+ {"version":3,"sources":["../../src/admin-api/errors.ts","../../src/admin-api/responses.ts","../../src/admin-api/redact.ts","../../src/admin-api/client.ts","../../src/admin-api/admin-auth.ts"],"sourcesContent":["/**\n * Typed error class for all admin API failures.\n * kind → exit code mapping lives in mcp/scripts/commands/_render-error.ts\n */\nexport class AdminApiError extends Error {\n constructor(\n public readonly kind:\n | 'network'\n | 'auth'\n | 'forbidden'\n | 'not_found'\n | 'invalid_request'\n | 'server'\n | 'response_shape',\n public readonly status: number | null,\n public readonly details: unknown,\n message: string,\n ) {\n super(message);\n this.name = 'AdminApiError';\n }\n}\n","/**\n * Vendored Zod response schemas — hand-authored from\n * mcp/src/admin-api/__snapshots__/openapi.test.ts.snap\n *\n * Snapshot drift is detected by cleargate-cli/test/admin-api/snapshot-drift.test.ts,\n * which reads the snapshot file at runtime and asserts field-set equality.\n */\nimport { z } from 'zod';\n\nexport const ProjectSchema = z\n .object({\n id: z.string(),\n name: z.string(),\n created_by: z.string(),\n created_at: z.string(),\n deleted_at: z.string().nullable(),\n })\n .strict();\n\nexport type Project = z.infer<typeof ProjectSchema>;\n\nexport const MemberSchema = z\n .object({\n id: z.string(),\n project_id: z.string(),\n email: z.string(),\n role: z.string(),\n display_name: z.string().nullable().optional(),\n created_at: z.string(),\n status: z.enum(['pending', 'active', 'expired']),\n })\n .strict();\n\nexport type Member = z.infer<typeof MemberSchema>;\n\nexport const InviteCreatedSchema = z\n .object({\n member: MemberSchema,\n invite_url: z.string(),\n invite_token: z.string(),\n invite_expires_in: z.number().int(),\n })\n .strict();\n\nexport type InviteCreated = z.infer<typeof InviteCreatedSchema>;\n\nexport const TokenMetaSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n })\n .strict();\n\nexport type TokenMeta = z.infer<typeof TokenMetaSchema>;\n\n// TokenIssued = TokenMeta + plaintext token field (returned exactly once)\nexport const TokenIssuedSchema = z\n .object({\n id: z.string(),\n member_id: z.string(),\n name: z.string(),\n created_at: z.string(),\n expires_at: z.string().nullable().optional(),\n last_used_at: z.string().nullable().optional(),\n revoked_at: z.string().nullable().optional(),\n token: z.string(),\n })\n .strict();\n\nexport type TokenIssued = z.infer<typeof TokenIssuedSchema>;\n\nexport const AuthExchangeResponseSchema = z\n .object({\n admin_token: z.string(),\n expires_at: z.string(),\n })\n .strict();\n\nexport type AuthExchangeResponse = z.infer<typeof AuthExchangeResponseSchema>;\n\nexport const ErrorBodySchema = z\n .object({\n error: z.string(),\n details: z.record(z.string(), z.unknown()).optional(),\n })\n .strict();\n\nexport type ErrorBody = z.infer<typeof ErrorBodySchema>;\n\n// ── Items admin API (STORY-004-09) ───────────────────────────────────────────\n\nexport const ItemSummarySchema = z\n .object({\n id: z.string(),\n cleargate_id: z.string(),\n type: z.string(),\n title: z.string(),\n status: z.string(),\n remote_id: z.string().nullable(),\n last_pushed_at: z.string().nullable(),\n pushed_by_member_id: z.string().nullable(),\n version: z.number().int(),\n updated_at: z.string(),\n current_payload: z.record(z.string(), z.unknown()).default({}),\n })\n .strict();\n\nexport type ItemSummary = z.infer<typeof ItemSummarySchema>;\n\nexport const ItemsListResponseSchema = z\n .object({\n items: z.array(ItemSummarySchema),\n next_cursor: z.string().nullable(),\n })\n .strict();\n\nexport type ItemsListResponse = z.infer<typeof ItemsListResponseSchema>;\n\nexport const ItemVersionSchema = z\n .object({\n version: z.number().int(),\n pushed_by_member_id: z.string().nullable(),\n pushed_at: z.string(),\n status: z.string(),\n diff_summary: z.string().nullable(),\n })\n .strict();\n\nexport type ItemVersion = z.infer<typeof ItemVersionSchema>;\n\nexport const ItemVersionsResponseSchema = z\n .object({\n versions: z.array(ItemVersionSchema),\n })\n .strict();\n\nexport type ItemVersionsResponse = z.infer<typeof ItemVersionsResponseSchema>;\n\n// ── Device-flow schemas (STORY-005-06) ───────────────────────────────────────\n\nexport const DeviceStartResponseSchema = z\n .object({\n device_code: z.string(),\n user_code: z.string(),\n verification_uri: z.string(),\n expires_in: z.number().int(),\n interval: z.number().int(),\n })\n .strict();\n\nexport type DeviceStartResponse = z.infer<typeof DeviceStartResponseSchema>;\n\nexport const DevicePollPendingResponseSchema = z\n .object({\n pending: z.literal(true),\n retry_after: z.number().int().optional(),\n })\n .strict();\n\nexport type DevicePollPendingResponse = z.infer<typeof DevicePollPendingResponseSchema>;\n\nexport const DevicePollSuccessResponseSchema = z\n .object({\n pending: z.literal(false),\n admin_token: z.string(),\n expires_at: z.string(),\n admin_user_id: z.string(),\n })\n .strict();\n\nexport type DevicePollSuccessResponse = z.infer<typeof DevicePollSuccessResponseSchema>;\n\n// ── Admin users API (STORY-006-09) ───────────────────────────────────────────\n\nexport const AdminUserSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n github_user_id: z.string().nullable(),\n is_root: z.boolean(),\n disabled_at: z.string().nullable(),\n created_at: z.string(),\n created_by: z.string().nullable(),\n })\n .strict();\n\nexport type AdminUser = z.infer<typeof AdminUserSchema>;\n\nexport const AdminUsersListResponseSchema = z\n .object({\n admin_users: z.array(AdminUserSchema),\n })\n .strict();\n\nexport type AdminUsersListResponse = z.infer<typeof AdminUsersListResponseSchema>;\n\nexport const UsersMeResponseSchema = z\n .object({\n id: z.string(),\n github_handle: z.string(),\n is_root: z.boolean(),\n })\n .strict();\n\nexport type UsersMeResponse = z.infer<typeof UsersMeResponseSchema>;\n","/**\n * Recursively replaces sensitive key values with '<redacted>'.\n * Used in debug log paths — never in success output.\n * Keys stripped: token, refresh_token, invite_token\n */\nconst SENSITIVE_KEYS = new Set(['token', 'refresh_token', 'invite_token']);\n\nexport function redactSensitive(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map((item) => redactSensitive(item));\n }\n if (obj !== null && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key)) {\n result[key] = '<redacted>';\n } else {\n result[key] = redactSensitive(value);\n }\n }\n return result;\n }\n return obj;\n}\n","/**\n * AdminApiClient — typed HTTP client for the ClearGate admin API.\n *\n * Key implementation notes:\n * - CLI method args are camelCase; wire bodies are snake_case (converted internally)\n * - DELETE requests MUST omit Content-Type (Fastify 5 FST_ERR_CTP_EMPTY_JSON_BODY)\n * - All 2xx responses are validated through vendored Zod schemas\n * - Errors map to AdminApiError with kind → exit code table in D6\n */\nimport { AdminApiError } from './errors.js';\nimport {\n ProjectSchema,\n InviteCreatedSchema,\n TokenIssuedSchema,\n type Project,\n type InviteCreated,\n type TokenIssued,\n} from './responses.js';\nimport { redactSensitive } from './redact.js';\n\nexport interface AdminApiClientOptions {\n baseUrl: string;\n token: string;\n fetch?: typeof globalThis.fetch;\n warn?: (msg: string) => void;\n userAgent?: string;\n}\n\nexport interface AdminApiClient {\n createProject(input: { name: string }): Promise<Project>;\n inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated>;\n issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued>;\n revokeToken(input: { tokenId: string }): Promise<void>;\n}\n\nfunction defaultWarn(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\nclass AdminApiClientImpl implements AdminApiClient {\n private readonly baseUrl: string;\n private readonly token: string;\n private readonly fetchFn: typeof globalThis.fetch;\n private readonly warn: (msg: string) => void;\n private readonly userAgent: string;\n\n constructor(opts: AdminApiClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, '');\n this.token = opts.token;\n this.fetchFn = opts.fetch ?? globalThis.fetch;\n this.warn = opts.warn ?? defaultWarn;\n this.userAgent = opts.userAgent ?? `cleargate`;\n }\n\n private debugLog(method: string, path: string, status: number, body: unknown): void {\n if (process.env['CLEARGATE_LOG_LEVEL'] === 'debug') {\n const redacted = redactSensitive(body);\n this.warn(`[admin-api] ${method} ${path} → ${status} ${JSON.stringify(redacted)}`);\n }\n }\n\n private async request<T>(\n method: string,\n urlPath: string,\n body?: Record<string, unknown>,\n ): Promise<T> {\n const url = `${this.baseUrl}/admin-api/v1${urlPath}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n 'User-Agent': this.userAgent,\n Accept: 'application/json',\n };\n\n // CRITICAL: omit Content-Type on requests without body (DELETE)\n // Fastify 5 throws FST_ERR_CTP_EMPTY_JSON_BODY if Content-Type is set with empty body\n if (body !== undefined) {\n headers['Content-Type'] = 'application/json';\n }\n\n let response: Response;\n try {\n response = await this.fetchFn(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (err) {\n throw new AdminApiError(\n 'network',\n null,\n err,\n `cannot reach ${this.baseUrl} (${err instanceof Error ? err.message : String(err)})`,\n );\n }\n\n // Parse response body when present\n let responseBody: unknown = null;\n const contentType = response.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n try {\n responseBody = await response.json();\n } catch {\n responseBody = null;\n }\n }\n\n this.debugLog(method, urlPath, response.status, responseBody);\n\n // Map HTTP status to AdminApiError kinds\n if (!response.ok) {\n const errBody = responseBody as { error?: string; details?: unknown } | null;\n if (response.status === 401) {\n throw new AdminApiError('auth', 401, responseBody, 'Admin token rejected.');\n }\n if (response.status === 403) {\n throw new AdminApiError('forbidden', 403, responseBody, 'Token is not admin-role.');\n }\n if (response.status === 404) {\n throw new AdminApiError('not_found', 404, responseBody, 'Not found.');\n }\n if (response.status === 400 || response.status === 409) {\n throw new AdminApiError(\n 'invalid_request',\n response.status,\n errBody?.details ?? responseBody,\n `Invalid request: ${errBody?.error ?? 'unknown'}`,\n );\n }\n if (response.status >= 500) {\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Server error ${response.status}.`,\n );\n }\n throw new AdminApiError(\n 'server',\n response.status,\n responseBody,\n `Unexpected status ${response.status}.`,\n );\n }\n\n return responseBody as T;\n }\n\n async createProject(input: { name: string }): Promise<Project> {\n const raw = await this.request<unknown>('POST', '/projects', { name: input.name });\n const parsed = ProjectSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async inviteMember(input: {\n projectId: string;\n email: string;\n role: 'user' | 'service';\n displayName?: string;\n }): Promise<InviteCreated> {\n const body: Record<string, unknown> = {\n email: input.email,\n role: input.role,\n };\n if (input.displayName !== undefined) {\n body['display_name'] = input.displayName;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/members`,\n body,\n );\n const parsed = InviteCreatedSchema.safeParse(raw);\n if (!parsed.success) {\n // Try MemberSchema in case the server returned a member-only response\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async issueToken(input: {\n projectId: string;\n memberId: string;\n name: string;\n expiresAt?: string;\n }): Promise<TokenIssued> {\n const body: Record<string, unknown> = {\n member_id: input.memberId,\n name: input.name,\n };\n if (input.expiresAt !== undefined) {\n body['expires_at'] = input.expiresAt;\n }\n const raw = await this.request<unknown>(\n 'POST',\n `/projects/${input.projectId}/tokens`,\n body,\n );\n const parsed = TokenIssuedSchema.safeParse(raw);\n if (!parsed.success) {\n throw new AdminApiError(\n 'response_shape',\n null,\n parsed.error,\n 'Server returned unexpected response shape (CLI may be out of date).',\n );\n }\n return parsed.data;\n }\n\n async revokeToken(input: { tokenId: string }): Promise<void> {\n // No body — Content-Type must be omitted (Fastify 5 CTP quirk)\n await this.request<unknown>('DELETE', `/tokens/${input.tokenId}`, undefined);\n }\n}\n\nexport function createAdminApiClient(opts: AdminApiClientOptions): AdminApiClient {\n return new AdminApiClientImpl(opts);\n}\n","/**\n * Loads an admin JWT for use with cleargate-admin CLI commands.\n *\n * Load order:\n * 1. CLEARGATE_ADMIN_TOKEN env var (wins immediately — file is not read)\n * 2. ~/.cleargate/admin-auth.json (shape: { version: 1, token: string })\n *\n * DISTINCT from FileTokenStore: that file holds user profile → refresh-token maps.\n * This file holds a single admin JWT acquired out-of-band via dev-issue-token.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { z } from 'zod';\n\nexport const AdminAuthFileSchema = z\n .object({\n version: z.literal(1),\n token: z.string().min(1),\n })\n .strict();\n\nexport interface LoadAdminAuthOptions {\n env?: NodeJS.ProcessEnv;\n filePath?: string;\n homedir?: () => string;\n warn?: (msg: string) => void;\n}\n\nexport interface AdminAuth {\n token: string;\n source: 'env' | 'file';\n}\n\nconst MISSING_TOKEN_ERROR =\n 'No admin token. Set CLEARGATE_ADMIN_TOKEN or write ~/.cleargate/admin-auth.json (chmod 600). See README §admin-jwt.';\n\nexport function loadAdminAuth(opts?: LoadAdminAuthOptions): AdminAuth {\n const env = opts?.env ?? process.env;\n const warn = opts?.warn ?? ((msg: string) => process.stderr.write(msg + '\\n'));\n\n // Env wins — file is not read at all when env is set\n const envToken = env['CLEARGATE_ADMIN_TOKEN'];\n if (envToken) {\n return { token: envToken, source: 'env' };\n }\n\n // Resolve file path\n const homedirFn = opts?.homedir ?? os.homedir;\n const filePath =\n opts?.filePath ?? path.join(homedirFn(), '.cleargate', 'admin-auth.json');\n\n // Try file\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch (err) {\n if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(MISSING_TOKEN_ERROR);\n }\n throw new Error(`Failed to read admin-auth file at ${filePath}: ${String(err)}`);\n }\n\n // Check file permissions (warn if too permissive)\n try {\n const stat = fs.statSync(filePath);\n const mode = stat.mode & 0o777;\n if (mode & 0o077) {\n warn(\n `cleargate-admin: warning: ${filePath} is group/world readable (mode ${(mode).toString(8).padStart(3, '0')}). Run: chmod 600 ${filePath}`,\n );\n }\n } catch {\n // If we can't stat the file, ignore — the read already succeeded\n }\n\n // Parse JSON\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Failed to parse admin-auth file at ${filePath}: invalid JSON`);\n }\n\n // Validate with strict schema\n const result = AdminAuthFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Invalid admin-auth file at ${filePath}: ${result.error.message}`,\n );\n }\n\n return { token: result.data.token, source: 'file' };\n}\n"],"mappings":"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","names":["path","z"]}
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/bootstrap-root.ts
4
+ import pg from "pg";
5
+ var HANDLE_RE = /^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
6
+ function isValidHandle(handle) {
7
+ return HANDLE_RE.test(handle);
8
+ }
9
+ var SCRUB = /(postgres(?:ql)?:\/\/[^:/@]+):[^@/]+@/gi;
10
+ function scrubPassword(s) {
11
+ return s.replace(SCRUB, "$1:***@");
12
+ }
13
+ async function bootstrapRootHandler(opts) {
14
+ const env = opts.env ?? process.env;
15
+ const stdoutFn = opts.stdout ?? ((s) => process.stdout.write(s + "\n"));
16
+ const stderrFn = opts.stderr ?? ((s) => process.stderr.write(s + "\n"));
17
+ const exitFn = opts.exit ?? ((code) => process.exit(code));
18
+ const force = opts.force ?? false;
19
+ const { handle } = opts;
20
+ if (!isValidHandle(handle)) {
21
+ stderrFn(`cleargate: error: '${handle}' is not a valid GitHub handle`);
22
+ return exitFn(1);
23
+ }
24
+ const url = opts.databaseUrl ?? env["DATABASE_URL"];
25
+ if (!url) {
26
+ stderrFn("cleargate: error: DATABASE_URL is required (set env or pass --database-url)");
27
+ return exitFn(2);
28
+ }
29
+ const clientFactory = opts.pgClientFactory ?? ((connStr) => new pg.Client({ connectionString: connStr }));
30
+ const client = clientFactory(url);
31
+ try {
32
+ await client.connect();
33
+ } catch (err) {
34
+ const raw = err instanceof Error ? `${err.message} ${err.stack ?? ""}` : String(err);
35
+ stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);
36
+ return exitFn(3);
37
+ }
38
+ let result;
39
+ let sqlError = void 0;
40
+ try {
41
+ result = await runSql(client, handle, force);
42
+ } catch (err) {
43
+ sqlError = err;
44
+ result = { exitCode: 3, kind: "stderr", message: "" };
45
+ }
46
+ try {
47
+ await client.end();
48
+ } catch {
49
+ }
50
+ if (sqlError !== void 0) {
51
+ const err = sqlError;
52
+ const raw = err instanceof Error ? `${err.message} ${err.stack ?? ""}` : String(err);
53
+ stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);
54
+ return exitFn(3);
55
+ }
56
+ if (result.kind === "stdout") {
57
+ stdoutFn(result.message);
58
+ } else {
59
+ stderrFn(result.message);
60
+ }
61
+ return exitFn(result.exitCode);
62
+ }
63
+ async function runSql(client, handle, force) {
64
+ await client.query("BEGIN");
65
+ const existing = await client.query(
66
+ "SELECT id, is_root FROM admin_users WHERE github_handle = $1",
67
+ [handle]
68
+ );
69
+ if (existing.rows.length > 0) {
70
+ const row = existing.rows[0];
71
+ if (row["is_root"] === true) {
72
+ await client.query("COMMIT");
73
+ return {
74
+ exitCode: 0,
75
+ kind: "stdout",
76
+ message: `Root admin '${handle}' already exists; no change.`
77
+ };
78
+ }
79
+ if (force) {
80
+ await client.query(
81
+ "UPDATE admin_users SET is_root = true WHERE github_handle = $1",
82
+ [handle]
83
+ );
84
+ await client.query("COMMIT");
85
+ return {
86
+ exitCode: 0,
87
+ kind: "stdout",
88
+ message: `Promoted '${handle}' to root admin.`
89
+ };
90
+ }
91
+ await client.query("ROLLBACK");
92
+ return {
93
+ exitCode: 1,
94
+ kind: "stderr",
95
+ message: `cleargate: error: '${handle}' exists but is not a root admin; pass --force to promote`
96
+ };
97
+ }
98
+ if (!force) {
99
+ const rootCount = await client.query(
100
+ "SELECT count(*) AS cnt FROM admin_users WHERE is_root = true"
101
+ );
102
+ const cnt = Number(rootCount.rows[0]["cnt"] ?? 0);
103
+ if (cnt >= 1) {
104
+ await client.query("ROLLBACK");
105
+ return {
106
+ exitCode: 1,
107
+ kind: "stderr",
108
+ message: "cleargate: error: refusing to create a second root admin; pass --force to override"
109
+ };
110
+ }
111
+ }
112
+ await client.query(
113
+ `INSERT INTO admin_users (github_handle, is_root)
114
+ VALUES ($1, true)
115
+ ON CONFLICT (github_handle) DO UPDATE SET is_root = EXCLUDED.is_root`,
116
+ [handle]
117
+ );
118
+ await client.query("COMMIT");
119
+ return {
120
+ exitCode: 0,
121
+ kind: "stdout",
122
+ message: `Bootstrapped root admin '${handle}'.`
123
+ };
124
+ }
125
+ export {
126
+ bootstrapRootHandler,
127
+ isValidHandle,
128
+ scrubPassword
129
+ };
130
+ //# sourceMappingURL=bootstrap-root-FGWDICDT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/bootstrap-root.ts"],"sourcesContent":["/**\n * `cleargate admin bootstrap-root <handle>` — seed the first root admin.\n *\n * Idempotent: running the same handle twice is safe. Provides a `--force` flag\n * to either promote an existing non-root user or insert a second root when one\n * already exists.\n *\n * Exit codes:\n * 0 — success (including idempotent no-op)\n * 1 — validation error (bad handle, second-root guard refused)\n * 2 — missing DATABASE_URL\n * 3 — connection / query failure\n *\n * DATABASE_URL password is scrubbed from all error output.\n *\n * STORY-011-03.\n */\n\nimport pg from 'pg';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle validation\n// ─────────────────────────────────────────────────────────────────────────────\n\n// GitHub handle grammar: starts with alphanumeric, ends with alphanumeric,\n// allows hyphens in the middle, max 39 chars total.\n// Single-char handles (no middle section) are allowed.\n// Trailing hyphens are rejected by requiring the last char to be alphanumeric\n// when the handle is longer than 1 char.\nconst HANDLE_RE = /^[A-Za-z0-9]([A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;\n\nexport function isValidHandle(handle: string): boolean {\n return HANDLE_RE.test(handle);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Password scrubber\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SCRUB = /(postgres(?:ql)?:\\/\\/[^:/@]+):[^@/]+@/gi;\n\nexport function scrubPassword(s: string): string {\n return s.replace(SCRUB, '$1:***@');\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Minimal pg client surface needed by this handler. */\nexport interface PgClientLike {\n // connect() returns Promise<void> or Promise<Client> depending on pg version.\n // We accept either by using Promise<unknown>.\n connect(): Promise<unknown>;\n query(text: string, values?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;\n end(): Promise<void>;\n}\n\nexport interface BootstrapRootOptions {\n handle: string;\n databaseUrl?: string;\n force?: boolean;\n env?: NodeJS.ProcessEnv;\n stdout?: (s: string) => void;\n stderr?: (s: string) => void;\n exit?: (code: number) => never;\n /** Test seam — factory receives the resolved connection string */\n pgClientFactory?: (url: string) => PgClientLike;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal result — avoids exception-based control flow bleeding into the\n// SQL error catch block (exit seam throws in tests; we must not catch those).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface SqlResult {\n exitCode: number;\n kind: 'stdout' | 'stderr';\n message: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Main handler\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport async function bootstrapRootHandler(opts: BootstrapRootOptions): Promise<void> {\n const env = opts.env ?? process.env;\n const stdoutFn = opts.stdout ?? ((s: string) => process.stdout.write(s + '\\n'));\n const stderrFn = opts.stderr ?? ((s: string) => process.stderr.write(s + '\\n'));\n const exitFn = opts.exit ?? ((code: number): never => process.exit(code));\n const force = opts.force ?? false;\n\n // ── 1. Handle validation ────────────────────────────────────────────────────\n const { handle } = opts;\n if (!isValidHandle(handle)) {\n stderrFn(`cleargate: error: '${handle}' is not a valid GitHub handle`);\n return exitFn(1);\n }\n\n // ── 2. Resolve DATABASE_URL ─────────────────────────────────────────────────\n const url = opts.databaseUrl ?? env['DATABASE_URL'];\n if (!url) {\n stderrFn('cleargate: error: DATABASE_URL is required (set env or pass --database-url)');\n return exitFn(2);\n }\n\n // ── 3. Connect ──────────────────────────────────────────────────────────────\n const clientFactory =\n opts.pgClientFactory ??\n ((connStr: string): PgClientLike => new pg.Client({ connectionString: connStr }));\n\n const client = clientFactory(url);\n\n try {\n await client.connect();\n } catch (err) {\n const raw = err instanceof Error ? `${err.message} ${err.stack ?? ''}` : String(err);\n stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);\n return exitFn(3);\n }\n\n // ── 4. Run SQL (returns a SqlResult, never throws for logic branches) ────────\n // By returning a value rather than calling exitFn() inside runSql(), we ensure\n // that if the test exit-seam throws, the throw propagates out of the whole\n // handler rather than being caught here as a \"database error\".\n let result: SqlResult;\n let sqlError: unknown = undefined;\n\n try {\n result = await runSql(client, handle, force);\n } catch (err) {\n sqlError = err;\n result = { exitCode: 3, kind: 'stderr', message: '' }; // filled below\n }\n\n // ── 5. Close connection ─────────────────────────────────────────────────────\n try {\n await client.end();\n } catch {\n // ignore cleanup error\n }\n\n // ── 6. Handle SQL error ─────────────────────────────────────────────────────\n if (sqlError !== undefined) {\n const err = sqlError;\n const raw = err instanceof Error ? `${err.message} ${err.stack ?? ''}` : String(err);\n stderrFn(`cleargate: error: cannot reach database (${scrubPassword(raw.trim())})`);\n return exitFn(3);\n }\n\n // ── 7. Emit message and exit ────────────────────────────────────────────────\n if (result!.kind === 'stdout') {\n stdoutFn(result!.message);\n } else {\n stderrFn(result!.message);\n }\n return exitFn(result!.exitCode);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SQL logic — returns SqlResult, never calls exitFn\n// ─────────────────────────────────────────────────────────────────────────────\n\nasync function runSql(\n client: PgClientLike,\n handle: string,\n force: boolean,\n): Promise<SqlResult> {\n await client.query('BEGIN');\n\n // Step 1: does the handle already exist?\n const existing = await client.query(\n 'SELECT id, is_root FROM admin_users WHERE github_handle = $1',\n [handle],\n );\n\n if (existing.rows.length > 0) {\n const row = existing.rows[0]!;\n\n if (row['is_root'] === true) {\n // Branch A: already a root admin — idempotent no-op\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Root admin '${handle}' already exists; no change.`,\n };\n }\n\n // Row exists but is_root=false\n if (force) {\n // Branch B: promote\n await client.query(\n 'UPDATE admin_users SET is_root = true WHERE github_handle = $1',\n [handle],\n );\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Promoted '${handle}' to root admin.`,\n };\n }\n\n // Branch C: existing non-root, no --force\n await client.query('ROLLBACK');\n return {\n exitCode: 1,\n kind: 'stderr',\n message: `cleargate: error: '${handle}' exists but is not a root admin; pass --force to promote`,\n };\n }\n\n // No row for this handle — check if another root exists\n if (!force) {\n const rootCount = await client.query(\n 'SELECT count(*) AS cnt FROM admin_users WHERE is_root = true',\n );\n const cnt = Number((rootCount.rows[0] as { cnt: string })['cnt'] ?? 0);\n\n if (cnt >= 1) {\n // Branch D: refuse second root without --force\n await client.query('ROLLBACK');\n return {\n exitCode: 1,\n kind: 'stderr',\n message:\n 'cleargate: error: refusing to create a second root admin; pass --force to override',\n };\n }\n }\n\n // Branch E: insert (first root, or --force allows a second root)\n await client.query(\n `INSERT INTO admin_users (github_handle, is_root)\n VALUES ($1, true)\n ON CONFLICT (github_handle) DO UPDATE SET is_root = EXCLUDED.is_root`,\n [handle],\n );\n await client.query('COMMIT');\n return {\n exitCode: 0,\n kind: 'stdout',\n message: `Bootstrapped root admin '${handle}'.`,\n };\n}\n"],"mappings":"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","names":[]}