flanders 0.6.0 → 0.7.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.
@@ -1,33 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Install = void 0;
4
- exports.stripYamlFrontmatter = stripYamlFrontmatter;
5
4
  exports.parseInstallFlags = parseInstallFlags;
6
5
  const FlandersConfig_1 = require("../workspace/FlandersConfig");
7
6
  const PromptHelper_1 = require("../ui/PromptHelper");
8
- const fsUtils_1 = require("../system/fsUtils");
9
- const skills_1 = require("../prompts/skills");
7
+ const skillArtifacts_1 = require("./skillArtifacts");
10
8
  const InstallModelProbe_1 = require("./InstallModelProbe");
11
- const SKILLS = [
12
- { name: "flanders-spec", body: skills_1.specSkillBody },
13
- { name: "flanders-plan", body: skills_1.planSkillBody },
14
- { name: "flanders-work", body: skills_1.workSkillBody }
15
- ];
16
- function stripYamlFrontmatter(body) {
17
- if (!body.startsWith("---\n") && !body.startsWith("---\r\n")) {
18
- return body;
19
- }
20
- const newlineAfterOpener = body.indexOf("\n") + 1;
21
- const closerIndex = body.indexOf("\n---\n", newlineAfterOpener);
22
- if (closerIndex === -1) {
23
- const closerCrlf = body.indexOf("\n---\r\n", newlineAfterOpener);
24
- if (closerCrlf === -1) {
25
- return body;
26
- }
27
- return body.slice(closerCrlf + "\n---\r\n".length);
28
- }
29
- return body.slice(closerIndex + "\n---\n".length);
30
- }
31
9
  async function promptChoice(ask, args) {
32
10
  try {
33
11
  return await (0, PromptHelper_1.askChoice)(ask, args);
@@ -298,14 +276,29 @@ class Install {
298
276
  result() {
299
277
  return this._runPromise;
300
278
  }
301
- async _resolveCuratedChoice(headerLabel, question, curatedValues, defaultLabel, customLabel, customPlaceholder, contexts) {
279
+ async _resolveCuratedChoice(headerLabel, question, curatedValues, defaultLabel, customLabel, customPlaceholder, contexts, preselect) {
302
280
  const options = curatedValues.map(v => ({ label: v }));
303
281
  options.push({ label: defaultLabel });
304
282
  options.push({ label: customLabel });
283
+ let preselectLabel;
284
+ let customDefault;
285
+ if (preselect !== undefined) {
286
+ if (preselect === "") {
287
+ preselectLabel = defaultLabel;
288
+ }
289
+ else if (curatedValues.includes(preselect)) {
290
+ preselectLabel = preselect;
291
+ }
292
+ else {
293
+ preselectLabel = customLabel;
294
+ customDefault = preselect;
295
+ }
296
+ }
305
297
  const option = await promptChoice(contexts.ask, {
306
298
  header: headerLabel,
307
299
  question,
308
- options
300
+ options,
301
+ defaultLabel: preselectLabel
309
302
  });
310
303
  if (!option) {
311
304
  return null;
@@ -319,7 +312,8 @@ class Install {
319
312
  if (option.label === customLabel) {
320
313
  const text = await promptText(contexts.ask, {
321
314
  question,
322
- placeholder: customPlaceholder
315
+ placeholder: customPlaceholder,
316
+ default: customDefault
323
317
  });
324
318
  if (text === null) {
325
319
  return null;
@@ -331,8 +325,35 @@ class Install {
331
325
  }
332
326
  return option.label;
333
327
  }
334
- async _resolveClaudeModel(roleLabel, headerLabel, contexts) {
328
+ async _resolveClaudeModel(roleLabel, headerLabel, contexts, preselect) {
335
329
  const question = `Which model should ${roleLabel} use?`;
330
+ let topDefault;
331
+ let customDefault;
332
+ let preselectedFamily;
333
+ let preselectedEntryLabel;
334
+ if (preselect !== undefined) {
335
+ if (preselect === "") {
336
+ topDefault = "default configured model";
337
+ }
338
+ else {
339
+ const alias = CLAUDE_CROSS_FAMILY_ALIASES.find(a => a.value === preselect);
340
+ if (alias) {
341
+ topDefault = alias.label;
342
+ }
343
+ else {
344
+ const family = CLAUDE_MODEL_FAMILIES.find(f => f.entries.some(e => e.value === preselect));
345
+ if (family) {
346
+ topDefault = family.family;
347
+ preselectedFamily = family;
348
+ preselectedEntryLabel = family.entries.find(e => e.value === preselect).label;
349
+ }
350
+ else {
351
+ topDefault = "enter a custom value…";
352
+ customDefault = preselect;
353
+ }
354
+ }
355
+ }
356
+ }
336
357
  topLevel: for (;;) {
337
358
  const topOptions = [
338
359
  ...CLAUDE_MODEL_FAMILIES.map(family => ({ label: family.family })),
@@ -343,7 +364,8 @@ class Install {
343
364
  const top = await promptChoice(contexts.ask, {
344
365
  header: headerLabel,
345
366
  question,
346
- options: topOptions
367
+ options: topOptions,
368
+ defaultLabel: topDefault
347
369
  });
348
370
  if (!top) {
349
371
  return null;
@@ -357,7 +379,8 @@ class Install {
357
379
  if (top.label === "enter a custom value…") {
358
380
  const text = await promptText(contexts.ask, {
359
381
  question,
360
- placeholder: "leave empty for the default configured model"
382
+ placeholder: "leave empty for the default configured model",
383
+ default: customDefault
361
384
  });
362
385
  if (text === null) {
363
386
  return null;
@@ -378,7 +401,8 @@ class Install {
378
401
  const choice = await promptChoice(contexts.ask, {
379
402
  header: headerLabel,
380
403
  question: `Which ${family.family} model should ${roleLabel} use?`,
381
- options: familyOptions
404
+ options: familyOptions,
405
+ defaultLabel: family === preselectedFamily ? preselectedEntryLabel : undefined
382
406
  });
383
407
  if (!choice) {
384
408
  return null;
@@ -394,7 +418,7 @@ class Install {
394
418
  }
395
419
  }
396
420
  }
397
- async _resolveRoleModel(roleLabel, headerLabel, tool, suppliedModel, contexts) {
421
+ async _resolveRoleModel(roleLabel, headerLabel, tool, suppliedModel, contexts, preselect) {
398
422
  if (suppliedModel !== undefined) {
399
423
  return suppliedModel;
400
424
  }
@@ -402,7 +426,7 @@ class Install {
402
426
  return null;
403
427
  }
404
428
  if (tool === "claude") {
405
- return await this._resolveClaudeModel(roleLabel, headerLabel, contexts);
429
+ return await this._resolveClaudeModel(roleLabel, headerLabel, contexts, preselect);
406
430
  }
407
431
  if (!this._modelProbeCache.has(tool)) {
408
432
  const result = await (0, InstallModelProbe_1.probeModelList)(contexts.script);
@@ -418,10 +442,20 @@ class Install {
418
442
  if (probeResult.kind === "list") {
419
443
  const options = probeResult.models.map(m => ({ label: m }));
420
444
  options.push({ label: "default configured model" });
445
+ let modelDefaultLabel;
446
+ if (preselect !== undefined) {
447
+ if (preselect === "") {
448
+ modelDefaultLabel = "default configured model";
449
+ }
450
+ else if (probeResult.models.includes(preselect)) {
451
+ modelDefaultLabel = preselect;
452
+ }
453
+ }
421
454
  const option = await promptChoice(contexts.ask, {
422
455
  header: headerLabel,
423
456
  question: `Which model should ${roleLabel} use?`,
424
- options
457
+ options,
458
+ defaultLabel: modelDefaultLabel
425
459
  });
426
460
  if (!option) {
427
461
  return null;
@@ -433,7 +467,8 @@ class Install {
433
467
  }
434
468
  const text = await promptText(contexts.ask, {
435
469
  question: `Which model should ${roleLabel} use?`,
436
- placeholder: "leave empty for the default configured model"
470
+ placeholder: "leave empty for the default configured model",
471
+ default: preselect
437
472
  });
438
473
  if (text === null) {
439
474
  return null;
@@ -443,7 +478,7 @@ class Install {
443
478
  }
444
479
  return text;
445
480
  }
446
- async _resolveRoleEffort(roleLabel, headerLabel, tool, suppliedEffort, contexts) {
481
+ async _resolveRoleEffort(roleLabel, headerLabel, tool, suppliedEffort, contexts, preselect) {
447
482
  if (suppliedEffort !== undefined) {
448
483
  return suppliedEffort;
449
484
  }
@@ -453,10 +488,15 @@ class Install {
453
488
  if (tool === "codex") {
454
489
  const options = CODEX_EFFORT_LEVELS.map(e => ({ label: e }));
455
490
  options.push({ label: "default configured effort" });
491
+ let effortDefaultLabel;
492
+ if (preselect !== undefined) {
493
+ effortDefaultLabel = preselect === "" ? "default configured effort" : preselect;
494
+ }
456
495
  const option = await promptChoice(contexts.ask, {
457
496
  header: headerLabel,
458
497
  question: `What effort level should ${roleLabel} use?`,
459
- options
498
+ options,
499
+ defaultLabel: effortDefaultLabel
460
500
  });
461
501
  if (!option) {
462
502
  return null;
@@ -466,9 +506,9 @@ class Install {
466
506
  }
467
507
  return option.label === "default configured effort" ? "" : option.label;
468
508
  }
469
- return await this._resolveCuratedChoice(headerLabel, `What effort level should ${roleLabel} use?`, CLAUDE_EFFORT_LEVELS, "default configured effort", "enter a custom value…", "leave empty for the default configured effort", contexts);
509
+ return await this._resolveCuratedChoice(headerLabel, `What effort level should ${roleLabel} use?`, CLAUDE_EFFORT_LEVELS, "default configured effort", "enter a custom value…", "leave empty for the default configured effort", contexts, preselect);
470
510
  }
471
- async _resolveReviewer(idx, supplied, contexts) {
511
+ async _resolveReviewer(idx, supplied, contexts, storedReviewer) {
472
512
  const ordinal = idx === 1 ? "" : ` ${idx}`;
473
513
  const roleLabel = `reviewer${ordinal}`;
474
514
  let tool;
@@ -485,7 +525,8 @@ class Install {
485
525
  options: [
486
526
  { label: "claude", description: "Use Claude Code" },
487
527
  { label: "codex", description: "Use Codex CLI" }
488
- ]
528
+ ],
529
+ defaultLabel: storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.tool
489
530
  });
490
531
  if (!option) {
491
532
  return null;
@@ -495,17 +536,18 @@ class Install {
495
536
  }
496
537
  tool = option.label;
497
538
  }
498
- const model = await this._resolveRoleModel(roleLabel, `Reviewer${ordinal} model`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.model, contexts);
539
+ const model = await this._resolveRoleModel(roleLabel, `Reviewer${ordinal} model`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.model, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.model);
499
540
  if (model === null) {
500
541
  return null;
501
542
  }
502
- const effort = await this._resolveRoleEffort(roleLabel, `Reviewer${ordinal} effort`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.effort, contexts);
543
+ const effort = await this._resolveRoleEffort(roleLabel, `Reviewer${ordinal} effort`, tool, supplied === null || supplied === void 0 ? void 0 : supplied.effort, contexts, storedReviewer === null || storedReviewer === void 0 ? void 0 : storedReviewer.effort);
503
544
  if (effort === null) {
504
545
  return null;
505
546
  }
506
547
  return { tool, model, effort };
507
548
  }
508
549
  async _run(rawArgs, options, contexts) {
550
+ var _a;
509
551
  try {
510
552
  const parsed = parseInstallFlags(rawArgs);
511
553
  if (!parsed.ok) {
@@ -523,7 +565,7 @@ class Install {
523
565
  }
524
566
  const option = await promptChoice(contexts.ask, {
525
567
  header: "Skills tool",
526
- question: "Which AI tool(s) should the skills be installed for?",
568
+ question: "Which AI tool(s) should the skills be installed for, neighbor?",
527
569
  options: [
528
570
  { label: "claude", description: "Install skills for Claude Code" },
529
571
  { label: "codex", description: "Install skills for Codex CLI" },
@@ -576,6 +618,14 @@ class Install {
576
618
  }
577
619
  mode = option.label;
578
620
  }
621
+ const storedConfig = await (0, FlandersConfig_1.readScope)(contexts.fs, {
622
+ scope: mode,
623
+ projectRoot: options.projectRoot,
624
+ homeDir: contexts.platform.homedir()
625
+ });
626
+ if (this._disposed) {
627
+ return 1;
628
+ }
579
629
  let workerTool;
580
630
  if (answers.workerTool !== undefined) {
581
631
  workerTool = answers.workerTool;
@@ -585,12 +635,13 @@ class Install {
585
635
  return 1;
586
636
  }
587
637
  const option = await promptChoice(contexts.ask, {
588
- header: "Worker tool",
638
+ header: "Worker tool, neighborino",
589
639
  question: "Which AI tool should the worker use?",
590
640
  options: [
591
641
  { label: "claude", description: "Use Claude Code" },
592
642
  { label: "codex", description: "Use Codex CLI" }
593
- ]
643
+ ],
644
+ defaultLabel: storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.tool
594
645
  });
595
646
  if (!option) {
596
647
  return 1;
@@ -600,19 +651,20 @@ class Install {
600
651
  }
601
652
  workerTool = option.label;
602
653
  }
603
- const workerModel = await this._resolveRoleModel("the worker", "Worker model", workerTool, answers.workerModel, contexts);
654
+ const workerModel = await this._resolveRoleModel("the worker", "Worker model", workerTool, answers.workerModel, contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.model);
604
655
  if (workerModel === null) {
605
656
  return 1;
606
657
  }
607
- const workerEffort = await this._resolveRoleEffort("the worker", "Worker effort", workerTool, answers.workerEffort, contexts);
658
+ const workerEffort = await this._resolveRoleEffort("the worker", "Worker effort", workerTool, answers.workerEffort, contexts, storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.worker.effort);
608
659
  if (workerEffort === null) {
609
660
  return 1;
610
661
  }
611
662
  const suppliedReviewers = answers.reviewers;
663
+ const storedReviewers = storedConfig === null || storedConfig === void 0 ? void 0 : storedConfig.reviewers;
612
664
  const reviewers = [];
613
665
  if (suppliedReviewers && suppliedReviewers.length > 0) {
614
666
  for (let i = 0; i < suppliedReviewers.length; i++) {
615
- const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], contexts);
667
+ const reviewer = await this._resolveReviewer(i + 1, suppliedReviewers[i], contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]);
616
668
  if (reviewer === null) {
617
669
  return 1;
618
670
  }
@@ -622,7 +674,7 @@ class Install {
622
674
  else {
623
675
  let idx = 1;
624
676
  for (;;) {
625
- const reviewer = await this._resolveReviewer(idx, undefined, contexts);
677
+ const reviewer = await this._resolveReviewer(idx, undefined, contexts, storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[idx - 1]);
626
678
  if (reviewer === null) {
627
679
  return 1;
628
680
  }
@@ -630,13 +682,17 @@ class Install {
630
682
  if (this._disposed) {
631
683
  return 1;
632
684
  }
685
+ const moreDefault = storedReviewers !== undefined
686
+ ? (idx < storedReviewers.length ? "yes" : "no")
687
+ : undefined;
633
688
  const more = await promptChoice(contexts.ask, {
634
689
  header: "Configure another reviewer?",
635
- question: "Configure another reviewer?",
690
+ question: "Okely-dokely — care to configure another reviewer?",
636
691
  options: [
637
692
  { label: "no", description: "Stop adding reviewers" },
638
693
  { label: "yes", description: "Configure another reviewer in the ordered list" }
639
- ]
694
+ ],
695
+ defaultLabel: moreDefault
640
696
  });
641
697
  if (!more) {
642
698
  return 1;
@@ -665,6 +721,9 @@ class Install {
665
721
  }
666
722
  else {
667
723
  const reviewerCount = reviewers.length;
724
+ const minimumDefault = storedConfig !== null
725
+ ? Math.min(storedConfig.minimumReviews, reviewerCount)
726
+ : reviewerCount;
668
727
  if (answers.reviewerMinimum !== undefined) {
669
728
  minimumReviews = answers.reviewerMinimum;
670
729
  }
@@ -676,7 +735,8 @@ class Install {
676
735
  while (chosen === null) {
677
736
  const entry = await promptText(contexts.ask, {
678
737
  question: "Minimum reviewers that must run to a verdict in each review round",
679
- placeholder: `1-${reviewerCount}, empty for ${reviewerCount}`
738
+ placeholder: `1-${reviewerCount}, empty for ${minimumDefault}`,
739
+ default: String(minimumDefault)
680
740
  });
681
741
  if (entry === null) {
682
742
  return 1;
@@ -685,17 +745,12 @@ class Install {
685
745
  return 1;
686
746
  }
687
747
  const trimmed = entry.trim();
688
- if (trimmed === "") {
689
- chosen = reviewerCount;
748
+ const parsed = Number(trimmed);
749
+ if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
750
+ chosen = parsed;
690
751
  }
691
752
  else {
692
- const parsed = Number(trimmed);
693
- if (/^\d+$/.test(trimmed) && parsed >= 1 && parsed <= reviewerCount) {
694
- chosen = parsed;
695
- }
696
- else {
697
- contexts.output.write(`Enter an integer between 1 and ${reviewerCount}, or leave empty for ${reviewerCount}.\n`);
698
- }
753
+ contexts.output.write(`Whoopsie enter an integer between 1 and ${reviewerCount}, or leave empty for ${minimumDefault}.\n`);
699
754
  }
700
755
  }
701
756
  minimumReviews = chosen;
@@ -715,13 +770,15 @@ class Install {
715
770
  const reviewer = reviewers[i];
716
771
  const modelLabel = reviewer.model === "" ? "default configured model" : reviewer.model;
717
772
  const effortLabel = reviewer.effort === "" ? "default configured effort" : reviewer.effort;
773
+ const optionalDefault = ((_a = storedReviewers === null || storedReviewers === void 0 ? void 0 : storedReviewers[i]) === null || _a === void 0 ? void 0 : _a.optional) === true ? "yes" : "no";
718
774
  const optionalOption = await promptChoice(contexts.ask, {
719
775
  header: `Reviewer ${i + 1} optional`,
720
776
  question: `Is reviewer ${i + 1} (${reviewer.tool} · ${modelLabel} · ${effortLabel}) optional?`,
721
777
  options: [
722
778
  { label: "no", description: "Required — always waits out its rate-limit waits; the round never completes without its verdict" },
723
779
  { label: "yes", description: "Optional — reviews exactly like a required reviewer; the only difference is the round abandons it while it is in a rate-limit wait, once every required reviewer is in and the minimum is met" }
724
- ]
780
+ ],
781
+ defaultLabel: optionalDefault
725
782
  });
726
783
  if (!optionalOption) {
727
784
  return 1;
@@ -742,63 +799,23 @@ class Install {
742
799
  const scopeRoot = mode === "global"
743
800
  ? contexts.platform.homedir()
744
801
  : options.projectRoot;
745
- for (const skill of SKILLS) {
746
- if (!skill.body) {
747
- contexts.output.writeError(`Skill "${skill.name}" has no content.\n`);
748
- return 1;
749
- }
750
- }
751
- const writeClaude = skillsTool === "claude" || skillsTool === "both";
752
- const writeCodex = skillsTool === "codex" || skillsTool === "both";
753
802
  const writtenPaths = [];
754
- if (writeClaude) {
755
- const claudeSkillsRoot = (0, fsUtils_1.joinPath)(scopeRoot, ".claude/skills");
756
- for (const skill of SKILLS) {
757
- if (this._disposed) {
758
- return 1;
759
- }
760
- const skillFolder = (0, fsUtils_1.joinPath)(claudeSkillsRoot, skill.name);
761
- try {
762
- await contexts.fs.mkdir(skillFolder, { recursive: true });
763
- }
764
- catch {
765
- contexts.output.writeError(`Cannot create destination: ${skillFolder}\n`);
766
- return 1;
803
+ const tools = [];
804
+ if (skillsTool === "claude" || skillsTool === "both") {
805
+ tools.push("claude");
806
+ }
807
+ if (skillsTool === "codex" || skillsTool === "both") {
808
+ tools.push("codex");
809
+ }
810
+ for (const tool of tools) {
811
+ const result = await (0, skillArtifacts_1.writeSkillArtifacts)(contexts.fs, scopeRoot, tool, () => this._disposed);
812
+ if (!result.ok) {
813
+ if (result.diagnostic !== null) {
814
+ contexts.output.writeError(result.diagnostic);
767
815
  }
768
- const filePath = (0, fsUtils_1.joinPath)(skillFolder, "SKILL.md");
769
- try {
770
- await contexts.fs.writeFile(filePath, skill.body);
771
- writtenPaths.push(filePath);
772
- }
773
- catch {
774
- contexts.output.writeError(`Cannot write file: ${filePath}\n`);
775
- return 1;
776
- }
777
- }
778
- }
779
- if (writeCodex) {
780
- const codexPromptsRoot = (0, fsUtils_1.joinPath)(scopeRoot, ".codex/prompts");
781
- try {
782
- await contexts.fs.mkdir(codexPromptsRoot, { recursive: true });
783
- }
784
- catch {
785
- contexts.output.writeError(`Cannot create destination: ${codexPromptsRoot}\n`);
786
816
  return 1;
787
817
  }
788
- for (const skill of SKILLS) {
789
- if (this._disposed) {
790
- return 1;
791
- }
792
- const filePath = (0, fsUtils_1.joinPath)(codexPromptsRoot, `${skill.name}.md`);
793
- try {
794
- await contexts.fs.writeFile(filePath, stripYamlFrontmatter(skill.body));
795
- writtenPaths.push(filePath);
796
- }
797
- catch {
798
- contexts.output.writeError(`Cannot write file: ${filePath}\n`);
799
- return 1;
800
- }
801
- }
818
+ writtenPaths.push(...result.writtenPaths);
802
819
  }
803
820
  if (this._disposed) {
804
821
  return 1;
@@ -0,0 +1,19 @@
1
+ import type { FsContext, OutputContext } from "../contexts";
2
+ import type { PlatformContext } from "../workspace/Workspace";
3
+ export type UpdateContexts = Readonly<{
4
+ fs: FsContext;
5
+ output: OutputContext;
6
+ platform: PlatformContext;
7
+ }>;
8
+ export type UpdateOptions = Readonly<{
9
+ projectRoot: string;
10
+ }>;
11
+ export declare class Update {
12
+ private _disposed;
13
+ private _runPromise;
14
+ constructor(rawArgs: readonly string[], options: UpdateOptions, contexts: UpdateContexts);
15
+ result(): Promise<number>;
16
+ private _isInstalled;
17
+ private _run;
18
+ dispose(): Promise<void>;
19
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Update = void 0;
4
+ const skillArtifacts_1 = require("./skillArtifacts");
5
+ class Update {
6
+ constructor(rawArgs, options, contexts) {
7
+ this._disposed = false;
8
+ this._runPromise = this._run(rawArgs, options, contexts);
9
+ this._runPromise.catch(() => { });
10
+ }
11
+ result() {
12
+ return this._runPromise;
13
+ }
14
+ async _isInstalled(fs, scopeRoot, tool) {
15
+ for (const path of (0, skillArtifacts_1.skillArtifactPaths)(scopeRoot, tool)) {
16
+ if (await fs.exists(path)) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ async _run(rawArgs, options, contexts) {
23
+ try {
24
+ if (rawArgs.length > 0) {
25
+ contexts.output.writeError("The update command takes no arguments.\n");
26
+ return 1;
27
+ }
28
+ const homeDir = contexts.platform.homedir();
29
+ const destinations = [
30
+ { scopeRoot: options.projectRoot, tool: "claude" },
31
+ { scopeRoot: options.projectRoot, tool: "codex" },
32
+ { scopeRoot: homeDir, tool: "claude" },
33
+ { scopeRoot: homeDir, tool: "codex" }
34
+ ];
35
+ const writtenPaths = [];
36
+ let found = false;
37
+ for (const dest of destinations) {
38
+ if (this._disposed) {
39
+ return 1;
40
+ }
41
+ if (!(await this._isInstalled(contexts.fs, dest.scopeRoot, dest.tool))) {
42
+ continue;
43
+ }
44
+ found = true;
45
+ const result = await (0, skillArtifacts_1.writeSkillArtifacts)(contexts.fs, dest.scopeRoot, dest.tool, () => this._disposed);
46
+ if (!result.ok) {
47
+ if (result.diagnostic !== null) {
48
+ contexts.output.writeError(result.diagnostic);
49
+ }
50
+ return 1;
51
+ }
52
+ writtenPaths.push(...result.writtenPaths);
53
+ }
54
+ if (this._disposed) {
55
+ return 1;
56
+ }
57
+ if (!found) {
58
+ contexts.output.writeError("Well, hi-diddly-ho! There are no Flanders skills installed anywhere to refresh. Run npx flanders install to set them up first.\n");
59
+ return 1;
60
+ }
61
+ for (const p of writtenPaths) {
62
+ contexts.output.write(`${p}\n`);
63
+ }
64
+ return 0;
65
+ }
66
+ catch (e) {
67
+ if (!this._disposed) {
68
+ contexts.output.writeError(`${e instanceof Error ? e.message : String(e)}\n`);
69
+ }
70
+ return 1;
71
+ }
72
+ }
73
+ async dispose() {
74
+ if (this._disposed) {
75
+ try {
76
+ await this._runPromise;
77
+ }
78
+ catch {
79
+ }
80
+ return;
81
+ }
82
+ this._disposed = true;
83
+ try {
84
+ await this._runPromise;
85
+ }
86
+ catch {
87
+ }
88
+ }
89
+ }
90
+ exports.Update = Update;
@@ -0,0 +1,18 @@
1
+ import type { FsContext } from "../contexts";
2
+ type SkillDef = Readonly<{
3
+ name: string;
4
+ body: string;
5
+ }>;
6
+ export declare const SKILLS: readonly SkillDef[];
7
+ export declare function skillArtifactPath(scopeRoot: string, tool: "claude" | "codex", skillName: string): string;
8
+ export declare function skillArtifactPaths(scopeRoot: string, tool: "claude" | "codex"): readonly string[];
9
+ export declare function stripYamlFrontmatter(body: string): string;
10
+ export type WriteSkillArtifactsResult = Readonly<{
11
+ ok: true;
12
+ writtenPaths: readonly string[];
13
+ }> | Readonly<{
14
+ ok: false;
15
+ diagnostic: string | null;
16
+ }>;
17
+ export declare function writeSkillArtifacts(fs: FsContext, scopeRoot: string, tool: "claude" | "codex", isDisposed: () => boolean): Promise<WriteSkillArtifactsResult>;
18
+ export {};