@ttctl/core 0.0.0 → 0.1.0-rc.1

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 (195) hide show
  1. package/README.md +49 -9
  2. package/dist/__generated__/gateway.d.ts +4546 -0
  3. package/dist/__generated__/gateway.d.ts.map +1 -0
  4. package/dist/__generated__/gateway.js +9 -0
  5. package/dist/__generated__/gateway.js.map +1 -0
  6. package/dist/__generated__/talent-profile-zod-schemas.d.ts +1187 -0
  7. package/dist/__generated__/talent-profile-zod-schemas.d.ts.map +1 -0
  8. package/dist/__generated__/talent-profile-zod-schemas.js +1136 -0
  9. package/dist/__generated__/talent-profile-zod-schemas.js.map +1 -0
  10. package/dist/__generated__/talent-profile.d.ts +1397 -0
  11. package/dist/__generated__/talent-profile.d.ts.map +1 -0
  12. package/dist/__generated__/talent-profile.js +9 -0
  13. package/dist/__generated__/talent-profile.js.map +1 -0
  14. package/dist/__generated__/zod-schemas.d.ts +2895 -0
  15. package/dist/__generated__/zod-schemas.d.ts.map +1 -0
  16. package/dist/__generated__/zod-schemas.js +3121 -0
  17. package/dist/__generated__/zod-schemas.js.map +1 -0
  18. package/dist/__tests__/fixtures/profile/builders.d.ts +74 -0
  19. package/dist/__tests__/fixtures/profile/builders.d.ts.map +1 -0
  20. package/dist/__tests__/fixtures/profile/builders.js +196 -0
  21. package/dist/__tests__/fixtures/profile/builders.js.map +1 -0
  22. package/dist/__tests__/fixtures/profile/data.d.ts +39 -0
  23. package/dist/__tests__/fixtures/profile/data.d.ts.map +1 -0
  24. package/dist/__tests__/fixtures/profile/data.js +230 -0
  25. package/dist/__tests__/fixtures/profile/data.js.map +1 -0
  26. package/dist/__tests__/fixtures/profile/index.d.ts +9 -0
  27. package/dist/__tests__/fixtures/profile/index.d.ts.map +1 -0
  28. package/dist/__tests__/fixtures/profile/index.js +10 -0
  29. package/dist/__tests__/fixtures/profile/index.js.map +1 -0
  30. package/dist/__tests__/fixtures/profile/types.d.ts +53 -0
  31. package/dist/__tests__/fixtures/profile/types.d.ts.map +1 -0
  32. package/dist/__tests__/fixtures/profile/types.js +4 -0
  33. package/dist/__tests__/fixtures/profile/types.js.map +1 -0
  34. package/dist/auth/errors.d.ts +82 -0
  35. package/dist/auth/errors.d.ts.map +1 -0
  36. package/dist/auth/errors.js +68 -0
  37. package/dist/auth/errors.js.map +1 -0
  38. package/dist/auth.d.ts +192 -0
  39. package/dist/auth.d.ts.map +1 -0
  40. package/dist/auth.js +294 -0
  41. package/dist/auth.js.map +1 -0
  42. package/dist/config.d.ts +212 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +349 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/configLock.d.ts +50 -0
  47. package/dist/configLock.d.ts.map +1 -0
  48. package/dist/configLock.js +88 -0
  49. package/dist/configLock.js.map +1 -0
  50. package/dist/configWriter.d.ts +97 -0
  51. package/dist/configWriter.d.ts.map +1 -0
  52. package/dist/configWriter.js +687 -0
  53. package/dist/configWriter.js.map +1 -0
  54. package/dist/index.d.ts +37 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +28 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/kill-switch.d.ts +161 -0
  59. package/dist/kill-switch.d.ts.map +1 -0
  60. package/dist/kill-switch.js +235 -0
  61. package/dist/kill-switch.js.map +1 -0
  62. package/dist/lib/date.d.ts +58 -0
  63. package/dist/lib/date.d.ts.map +1 -0
  64. package/dist/lib/date.js +104 -0
  65. package/dist/lib/date.js.map +1 -0
  66. package/dist/lib/diagnostic-log.d.ts +159 -0
  67. package/dist/lib/diagnostic-log.d.ts.map +1 -0
  68. package/dist/lib/diagnostic-log.js +186 -0
  69. package/dist/lib/diagnostic-log.js.map +1 -0
  70. package/dist/lib/package-version.d.ts +19 -0
  71. package/dist/lib/package-version.d.ts.map +1 -0
  72. package/dist/lib/package-version.js +38 -0
  73. package/dist/lib/package-version.js.map +1 -0
  74. package/dist/lib/redact.d.ts +153 -0
  75. package/dist/lib/redact.d.ts.map +1 -0
  76. package/dist/lib/redact.js +207 -0
  77. package/dist/lib/redact.js.map +1 -0
  78. package/dist/lib/text.d.ts +14 -0
  79. package/dist/lib/text.d.ts.map +1 -0
  80. package/dist/lib/text.js +21 -0
  81. package/dist/lib/text.js.map +1 -0
  82. package/dist/lib/wire-shape.d.ts +131 -0
  83. package/dist/lib/wire-shape.d.ts.map +1 -0
  84. package/dist/lib/wire-shape.js +376 -0
  85. package/dist/lib/wire-shape.js.map +1 -0
  86. package/dist/onepassword.d.ts +29 -0
  87. package/dist/onepassword.d.ts.map +1 -0
  88. package/dist/onepassword.js +112 -0
  89. package/dist/onepassword.js.map +1 -0
  90. package/dist/services/_shared/transport.d.ts +148 -0
  91. package/dist/services/_shared/transport.d.ts.map +1 -0
  92. package/dist/services/_shared/transport.js +102 -0
  93. package/dist/services/_shared/transport.js.map +1 -0
  94. package/dist/services/applications/index.d.ts +210 -0
  95. package/dist/services/applications/index.d.ts.map +1 -0
  96. package/dist/services/applications/index.js +240 -0
  97. package/dist/services/applications/index.js.map +1 -0
  98. package/dist/services/availability/index.d.ts +254 -0
  99. package/dist/services/availability/index.d.ts.map +1 -0
  100. package/dist/services/availability/index.js +310 -0
  101. package/dist/services/availability/index.js.map +1 -0
  102. package/dist/services/contracts/index.d.ts +132 -0
  103. package/dist/services/contracts/index.d.ts.map +1 -0
  104. package/dist/services/contracts/index.js +211 -0
  105. package/dist/services/contracts/index.js.map +1 -0
  106. package/dist/services/engagements/index.d.ts +504 -0
  107. package/dist/services/engagements/index.d.ts.map +1 -0
  108. package/dist/services/engagements/index.js +613 -0
  109. package/dist/services/engagements/index.js.map +1 -0
  110. package/dist/services/jobs/index.d.ts +490 -0
  111. package/dist/services/jobs/index.d.ts.map +1 -0
  112. package/dist/services/jobs/index.js +753 -0
  113. package/dist/services/jobs/index.js.map +1 -0
  114. package/dist/services/payments/index.d.ts +415 -0
  115. package/dist/services/payments/index.d.ts.map +1 -0
  116. package/dist/services/payments/index.js +636 -0
  117. package/dist/services/payments/index.js.map +1 -0
  118. package/dist/services/profile/__tests__/fixtures.d.ts +214 -0
  119. package/dist/services/profile/__tests__/fixtures.d.ts.map +1 -0
  120. package/dist/services/profile/__tests__/fixtures.js +176 -0
  121. package/dist/services/profile/__tests__/fixtures.js.map +1 -0
  122. package/dist/services/profile/basic/index.d.ts +390 -0
  123. package/dist/services/profile/basic/index.d.ts.map +1 -0
  124. package/dist/services/profile/basic/index.js +1007 -0
  125. package/dist/services/profile/basic/index.js.map +1 -0
  126. package/dist/services/profile/certifications/index.d.ts +74 -0
  127. package/dist/services/profile/certifications/index.d.ts.map +1 -0
  128. package/dist/services/profile/certifications/index.js +169 -0
  129. package/dist/services/profile/certifications/index.js.map +1 -0
  130. package/dist/services/profile/education/index.d.ts +73 -0
  131. package/dist/services/profile/education/index.d.ts.map +1 -0
  132. package/dist/services/profile/education/index.js +168 -0
  133. package/dist/services/profile/education/index.js.map +1 -0
  134. package/dist/services/profile/employment/index.d.ts +111 -0
  135. package/dist/services/profile/employment/index.d.ts.map +1 -0
  136. package/dist/services/profile/employment/index.js +202 -0
  137. package/dist/services/profile/employment/index.js.map +1 -0
  138. package/dist/services/profile/external/index.d.ts +219 -0
  139. package/dist/services/profile/external/index.d.ts.map +1 -0
  140. package/dist/services/profile/external/index.js +560 -0
  141. package/dist/services/profile/external/index.js.map +1 -0
  142. package/dist/services/profile/index.d.ts +24 -0
  143. package/dist/services/profile/index.d.ts.map +1 -0
  144. package/dist/services/profile/index.js +26 -0
  145. package/dist/services/profile/index.js.map +1 -0
  146. package/dist/services/profile/industries/index.d.ts +130 -0
  147. package/dist/services/profile/industries/index.d.ts.map +1 -0
  148. package/dist/services/profile/industries/index.js +292 -0
  149. package/dist/services/profile/industries/index.js.map +1 -0
  150. package/dist/services/profile/portfolio/index.d.ts +352 -0
  151. package/dist/services/profile/portfolio/index.d.ts.map +1 -0
  152. package/dist/services/profile/portfolio/index.js +833 -0
  153. package/dist/services/profile/portfolio/index.js.map +1 -0
  154. package/dist/services/profile/resume/index.d.ts +60 -0
  155. package/dist/services/profile/resume/index.d.ts.map +1 -0
  156. package/dist/services/profile/resume/index.js +212 -0
  157. package/dist/services/profile/resume/index.js.map +1 -0
  158. package/dist/services/profile/reviews/index.d.ts +137 -0
  159. package/dist/services/profile/reviews/index.d.ts.map +1 -0
  160. package/dist/services/profile/reviews/index.js +431 -0
  161. package/dist/services/profile/reviews/index.js.map +1 -0
  162. package/dist/services/profile/shared.d.ts +127 -0
  163. package/dist/services/profile/shared.d.ts.map +1 -0
  164. package/dist/services/profile/shared.js +155 -0
  165. package/dist/services/profile/shared.js.map +1 -0
  166. package/dist/services/profile/skills/index.d.ts +212 -0
  167. package/dist/services/profile/skills/index.d.ts.map +1 -0
  168. package/dist/services/profile/skills/index.js +461 -0
  169. package/dist/services/profile/skills/index.js.map +1 -0
  170. package/dist/services/profile/visas/index.d.ts +74 -0
  171. package/dist/services/profile/visas/index.d.ts.map +1 -0
  172. package/dist/services/profile/visas/index.js +306 -0
  173. package/dist/services/profile/visas/index.js.map +1 -0
  174. package/dist/services/timesheet/index.d.ts +326 -0
  175. package/dist/services/timesheet/index.d.ts.map +1 -0
  176. package/dist/services/timesheet/index.js +324 -0
  177. package/dist/services/timesheet/index.js.map +1 -0
  178. package/dist/services/translations.d.ts +79 -0
  179. package/dist/services/translations.d.ts.map +1 -0
  180. package/dist/services/translations.js +136 -0
  181. package/dist/services/translations.js.map +1 -0
  182. package/dist/transport-resilience.d.ts +136 -0
  183. package/dist/transport-resilience.d.ts.map +1 -0
  184. package/dist/transport-resilience.js +247 -0
  185. package/dist/transport-resilience.js.map +1 -0
  186. package/dist/transport.d.ts +408 -0
  187. package/dist/transport.d.ts.map +1 -0
  188. package/dist/transport.js +691 -0
  189. package/dist/transport.js.map +1 -0
  190. package/dist/types.d.ts +41 -0
  191. package/dist/types.d.ts.map +1 -0
  192. package/dist/types.js +18 -0
  193. package/dist/types.js.map +1 -0
  194. package/package.json +40 -12
  195. package/index.js +0 -7
