Skip to main content

cmx_core/
paths.rs

1use anyhow::{Context, Result, bail};
2use std::path::PathBuf;
3
4use crate::gateway::Filesystem;
5use crate::platform::Platform;
6use crate::types::{ArtifactKind, InstallScope};
7
8/// Centralizes all path resolution for cmx configuration and install directories.
9///
10/// Production code constructs this via [`ConfigPaths::from_env`]; tests use
11/// [`ConfigPaths::for_test`] to inject arbitrary root directories and avoid
12/// touching the real home directory.
13pub struct ConfigPaths {
14    pub config_dir: PathBuf,
15    pub home_dir: PathBuf,
16    pub platform: Platform,
17}
18
19impl ConfigPaths {
20    /// Production constructor — derives paths from the real home and config directories.
21    pub fn from_env(platform: Platform) -> Result<Self> {
22        let home = dirs::home_dir().context("Could not determine home directory")?;
23        let config_dir = home.join(".config").join("context-mixer");
24        Ok(Self {
25            config_dir,
26            home_dir: home,
27            platform,
28        })
29    }
30
31    /// Test constructor — uses arbitrary root directories so no real home
32    /// directory is touched. Defaults to `Platform::Claude`.
33    pub fn for_test(home: PathBuf, config: PathBuf) -> Self {
34        Self {
35            config_dir: config,
36            home_dir: home,
37            platform: Platform::Claude,
38        }
39    }
40
41    /// Test constructor with explicit platform.
42    pub fn for_test_with_platform(home: PathBuf, config: PathBuf, platform: Platform) -> Self {
43        Self {
44            config_dir: config,
45            home_dir: home,
46            platform,
47        }
48    }
49
50    /// Return a view of these paths bound to a different platform.
51    ///
52    /// `home_dir` and `config_dir` are platform-independent — only the active
53    /// platform changes. `cmx doctor` uses this to survey every platform's
54    /// install directories and lock files from a single base, reusing all the
55    /// platform-aware path resolution without rebuilding it per platform.
56    #[must_use]
57    pub fn with_platform(&self, platform: Platform) -> ConfigPaths {
58        ConfigPaths {
59            config_dir: self.config_dir.clone(),
60            home_dir: self.home_dir.clone(),
61            platform,
62        }
63    }
64
65    /// Path to `sources.json`.
66    pub fn sources_path(&self) -> PathBuf {
67        self.config_dir.join("sources.json")
68    }
69
70    /// Directory where git-backed sources are cloned.
71    pub fn git_clones_dir(&self) -> PathBuf {
72        self.config_dir.join("sources")
73    }
74
75    /// Path to `sets.json` for the given scope.
76    ///
77    /// Sets are platform-independent — a single file per scope, unlike
78    /// [`lock_path`](Self::lock_path) which carries a per-platform slug.
79    pub fn sets_path(&self, scope: InstallScope) -> PathBuf {
80        if scope.is_local() {
81            PathBuf::from(".context-mixer").join("sets.json")
82        } else {
83            self.config_dir.join("sets.json")
84        }
85    }
86
87    /// Path to `config.json` (LLM gateway settings).
88    pub fn config_path(&self) -> PathBuf {
89        self.config_dir.join("config.json")
90    }
91
92    /// Default location of the canonical artifact home — under cmx's existing
93    /// config root, alongside `sources.json` and the lockfiles.
94    ///
95    /// This is the *default*; the `home` field in `config.json` can override it.
96    /// Use [`crate::config::resolve_artifact_home`] to get the effective home,
97    /// which consults the config first. Note this is unrelated to
98    /// [`home_dir`](Self::home_dir), which is the OS home (`$HOME`).
99    pub fn default_artifact_home(&self) -> PathBuf {
100        self.config_dir.join("home")
101    }
102
103    /// Path to the lock file for the given scope.
104    ///
105    /// Claude uses `cmx-lock.json` for backward compatibility. All other
106    /// platforms use `cmx-lock-<slug>.json`.
107    pub fn lock_path(&self, scope: InstallScope) -> PathBuf {
108        let file_name = if self.platform.slug().is_empty() {
109            "cmx-lock.json".to_string()
110        } else {
111            format!("cmx-lock-{}.json", self.platform.slug())
112        };
113
114        if scope.is_local() {
115            PathBuf::from(".context-mixer").join(&file_name)
116        } else {
117            self.config_dir.join(&file_name)
118        }
119    }
120
121    /// Directory where artifacts of the given kind and scope are installed.
122    ///
123    /// Resolution is delegated to [`Platform::install_subpath`], which encodes
124    /// each platform's layout (including per-kind divergence such as codex/pi
125    /// skills living under the shared `.agents/skills`). Local installs are
126    /// relative to the project root; global installs are anchored at `$HOME`.
127    ///
128    /// Returns `None` for unsupported `(platform, kind)` combinations. Callers
129    /// should gate on [`ensure_supports`](Self::ensure_supports) or
130    /// [`Platform::supports`] before calling this.
131    pub fn install_dir(&self, kind: ArtifactKind, scope: InstallScope) -> Option<PathBuf> {
132        let subpath = self.platform.install_subpath(kind, scope)?;
133        Some(if scope.is_local() {
134            subpath
135        } else {
136            self.home_dir.join(subpath)
137        })
138    }
139
140    /// Full path to where an artifact of `kind` named `name` is (or would be)
141    /// installed under `scope`, accounting for the platform's agent file format.
142    ///
143    /// Agents use the platform's [`agent_extension`](Platform::agent_extension)
144    /// (e.g. `.md`, or `.toml` for codex); skills resolve to a directory named
145    /// after the artifact.
146    ///
147    /// Returns `None` for unsupported `(platform, kind)` combinations.
148    pub fn installed_artifact_path(
149        &self,
150        kind: ArtifactKind,
151        name: &str,
152        scope: InstallScope,
153    ) -> Option<PathBuf> {
154        let dir = self.install_dir(kind, scope)?;
155        Some(kind.installed_path(name, &dir, self.platform.agent_extension()))
156    }
157
158    /// Returns `true` if an artifact of `kind` named `name` exists on disk under `scope`.
159    ///
160    /// Returns `false` for unsupported `(platform, kind)` combinations.
161    pub fn is_installed(
162        &self,
163        kind: ArtifactKind,
164        name: &str,
165        scope: InstallScope,
166        fs: &dyn Filesystem,
167    ) -> bool {
168        self.installed_artifact_path(kind, name, scope)
169            .is_some_and(|path| fs.exists(&path))
170    }
171
172    /// Verify the active platform supports the given artifact kind, returning a
173    /// user-facing error otherwise (e.g. pi has no agent concept).
174    pub fn ensure_supports(&self, kind: ArtifactKind) -> Result<()> {
175        if self.platform.supports(kind) {
176            Ok(())
177        } else {
178            bail!(
179                "The {platform} platform does not support {kind}s. \
180                 {platform} has no native {kind} concept.",
181                platform = self.platform,
182                kind = kind,
183            );
184        }
185    }
186
187    /// Like [`install_dir`](Self::install_dir), but returns `Err` for unsupported
188    /// `(platform, kind)` combinations instead of `None`.
189    pub fn require_install_dir(&self, kind: ArtifactKind, scope: InstallScope) -> Result<PathBuf> {
190        self.install_dir(kind, scope)
191            .ok_or_else(|| unsupported_artifact_error(self.platform, kind))
192    }
193
194    /// Like [`installed_artifact_path`](Self::installed_artifact_path), but returns
195    /// `Err` for unsupported `(platform, kind)` combinations instead of `None`.
196    pub fn require_installed_artifact_path(
197        &self,
198        kind: ArtifactKind,
199        name: &str,
200        scope: InstallScope,
201    ) -> Result<PathBuf> {
202        self.installed_artifact_path(kind, name, scope)
203            .ok_or_else(|| unsupported_artifact_error(self.platform, kind))
204    }
205}
206
207fn unsupported_artifact_error(platform: Platform, kind: ArtifactKind) -> anyhow::Error {
208    anyhow::anyhow!(
209        "The {platform} platform does not support {kind}s. \
210         {platform} has no native {kind} concept."
211    )
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::gateway::fakes::FakeFilesystem;
218    use crate::types::InstallScope;
219
220    fn test_paths() -> ConfigPaths {
221        ConfigPaths::for_test(
222            PathBuf::from("/home/testuser"),
223            PathBuf::from("/home/testuser/.config/context-mixer"),
224        )
225    }
226
227    fn test_paths_for(platform: Platform) -> ConfigPaths {
228        ConfigPaths::for_test_with_platform(
229            PathBuf::from("/home/testuser"),
230            PathBuf::from("/home/testuser/.config/context-mixer"),
231            platform,
232        )
233    }
234
235    // --- is_installed ---
236
237    #[test]
238    fn is_installed_returns_false_for_absent_artifact() {
239        let paths = test_paths();
240        let fs = FakeFilesystem::new();
241        assert!(!paths.is_installed(ArtifactKind::Agent, "my-agent", InstallScope::Global, &fs));
242    }
243
244    #[test]
245    fn is_installed_returns_true_when_file_present() {
246        let paths = test_paths();
247        let fs = FakeFilesystem::new();
248        let path = paths
249            .installed_artifact_path(ArtifactKind::Agent, "my-agent", InstallScope::Global)
250            .unwrap();
251        fs.add_file(path, "# agent");
252        assert!(paths.is_installed(ArtifactKind::Agent, "my-agent", InstallScope::Global, &fs));
253    }
254
255    // --- with_platform ---
256
257    #[test]
258    fn with_platform_rebinds_platform_keeping_dirs() {
259        let base = test_paths(); // Claude
260        let codex = base.with_platform(Platform::Codex);
261        assert_eq!(codex.platform, Platform::Codex);
262        // home_dir and config_dir are carried over unchanged.
263        assert_eq!(codex.config_dir, base.config_dir);
264        assert_eq!(codex.home_dir, base.home_dir);
265        // Path resolution now reflects the new platform.
266        assert_eq!(
267            codex.lock_path(InstallScope::Global),
268            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-codex.json")
269        );
270        assert_eq!(
271            codex.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
272            PathBuf::from("/home/testuser/.codex/agents")
273        );
274    }
275
276    // --- Claude (default) ---
277
278    #[test]
279    fn sources_path_returns_config_dir_sources_json() {
280        let paths = test_paths();
281        assert_eq!(
282            paths.sources_path(),
283            PathBuf::from("/home/testuser/.config/context-mixer/sources.json")
284        );
285    }
286
287    #[test]
288    fn git_clones_dir_returns_config_dir_sources() {
289        let paths = test_paths();
290        assert_eq!(
291            paths.git_clones_dir(),
292            PathBuf::from("/home/testuser/.config/context-mixer/sources")
293        );
294    }
295
296    #[test]
297    fn sets_path_global_returns_config_dir_sets_json() {
298        let paths = test_paths();
299        assert_eq!(
300            paths.sets_path(InstallScope::Global),
301            PathBuf::from("/home/testuser/.config/context-mixer/sets.json")
302        );
303    }
304
305    #[test]
306    fn sets_path_local_returns_relative_path() {
307        let paths = test_paths();
308        assert_eq!(paths.sets_path(InstallScope::Local), PathBuf::from(".context-mixer/sets.json"));
309    }
310
311    #[test]
312    fn config_path_returns_config_dir_config_json() {
313        let paths = test_paths();
314        assert_eq!(
315            paths.config_path(),
316            PathBuf::from("/home/testuser/.config/context-mixer/config.json")
317        );
318    }
319
320    #[test]
321    fn lock_path_global_returns_config_dir_lock_file() {
322        let paths = test_paths();
323        assert_eq!(
324            paths.lock_path(InstallScope::Global),
325            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock.json")
326        );
327    }
328
329    #[test]
330    fn lock_path_local_returns_relative_path() {
331        let paths = test_paths();
332        assert_eq!(
333            paths.lock_path(InstallScope::Local),
334            PathBuf::from(".context-mixer/cmx-lock.json")
335        );
336    }
337
338    #[test]
339    fn install_dir_agent_global_returns_home_claude_agents() {
340        let paths = test_paths();
341        assert_eq!(
342            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
343            PathBuf::from("/home/testuser/.claude/agents")
344        );
345    }
346
347    #[test]
348    fn install_dir_skill_global_returns_home_claude_skills() {
349        let paths = test_paths();
350        assert_eq!(
351            paths.install_dir(ArtifactKind::Skill, InstallScope::Global).unwrap(),
352            PathBuf::from("/home/testuser/.claude/skills")
353        );
354    }
355
356    #[test]
357    fn install_dir_agent_local_returns_relative_claude_agents() {
358        let paths = test_paths();
359        assert_eq!(
360            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
361            PathBuf::from(".claude/agents")
362        );
363    }
364
365    #[test]
366    fn install_dir_skill_local_returns_relative_claude_skills() {
367        let paths = test_paths();
368        assert_eq!(
369            paths.install_dir(ArtifactKind::Skill, InstallScope::Local).unwrap(),
370            PathBuf::from(".claude/skills")
371        );
372    }
373
374    // --- Cursor ---
375
376    #[test]
377    fn install_dir_cursor_agent_local_returns_cursor_agents() {
378        let paths = test_paths_for(Platform::Cursor);
379        assert_eq!(
380            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
381            PathBuf::from(".cursor/agents")
382        );
383    }
384
385    #[test]
386    fn install_dir_cursor_skill_local_returns_cursor_skills() {
387        let paths = test_paths_for(Platform::Cursor);
388        assert_eq!(
389            paths.install_dir(ArtifactKind::Skill, InstallScope::Local).unwrap(),
390            PathBuf::from(".cursor/skills")
391        );
392    }
393
394    #[test]
395    fn install_dir_cursor_agent_global_returns_home_cursor_agents() {
396        let paths = test_paths_for(Platform::Cursor);
397        assert_eq!(
398            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
399            PathBuf::from("/home/testuser/.cursor/agents")
400        );
401    }
402
403    #[test]
404    fn install_dir_cursor_skill_global_returns_home_cursor_skills() {
405        let paths = test_paths_for(Platform::Cursor);
406        assert_eq!(
407            paths.install_dir(ArtifactKind::Skill, InstallScope::Global).unwrap(),
408            PathBuf::from("/home/testuser/.cursor/skills")
409        );
410    }
411
412    #[test]
413    fn lock_path_cursor_global_uses_cursor_slug() {
414        let paths = test_paths_for(Platform::Cursor);
415        assert_eq!(
416            paths.lock_path(InstallScope::Global),
417            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-cursor.json")
418        );
419    }
420
421    #[test]
422    fn lock_path_cursor_local_uses_cursor_slug() {
423        let paths = test_paths_for(Platform::Cursor);
424        assert_eq!(
425            paths.lock_path(InstallScope::Local),
426            PathBuf::from(".context-mixer/cmx-lock-cursor.json")
427        );
428    }
429
430    // --- Copilot ---
431
432    #[test]
433    fn install_dir_copilot_agent_local_returns_github_agents() {
434        let paths = test_paths_for(Platform::Copilot);
435        assert_eq!(
436            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
437            PathBuf::from(".github/agents")
438        );
439    }
440
441    #[test]
442    fn install_dir_copilot_agent_global_returns_home_copilot_agents() {
443        let paths = test_paths_for(Platform::Copilot);
444        assert_eq!(
445            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
446            PathBuf::from("/home/testuser/.copilot/agents")
447        );
448    }
449
450    #[test]
451    fn lock_path_copilot_global_uses_copilot_slug() {
452        let paths = test_paths_for(Platform::Copilot);
453        assert_eq!(
454            paths.lock_path(InstallScope::Global),
455            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-copilot.json")
456        );
457    }
458
459    // --- Windsurf ---
460
461    #[test]
462    fn install_dir_windsurf_skill_global_returns_codeium_windsurf_skills() {
463        let paths = test_paths_for(Platform::Windsurf);
464        assert_eq!(
465            paths.install_dir(ArtifactKind::Skill, InstallScope::Global).unwrap(),
466            PathBuf::from("/home/testuser/.codeium/windsurf/skills")
467        );
468    }
469
470    #[test]
471    fn install_dir_windsurf_agent_local_returns_windsurf_agents() {
472        let paths = test_paths_for(Platform::Windsurf);
473        assert_eq!(
474            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
475            PathBuf::from(".windsurf/agents")
476        );
477    }
478
479    #[test]
480    fn lock_path_windsurf_global_uses_windsurf_slug() {
481        let paths = test_paths_for(Platform::Windsurf);
482        assert_eq!(
483            paths.lock_path(InstallScope::Global),
484            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-windsurf.json")
485        );
486    }
487
488    // --- Gemini ---
489
490    #[test]
491    fn install_dir_gemini_agent_global_returns_home_gemini_agents() {
492        let paths = test_paths_for(Platform::Gemini);
493        assert_eq!(
494            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
495            PathBuf::from("/home/testuser/.gemini/agents")
496        );
497    }
498
499    #[test]
500    fn lock_path_gemini_global_uses_gemini_slug() {
501        let paths = test_paths_for(Platform::Gemini);
502        assert_eq!(
503            paths.lock_path(InstallScope::Global),
504            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-gemini.json")
505        );
506    }
507
508    // --- opencode ---
509
510    #[test]
511    fn install_dir_opencode_agent_local_uses_singular_leaf() {
512        let paths = test_paths_for(Platform::Opencode);
513        assert_eq!(
514            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
515            PathBuf::from(".opencode/agent")
516        );
517    }
518
519    #[test]
520    fn install_dir_opencode_agent_global_uses_xdg_config() {
521        let paths = test_paths_for(Platform::Opencode);
522        assert_eq!(
523            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
524            PathBuf::from("/home/testuser/.config/opencode/agent")
525        );
526    }
527
528    #[test]
529    fn install_dir_opencode_skill_uses_shared_dot_agents() {
530        let paths = test_paths_for(Platform::Opencode);
531        assert_eq!(
532            paths.install_dir(ArtifactKind::Skill, InstallScope::Local).unwrap(),
533            PathBuf::from(".agents/skills")
534        );
535        assert_eq!(
536            paths.install_dir(ArtifactKind::Skill, InstallScope::Global).unwrap(),
537            PathBuf::from("/home/testuser/.agents/skills")
538        );
539    }
540
541    #[test]
542    fn lock_path_opencode_uses_opencode_slug() {
543        let paths = test_paths_for(Platform::Opencode);
544        assert_eq!(
545            paths.lock_path(InstallScope::Global),
546            PathBuf::from("/home/testuser/.config/context-mixer/cmx-lock-opencode.json")
547        );
548    }
549
550    // --- codex ---
551
552    #[test]
553    fn install_dir_codex_agent_uses_dot_codex_agents() {
554        let paths = test_paths_for(Platform::Codex);
555        assert_eq!(
556            paths.install_dir(ArtifactKind::Agent, InstallScope::Local).unwrap(),
557            PathBuf::from(".codex/agents")
558        );
559        assert_eq!(
560            paths.install_dir(ArtifactKind::Agent, InstallScope::Global).unwrap(),
561            PathBuf::from("/home/testuser/.codex/agents")
562        );
563    }
564
565    #[test]
566    fn install_dir_codex_skill_uses_shared_dot_agents() {
567        let paths = test_paths_for(Platform::Codex);
568        assert_eq!(
569            paths.install_dir(ArtifactKind::Skill, InstallScope::Global).unwrap(),
570            PathBuf::from("/home/testuser/.agents/skills")
571        );
572    }
573
574    #[test]
575    fn installed_artifact_path_codex_agent_is_toml() {
576        let paths = test_paths_for(Platform::Codex);
577        assert_eq!(
578            paths
579                .installed_artifact_path(ArtifactKind::Agent, "my-agent", InstallScope::Global)
580                .unwrap(),
581            PathBuf::from("/home/testuser/.codex/agents/my-agent.toml")
582        );
583    }
584
585    #[test]
586    fn installed_artifact_path_default_agent_is_md() {
587        let paths = test_paths();
588        assert_eq!(
589            paths
590                .installed_artifact_path(ArtifactKind::Agent, "my-agent", InstallScope::Local)
591                .unwrap(),
592            PathBuf::from(".claude/agents/my-agent.md")
593        );
594    }
595
596    #[test]
597    fn installed_artifact_path_skill_is_directory() {
598        let paths = test_paths_for(Platform::Codex);
599        assert_eq!(
600            paths
601                .installed_artifact_path(ArtifactKind::Skill, "my-skill", InstallScope::Local)
602                .unwrap(),
603            PathBuf::from(".agents/skills/my-skill")
604        );
605    }
606
607    // --- pi ---
608
609    #[test]
610    fn install_dir_pi_skill_uses_shared_dot_agents() {
611        let paths = test_paths_for(Platform::Pi);
612        assert_eq!(
613            paths.install_dir(ArtifactKind::Skill, InstallScope::Local).unwrap(),
614            PathBuf::from(".agents/skills")
615        );
616    }
617
618    // --- require_install_dir ---
619
620    #[test]
621    fn require_install_dir_returns_ok_for_supported_combo() {
622        let paths = test_paths(); // Claude platform
623        let result = paths.require_install_dir(ArtifactKind::Skill, InstallScope::Global);
624        assert!(result.is_ok());
625        assert_eq!(result.unwrap(), PathBuf::from("/home/testuser/.claude/skills"));
626    }
627
628    #[test]
629    fn require_install_dir_returns_err_for_unsupported_combo() {
630        let paths = test_paths_for(Platform::Pi);
631        let err = paths
632            .require_install_dir(ArtifactKind::Agent, InstallScope::Global)
633            .unwrap_err();
634        let msg = err.to_string();
635        assert!(msg.contains("pi"), "error should name the platform: {msg}");
636        assert!(msg.contains("agent"), "error should name the kind: {msg}");
637    }
638
639    // --- require_installed_artifact_path ---
640
641    #[test]
642    fn require_installed_artifact_path_returns_ok_for_supported_combo() {
643        let paths = test_paths();
644        let result = paths.require_installed_artifact_path(
645            ArtifactKind::Agent,
646            "my-agent",
647            InstallScope::Global,
648        );
649        assert!(result.is_ok());
650        assert_eq!(result.unwrap(), PathBuf::from("/home/testuser/.claude/agents/my-agent.md"));
651    }
652
653    #[test]
654    fn require_installed_artifact_path_returns_err_for_unsupported_combo() {
655        let paths = test_paths_for(Platform::Pi);
656        let err = paths
657            .require_installed_artifact_path(ArtifactKind::Agent, "x", InstallScope::Global)
658            .unwrap_err();
659        let msg = err.to_string();
660        assert!(msg.contains("pi"), "error should name the platform: {msg}");
661        assert!(msg.contains("agent"), "error should name the kind: {msg}");
662    }
663
664    #[test]
665    fn ensure_supports_pi_rejects_agents() {
666        let paths = test_paths_for(Platform::Pi);
667        let err = paths.ensure_supports(ArtifactKind::Agent).unwrap_err().to_string();
668        assert!(err.contains("pi"), "error should name the platform: {err}");
669        assert!(err.contains("agent"), "error should name the kind: {err}");
670    }
671
672    #[test]
673    fn ensure_supports_pi_allows_skills() {
674        let paths = test_paths_for(Platform::Pi);
675        assert!(paths.ensure_supports(ArtifactKind::Skill).is_ok());
676    }
677
678    #[test]
679    fn ensure_supports_codex_allows_both() {
680        let paths = test_paths_for(Platform::Codex);
681        assert!(paths.ensure_supports(ArtifactKind::Agent).is_ok());
682        assert!(paths.ensure_supports(ArtifactKind::Skill).is_ok());
683    }
684}