@@ -0,0 +1,212 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Source pattern for a 1Password item reference, exported as a STRING (not
4
+ * a `RegExp`) so non-schema consumers (the CLI's interactive `auth init`
5
+ * flow's validation closure) can adopt the canonical pattern without
6
+ * pulling the entire Zod schema into their bundle. Analogous to
7
+ * {@link ./lib/redact.ts#BEARER_PATTERN_SOURCE}.
8
+ *
9
+ * Forms accepted:
10
+ * - `op://VAULT/ITEM` (2-segment) — relies on `op` CLI default-account or
11
+ * `OP_ACCOUNT` env. Works for single-account setups.
12
+ * - `op://ACCOUNT/VAULT/ITEM` (3-segment) — selects a specific account
13
+ * when the user has multiple `op` accounts configured. ACCOUNT is
14
+ * forwarded verbatim to `op item get --account` (accepts UUID,
15
+ * shorthand, or sign-in email — `op` CLI validates).
16
+ *
17
+ * Per-field references (`op://VAULT/ITEM/FIELD` or 4+ segments) are
18
+ * deliberately NOT supported — TTCtl always reads `username` and `password`
19
+ * from a single LOGIN-category item. The pattern rejects 4+ segments.
20
+ */
21
+ export declare const OP_REF_PATTERN_SOURCE: "^op://(?:[^/]+/)?[^/]+/[^/]+$";
22
+ /**
23
+ * Canonical user-facing hint paired with {@link OP_REF_PATTERN_SOURCE}.
24
+ * Used as the Zod error message AND as the CLI's interactive-prompt
25
+ * validation hint so both surfaces speak the same language. Phrased as a
26
+ * standalone sentence (no field-name prefix) because the schema's
27
+ * `formatLoadValidationError` adds the field path (`auth.credentials:`) and
28
+ * the CLI prompt has its own context.
29
+ */
30
+ export declare const OP_REF_PATTERN_HINT: "Expected op://[account/]vault/item \u2014 no /field suffix";
31
+ /**
32
+ * Literal credentials in YAML — discouraged for daily use; the 1Password form
33
+ * is recommended.
34
+ *
35
+ * Field name is `username` (matching 1Password's `USERNAME` purpose semantics
36
+ * and the issue's user-facing schema). The value must be a valid email since
37
+ * Toptal's `EmailPasswordSignIn` mutation only accepts emails.
38
+ */
39
+ declare const LiteralCredentialsSchema: z.ZodObject<{
40
+ username: z.ZodEmail;
41
+ password: z.ZodString;
42
+ }, z.core.$strict>;
43
+ /**
44
+ * Polymorphic credentials value:
45
+ * - string → 1Password item reference (Form A)
46
+ * - object → literal { username, password } (Form B)
47
+ *
48
+ * The runtime `signIn()` flow collapses both into the internal `Credentials`
49
+ * shape `{ email, password }` (the GraphQL mutation parameter name is
50
+ * `email` — the YAML/op:// surface uses `username` to match 1Password's
51
+ * USERNAME purpose).
52
+ */
53
+ export declare const AuthCredentialsSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
54
+ username: z.ZodEmail;
55
+ password: z.ZodString;
56
+ }, z.core.$strict>]>;
57
+ export type AuthCredentials = z.infer<typeof AuthCredentialsSchema>;
58
+ export type LiteralAuthCredentials = z.infer<typeof LiteralCredentialsSchema>;
59
+ /**
60
+ * `auth` block schema for runtime LOAD operations. Strict — rejects unknown
61
+ * fields and rejects an empty `auth: {}` (the user must author at least one
62
+ * of `credentials` or `token` for the runtime to be useful).
63
+ *
64
+ * Four valid lifecycle states (Forms A-D from the design doc):
65
+ *
66
+ * - **Form A** — `credentials: "op://..."` (1Password reference). No token
67
+ * yet — `auth signin` will write one.
68
+ * - **Form B** — `credentials: { username, password }` (literal). No token
69
+ * yet.
70
+ * - **Form C** — `token: "..."` only (no credentials). The bearer is
71
+ * supplied directly; `auth signin` is not applicable. Common in the E2E
72
+ * sandbox and in deployments that bootstrap a token out-of-band.
73
+ * - **Form D** — both `credentials` AND `token`. Post-signin state for
74
+ * Form A or B configs; the captured bearer is persisted alongside the
75
+ * credentials.
76
+ */
77
+ declare const AuthBlockLoadSchema: z.ZodObject<{
78
+ credentials: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
79
+ username: z.ZodEmail;
80
+ password: z.ZodString;
81
+ }, z.core.$strict>]>>;
82
+ token: z.ZodOptional<z.ZodString>;
83
+ }, z.core.$strict>;
84
+ export type AuthBlock = z.infer<typeof AuthBlockLoadSchema>;
85
+ /**
86
+ * Top-level config schema (load form). Single config; no profiles.
87
+ *
88
+ * Compared to the prior shape:
89
+ * - Removed top-level `auth` polymorphic value (string or `{email,password}`)
90
+ * in favor of a structured `auth: { credentials?, token? }` block.
91
+ * - Removed `auth-token-path` field — the captured bearer now lives inside
92
+ * the same YAML file at `auth.token`. Project-scoped configs are routed
93
+ * via direnv (see README § Configuration).
94
+ */
95
+ export declare const ConfigLoadSchema: z.ZodObject<{
96
+ auth: z.ZodObject<{
97
+ credentials: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
98
+ username: z.ZodEmail;
99
+ password: z.ZodString;
100
+ }, z.core.$strict>]>>;
101
+ token: z.ZodOptional<z.ZodString>;
102
+ }, z.core.$strict>;
103
+ }, z.core.$strict>;
104
+ /**
105
+ * Top-level config schema (write form). Permits empty `auth: {}` for the
106
+ * post-signout transient state when the source was Form C.
107
+ */
108
+ export declare const ConfigWriteSchema: z.ZodObject<{
109
+ auth: z.ZodObject<{
110
+ credentials: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
111
+ username: z.ZodEmail;
112
+ password: z.ZodString;
113
+ }, z.core.$strict>]>>;
114
+ token: z.ZodOptional<z.ZodString>;
115
+ }, z.core.$strict>;
116
+ }, z.core.$strict>;
117
+ /**
118
+ * Runtime-validated config object (load form). The shape clients read from
119
+ * disk after `loadConfigFile`.
120
+ */
121
+ export type TtctlConfig = z.infer<typeof ConfigLoadSchema>;
122
+ /**
123
+ * Permissive config shape used for write-back (`persistAuthToken`,
124
+ * `signOut`). Empty `auth: {}` is acceptable mid-lifecycle.
125
+ */
126
+ export type TtctlConfigWritable = z.infer<typeof ConfigWriteSchema>;
127
+ /**
128
+ * Discriminator on `ConfigError` so consumers can switch on error class
129
+ * without pattern-matching prose messages.
130
+ *
131
+ * - `NO_CREDS` — config not found via the resolution chain, or the
132
+ * chosen path doesn't exist on disk.
133
+ * - `PARSE` — YAML parse error (malformed file).
134
+ * - `VALIDATION` — schema validation error (wrong shape, missing fields).
135
+ * - `PERMISSION` — file exists but is not accessible (EACCES / EPERM), OR
136
+ * file is at an unsafe location (sync-root, symlink) per
137
+ * the security gates in the resolution chain.
138
+ * - `LOCKED` — advisory write-back lock could not be acquired within
139
+ * the contention budget (≤1s). Another ttctl process
140
+ * (CLI signin or long-running MCP tool call) holds the
141
+ * sibling lockfile. Retry the operation.
142
+ */
143
+ export type ConfigErrorCode = "NO_CREDS" | "PARSE" | "VALIDATION" | "PERMISSION" | "LOCKED";
144
+ export declare class ConfigError extends Error {
145
+ readonly code: ConfigErrorCode;
146
+ readonly path?: string | undefined;
147
+ readonly name = "ConfigError";
148
+ constructor(message: string, code: ConfigErrorCode, path?: string | undefined);
149
+ }
150
+ /**
151
+ * Read, parse, and validate the config file at `path`.
152
+ *
153
+ * Pre-flight `fs.stat`:
154
+ * - ENOENT → `ConfigError(code: NO_CREDS)` so a missing explicit path or
155
+ * `TTCTL_CONFIG_FILE` value surfaces uniformly with the
156
+ * "no config found anywhere" branch in `resolveConfig`.
157
+ * - EACCES / EPERM → `ConfigError(code: PERMISSION)`.
158
+ * - Mode-strictness gate (POSIX): refuse to LOAD when the file is
159
+ * world-writable (`mode & 0o002 != 0` — covers 0666, 0777, etc.).
160
+ * Closes a TOCTOU window on credential reads. Throws
161
+ * `ConfigError(PERMISSION)` with the actual mode in the error message.
162
+ * - Mode-warning (POSIX): emit a stderr warning when group/world bits
163
+ * other than world-writable are set (e.g. 0644). Non-fatal; the file
164
+ * may carry a 1Password reference (low-risk on its own) or, in literal
165
+ * form, a plaintext password — `0o600` is the right posture.
166
+ */
167
+ export declare function loadConfigFile(path: string): TtctlConfig;
168
+ /**
169
+ * Options for `resolveConfig`. Currently only `path`; kept as an options
170
+ * object so future flags don't require another signature break.
171
+ */
172
+ export interface ResolveConfigOptions {
173
+ /**
174
+ * Explicit config path. Wins over `TTCTL_CONFIG_FILE` and the home default.
175
+ * Used verbatim — no existence pre-check (`loadConfigFile` surfaces ENOENT
176
+ * as `code: 'NO_CREDS'`).
177
+ */
178
+ path?: string;
179
+ }
180
+ /**
181
+ * Deterministic config-path resolution.
182
+ *
183
+ * Precedence (highest → lowest):
184
+ *
185
+ * 1. Explicit `path` argument (passed by the caller — e.g., `--config`
186
+ * flag from #93). Used verbatim, no existence check.
187
+ * 2. `TTCTL_CONFIG_FILE` env var. Same verbatim treatment as `path`.
188
+ * 3. `~/.ttctl.yaml` (POSIX home dotfile), when the file exists on disk.
189
+ *
190
+ * Returns `null` when none of the above produce a path. Callers that want
191
+ * project-scoped configs use `direnv` to set `TTCTL_CONFIG_FILE` per
192
+ * directory — the CWD `./.ttctl.yaml` is NOT consulted (closed since #92).
193
+ *
194
+ * NOTE: XDG paths (`$XDG_CONFIG_HOME/ttctl/config.yaml`,
195
+ * `~/.config/ttctl/config.yaml`) are NO LONGER consulted. The single-file
196
+ * model places the captured bearer in the same YAML; `~/.ttctl.yaml` is
197
+ * the canonical home location.
198
+ */
199
+ export declare function discoverConfigPath(path?: string): string | null;
200
+ /**
201
+ * Convenience: discover and load.
202
+ *
203
+ * When the resolution chain returns `null` AND a `./.ttctl.yaml` exists in
204
+ * `process.cwd()`, surface a deprecation message pointing the user at the
205
+ * supported escape hatches. The CWD file is NEVER auto-loaded.
206
+ */
207
+ export declare function resolveConfig(options?: ResolveConfigOptions): {
208
+ config: TtctlConfig;
209
+ path: string;
210
+ };
211
+ export {};
212
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,EAAG,+BAAwC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAG,4DAAgE,CAAC;AAIpG;;;;;;;GAOG;AACH,QAAA,MAAM,wBAAwB;;;kBAKnB,CAAC;AAEZ;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB;;;oBAAkE,CAAC;AAErG,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE9E;;;;;;;;;;;;;;;;;GAiBG;AACH,QAAA,MAAM,mBAAmB;;;;;;kBAgBrB,CAAC;AAEL,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAiB5D;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;kBAIlB,CAAC;AAEZ;;;GAGG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;kBAInB,CAAC;AAEZ;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAEpE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,OAAO,GAAG,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE5F,qBAAa,WAAY,SAAQ,KAAK;aAIlB,IAAI,EAAE,eAAe;aACrB,IAAI,CAAC,EAAE,MAAM;IAJ/B,SAAkB,IAAI,iBAAiB;gBAErC,OAAO,EAAE,MAAM,EACC,IAAI,EAAE,eAAe,EACrB,IAAI,CAAC,EAAE,MAAM,YAAA;CAIhC;AAoFD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAkExD;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU/D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAkBvG"}
package/dist/config.js ADDED
@@ -0,0 +1,349 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (C) 2026 Oleksii PELYKH
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join, resolve } from "node:path";
6
+ import { parse as parseYaml } from "yaml";
7
+ import { z } from "zod";
8
+ /**
9
+ * Source pattern for a 1Password item reference, exported as a STRING (not
10
+ * a `RegExp`) so non-schema consumers (the CLI's interactive `auth init`
11
+ * flow's validation closure) can adopt the canonical pattern without
12
+ * pulling the entire Zod schema into their bundle. Analogous to
13
+ * {@link ./lib/redact.ts#BEARER_PATTERN_SOURCE}.
14
+ *
15
+ * Forms accepted:
16
+ * - `op://VAULT/ITEM` (2-segment) — relies on `op` CLI default-account or
17
+ * `OP_ACCOUNT` env. Works for single-account setups.
18
+ * - `op://ACCOUNT/VAULT/ITEM` (3-segment) — selects a specific account
19
+ * when the user has multiple `op` accounts configured. ACCOUNT is
20
+ * forwarded verbatim to `op item get --account` (accepts UUID,
21
+ * shorthand, or sign-in email — `op` CLI validates).
22
+ *
23
+ * Per-field references (`op://VAULT/ITEM/FIELD` or 4+ segments) are
24
+ * deliberately NOT supported — TTCtl always reads `username` and `password`
25
+ * from a single LOGIN-category item. The pattern rejects 4+ segments.
26
+ */
27
+ export const OP_REF_PATTERN_SOURCE = "^op://(?:[^/]+/)?[^/]+/[^/]+$";
28
+ /**
29
+ * Canonical user-facing hint paired with {@link OP_REF_PATTERN_SOURCE}.
30
+ * Used as the Zod error message AND as the CLI's interactive-prompt
31
+ * validation hint so both surfaces speak the same language. Phrased as a
32
+ * standalone sentence (no field-name prefix) because the schema's
33
+ * `formatLoadValidationError` adds the field path (`auth.credentials:`) and
34
+ * the CLI prompt has its own context.
35
+ */
36
+ export const OP_REF_PATTERN_HINT = "Expected op://[account/]vault/item — no /field suffix";
37
+ const OnePasswordReferenceSchema = z.string().regex(new RegExp(OP_REF_PATTERN_SOURCE), OP_REF_PATTERN_HINT);
38
+ /**
39
+ * Literal credentials in YAML — discouraged for daily use; the 1Password form
40
+ * is recommended.
41
+ *
42
+ * Field name is `username` (matching 1Password's `USERNAME` purpose semantics
43
+ * and the issue's user-facing schema). The value must be a valid email since
44
+ * Toptal's `EmailPasswordSignIn` mutation only accepts emails.
45
+ */
46
+ const LiteralCredentialsSchema = z
47
+ .object({
48
+ username: z.email({ message: "auth.credentials.username must be a valid email address" }),
49
+ password: z.string().min(1, { message: "auth.credentials.password must be a non-empty string" }),
50
+ })
51
+ .strict();
52
+ /**
53
+ * Polymorphic credentials value:
54
+ * - string → 1Password item reference (Form A)
55
+ * - object → literal { username, password } (Form B)
56
+ *
57
+ * The runtime `signIn()` flow collapses both into the internal `Credentials`
58
+ * shape `{ email, password }` (the GraphQL mutation parameter name is
59
+ * `email` — the YAML/op:// surface uses `username` to match 1Password's
60
+ * USERNAME purpose).
61
+ */
62
+ export const AuthCredentialsSchema = z.union([OnePasswordReferenceSchema, LiteralCredentialsSchema]);
63
+ /**
64
+ * `auth` block schema for runtime LOAD operations. Strict — rejects unknown
65
+ * fields and rejects an empty `auth: {}` (the user must author at least one
66
+ * of `credentials` or `token` for the runtime to be useful).
67
+ *
68
+ * Four valid lifecycle states (Forms A-D from the design doc):
69
+ *
70
+ * - **Form A** — `credentials: "op://..."` (1Password reference). No token
71
+ * yet — `auth signin` will write one.
72
+ * - **Form B** — `credentials: { username, password }` (literal). No token
73
+ * yet.
74
+ * - **Form C** — `token: "..."` only (no credentials). The bearer is
75
+ * supplied directly; `auth signin` is not applicable. Common in the E2E
76
+ * sandbox and in deployments that bootstrap a token out-of-band.
77
+ * - **Form D** — both `credentials` AND `token`. Post-signin state for
78
+ * Form A or B configs; the captured bearer is persisted alongside the
79
+ * credentials.
80
+ */
81
+ const AuthBlockLoadSchema = z
82
+ .object({
83
+ credentials: AuthCredentialsSchema.optional(),
84
+ token: z.string().min(1, { message: "auth.token must be a non-empty string" }).optional(),
85
+ })
86
+ .strict()
87
+ .superRefine((auth, ctx) => {
88
+ if (auth.credentials === undefined && auth.token === undefined) {
89
+ ctx.addIssue({
90
+ code: "custom",
91
+ message: "auth must contain at least one of 'credentials' (op:// reference or { username, password } object) " +
92
+ "or 'token' (bearer string captured by `ttctl auth signin`)",
93
+ path: [],
94
+ });
95
+ }
96
+ });
97
+ /**
98
+ * `auth` block schema for runtime WRITE operations. Permissive — both
99
+ * `credentials` and `token` are optional, AND empty `auth: {}` is accepted.
100
+ *
101
+ * Used by `persistAuthToken` and the signout flow during the brief window
102
+ * between mutation and disk-write. The strict load schema rejects empty
103
+ * `auth: {}` at the next file-load — see DQ-6 in the design doc.
104
+ */
105
+ const AuthBlockWriteSchema = z
106
+ .object({
107
+ credentials: AuthCredentialsSchema.optional(),
108
+ token: z.string().min(1).optional(),
109
+ })
110
+ .strict();
111
+ /**
112
+ * Top-level config schema (load form). Single config; no profiles.
113
+ *
114
+ * Compared to the prior shape:
115
+ * - Removed top-level `auth` polymorphic value (string or `{email,password}`)
116
+ * in favor of a structured `auth: { credentials?, token? }` block.
117
+ * - Removed `auth-token-path` field — the captured bearer now lives inside
118
+ * the same YAML file at `auth.token`. Project-scoped configs are routed
119
+ * via direnv (see README § Configuration).
120
+ */
121
+ export const ConfigLoadSchema = z
122
+ .object({
123
+ auth: AuthBlockLoadSchema,
124
+ })
125
+ .strict();
126
+ /**
127
+ * Top-level config schema (write form). Permits empty `auth: {}` for the
128
+ * post-signout transient state when the source was Form C.
129
+ */
130
+ export const ConfigWriteSchema = z
131
+ .object({
132
+ auth: AuthBlockWriteSchema,
133
+ })
134
+ .strict();
135
+ export class ConfigError extends Error {
136
+ code;
137
+ path;
138
+ name = "ConfigError";
139
+ constructor(message, code, path) {
140
+ super(message);
141
+ this.code = code;
142
+ this.path = path;
143
+ }
144
+ }
145
+ /**
146
+ * Map a Zod issue path to a human-friendly field reference for surfacing
147
+ * validation failures. Walks the issue's path array to build a dotted-prose
148
+ * descriptor (e.g. `["auth", "credentials", "password"]` → `auth.credentials.password`).
149
+ *
150
+ * The empty-path case (refinement-level errors) returns `auth` as the
151
+ * operative scope so the message reads naturally.
152
+ */
153
+ function formatIssuePath(path) {
154
+ if (path.length === 0)
155
+ return "auth";
156
+ const parts = [];
157
+ for (const segment of path) {
158
+ if (typeof segment === "number") {
159
+ parts[parts.length - 1] = `${parts[parts.length - 1] ?? ""}[${segment.toString()}]`;
160
+ }
161
+ else if (typeof segment === "string") {
162
+ parts.push(segment);
163
+ }
164
+ else {
165
+ // symbol key — zod permits this in object schemas but our config has
166
+ // no symbol-keyed fields. Render via .toString() so the message is
167
+ // still readable rather than dropping the segment.
168
+ parts.push(segment.toString());
169
+ }
170
+ }
171
+ return parts.join(".");
172
+ }
173
+ /**
174
+ * Detect the legacy pre-#107 config shape (`auth` as a top-level string,
175
+ * e.g. `auth: "op://Personal/ttctl"`) and return a tailored migration error,
176
+ * or `null` if the parsed YAML does not match this shape.
177
+ *
178
+ * The post-#107 `ConfigLoadSchema` rejects `auth: <string>` at union
179
+ * discrimination, so `superRefine` never fires for this case — the dedicated
180
+ * migration message MUST land before Zod runs. Detection point is mechanical:
181
+ * after `parseYaml(raw)`, before `ConfigLoadSchema.safeParse(parsed)`.
182
+ *
183
+ * Other malformed shapes (typos, missing fields, wrong scalar types other
184
+ * than string at `auth`) continue to flow through `formatLoadValidationError`.
185
+ *
186
+ * The error code stays `VALIDATION` — preserves the CLI/MCP exit-code
187
+ * contract from #107. The migration text is part of `ConfigError.message`,
188
+ * not a separate stderr emit, so JSON wire-format consumers see one
189
+ * coherent `{ code, message }` payload.
190
+ */
191
+ function detectLegacyShape(parsed, path) {
192
+ if (parsed !== null && typeof parsed === "object" && "auth" in parsed && typeof parsed.auth === "string") {
193
+ const literal = parsed.auth;
194
+ return new ConfigError([
195
+ `Config file ${path} uses the legacy pre-#107 shape:`,
196
+ ``,
197
+ ` auth: "${literal}"`,
198
+ ``,
199
+ `Migrate to the structured shape:`,
200
+ ``,
201
+ ` auth:`,
202
+ ` credentials: "${literal}"`,
203
+ ``,
204
+ `See CLAUDE.md § Auth Model for the canonical migration recipe.`,
205
+ ].join("\n"), "VALIDATION", path);
206
+ }
207
+ return null;
208
+ }
209
+ /**
210
+ * Render a Zod safe-parse failure into a single-line `ConfigError` message
211
+ * that names the failing branch / field where possible. Defends against the
212
+ * naked `z.union` "Invalid input" UX.
213
+ */
214
+ function formatLoadValidationError(err, path) {
215
+ const lines = [];
216
+ for (const issue of err.issues) {
217
+ const fieldPath = formatIssuePath(issue.path);
218
+ lines.push(`${fieldPath}: ${issue.message}`);
219
+ }
220
+ const summary = lines.length === 0 ? "Invalid config shape" : lines.join("; ");
221
+ return new ConfigError(`Config validation failed: ${summary}`, "VALIDATION", path);
222
+ }
223
+ /**
224
+ * Read, parse, and validate the config file at `path`.
225
+ *
226
+ * Pre-flight `fs.stat`:
227
+ * - ENOENT → `ConfigError(code: NO_CREDS)` so a missing explicit path or
228
+ * `TTCTL_CONFIG_FILE` value surfaces uniformly with the
229
+ * "no config found anywhere" branch in `resolveConfig`.
230
+ * - EACCES / EPERM → `ConfigError(code: PERMISSION)`.
231
+ * - Mode-strictness gate (POSIX): refuse to LOAD when the file is
232
+ * world-writable (`mode & 0o002 != 0` — covers 0666, 0777, etc.).
233
+ * Closes a TOCTOU window on credential reads. Throws
234
+ * `ConfigError(PERMISSION)` with the actual mode in the error message.
235
+ * - Mode-warning (POSIX): emit a stderr warning when group/world bits
236
+ * other than world-writable are set (e.g. 0644). Non-fatal; the file
237
+ * may carry a 1Password reference (low-risk on its own) or, in literal
238
+ * form, a plaintext password — `0o600` is the right posture.
239
+ */
240
+ export function loadConfigFile(path) {
241
+ const stat = (() => {
242
+ try {
243
+ return statSync(path);
244
+ }
245
+ catch (err) {
246
+ const e = err;
247
+ if (e.code === "ENOENT") {
248
+ throw new ConfigError(`Config file not found: ${path}`, "NO_CREDS", path);
249
+ }
250
+ if (e.code === "EACCES" || e.code === "EPERM") {
251
+ throw new ConfigError(`Cannot access config file (permission denied): ${path}`, "PERMISSION", path);
252
+ }
253
+ throw new ConfigError(`Cannot stat config file: ${e.message}`, "NO_CREDS", path);
254
+ }
255
+ })();
256
+ if (process.platform !== "win32") {
257
+ const mode = stat.mode & 0o777;
258
+ if ((mode & 0o002) !== 0) {
259
+ const modeStr = mode.toString(8).padStart(3, "0");
260
+ throw new ConfigError(`Refusing to load world-writable config file ${path} (mode 0${modeStr}). ` +
261
+ `The file may contain a captured bearer token; world-writable mode opens a TOCTOU window. ` +
262
+ `Run: chmod 600 ${path}`, "PERMISSION", path);
263
+ }
264
+ if ((mode & 0o077) !== 0) {
265
+ const modeStr = mode.toString(8).padStart(3, "0");
266
+ process.stderr.write(`WARNING: config file ${path} is group/world readable (mode 0${modeStr}). ` +
267
+ `It may contain a 1Password reference, plaintext password, or captured bearer. ` +
268
+ `Run: chmod 600 ${path}\n`);
269
+ }
270
+ }
271
+ let raw;
272
+ try {
273
+ raw = readFileSync(path, "utf8");
274
+ }
275
+ catch (err) {
276
+ const e = err;
277
+ if (e.code === "EACCES" || e.code === "EPERM") {
278
+ throw new ConfigError(`Cannot read config file (permission denied): ${path}`, "PERMISSION", path);
279
+ }
280
+ throw new ConfigError(`Cannot read config file: ${e.message}`, "NO_CREDS", path);
281
+ }
282
+ let parsed;
283
+ try {
284
+ parsed = parseYaml(raw);
285
+ }
286
+ catch (err) {
287
+ throw new ConfigError(`Invalid YAML: ${err.message}`, "PARSE", path);
288
+ }
289
+ const legacyError = detectLegacyShape(parsed, path);
290
+ if (legacyError !== null) {
291
+ throw legacyError;
292
+ }
293
+ const result = ConfigLoadSchema.safeParse(parsed);
294
+ if (!result.success) {
295
+ throw formatLoadValidationError(result.error, path);
296
+ }
297
+ return result.data;
298
+ }
299
+ /**
300
+ * Deterministic config-path resolution.
301
+ *
302
+ * Precedence (highest → lowest):
303
+ *
304
+ * 1. Explicit `path` argument (passed by the caller — e.g., `--config`
305
+ * flag from #93). Used verbatim, no existence check.
306
+ * 2. `TTCTL_CONFIG_FILE` env var. Same verbatim treatment as `path`.
307
+ * 3. `~/.ttctl.yaml` (POSIX home dotfile), when the file exists on disk.
308
+ *
309
+ * Returns `null` when none of the above produce a path. Callers that want
310
+ * project-scoped configs use `direnv` to set `TTCTL_CONFIG_FILE` per
311
+ * directory — the CWD `./.ttctl.yaml` is NOT consulted (closed since #92).
312
+ *
313
+ * NOTE: XDG paths (`$XDG_CONFIG_HOME/ttctl/config.yaml`,
314
+ * `~/.config/ttctl/config.yaml`) are NO LONGER consulted. The single-file
315
+ * model places the captured bearer in the same YAML; `~/.ttctl.yaml` is
316
+ * the canonical home location.
317
+ */
318
+ export function discoverConfigPath(path) {
319
+ if (path !== undefined)
320
+ return path;
321
+ const envPath = process.env["TTCTL_CONFIG_FILE"];
322
+ if (envPath !== undefined && envPath !== "")
323
+ return envPath;
324
+ const homePath = join(homedir(), ".ttctl.yaml");
325
+ if (existsSync(homePath))
326
+ return homePath;
327
+ return null;
328
+ }
329
+ /**
330
+ * Convenience: discover and load.
331
+ *
332
+ * When the resolution chain returns `null` AND a `./.ttctl.yaml` exists in
333
+ * `process.cwd()`, surface a deprecation message pointing the user at the
334
+ * supported escape hatches. The CWD file is NEVER auto-loaded.
335
+ */
336
+ export function resolveConfig(options = {}) {
337
+ const path = discoverConfigPath(options.path);
338
+ if (path === null) {
339
+ const legacyCwdPath = resolve(process.cwd(), ".ttctl.yaml");
340
+ if (existsSync(legacyCwdPath)) {
341
+ throw new ConfigError(`No config found via --config, TTCTL_CONFIG_FILE, or ~/.ttctl.yaml. ` +
342
+ `Note: a CWD .ttctl.yaml was found at ${legacyCwdPath} but is not auto-discovered. ` +
343
+ `Set TTCTL_CONFIG_FILE=${legacyCwdPath} (e.g. via direnv) or move it to ~/.ttctl.yaml.`, "NO_CREDS");
344
+ }
345
+ throw new ConfigError("No config found. Pass --config <path>, set TTCTL_CONFIG_FILE, or place config at ~/.ttctl.yaml. See README for setup.", "NO_CREDS");
346
+ }
347
+ return { config: loadConfigFile(path), path };
348
+ }
349
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Handle returned by `acquireConfigLock`. The caller MUST `release()` the
3
+ * handle when done — the recommended pattern is `try { ... } finally { await
4
+ * handle.release(); }` so the lock is released on every error path
5
+ * (read-fail, parse-fail, mtime-drift, write-fail, rename-fail, post-rename
6
+ * verify-fail).
7
+ *
8
+ * `release` is idempotent in practice — `proper-lockfile` registers an
9
+ * exit-handler via `signal-exit` that auto-cleans the lock on process death,
10
+ * so a missed release doesn't strand the lock across runs. But explicit
11
+ * release is still required for the in-process retry semantics: a deferred
12
+ * release means the next `acquireConfigLock` in the SAME process would see
13
+ * the lock held by itself and deadlock.
14
+ */
15
+ export interface ConfigLockHandle {
16
+ release: () => Promise<void>;
17
+ }
18
+ /**
19
+ * Acquire an advisory exclusive lock for `configPath`.
20
+ *
21
+ * Implementation: `proper-lockfile` creates `<configPath>.lock` as a
22
+ * sibling DIRECTORY via atomic `mkdir(2)`. The `mkdir` syscall is atomic on
23
+ * ext4, APFS, NTFS, and any other filesystem with POSIX-equivalent
24
+ * semantics — `EEXIST` is the contention signal.
25
+ *
26
+ * Why sibling-lockfile and NOT self-lock: the atomic-rename pattern in
27
+ * `performYamlMutation` swaps the inode at `<configPath>` mid-operation. A
28
+ * `flock(fd)` on the original inode does NOT survive the rename and a
29
+ * concurrent locker on the post-rename inode sees no contention. The
30
+ * sibling pattern sidesteps this — the `.lock` directory's existence is the
31
+ * lock signal, independent of the config file's inode.
32
+ *
33
+ * Cross-platform: works identically on Linux/macOS/Windows — `mkdir`-based
34
+ * atomic creation is portable. No Windows no-op needed (in contrast to the
35
+ * existing POSIX-mode logic in `loadConfigFile` and `performYamlMutation`,
36
+ * which IS Windows-skipped because mode bits aren't meaningful).
37
+ *
38
+ * Contention: ≤1.0s wall-clock budget per `LOCK_RETRY_OPTIONS`. On timeout,
39
+ * throws `ConfigError(LOCKED)` naming the path and suggesting retry — never
40
+ * blocks indefinitely.
41
+ *
42
+ * `realpath: false` skips proper-lockfile's pre-flight `realpath()` check.
43
+ * The caller has already resolved `configPath` to an absolute, symlink-free
44
+ * path via `assertSafePath`, AND the config file may not exist yet (e.g.,
45
+ * the lock is acquired BEFORE the stat baseline that throws ENOENT). With
46
+ * `realpath: true` (the library default), a missing file would surface as
47
+ * an opaque proper-lockfile error before our own ENOENT path runs.
48
+ */
49
+ export declare function acquireConfigLock(configPath: string): Promise<ConfigLockHandle>;
50
+ //# sourceMappingURL=configLock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configLock.d.ts","sourceRoot":"","sources":["../src/configLock.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AA6BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiCrF"}