@koda-sl/baker-cli 0.232.0 → 0.233.0-dev.bfa5a91e4
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.
- package/README.md +8 -1
- package/dist/{chunk-EKLAHWSF.js → chunk-CMPAHYLB.js} +4 -4
- package/dist/chunk-CMPAHYLB.js.map +1 -0
- package/dist/cli.js +578 -506
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-EKLAHWSF.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-CMPAHYLB.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -565,498 +565,6 @@ function listSchemas() {
|
|
|
565
565
|
return [...registry.keys()];
|
|
566
566
|
}
|
|
567
567
|
|
|
568
|
-
// src/commands/actions/tagError.ts
|
|
569
|
-
var UNKNOWN_TAG_RE = /Unknown action tag\(s\): ([^.]+)\. Valid tags: ([^.]+)\./;
|
|
570
|
-
function splitSlugs(list) {
|
|
571
|
-
return list.split(",").map((slug) => slug.trim()).filter((slug) => slug.length > 0);
|
|
572
|
-
}
|
|
573
|
-
function actionTagFix(message) {
|
|
574
|
-
const match = UNKNOWN_TAG_RE.exec(message);
|
|
575
|
-
if (!match) {
|
|
576
|
-
return null;
|
|
577
|
-
}
|
|
578
|
-
const [, unknownList, validList] = match;
|
|
579
|
-
const unknownTags = splitSlugs(unknownList ?? "");
|
|
580
|
-
const validTags = splitSlugs(validList ?? "");
|
|
581
|
-
const first = unknownTags[0];
|
|
582
|
-
if (first === void 0 || validTags.length === 0) {
|
|
583
|
-
return null;
|
|
584
|
-
}
|
|
585
|
-
return {
|
|
586
|
-
action: "retry_with_a_valid_tag",
|
|
587
|
-
explanation: `The tag(s) ${unknownTags.join(", ")} are not in this company's taxonomy. \`validTags\` below is the complete accepted set \u2014 pick from it and re-run the same command. Only mint a new tag when none of them names this work; a near-duplicate of an existing tag splits the backlog's filter instead of extending it.`,
|
|
588
|
-
unknownTags,
|
|
589
|
-
validTags,
|
|
590
|
-
mintCommand: `baker actions tags create --slug ${first} --description "<what ${first} groups>"`,
|
|
591
|
-
listCommand: "baker actions tags list"
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// src/commands/actions/shared.ts
|
|
596
|
-
function writeOk(data) {
|
|
597
|
-
writeJson({ ok: true, data: data ?? null });
|
|
598
|
-
}
|
|
599
|
-
function failValidation(message) {
|
|
600
|
-
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
601
|
-
process.exit(1);
|
|
602
|
-
}
|
|
603
|
-
function failApi(err) {
|
|
604
|
-
if (err instanceof ApiError) {
|
|
605
|
-
const fix = actionTagFix(err.message);
|
|
606
|
-
writeJson({ ok: false, error: { code: err.code, message: err.message, ...fix ? { fix } : {} } });
|
|
607
|
-
process.exit(1);
|
|
608
|
-
}
|
|
609
|
-
if (err instanceof Error) {
|
|
610
|
-
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: err.message } });
|
|
611
|
-
process.exit(1);
|
|
612
|
-
}
|
|
613
|
-
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
614
|
-
process.exit(1);
|
|
615
|
-
}
|
|
616
|
-
function generateTempId() {
|
|
617
|
-
return `temp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
618
|
-
}
|
|
619
|
-
function isTempId(id) {
|
|
620
|
-
return id.startsWith("temp_");
|
|
621
|
-
}
|
|
622
|
-
function parseTagList(value) {
|
|
623
|
-
if (typeof value !== "string") {
|
|
624
|
-
return void 0;
|
|
625
|
-
}
|
|
626
|
-
const tags = value.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
627
|
-
return [...new Set(tags)];
|
|
628
|
-
}
|
|
629
|
-
function fold(text2) {
|
|
630
|
-
return text2.normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
631
|
-
}
|
|
632
|
-
var SCHEDULE_SIGNALS = [
|
|
633
|
-
/\brecurr(?:ing|ence)\b/i,
|
|
634
|
-
/\bcadence\b/i,
|
|
635
|
-
/\bcron\b/i,
|
|
636
|
-
/\bschedule[ds]?\b/i,
|
|
637
|
-
/\b(?:daily|weekly|monthly|quarterly|annual(?:ly)?|biweekly|nightly)\b/i,
|
|
638
|
-
/\bevery\s+(?:day|week|month|quarter|year|morning|monday|tuesday|wednesday|thursday|friday|saturday|sunday|\d)/i,
|
|
639
|
-
/\beach\s+(?:day|week|month|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i,
|
|
640
|
-
/\bremind(?:er|s)?\b/i,
|
|
641
|
-
/\brun\s+at\b/i
|
|
642
|
-
];
|
|
643
|
-
var ACTION_PRIORITIES = ["urgent", "high", "medium", "low"];
|
|
644
|
-
function parsePriority(value, { allowClear }) {
|
|
645
|
-
if (value === void 0) {
|
|
646
|
-
return void 0;
|
|
647
|
-
}
|
|
648
|
-
if (typeof value !== "string") {
|
|
649
|
-
failValidation(
|
|
650
|
-
`--priority must be one of: ${ACTION_PRIORITIES.join(", ")}${allowClear ? ", or 'none' to clear" : ""}.`
|
|
651
|
-
);
|
|
652
|
-
}
|
|
653
|
-
const trimmed = value.trim().toLowerCase();
|
|
654
|
-
if (trimmed === "") {
|
|
655
|
-
if (allowClear) {
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
return void 0;
|
|
659
|
-
}
|
|
660
|
-
if (allowClear && (trimmed === "none" || trimmed === "clear")) {
|
|
661
|
-
return null;
|
|
662
|
-
}
|
|
663
|
-
if (ACTION_PRIORITIES.includes(trimmed)) {
|
|
664
|
-
return trimmed;
|
|
665
|
-
}
|
|
666
|
-
failValidation(
|
|
667
|
-
`Unknown --priority "${value}". Expected one of: ${ACTION_PRIORITIES.join(", ")}${allowClear ? ", or 'none' to clear" : ""}.`
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
function looksScheduled(name, description) {
|
|
671
|
-
const haystack = fold(`${name}
|
|
672
|
-
${description}`);
|
|
673
|
-
return SCHEDULE_SIGNALS.some((re2) => re2.test(haystack));
|
|
674
|
-
}
|
|
675
|
-
var CAMPAIGN_ENTITY = String.raw`(?:campaigns?|campanas?|ad\s*sets?|adsets?|ad\s*groups?|adgroups?|ads?|anuncios?|conjuntos?\s+de\s+anuncios|grupos?\s+de\s+anuncios)`;
|
|
676
|
-
var LIFECYCLE_VERB = "(?:pause|unpause|enable|disable|launch|restructure|duplicate|clone|rebuild|prune|reweight|split|consolidate|pausar|pausa|activar|podar|poda|reponderar|duplicar|reestructurar|consolidar)";
|
|
677
|
-
var BUDGET_VERB = "(?:cap|raise|lower|increase|decrease|adjust|change|set|update|reallocate|shift|limitar|subir|bajar|ajustar|aplicar|reasignar)";
|
|
678
|
-
var re = (...parts) => new RegExp(parts.join(""), "i");
|
|
679
|
-
var SURFACE_RULES = [
|
|
680
|
-
{
|
|
681
|
-
surface: "ask-user",
|
|
682
|
-
signals: [
|
|
683
|
-
/\bproduction\s+(?:domain|url|site|hostname)\b/i,
|
|
684
|
-
/\b(?:confirm|check|verify|confirmar|verificar)\s+(?:with|w\/|con)\s+(?:the\s+|el\s+|la\s+)?(?:client|user|customer|team|cliente|usuario|equipo)\b/i,
|
|
685
|
-
/\bmissing\s+(?:the\s+)?(?:value|values|id|ids|url|budget|domain|credentials?|account\s+id|measurement\s+id)\b/i,
|
|
686
|
-
/\b(?:ask|get)\s+(?:the\s+)?(?:client|user)\s+for\b/i,
|
|
687
|
-
/\b(?:pedir|solicitar)\s+(?:al\s+)?cliente\b/i
|
|
688
|
-
],
|
|
689
|
-
hint: "This is a missing input, not blocked work. Ask for it with AskUserQuestion (one question), then do the work in the same turn \u2014 filing the Task AND asking the question is the same request twice."
|
|
690
|
-
},
|
|
691
|
-
{
|
|
692
|
-
surface: "analysis",
|
|
693
|
-
nameOnly: true,
|
|
694
|
-
signals: [
|
|
695
|
-
/^\s*(?:re-?)?(?:investigat|analy[sz]|audit|review|document|assess|evaluat|research|map|inventor|examin|diagnos|verif|validat|benchmark)\w*\b/i,
|
|
696
|
-
/^\s*(?:auditar|auditoria|investigar|investigacion|revisar|revision|analizar|analisis|documentar|evaluar|evaluacion|diagnosticar|diagnostico|mapear|inventariar|verificar|verificacion|comprobar|estudiar|estudio)\b/i
|
|
697
|
-
],
|
|
698
|
-
hint: "This names the analysis itself \u2014 investigating, auditing and documenting are work you do now, not work you file. Run it in this chat and deliver the finding; a Task is for a fix something genuinely blocks (bar: `__tooling__/docs/tools/baker/actions.md`)."
|
|
699
|
-
},
|
|
700
|
-
{
|
|
701
|
-
surface: "ads-write",
|
|
702
|
-
// "Approve X" is a human decision, not a staged write — and the budget
|
|
703
|
-
// signals are broad enough to swallow it otherwise. Name-anchored so a
|
|
704
|
-
// description recalling an earlier approval doesn't veto real work.
|
|
705
|
-
exceptName: [/^\s*(?:approve|approval|sign[-\s]?off|aprobar|aprobacion)\b/i],
|
|
706
|
-
// Bare `keywords` is the work when it's the subject of the Task, but plain
|
|
707
|
-
// context when a description happens to discuss search terms.
|
|
708
|
-
nameOnlySignals: [/\b(?:keywords?|palabras\s+clave)\b/i],
|
|
709
|
-
signals: [
|
|
710
|
-
// Targeting.
|
|
711
|
-
/\b(?:geo|location|geographic|geografic\w*)[\s-]*(?:targeting|target|segmentacion)\b/i,
|
|
712
|
-
/\bsegmentacion\s+(?:geografica|por\s+ubicacion)\b/i,
|
|
713
|
-
/\bpresence[-\s_]?(?:only|or[-\s_]?interest)\b/i,
|
|
714
|
-
/\b(?:ad\s*schedul\w*|dayparting|calendario\s+de\s+anuncios)\b/i,
|
|
715
|
-
// Extensions / assets.
|
|
716
|
-
/\b(?:callouts?|sitelinks?|site\s*links?|structured\s+snippets?|price\s+extensions?|lead\s+form\s+extensions?|extensiones|asset\s+groups?)\b/i,
|
|
717
|
-
// Ad copy. Compound forms only — bare `headline` and `copy` belong to
|
|
718
|
-
// `landing`, and an RSA headline has to say so.
|
|
719
|
-
/\b(?:rsas?|responsive\s+search\s+ads?|ad\s+copy|copy\s+de\s+(?:los\s+)?anuncios)\b/i,
|
|
720
|
-
/\b(?:headline|titular|description)\s+(?:pool|set|slots?)\b/i,
|
|
721
|
-
/\bdisplay\s+paths?\b|\brutas?\s+visibles?\b/i,
|
|
722
|
-
// Keywords. `negatives` is bare in English because in this product's
|
|
723
|
-
// vocabulary the noun is always negative keywords; the Spanish side is
|
|
724
|
-
// feminine-only, because "resultados negativos" is a real phrase.
|
|
725
|
-
/\bmatch\s+types?\b|\bconcordancias?\b/i,
|
|
726
|
-
/\bnegatives?\b/i,
|
|
727
|
-
/\b(?:palabras\s+clave\s+)?negativas\b/i,
|
|
728
|
-
/\bkeyword\s+lists?\b|\blistas?\s+de\s+palabras\s+clave\b/i,
|
|
729
|
-
// Audiences. `LAL` is the operators' own shorthand for a lookalike and
|
|
730
|
-
// has no other reading in this corpus.
|
|
731
|
-
/\b(?:in-?market|affinity|audience\s+signals?|senales?\s+de\s+audiencia)\b/i,
|
|
732
|
-
/\b(?:audiencias?|lal)\b/i,
|
|
733
|
-
/\blistas?\s+de\s+(?:exclusion|remarketing)\b|\bexclusion\s+lists?\b/i,
|
|
734
|
-
// URLs.
|
|
735
|
-
/\btracking\s+(?:url\s+)?templates?\b|\bfinal\s+urls?\b|\bfinal\s+url\s+suffix\b/i,
|
|
736
|
-
// Bidding.
|
|
737
|
-
/\bbid\s+(?:adjustments?|modifiers?|strateg\w*)\b|\bajustes?\s+de\s+puja\b/i,
|
|
738
|
-
/\b(?:tcpa|troas|target\s+cpa|target\s+roas)\b/i,
|
|
739
|
-
// Lifecycle. Bare `campaign` is deliberately NOT a signal — it appears
|
|
740
|
-
// everywhere and would strip tag-manager and landing of correct
|
|
741
|
-
// routings, so every campaign-shaped rule needs a verb or a specific
|
|
742
|
-
// setting noun next to it.
|
|
743
|
-
// Bounded, and never across the name/description boundary. `ads` is an
|
|
744
|
-
// ordinary word in this domain, so an unbounded gap lets a verb in the
|
|
745
|
-
// name pair with a passing mention in the description — and since
|
|
746
|
-
// ads-write is evaluated first, that spurious match outranks the surface
|
|
747
|
-
// which should have answered.
|
|
748
|
-
re(String.raw`\b`, LIFECYCLE_VERB, String.raw`\b[^\n]{0,40}\b`, CAMPAIGN_ENTITY, String.raw`\b`),
|
|
749
|
-
// Budget. Two complementary shapes — verb before, quantity after — so a
|
|
750
|
-
// change stated either way lands, guarded by the `exceptName` veto above.
|
|
751
|
-
/\b(?:daily|campaign|lifetime|ad\s*set|minimum|min|competitor)\s+budgets?\b/i,
|
|
752
|
-
/\bpresupuestos?\b/i,
|
|
753
|
-
re(String.raw`\b`, BUDGET_VERB, String.raw`\b[\s\S]{0,24}\bbudgets?\b`),
|
|
754
|
-
/\bbudgets?\b[\s\S]{0,24}\b(?:at|to|cap|down|up|\d|%|€|\$)/i,
|
|
755
|
-
/\bnegative\s+keywords?\b/i,
|
|
756
|
-
/\b(?:lookalike|custom)\s+audience\b/i,
|
|
757
|
-
/\bswap\s+(?:the\s+)?creative\b/i
|
|
758
|
-
],
|
|
759
|
-
hint: "This is an ad-platform change the write surface covers \u2014 stage it now with `baker ads google|meta|linkedin` and it applies at publish (guides: `__tooling__/docs/tools/baker/ads-google.md`, `__tooling__/docs/tools/baker/ads-meta.md`, `__tooling__/docs/tools/baker/ads-linkedin.md`). Staging is not going live."
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
surface: "tag-manager",
|
|
763
|
-
signals: [
|
|
764
|
-
/\bgtm\b/i,
|
|
765
|
-
/\btag\s*manager\b/i,
|
|
766
|
-
/\bdata\s*layer\b/i,
|
|
767
|
-
/\bfiring\s+trigger\b/i,
|
|
768
|
-
/\bbuilt-?in\s+variables?\b/i,
|
|
769
|
-
/\bconsent\s+(?:mode|settings?|state)\b/i,
|
|
770
|
-
/\bga4\s+(?:config|configuration|event)\b/i,
|
|
771
|
-
/\bcontainer\s+(?:tag|trigger|variable)s?\b/i,
|
|
772
|
-
/\b(?:conversion|form|submit|submission|click|purchase|lead|custom)\s+event\b/i,
|
|
773
|
-
/\btrack\w*\b[\s\S]*\bevent\b/i,
|
|
774
|
-
/\beventos?\s+de\s+(?:formulario|conversion|clic|compra)\b/i,
|
|
775
|
-
/\bconsentimiento\b/i,
|
|
776
|
-
/\bmedicion\b/i
|
|
777
|
-
],
|
|
778
|
-
except: [/\bserver-?\s?side\b/i, /\bsgtm\b/i, /\bhosting\b/i],
|
|
779
|
-
hint: "This is a change inside the GTM container \u2014 `baker tag-manager` stages it now and it applies at publish (guide: `__tooling__/docs/tools/baker/tag-manager.md`). Stage it instead of filing it, and if several container fixes really are blocked, they belong in ONE Task, not one per finding."
|
|
780
|
-
},
|
|
781
|
-
{
|
|
782
|
-
surface: "site-tags",
|
|
783
|
-
signals: [
|
|
784
|
-
/\bpixel\b/i,
|
|
785
|
-
/\binsight\s+tag\b/i,
|
|
786
|
-
/\bclarity\b/i,
|
|
787
|
-
/\bhotjar\b/i,
|
|
788
|
-
/\bgtm\s+snippet\b/i,
|
|
789
|
-
/\bcapi\b/i,
|
|
790
|
-
/\b(?:install|add|remove|swap|instalar|colocar)\b[\s\S]*\b(?:snippet|script|tag)\b/i
|
|
791
|
-
],
|
|
792
|
-
// Two vetoes. A structured snippet is a Google Ads extension, not a script
|
|
793
|
-
// on the page — `ads-write` claims it above, and this stops both rules
|
|
794
|
-
// being able to answer. Server-side delivery is infrastructure nobody here
|
|
795
|
-
// can provision, the same call `tag-manager` already makes for sGTM.
|
|
796
|
-
except: [/\bstructured\s+snippets?\b/i, /\bserver-?\s?side\b/i, /\bhosting\b/i],
|
|
797
|
-
hint: "This is a tag or script on the site \u2014 it goes through the `request_tag_input` approval form in this chat, which also collects any secret values (guide: `__tooling__/docs/tools/baker/tags.md`). Show the form instead of filing a Task."
|
|
798
|
-
},
|
|
799
|
-
{
|
|
800
|
-
surface: "landing",
|
|
801
|
-
nameOnlySignals: [/\b(?:landing\s+pages?|landings?|hero|headline|above\s+the\s+fold|pagina)\b/i],
|
|
802
|
-
signals: [/\b(?:rewrite|tighten|restyle)\b[\s\S]*\b(?:copy|page|section)\b/i],
|
|
803
|
-
hint: "This is a page change \u2014 build it in this chat with the `/landing` skill. Only file it if something outside the page blocks the work."
|
|
804
|
-
},
|
|
805
|
-
{
|
|
806
|
-
surface: "flow",
|
|
807
|
-
signals: [/\b(?:form|flow)\s+(?:step|steps|branching|logic|redirect|behaviou?r)\b/i, /\bthank\s*you\s+redirect\b/i],
|
|
808
|
-
hint: "This is form behaviour \u2014 change it in this chat with the `/flow-builder` skill. Only file it if something outside the form blocks the work."
|
|
809
|
-
}
|
|
810
|
-
];
|
|
811
|
-
var FAN_OUT_NUDGE_THRESHOLD = 2;
|
|
812
|
-
var FAN_OUT_THRESHOLD = 3;
|
|
813
|
-
var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at; guide: `__tooling__/docs/tools/baker/scheduled-actions.md`). Do NOT capture "set up a scheduled action" as a Work Action.';
|
|
814
|
-
function looksExecutable(name, description) {
|
|
815
|
-
const foldedName = fold(name);
|
|
816
|
-
const full = `${foldedName}
|
|
817
|
-
${fold(description)}`;
|
|
818
|
-
for (const rule of SURFACE_RULES) {
|
|
819
|
-
const haystack = rule.nameOnly ? foldedName : full;
|
|
820
|
-
if (rule.except?.some((re2) => re2.test(full))) {
|
|
821
|
-
continue;
|
|
822
|
-
}
|
|
823
|
-
if (rule.exceptName?.some((re2) => re2.test(foldedName))) {
|
|
824
|
-
continue;
|
|
825
|
-
}
|
|
826
|
-
if (rule.signals.some((re2) => re2.test(haystack))) {
|
|
827
|
-
return { surface: rule.surface, hint: rule.hint };
|
|
828
|
-
}
|
|
829
|
-
if (rule.nameOnlySignals?.some((re2) => re2.test(foldedName))) {
|
|
830
|
-
return { surface: rule.surface, hint: rule.hint };
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
835
|
-
function buildCreateHints({
|
|
836
|
-
name,
|
|
837
|
-
description,
|
|
838
|
-
tempId,
|
|
839
|
-
tags,
|
|
840
|
-
prioritySet,
|
|
841
|
-
draftCreateCount
|
|
842
|
-
}) {
|
|
843
|
-
const hints = [];
|
|
844
|
-
const advisory = advisoryHint(name, description);
|
|
845
|
-
if (advisory) {
|
|
846
|
-
hints.push(
|
|
847
|
-
`${advisory.hint} If nothing actually blocks it: baker actions draft remove ${tempId}, then do the work.`
|
|
848
|
-
);
|
|
849
|
-
}
|
|
850
|
-
if (draftCreateCount >= FAN_OUT_THRESHOLD) {
|
|
851
|
-
hints.push(
|
|
852
|
-
`${draftCreateCount} Tasks are staged in this chat. Re-read them with \`baker actions draft\`: can any be done now with a surface you already have, and do any two touch the same system (same container, same campaign, same page)? Those belong in ONE Task \u2014 fold them together with \`baker actions update\` and drop the extras with \`baker actions draft remove <tempId>\`.`
|
|
853
|
-
);
|
|
854
|
-
} else if (draftCreateCount === FAN_OUT_NUDGE_THRESHOLD) {
|
|
855
|
-
hints.push(
|
|
856
|
-
`Second Task staged in this chat. If these two land on the same system (same container, same campaign, same page) they are ONE Task \u2014 fold them with \`baker actions update\` and drop the extra with \`baker actions draft remove ${tempId}\`.`
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
hints.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
|
|
860
|
-
if (!description) {
|
|
861
|
-
hints.push("Add description: baker actions update <tempId> --description '...' (what/why/where/done-when)");
|
|
862
|
-
}
|
|
863
|
-
if (!tags || tags.length === 0) {
|
|
864
|
-
hints.push(
|
|
865
|
-
"MISSING --tags. This action is invisible to the backlog's tag filter. Re-run with --tags <slug,...> (`baker actions tags list` for the taxonomy, `baker actions tags create --slug <slug>` to mint) \u2014 or `baker actions update <tempId> --tags <slug,...>`."
|
|
866
|
-
);
|
|
867
|
-
}
|
|
868
|
-
if (!prioritySet) {
|
|
869
|
-
hints.push(
|
|
870
|
-
`MISSING --priority. Without it this action ranks as 'normal' (medium) in the do-first ordering, so urgent/high client work won't surface first. Re-run with --priority ${ACTION_PRIORITIES.join("|")} \u2014 or \`baker actions update <tempId> --priority <level>\`.`
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
return hints;
|
|
874
|
-
}
|
|
875
|
-
function advisoryHint(name, description) {
|
|
876
|
-
if (looksScheduled(name, description)) {
|
|
877
|
-
return { kind: "scheduled", hint: SCHEDULED_HINT };
|
|
878
|
-
}
|
|
879
|
-
const executable = looksExecutable(name, description);
|
|
880
|
-
return executable ? { kind: "executable", ...executable } : null;
|
|
881
|
-
}
|
|
882
|
-
var ACTIONS_LIST_DEFAULT_LIMIT = 500;
|
|
883
|
-
function buildListHints({ returned, limit }) {
|
|
884
|
-
if (returned < limit) {
|
|
885
|
-
return [];
|
|
886
|
-
}
|
|
887
|
-
return [
|
|
888
|
-
`This list is capped at ${limit} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit * 2} (or narrow with --status / --q).`
|
|
889
|
-
];
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
// src/commands/actions/skillCatalog.ts
|
|
893
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
894
|
-
import { dirname, join } from "path";
|
|
895
|
-
var DESCRIPTION_MAX = 600;
|
|
896
|
-
var SKILLS_SUBPATH = join(".claude", "skills");
|
|
897
|
-
var EXCLUDED_SKILLS = /* @__PURE__ */ new Set(["actions"]);
|
|
898
|
-
function stripQuotes(value) {
|
|
899
|
-
const trimmed = value.trim();
|
|
900
|
-
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
901
|
-
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n");
|
|
902
|
-
}
|
|
903
|
-
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
904
|
-
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
905
|
-
}
|
|
906
|
-
return trimmed;
|
|
907
|
-
}
|
|
908
|
-
function extractFrontmatterLines(md) {
|
|
909
|
-
if (!md.startsWith("---")) return null;
|
|
910
|
-
const end = md.indexOf("\n---", 3);
|
|
911
|
-
if (end === -1) return null;
|
|
912
|
-
return md.slice(md.indexOf("\n", 3) + 1, end).split("\n");
|
|
913
|
-
}
|
|
914
|
-
var BLOCK_SCALAR = /^([|>])[+-]?$/;
|
|
915
|
-
function collectBlockLines(lines, start) {
|
|
916
|
-
const collected = [];
|
|
917
|
-
for (let i = start; i < lines.length; i++) {
|
|
918
|
-
const line = lines[i] ?? "";
|
|
919
|
-
if (line.trim() === "") {
|
|
920
|
-
collected.push("");
|
|
921
|
-
} else if (/^\s/.test(line)) {
|
|
922
|
-
collected.push(line.trim());
|
|
923
|
-
} else {
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
while (collected.length > 0 && collected.at(-1) === "") collected.pop();
|
|
928
|
-
return collected;
|
|
929
|
-
}
|
|
930
|
-
function foldLines(collected) {
|
|
931
|
-
const paragraphs = [];
|
|
932
|
-
let buffer = [];
|
|
933
|
-
for (const line of collected) {
|
|
934
|
-
if (line === "") {
|
|
935
|
-
if (buffer.length > 0) paragraphs.push(buffer.join(" "));
|
|
936
|
-
buffer = [];
|
|
937
|
-
} else {
|
|
938
|
-
buffer.push(line);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
if (buffer.length > 0) paragraphs.push(buffer.join(" "));
|
|
942
|
-
return paragraphs.join("\n");
|
|
943
|
-
}
|
|
944
|
-
function readBlockScalar(lines, start, style) {
|
|
945
|
-
const collected = collectBlockLines(lines, start);
|
|
946
|
-
return style === "|" ? collected.join("\n") : foldLines(collected);
|
|
947
|
-
}
|
|
948
|
-
function readField(lines, field) {
|
|
949
|
-
for (let i = 0; i < lines.length; i++) {
|
|
950
|
-
const match = (lines[i] ?? "").match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
951
|
-
if (!match || match[1] !== field) continue;
|
|
952
|
-
const inline = (match[2] ?? "").trim();
|
|
953
|
-
const scalar = inline.match(BLOCK_SCALAR);
|
|
954
|
-
if (scalar) {
|
|
955
|
-
return readBlockScalar(lines, i + 1, scalar[1]);
|
|
956
|
-
}
|
|
957
|
-
return stripQuotes(inline);
|
|
958
|
-
}
|
|
959
|
-
return null;
|
|
960
|
-
}
|
|
961
|
-
function parseSkillFrontmatter(md) {
|
|
962
|
-
const lines = extractFrontmatterLines(md);
|
|
963
|
-
if (!lines) return null;
|
|
964
|
-
const name = readField(lines, "name");
|
|
965
|
-
const description = readField(lines, "description");
|
|
966
|
-
if (!name || !description) return null;
|
|
967
|
-
const trimmed = description.length > DESCRIPTION_MAX ? `${description.slice(0, DESCRIPTION_MAX)}\u2026` : description;
|
|
968
|
-
return { name, description: trimmed };
|
|
969
|
-
}
|
|
970
|
-
function findSkillsDir(startDir) {
|
|
971
|
-
let dir = startDir;
|
|
972
|
-
for (; ; ) {
|
|
973
|
-
const candidate = join(dir, SKILLS_SUBPATH);
|
|
974
|
-
if (existsSync(candidate)) return candidate;
|
|
975
|
-
const parent = dirname(dir);
|
|
976
|
-
if (parent === dir) return null;
|
|
977
|
-
dir = parent;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
function readSkillCatalog(startDir) {
|
|
981
|
-
const skillsDir = findSkillsDir(startDir);
|
|
982
|
-
if (!skillsDir) return [];
|
|
983
|
-
const entries = [];
|
|
984
|
-
for (const dirent of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
985
|
-
if (!dirent.isDirectory() || EXCLUDED_SKILLS.has(dirent.name)) continue;
|
|
986
|
-
const skillFile = join(skillsDir, dirent.name, "SKILL.md");
|
|
987
|
-
if (!existsSync(skillFile)) continue;
|
|
988
|
-
try {
|
|
989
|
-
const parsed = parseSkillFrontmatter(readFileSync(skillFile, "utf8"));
|
|
990
|
-
if (parsed) entries.push(parsed);
|
|
991
|
-
} catch {
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// src/commands/actions/claim.ts
|
|
998
|
-
registerSchema({
|
|
999
|
-
command: "actions.claim",
|
|
1000
|
-
description: "Claim an action for the current chat (live \u2014 visible to other chats immediately). Claim only what you're actively working on now: it's required before `complete`, but NOT for `update` or `discard` (those stage without a claim). Returns action details plus a fast-model recommendation of which skills to load for the work (`recommendedSkills`) and a routing hint: load every skill that owns part of the work and the tool doc for each baker CLI family you'll use.",
|
|
1001
|
-
args: {
|
|
1002
|
-
id: { type: "string", description: "Action ID", required: true }
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1005
|
-
async function recommendSkills(actionId) {
|
|
1006
|
-
const skills = readSkillCatalog(process.cwd());
|
|
1007
|
-
if (skills.length === 0) return [];
|
|
1008
|
-
const response = await apiPost("/api/actions/recommend-skills", {
|
|
1009
|
-
actionId,
|
|
1010
|
-
skills
|
|
1011
|
-
});
|
|
1012
|
-
return response.data.recommendations;
|
|
1013
|
-
}
|
|
1014
|
-
var ROUTING_HINT = "Actions often span several surfaces (a landing page plus a Google Ads change, a sheet pull plus an audience upload). Before starting, load EVERY skill that owns part of the work and read the tool doc (__tooling__/docs/tools/baker/<family>.md) for each baker CLI family you'll use. If the action references source material living in a connected tool (an email attachment, a call, a CRM record, a spreadsheet), reach for that tool too. If it describes recurring or future-dated work, it belongs on a schedule \u2014 see __tooling__/docs/tools/baker/scheduled-actions.md.";
|
|
1015
|
-
function buildHints(recommendations) {
|
|
1016
|
-
if (recommendations.length === 0) {
|
|
1017
|
-
return ["Review the action name, description, and tags above.", ROUTING_HINT];
|
|
1018
|
-
}
|
|
1019
|
-
return [
|
|
1020
|
-
"Recommended skills for this action (load the ones you'll use):",
|
|
1021
|
-
...recommendations.map((r) => ` /${r.name} \u2014 ${r.reason}`),
|
|
1022
|
-
"Suggestions come from the action's name/description/tags only \u2014 the work may need more.",
|
|
1023
|
-
ROUTING_HINT
|
|
1024
|
-
];
|
|
1025
|
-
}
|
|
1026
|
-
var claimCommand = defineCommand({
|
|
1027
|
-
meta: {
|
|
1028
|
-
name: "claim",
|
|
1029
|
-
description: "Claim an action so other chats see you're working on it. Required before `complete` (update and discard don't need a claim). Example: baker actions claim <action-id>"
|
|
1030
|
-
},
|
|
1031
|
-
args: {
|
|
1032
|
-
id: { type: "positional", description: "Action ID", required: false },
|
|
1033
|
-
"action-id": { type: "string", description: "Action ID", required: false }
|
|
1034
|
-
},
|
|
1035
|
-
run: async ({ args }) => {
|
|
1036
|
-
try {
|
|
1037
|
-
const id = args.id || args["action-id"];
|
|
1038
|
-
if (!id) {
|
|
1039
|
-
failValidation("Action ID is required.");
|
|
1040
|
-
}
|
|
1041
|
-
validateConvexId(id);
|
|
1042
|
-
const chatId = requireChatId();
|
|
1043
|
-
const response = await apiPost("/api/actions/claim", {
|
|
1044
|
-
actionId: id,
|
|
1045
|
-
chatId
|
|
1046
|
-
});
|
|
1047
|
-
let recommendedSkills = [];
|
|
1048
|
-
try {
|
|
1049
|
-
recommendedSkills = await recommendSkills(id);
|
|
1050
|
-
} catch {
|
|
1051
|
-
}
|
|
1052
|
-
const data = response.data ? { ...response.data, recommendedSkills } : response.data;
|
|
1053
|
-
writeJson({ ok: response.ok, data, hints: buildHints(recommendedSkills) });
|
|
1054
|
-
} catch (err) {
|
|
1055
|
-
failApi(err);
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
});
|
|
1059
|
-
|
|
1060
568
|
// ../api/src/actions.ts
|
|
1061
569
|
import { z } from "zod";
|
|
1062
570
|
var actionStatusSchema = z.enum(["pending", "in_progress", "completed", "discarded"]);
|
|
@@ -1199,9 +707,13 @@ function okResponse(data) {
|
|
|
1199
707
|
function okOnlySchema() {
|
|
1200
708
|
return z.object({ ok: z.literal(true) });
|
|
1201
709
|
}
|
|
710
|
+
var ACTION_TEMP_ID_PREFIX = "temp_";
|
|
711
|
+
var actionTempIdSchema = z.string().refine((value) => value.startsWith(ACTION_TEMP_ID_PREFIX) && value.length > ACTION_TEMP_ID_PREFIX.length, {
|
|
712
|
+
message: "tempId must start with `temp_` and have something after it (e.g. temp_hero_copy). Omit --temp-id to get a generated one."
|
|
713
|
+
});
|
|
1202
714
|
var actionsCreateRequestSchema = z.object({
|
|
1203
715
|
chatId: z.string(),
|
|
1204
|
-
tempId:
|
|
716
|
+
tempId: actionTempIdSchema,
|
|
1205
717
|
name: z.string().min(1),
|
|
1206
718
|
description: z.string(),
|
|
1207
719
|
tags: z.array(z.string()).optional(),
|
|
@@ -6665,6 +6177,498 @@ var videosIngestResponseSchema = z21.object({
|
|
|
6665
6177
|
var videosDeleteRequestSchema = z21.object({ id: z21.string().min(1, "Missing video ID") });
|
|
6666
6178
|
var videosDeleteResponseSchema = z21.object({ ok: z21.literal(true) });
|
|
6667
6179
|
|
|
6180
|
+
// src/commands/actions/tagError.ts
|
|
6181
|
+
var UNKNOWN_TAG_RE = /Unknown action tag\(s\): ([^.]+)\. Valid tags: ([^.]+)\./;
|
|
6182
|
+
function splitSlugs(list) {
|
|
6183
|
+
return list.split(",").map((slug) => slug.trim()).filter((slug) => slug.length > 0);
|
|
6184
|
+
}
|
|
6185
|
+
function actionTagFix(message) {
|
|
6186
|
+
const match = UNKNOWN_TAG_RE.exec(message);
|
|
6187
|
+
if (!match) {
|
|
6188
|
+
return null;
|
|
6189
|
+
}
|
|
6190
|
+
const [, unknownList, validList] = match;
|
|
6191
|
+
const unknownTags = splitSlugs(unknownList ?? "");
|
|
6192
|
+
const validTags = splitSlugs(validList ?? "");
|
|
6193
|
+
const first = unknownTags[0];
|
|
6194
|
+
if (first === void 0 || validTags.length === 0) {
|
|
6195
|
+
return null;
|
|
6196
|
+
}
|
|
6197
|
+
return {
|
|
6198
|
+
action: "retry_with_a_valid_tag",
|
|
6199
|
+
explanation: `The tag(s) ${unknownTags.join(", ")} are not in this company's taxonomy. \`validTags\` below is the complete accepted set \u2014 pick from it and re-run the same command. Only mint a new tag when none of them names this work; a near-duplicate of an existing tag splits the backlog's filter instead of extending it.`,
|
|
6200
|
+
unknownTags,
|
|
6201
|
+
validTags,
|
|
6202
|
+
mintCommand: `baker actions tags create --slug ${first} --description "<what ${first} groups>"`,
|
|
6203
|
+
listCommand: "baker actions tags list"
|
|
6204
|
+
};
|
|
6205
|
+
}
|
|
6206
|
+
|
|
6207
|
+
// src/commands/actions/shared.ts
|
|
6208
|
+
function writeOk(data) {
|
|
6209
|
+
writeJson({ ok: true, data: data ?? null });
|
|
6210
|
+
}
|
|
6211
|
+
function failValidation(message) {
|
|
6212
|
+
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
6213
|
+
process.exit(1);
|
|
6214
|
+
}
|
|
6215
|
+
function failApi(err) {
|
|
6216
|
+
if (err instanceof ApiError) {
|
|
6217
|
+
const fix = actionTagFix(err.message);
|
|
6218
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message, ...fix ? { fix } : {} } });
|
|
6219
|
+
process.exit(1);
|
|
6220
|
+
}
|
|
6221
|
+
if (err instanceof Error) {
|
|
6222
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: err.message } });
|
|
6223
|
+
process.exit(1);
|
|
6224
|
+
}
|
|
6225
|
+
writeJson({ ok: false, error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
|
|
6226
|
+
process.exit(1);
|
|
6227
|
+
}
|
|
6228
|
+
function generateTempId() {
|
|
6229
|
+
return `${ACTION_TEMP_ID_PREFIX}${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
6230
|
+
}
|
|
6231
|
+
function isTempId(id) {
|
|
6232
|
+
return id.startsWith(ACTION_TEMP_ID_PREFIX);
|
|
6233
|
+
}
|
|
6234
|
+
function parseTagList(value) {
|
|
6235
|
+
if (typeof value !== "string") {
|
|
6236
|
+
return void 0;
|
|
6237
|
+
}
|
|
6238
|
+
const tags = value.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
|
|
6239
|
+
return [...new Set(tags)];
|
|
6240
|
+
}
|
|
6241
|
+
function fold(text2) {
|
|
6242
|
+
return text2.normalize("NFD").replace(/[̀-ͯ]/g, "");
|
|
6243
|
+
}
|
|
6244
|
+
var SCHEDULE_SIGNALS = [
|
|
6245
|
+
/\brecurr(?:ing|ence)\b/i,
|
|
6246
|
+
/\bcadence\b/i,
|
|
6247
|
+
/\bcron\b/i,
|
|
6248
|
+
/\bschedule[ds]?\b/i,
|
|
6249
|
+
/\b(?:daily|weekly|monthly|quarterly|annual(?:ly)?|biweekly|nightly)\b/i,
|
|
6250
|
+
/\bevery\s+(?:day|week|month|quarter|year|morning|monday|tuesday|wednesday|thursday|friday|saturday|sunday|\d)/i,
|
|
6251
|
+
/\beach\s+(?:day|week|month|monday|tuesday|wednesday|thursday|friday|saturday|sunday)\b/i,
|
|
6252
|
+
/\bremind(?:er|s)?\b/i,
|
|
6253
|
+
/\brun\s+at\b/i
|
|
6254
|
+
];
|
|
6255
|
+
var ACTION_PRIORITIES = ["urgent", "high", "medium", "low"];
|
|
6256
|
+
function parsePriority(value, { allowClear }) {
|
|
6257
|
+
if (value === void 0) {
|
|
6258
|
+
return void 0;
|
|
6259
|
+
}
|
|
6260
|
+
if (typeof value !== "string") {
|
|
6261
|
+
failValidation(
|
|
6262
|
+
`--priority must be one of: ${ACTION_PRIORITIES.join(", ")}${allowClear ? ", or 'none' to clear" : ""}.`
|
|
6263
|
+
);
|
|
6264
|
+
}
|
|
6265
|
+
const trimmed = value.trim().toLowerCase();
|
|
6266
|
+
if (trimmed === "") {
|
|
6267
|
+
if (allowClear) {
|
|
6268
|
+
return null;
|
|
6269
|
+
}
|
|
6270
|
+
return void 0;
|
|
6271
|
+
}
|
|
6272
|
+
if (allowClear && (trimmed === "none" || trimmed === "clear")) {
|
|
6273
|
+
return null;
|
|
6274
|
+
}
|
|
6275
|
+
if (ACTION_PRIORITIES.includes(trimmed)) {
|
|
6276
|
+
return trimmed;
|
|
6277
|
+
}
|
|
6278
|
+
failValidation(
|
|
6279
|
+
`Unknown --priority "${value}". Expected one of: ${ACTION_PRIORITIES.join(", ")}${allowClear ? ", or 'none' to clear" : ""}.`
|
|
6280
|
+
);
|
|
6281
|
+
}
|
|
6282
|
+
function looksScheduled(name, description) {
|
|
6283
|
+
const haystack = fold(`${name}
|
|
6284
|
+
${description}`);
|
|
6285
|
+
return SCHEDULE_SIGNALS.some((re2) => re2.test(haystack));
|
|
6286
|
+
}
|
|
6287
|
+
var CAMPAIGN_ENTITY = String.raw`(?:campaigns?|campanas?|ad\s*sets?|adsets?|ad\s*groups?|adgroups?|ads?|anuncios?|conjuntos?\s+de\s+anuncios|grupos?\s+de\s+anuncios)`;
|
|
6288
|
+
var LIFECYCLE_VERB = "(?:pause|unpause|enable|disable|launch|restructure|duplicate|clone|rebuild|prune|reweight|split|consolidate|pausar|pausa|activar|podar|poda|reponderar|duplicar|reestructurar|consolidar)";
|
|
6289
|
+
var BUDGET_VERB = "(?:cap|raise|lower|increase|decrease|adjust|change|set|update|reallocate|shift|limitar|subir|bajar|ajustar|aplicar|reasignar)";
|
|
6290
|
+
var re = (...parts) => new RegExp(parts.join(""), "i");
|
|
6291
|
+
var SURFACE_RULES = [
|
|
6292
|
+
{
|
|
6293
|
+
surface: "ask-user",
|
|
6294
|
+
signals: [
|
|
6295
|
+
/\bproduction\s+(?:domain|url|site|hostname)\b/i,
|
|
6296
|
+
/\b(?:confirm|check|verify|confirmar|verificar)\s+(?:with|w\/|con)\s+(?:the\s+|el\s+|la\s+)?(?:client|user|customer|team|cliente|usuario|equipo)\b/i,
|
|
6297
|
+
/\bmissing\s+(?:the\s+)?(?:value|values|id|ids|url|budget|domain|credentials?|account\s+id|measurement\s+id)\b/i,
|
|
6298
|
+
/\b(?:ask|get)\s+(?:the\s+)?(?:client|user)\s+for\b/i,
|
|
6299
|
+
/\b(?:pedir|solicitar)\s+(?:al\s+)?cliente\b/i
|
|
6300
|
+
],
|
|
6301
|
+
hint: "This is a missing input, not blocked work. Ask for it with AskUserQuestion (one question), then do the work in the same turn \u2014 filing the Task AND asking the question is the same request twice."
|
|
6302
|
+
},
|
|
6303
|
+
{
|
|
6304
|
+
surface: "analysis",
|
|
6305
|
+
nameOnly: true,
|
|
6306
|
+
signals: [
|
|
6307
|
+
/^\s*(?:re-?)?(?:investigat|analy[sz]|audit|review|document|assess|evaluat|research|map|inventor|examin|diagnos|verif|validat|benchmark)\w*\b/i,
|
|
6308
|
+
/^\s*(?:auditar|auditoria|investigar|investigacion|revisar|revision|analizar|analisis|documentar|evaluar|evaluacion|diagnosticar|diagnostico|mapear|inventariar|verificar|verificacion|comprobar|estudiar|estudio)\b/i
|
|
6309
|
+
],
|
|
6310
|
+
hint: "This names the analysis itself \u2014 investigating, auditing and documenting are work you do now, not work you file. Run it in this chat and deliver the finding; a Task is for a fix something genuinely blocks (bar: `__tooling__/docs/tools/baker/actions.md`)."
|
|
6311
|
+
},
|
|
6312
|
+
{
|
|
6313
|
+
surface: "ads-write",
|
|
6314
|
+
// "Approve X" is a human decision, not a staged write — and the budget
|
|
6315
|
+
// signals are broad enough to swallow it otherwise. Name-anchored so a
|
|
6316
|
+
// description recalling an earlier approval doesn't veto real work.
|
|
6317
|
+
exceptName: [/^\s*(?:approve|approval|sign[-\s]?off|aprobar|aprobacion)\b/i],
|
|
6318
|
+
// Bare `keywords` is the work when it's the subject of the Task, but plain
|
|
6319
|
+
// context when a description happens to discuss search terms.
|
|
6320
|
+
nameOnlySignals: [/\b(?:keywords?|palabras\s+clave)\b/i],
|
|
6321
|
+
signals: [
|
|
6322
|
+
// Targeting.
|
|
6323
|
+
/\b(?:geo|location|geographic|geografic\w*)[\s-]*(?:targeting|target|segmentacion)\b/i,
|
|
6324
|
+
/\bsegmentacion\s+(?:geografica|por\s+ubicacion)\b/i,
|
|
6325
|
+
/\bpresence[-\s_]?(?:only|or[-\s_]?interest)\b/i,
|
|
6326
|
+
/\b(?:ad\s*schedul\w*|dayparting|calendario\s+de\s+anuncios)\b/i,
|
|
6327
|
+
// Extensions / assets.
|
|
6328
|
+
/\b(?:callouts?|sitelinks?|site\s*links?|structured\s+snippets?|price\s+extensions?|lead\s+form\s+extensions?|extensiones|asset\s+groups?)\b/i,
|
|
6329
|
+
// Ad copy. Compound forms only — bare `headline` and `copy` belong to
|
|
6330
|
+
// `landing`, and an RSA headline has to say so.
|
|
6331
|
+
/\b(?:rsas?|responsive\s+search\s+ads?|ad\s+copy|copy\s+de\s+(?:los\s+)?anuncios)\b/i,
|
|
6332
|
+
/\b(?:headline|titular|description)\s+(?:pool|set|slots?)\b/i,
|
|
6333
|
+
/\bdisplay\s+paths?\b|\brutas?\s+visibles?\b/i,
|
|
6334
|
+
// Keywords. `negatives` is bare in English because in this product's
|
|
6335
|
+
// vocabulary the noun is always negative keywords; the Spanish side is
|
|
6336
|
+
// feminine-only, because "resultados negativos" is a real phrase.
|
|
6337
|
+
/\bmatch\s+types?\b|\bconcordancias?\b/i,
|
|
6338
|
+
/\bnegatives?\b/i,
|
|
6339
|
+
/\b(?:palabras\s+clave\s+)?negativas\b/i,
|
|
6340
|
+
/\bkeyword\s+lists?\b|\blistas?\s+de\s+palabras\s+clave\b/i,
|
|
6341
|
+
// Audiences. `LAL` is the operators' own shorthand for a lookalike and
|
|
6342
|
+
// has no other reading in this corpus.
|
|
6343
|
+
/\b(?:in-?market|affinity|audience\s+signals?|senales?\s+de\s+audiencia)\b/i,
|
|
6344
|
+
/\b(?:audiencias?|lal)\b/i,
|
|
6345
|
+
/\blistas?\s+de\s+(?:exclusion|remarketing)\b|\bexclusion\s+lists?\b/i,
|
|
6346
|
+
// URLs.
|
|
6347
|
+
/\btracking\s+(?:url\s+)?templates?\b|\bfinal\s+urls?\b|\bfinal\s+url\s+suffix\b/i,
|
|
6348
|
+
// Bidding.
|
|
6349
|
+
/\bbid\s+(?:adjustments?|modifiers?|strateg\w*)\b|\bajustes?\s+de\s+puja\b/i,
|
|
6350
|
+
/\b(?:tcpa|troas|target\s+cpa|target\s+roas)\b/i,
|
|
6351
|
+
// Lifecycle. Bare `campaign` is deliberately NOT a signal — it appears
|
|
6352
|
+
// everywhere and would strip tag-manager and landing of correct
|
|
6353
|
+
// routings, so every campaign-shaped rule needs a verb or a specific
|
|
6354
|
+
// setting noun next to it.
|
|
6355
|
+
// Bounded, and never across the name/description boundary. `ads` is an
|
|
6356
|
+
// ordinary word in this domain, so an unbounded gap lets a verb in the
|
|
6357
|
+
// name pair with a passing mention in the description — and since
|
|
6358
|
+
// ads-write is evaluated first, that spurious match outranks the surface
|
|
6359
|
+
// which should have answered.
|
|
6360
|
+
re(String.raw`\b`, LIFECYCLE_VERB, String.raw`\b[^\n]{0,40}\b`, CAMPAIGN_ENTITY, String.raw`\b`),
|
|
6361
|
+
// Budget. Two complementary shapes — verb before, quantity after — so a
|
|
6362
|
+
// change stated either way lands, guarded by the `exceptName` veto above.
|
|
6363
|
+
/\b(?:daily|campaign|lifetime|ad\s*set|minimum|min|competitor)\s+budgets?\b/i,
|
|
6364
|
+
/\bpresupuestos?\b/i,
|
|
6365
|
+
re(String.raw`\b`, BUDGET_VERB, String.raw`\b[\s\S]{0,24}\bbudgets?\b`),
|
|
6366
|
+
/\bbudgets?\b[\s\S]{0,24}\b(?:at|to|cap|down|up|\d|%|€|\$)/i,
|
|
6367
|
+
/\bnegative\s+keywords?\b/i,
|
|
6368
|
+
/\b(?:lookalike|custom)\s+audience\b/i,
|
|
6369
|
+
/\bswap\s+(?:the\s+)?creative\b/i
|
|
6370
|
+
],
|
|
6371
|
+
hint: "This is an ad-platform change the write surface covers \u2014 stage it now with `baker ads google|meta|linkedin` and it applies at publish (guides: `__tooling__/docs/tools/baker/ads-google.md`, `__tooling__/docs/tools/baker/ads-meta.md`, `__tooling__/docs/tools/baker/ads-linkedin.md`). Staging is not going live."
|
|
6372
|
+
},
|
|
6373
|
+
{
|
|
6374
|
+
surface: "tag-manager",
|
|
6375
|
+
signals: [
|
|
6376
|
+
/\bgtm\b/i,
|
|
6377
|
+
/\btag\s*manager\b/i,
|
|
6378
|
+
/\bdata\s*layer\b/i,
|
|
6379
|
+
/\bfiring\s+trigger\b/i,
|
|
6380
|
+
/\bbuilt-?in\s+variables?\b/i,
|
|
6381
|
+
/\bconsent\s+(?:mode|settings?|state)\b/i,
|
|
6382
|
+
/\bga4\s+(?:config|configuration|event)\b/i,
|
|
6383
|
+
/\bcontainer\s+(?:tag|trigger|variable)s?\b/i,
|
|
6384
|
+
/\b(?:conversion|form|submit|submission|click|purchase|lead|custom)\s+event\b/i,
|
|
6385
|
+
/\btrack\w*\b[\s\S]*\bevent\b/i,
|
|
6386
|
+
/\beventos?\s+de\s+(?:formulario|conversion|clic|compra)\b/i,
|
|
6387
|
+
/\bconsentimiento\b/i,
|
|
6388
|
+
/\bmedicion\b/i
|
|
6389
|
+
],
|
|
6390
|
+
except: [/\bserver-?\s?side\b/i, /\bsgtm\b/i, /\bhosting\b/i],
|
|
6391
|
+
hint: "This is a change inside the GTM container \u2014 `baker tag-manager` stages it now and it applies at publish (guide: `__tooling__/docs/tools/baker/tag-manager.md`). Stage it instead of filing it, and if several container fixes really are blocked, they belong in ONE Task, not one per finding."
|
|
6392
|
+
},
|
|
6393
|
+
{
|
|
6394
|
+
surface: "site-tags",
|
|
6395
|
+
signals: [
|
|
6396
|
+
/\bpixel\b/i,
|
|
6397
|
+
/\binsight\s+tag\b/i,
|
|
6398
|
+
/\bclarity\b/i,
|
|
6399
|
+
/\bhotjar\b/i,
|
|
6400
|
+
/\bgtm\s+snippet\b/i,
|
|
6401
|
+
/\bcapi\b/i,
|
|
6402
|
+
/\b(?:install|add|remove|swap|instalar|colocar)\b[\s\S]*\b(?:snippet|script|tag)\b/i
|
|
6403
|
+
],
|
|
6404
|
+
// Two vetoes. A structured snippet is a Google Ads extension, not a script
|
|
6405
|
+
// on the page — `ads-write` claims it above, and this stops both rules
|
|
6406
|
+
// being able to answer. Server-side delivery is infrastructure nobody here
|
|
6407
|
+
// can provision, the same call `tag-manager` already makes for sGTM.
|
|
6408
|
+
except: [/\bstructured\s+snippets?\b/i, /\bserver-?\s?side\b/i, /\bhosting\b/i],
|
|
6409
|
+
hint: "This is a tag or script on the site \u2014 it goes through the `request_tag_input` approval form in this chat, which also collects any secret values (guide: `__tooling__/docs/tools/baker/tags.md`). Show the form instead of filing a Task."
|
|
6410
|
+
},
|
|
6411
|
+
{
|
|
6412
|
+
surface: "landing",
|
|
6413
|
+
nameOnlySignals: [/\b(?:landing\s+pages?|landings?|hero|headline|above\s+the\s+fold|pagina)\b/i],
|
|
6414
|
+
signals: [/\b(?:rewrite|tighten|restyle)\b[\s\S]*\b(?:copy|page|section)\b/i],
|
|
6415
|
+
hint: "This is a page change \u2014 build it in this chat with the `/landing` skill. Only file it if something outside the page blocks the work."
|
|
6416
|
+
},
|
|
6417
|
+
{
|
|
6418
|
+
surface: "flow",
|
|
6419
|
+
signals: [/\b(?:form|flow)\s+(?:step|steps|branching|logic|redirect|behaviou?r)\b/i, /\bthank\s*you\s+redirect\b/i],
|
|
6420
|
+
hint: "This is form behaviour \u2014 change it in this chat with the `/flow-builder` skill. Only file it if something outside the form blocks the work."
|
|
6421
|
+
}
|
|
6422
|
+
];
|
|
6423
|
+
var FAN_OUT_NUDGE_THRESHOLD = 2;
|
|
6424
|
+
var FAN_OUT_THRESHOLD = 3;
|
|
6425
|
+
var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled Action instead \u2014 baker scheduled-actions create --cron "0 9 * * MON" (or --run-at; guide: `__tooling__/docs/tools/baker/scheduled-actions.md`). Do NOT capture "set up a scheduled action" as a Work Action.';
|
|
6426
|
+
function looksExecutable(name, description) {
|
|
6427
|
+
const foldedName = fold(name);
|
|
6428
|
+
const full = `${foldedName}
|
|
6429
|
+
${fold(description)}`;
|
|
6430
|
+
for (const rule of SURFACE_RULES) {
|
|
6431
|
+
const haystack = rule.nameOnly ? foldedName : full;
|
|
6432
|
+
if (rule.except?.some((re2) => re2.test(full))) {
|
|
6433
|
+
continue;
|
|
6434
|
+
}
|
|
6435
|
+
if (rule.exceptName?.some((re2) => re2.test(foldedName))) {
|
|
6436
|
+
continue;
|
|
6437
|
+
}
|
|
6438
|
+
if (rule.signals.some((re2) => re2.test(haystack))) {
|
|
6439
|
+
return { surface: rule.surface, hint: rule.hint };
|
|
6440
|
+
}
|
|
6441
|
+
if (rule.nameOnlySignals?.some((re2) => re2.test(foldedName))) {
|
|
6442
|
+
return { surface: rule.surface, hint: rule.hint };
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
return null;
|
|
6446
|
+
}
|
|
6447
|
+
function buildCreateHints({
|
|
6448
|
+
name,
|
|
6449
|
+
description,
|
|
6450
|
+
tempId,
|
|
6451
|
+
tags,
|
|
6452
|
+
prioritySet,
|
|
6453
|
+
draftCreateCount
|
|
6454
|
+
}) {
|
|
6455
|
+
const hints = [];
|
|
6456
|
+
const advisory = advisoryHint(name, description);
|
|
6457
|
+
if (advisory) {
|
|
6458
|
+
hints.push(
|
|
6459
|
+
`${advisory.hint} If nothing actually blocks it: baker actions draft remove ${tempId}, then do the work.`
|
|
6460
|
+
);
|
|
6461
|
+
}
|
|
6462
|
+
if (draftCreateCount >= FAN_OUT_THRESHOLD) {
|
|
6463
|
+
hints.push(
|
|
6464
|
+
`${draftCreateCount} Tasks are staged in this chat. Re-read them with \`baker actions draft\`: can any be done now with a surface you already have, and do any two touch the same system (same container, same campaign, same page)? Those belong in ONE Task \u2014 fold them together with \`baker actions update\` and drop the extras with \`baker actions draft remove <tempId>\`.`
|
|
6465
|
+
);
|
|
6466
|
+
} else if (draftCreateCount === FAN_OUT_NUDGE_THRESHOLD) {
|
|
6467
|
+
hints.push(
|
|
6468
|
+
`Second Task staged in this chat. If these two land on the same system (same container, same campaign, same page) they are ONE Task \u2014 fold them with \`baker actions update\` and drop the extra with \`baker actions draft remove ${tempId}\`.`
|
|
6469
|
+
);
|
|
6470
|
+
}
|
|
6471
|
+
hints.push(`Link dependencies: baker actions link --blocker <id> --blocked ${tempId}`);
|
|
6472
|
+
if (!description) {
|
|
6473
|
+
hints.push("Add description: baker actions update <tempId> --description '...' (what/why/where/done-when)");
|
|
6474
|
+
}
|
|
6475
|
+
if (!tags || tags.length === 0) {
|
|
6476
|
+
hints.push(
|
|
6477
|
+
"MISSING --tags. This action is invisible to the backlog's tag filter. Re-run with --tags <slug,...> (`baker actions tags list` for the taxonomy, `baker actions tags create --slug <slug>` to mint) \u2014 or `baker actions update <tempId> --tags <slug,...>`."
|
|
6478
|
+
);
|
|
6479
|
+
}
|
|
6480
|
+
if (!prioritySet) {
|
|
6481
|
+
hints.push(
|
|
6482
|
+
`MISSING --priority. Without it this action ranks as 'normal' (medium) in the do-first ordering, so urgent/high client work won't surface first. Re-run with --priority ${ACTION_PRIORITIES.join("|")} \u2014 or \`baker actions update <tempId> --priority <level>\`.`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
return hints;
|
|
6486
|
+
}
|
|
6487
|
+
function advisoryHint(name, description) {
|
|
6488
|
+
if (looksScheduled(name, description)) {
|
|
6489
|
+
return { kind: "scheduled", hint: SCHEDULED_HINT };
|
|
6490
|
+
}
|
|
6491
|
+
const executable = looksExecutable(name, description);
|
|
6492
|
+
return executable ? { kind: "executable", ...executable } : null;
|
|
6493
|
+
}
|
|
6494
|
+
var ACTIONS_LIST_DEFAULT_LIMIT = 500;
|
|
6495
|
+
function buildListHints({ returned, limit }) {
|
|
6496
|
+
if (returned < limit) {
|
|
6497
|
+
return [];
|
|
6498
|
+
}
|
|
6499
|
+
return [
|
|
6500
|
+
`This list is capped at ${limit} Tasks and came back full, so older Tasks exist that are NOT shown. Do not treat it as the whole backlog \u2014 re-run with --limit ${limit * 2} (or narrow with --status / --q).`
|
|
6501
|
+
];
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6504
|
+
// src/commands/actions/skillCatalog.ts
|
|
6505
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
6506
|
+
import { dirname, join } from "path";
|
|
6507
|
+
var DESCRIPTION_MAX = 600;
|
|
6508
|
+
var SKILLS_SUBPATH = join(".claude", "skills");
|
|
6509
|
+
var EXCLUDED_SKILLS = /* @__PURE__ */ new Set(["actions"]);
|
|
6510
|
+
function stripQuotes(value) {
|
|
6511
|
+
const trimmed = value.trim();
|
|
6512
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
6513
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\n/g, "\n");
|
|
6514
|
+
}
|
|
6515
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
6516
|
+
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
6517
|
+
}
|
|
6518
|
+
return trimmed;
|
|
6519
|
+
}
|
|
6520
|
+
function extractFrontmatterLines(md) {
|
|
6521
|
+
if (!md.startsWith("---")) return null;
|
|
6522
|
+
const end = md.indexOf("\n---", 3);
|
|
6523
|
+
if (end === -1) return null;
|
|
6524
|
+
return md.slice(md.indexOf("\n", 3) + 1, end).split("\n");
|
|
6525
|
+
}
|
|
6526
|
+
var BLOCK_SCALAR = /^([|>])[+-]?$/;
|
|
6527
|
+
function collectBlockLines(lines, start) {
|
|
6528
|
+
const collected = [];
|
|
6529
|
+
for (let i = start; i < lines.length; i++) {
|
|
6530
|
+
const line = lines[i] ?? "";
|
|
6531
|
+
if (line.trim() === "") {
|
|
6532
|
+
collected.push("");
|
|
6533
|
+
} else if (/^\s/.test(line)) {
|
|
6534
|
+
collected.push(line.trim());
|
|
6535
|
+
} else {
|
|
6536
|
+
break;
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
while (collected.length > 0 && collected.at(-1) === "") collected.pop();
|
|
6540
|
+
return collected;
|
|
6541
|
+
}
|
|
6542
|
+
function foldLines(collected) {
|
|
6543
|
+
const paragraphs = [];
|
|
6544
|
+
let buffer = [];
|
|
6545
|
+
for (const line of collected) {
|
|
6546
|
+
if (line === "") {
|
|
6547
|
+
if (buffer.length > 0) paragraphs.push(buffer.join(" "));
|
|
6548
|
+
buffer = [];
|
|
6549
|
+
} else {
|
|
6550
|
+
buffer.push(line);
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6553
|
+
if (buffer.length > 0) paragraphs.push(buffer.join(" "));
|
|
6554
|
+
return paragraphs.join("\n");
|
|
6555
|
+
}
|
|
6556
|
+
function readBlockScalar(lines, start, style) {
|
|
6557
|
+
const collected = collectBlockLines(lines, start);
|
|
6558
|
+
return style === "|" ? collected.join("\n") : foldLines(collected);
|
|
6559
|
+
}
|
|
6560
|
+
function readField(lines, field) {
|
|
6561
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6562
|
+
const match = (lines[i] ?? "").match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
6563
|
+
if (!match || match[1] !== field) continue;
|
|
6564
|
+
const inline = (match[2] ?? "").trim();
|
|
6565
|
+
const scalar = inline.match(BLOCK_SCALAR);
|
|
6566
|
+
if (scalar) {
|
|
6567
|
+
return readBlockScalar(lines, i + 1, scalar[1]);
|
|
6568
|
+
}
|
|
6569
|
+
return stripQuotes(inline);
|
|
6570
|
+
}
|
|
6571
|
+
return null;
|
|
6572
|
+
}
|
|
6573
|
+
function parseSkillFrontmatter(md) {
|
|
6574
|
+
const lines = extractFrontmatterLines(md);
|
|
6575
|
+
if (!lines) return null;
|
|
6576
|
+
const name = readField(lines, "name");
|
|
6577
|
+
const description = readField(lines, "description");
|
|
6578
|
+
if (!name || !description) return null;
|
|
6579
|
+
const trimmed = description.length > DESCRIPTION_MAX ? `${description.slice(0, DESCRIPTION_MAX)}\u2026` : description;
|
|
6580
|
+
return { name, description: trimmed };
|
|
6581
|
+
}
|
|
6582
|
+
function findSkillsDir(startDir) {
|
|
6583
|
+
let dir = startDir;
|
|
6584
|
+
for (; ; ) {
|
|
6585
|
+
const candidate = join(dir, SKILLS_SUBPATH);
|
|
6586
|
+
if (existsSync(candidate)) return candidate;
|
|
6587
|
+
const parent = dirname(dir);
|
|
6588
|
+
if (parent === dir) return null;
|
|
6589
|
+
dir = parent;
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
function readSkillCatalog(startDir) {
|
|
6593
|
+
const skillsDir = findSkillsDir(startDir);
|
|
6594
|
+
if (!skillsDir) return [];
|
|
6595
|
+
const entries = [];
|
|
6596
|
+
for (const dirent of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
6597
|
+
if (!dirent.isDirectory() || EXCLUDED_SKILLS.has(dirent.name)) continue;
|
|
6598
|
+
const skillFile = join(skillsDir, dirent.name, "SKILL.md");
|
|
6599
|
+
if (!existsSync(skillFile)) continue;
|
|
6600
|
+
try {
|
|
6601
|
+
const parsed = parseSkillFrontmatter(readFileSync(skillFile, "utf8"));
|
|
6602
|
+
if (parsed) entries.push(parsed);
|
|
6603
|
+
} catch {
|
|
6604
|
+
}
|
|
6605
|
+
}
|
|
6606
|
+
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
6607
|
+
}
|
|
6608
|
+
|
|
6609
|
+
// src/commands/actions/claim.ts
|
|
6610
|
+
registerSchema({
|
|
6611
|
+
command: "actions.claim",
|
|
6612
|
+
description: "Claim an action for the current chat (live \u2014 visible to other chats immediately). Claim only what you're actively working on now: it's required before `complete`, but NOT for `update` or `discard` (those stage without a claim). Returns action details plus a fast-model recommendation of which skills to load for the work (`recommendedSkills`) and a routing hint: load every skill that owns part of the work and the tool doc for each baker CLI family you'll use.",
|
|
6613
|
+
args: {
|
|
6614
|
+
id: { type: "string", description: "Action ID", required: true }
|
|
6615
|
+
}
|
|
6616
|
+
});
|
|
6617
|
+
async function recommendSkills(actionId) {
|
|
6618
|
+
const skills = readSkillCatalog(process.cwd());
|
|
6619
|
+
if (skills.length === 0) return [];
|
|
6620
|
+
const response = await apiPost("/api/actions/recommend-skills", {
|
|
6621
|
+
actionId,
|
|
6622
|
+
skills
|
|
6623
|
+
});
|
|
6624
|
+
return response.data.recommendations;
|
|
6625
|
+
}
|
|
6626
|
+
var ROUTING_HINT = "Actions often span several surfaces (a landing page plus a Google Ads change, a sheet pull plus an audience upload). Before starting, load EVERY skill that owns part of the work and read the tool doc (__tooling__/docs/tools/baker/<family>.md) for each baker CLI family you'll use. If the action references source material living in a connected tool (an email attachment, a call, a CRM record, a spreadsheet), reach for that tool too. If it describes recurring or future-dated work, it belongs on a schedule \u2014 see __tooling__/docs/tools/baker/scheduled-actions.md.";
|
|
6627
|
+
function buildHints(recommendations) {
|
|
6628
|
+
if (recommendations.length === 0) {
|
|
6629
|
+
return ["Review the action name, description, and tags above.", ROUTING_HINT];
|
|
6630
|
+
}
|
|
6631
|
+
return [
|
|
6632
|
+
"Recommended skills for this action (load the ones you'll use):",
|
|
6633
|
+
...recommendations.map((r) => ` /${r.name} \u2014 ${r.reason}`),
|
|
6634
|
+
"Suggestions come from the action's name/description/tags only \u2014 the work may need more.",
|
|
6635
|
+
ROUTING_HINT
|
|
6636
|
+
];
|
|
6637
|
+
}
|
|
6638
|
+
var claimCommand = defineCommand({
|
|
6639
|
+
meta: {
|
|
6640
|
+
name: "claim",
|
|
6641
|
+
description: "Claim an action so other chats see you're working on it. Required before `complete` (update and discard don't need a claim). Example: baker actions claim <action-id>"
|
|
6642
|
+
},
|
|
6643
|
+
args: {
|
|
6644
|
+
id: { type: "positional", description: "Action ID", required: false },
|
|
6645
|
+
"action-id": { type: "string", description: "Action ID", required: false }
|
|
6646
|
+
},
|
|
6647
|
+
run: async ({ args }) => {
|
|
6648
|
+
try {
|
|
6649
|
+
const id = args.id || args["action-id"];
|
|
6650
|
+
if (!id) {
|
|
6651
|
+
failValidation("Action ID is required.");
|
|
6652
|
+
}
|
|
6653
|
+
validateConvexId(id);
|
|
6654
|
+
const chatId = requireChatId();
|
|
6655
|
+
const response = await apiPost("/api/actions/claim", {
|
|
6656
|
+
actionId: id,
|
|
6657
|
+
chatId
|
|
6658
|
+
});
|
|
6659
|
+
let recommendedSkills = [];
|
|
6660
|
+
try {
|
|
6661
|
+
recommendedSkills = await recommendSkills(id);
|
|
6662
|
+
} catch {
|
|
6663
|
+
}
|
|
6664
|
+
const data = response.data ? { ...response.data, recommendedSkills } : response.data;
|
|
6665
|
+
writeJson({ ok: response.ok, data, hints: buildHints(recommendedSkills) });
|
|
6666
|
+
} catch (err) {
|
|
6667
|
+
failApi(err);
|
|
6668
|
+
}
|
|
6669
|
+
}
|
|
6670
|
+
});
|
|
6671
|
+
|
|
6668
6672
|
// src/commands/actions/complete.ts
|
|
6669
6673
|
import { defineCommand as defineCommand2 } from "citty";
|
|
6670
6674
|
registerSchema({
|
|
@@ -6739,7 +6743,11 @@ registerSchema({
|
|
|
6739
6743
|
description: `User priority for the do-first ordering. One of: ${ACTION_PRIORITIES.join(", ")}.`,
|
|
6740
6744
|
required: false
|
|
6741
6745
|
},
|
|
6742
|
-
"temp-id": {
|
|
6746
|
+
"temp-id": {
|
|
6747
|
+
type: "string",
|
|
6748
|
+
description: "Custom tempId \u2014 must start with `temp_` (e.g. temp_hero_copy). Auto-generated if omitted; omitting it is fine.",
|
|
6749
|
+
required: false
|
|
6750
|
+
}
|
|
6743
6751
|
}
|
|
6744
6752
|
});
|
|
6745
6753
|
var createCommand = defineCommand3({
|
|
@@ -6752,7 +6760,7 @@ var createCommand = defineCommand3({
|
|
|
6752
6760
|
description: { type: "string", description: "Description", required: false, default: "" },
|
|
6753
6761
|
tags: { type: "string", description: "Comma-separated tag slugs (see `baker actions tags list`)", required: false },
|
|
6754
6762
|
priority: { type: "string", description: `User priority: ${ACTION_PRIORITIES.join("|")}`, required: false },
|
|
6755
|
-
"temp-id": { type: "string", description: "Optional custom tempId", required: false }
|
|
6763
|
+
"temp-id": { type: "string", description: "Optional custom tempId (must start with `temp_`)", required: false }
|
|
6756
6764
|
},
|
|
6757
6765
|
run: async ({ args }) => {
|
|
6758
6766
|
try {
|
|
@@ -6760,8 +6768,15 @@ var createCommand = defineCommand3({
|
|
|
6760
6768
|
if (!name || name.trim().length === 0) {
|
|
6761
6769
|
failValidation("--name is required.");
|
|
6762
6770
|
}
|
|
6771
|
+
const customTempId = args["temp-id"];
|
|
6772
|
+
if (customTempId) {
|
|
6773
|
+
const parsed = actionTempIdSchema.safeParse(customTempId);
|
|
6774
|
+
if (!parsed.success) {
|
|
6775
|
+
failValidation(parsed.error.issues[0]?.message ?? "Invalid --temp-id.");
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6763
6778
|
const chatId = requireChatId();
|
|
6764
|
-
const tempId =
|
|
6779
|
+
const tempId = customTempId || generateTempId();
|
|
6765
6780
|
const tags = parseTagList(args.tags);
|
|
6766
6781
|
const priority = parsePriority(args.priority, { allowClear: false });
|
|
6767
6782
|
const response = await apiPost("/api/actions/create", {
|
|
@@ -43301,8 +43316,8 @@ import { defineCommand as defineCommand178 } from "citty";
|
|
|
43301
43316
|
// src/commands/scheduled-actions/shared.ts
|
|
43302
43317
|
var TEMP_SCHEDULED_ACTION_PREFIX = "temp_sched_";
|
|
43303
43318
|
var TEMP_ID_PREFIX = "temp_";
|
|
43304
|
-
function writeOk2(data) {
|
|
43305
|
-
writeJson({ ok: true, data: data ?? null });
|
|
43319
|
+
function writeOk2(data, hints) {
|
|
43320
|
+
writeJson({ ok: true, data: data ?? null, ...hints && hints.length > 0 ? { hints } : {} });
|
|
43306
43321
|
}
|
|
43307
43322
|
function failValidation4(message) {
|
|
43308
43323
|
writeJson({ ok: false, error: { code: "VALIDATION_ERROR", message } });
|
|
@@ -43343,10 +43358,41 @@ function isPromptWithoutAgent(args, agentDisabled) {
|
|
|
43343
43358
|
function failIfPromptWithoutAgent(args, agentDisabled) {
|
|
43344
43359
|
if (isPromptWithoutAgent(args, agentDisabled)) {
|
|
43345
43360
|
failValidation4(
|
|
43346
|
-
"--prompt only applies when
|
|
43361
|
+
"--prompt only applies when a run does the work; it has no effect with --mode remind, --no-spawn-agent or --spawn-agent false."
|
|
43347
43362
|
);
|
|
43348
43363
|
}
|
|
43349
43364
|
}
|
|
43365
|
+
function isModeAndSpawnAgentBothSet(args, mode) {
|
|
43366
|
+
const spawnAgentGiven = args["spawn-agent"] !== void 0 || args["no-spawn-agent"] === true || args.noSpawnAgent === true;
|
|
43367
|
+
return mode !== void 0 && spawnAgentGiven;
|
|
43368
|
+
}
|
|
43369
|
+
function failIfModeAndSpawnAgent(args, mode) {
|
|
43370
|
+
if (isModeAndSpawnAgentBothSet(args, mode)) {
|
|
43371
|
+
failValidation4("--mode and --spawn-agent/--no-spawn-agent set the same thing. Use --mode on its own.");
|
|
43372
|
+
}
|
|
43373
|
+
}
|
|
43374
|
+
var TASK_MODES = [
|
|
43375
|
+
{ id: "remind", does: "Opens the Task on schedule and leaves it. Nothing runs." },
|
|
43376
|
+
{ id: "recommend", does: "Opens a Task with what it would change and why. Touches nothing itself." },
|
|
43377
|
+
{ id: "propose", does: "Does the work and leaves it for the user to review, then publish." },
|
|
43378
|
+
{ id: "publish", does: "Does the work and publishes it as soon as it finishes \u2014 nobody reviews it first." }
|
|
43379
|
+
];
|
|
43380
|
+
var TASK_MODE_IDS = TASK_MODES.map((mode) => mode.id);
|
|
43381
|
+
var TASK_MODE_ARG_DESCRIPTION = `How far a run of this task goes on its own. ${TASK_MODES.map(
|
|
43382
|
+
(mode) => `${mode.id}: ${mode.does}`
|
|
43383
|
+
).join(" ")} Only set publish when the user asked for it outright \u2014 those runs go live unreviewed and can spend money.`;
|
|
43384
|
+
function isKnownTaskMode(raw) {
|
|
43385
|
+
return typeof raw === "string" && TASK_MODE_IDS.some((id) => id === raw);
|
|
43386
|
+
}
|
|
43387
|
+
function parseModeFlag(raw) {
|
|
43388
|
+
if (raw === void 0) {
|
|
43389
|
+
return void 0;
|
|
43390
|
+
}
|
|
43391
|
+
if (!isKnownTaskMode(raw)) {
|
|
43392
|
+
failValidation4(`--mode must be one of: ${TASK_MODE_IDS.join(", ")}.`);
|
|
43393
|
+
}
|
|
43394
|
+
return raw;
|
|
43395
|
+
}
|
|
43350
43396
|
function parseBooleanFlag(raw, flagName) {
|
|
43351
43397
|
if (raw === void 0) {
|
|
43352
43398
|
return void 0;
|
|
@@ -43402,6 +43448,12 @@ registerSchema({
|
|
|
43402
43448
|
},
|
|
43403
43449
|
timezone: { type: "string", description: "IANA timezone override; defaults to Company Timezone", required: false },
|
|
43404
43450
|
disabled: { type: "boolean", description: "Create disabled", required: false, default: false },
|
|
43451
|
+
mode: {
|
|
43452
|
+
type: "string",
|
|
43453
|
+
description: `${TASK_MODE_ARG_DESCRIPTION} Defaults to propose, or to the template's own mode with --template.`,
|
|
43454
|
+
required: false,
|
|
43455
|
+
enum: TASK_MODE_IDS
|
|
43456
|
+
},
|
|
43405
43457
|
"no-spawn-agent": {
|
|
43406
43458
|
type: "boolean",
|
|
43407
43459
|
description: "Do not spawn an agent when this scheduled action fires",
|
|
@@ -43428,6 +43480,7 @@ var createCommand3 = defineCommand178({
|
|
|
43428
43480
|
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z", required: false },
|
|
43429
43481
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
43430
43482
|
disabled: { type: "boolean", description: "Create disabled", required: false, default: false },
|
|
43483
|
+
mode: { type: "string", description: `One of: ${TASK_MODE_IDS.join(", ")}`, required: false },
|
|
43431
43484
|
"no-spawn-agent": { type: "boolean", description: "Disable agent spawning", required: false, default: false },
|
|
43432
43485
|
prompt: { type: "string", description: "Additional spawned-agent instructions", required: false },
|
|
43433
43486
|
template: { type: "string", description: "Template id to run on this schedule", required: false }
|
|
@@ -43445,7 +43498,9 @@ var createCommand3 = defineCommand178({
|
|
|
43445
43498
|
const schedule = buildScheduleBody(args, { required: true });
|
|
43446
43499
|
const chatId = requireChatId();
|
|
43447
43500
|
const noSpawnAgent = isNoSpawnAgentFlagSet(args);
|
|
43448
|
-
|
|
43501
|
+
const mode = parseModeFlag(args.mode);
|
|
43502
|
+
failIfModeAndSpawnAgent(args, mode);
|
|
43503
|
+
failIfPromptWithoutAgent(args, noSpawnAgent || mode === "remind");
|
|
43449
43504
|
const body = {
|
|
43450
43505
|
chatId,
|
|
43451
43506
|
name,
|
|
@@ -43454,6 +43509,9 @@ var createCommand3 = defineCommand178({
|
|
|
43454
43509
|
spawnAgent: !noSpawnAgent,
|
|
43455
43510
|
...schedule
|
|
43456
43511
|
};
|
|
43512
|
+
if (mode !== void 0) {
|
|
43513
|
+
body.mode = mode;
|
|
43514
|
+
}
|
|
43457
43515
|
if (typeof args.prompt === "string") {
|
|
43458
43516
|
body.agentPrompt = args.prompt;
|
|
43459
43517
|
}
|
|
@@ -43795,6 +43853,12 @@ registerSchema({
|
|
|
43795
43853
|
},
|
|
43796
43854
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
43797
43855
|
enabled: { type: "string", description: "Replacement enabled state", required: false, enum: ["true", "false"] },
|
|
43856
|
+
mode: {
|
|
43857
|
+
type: "string",
|
|
43858
|
+
description: TASK_MODE_ARG_DESCRIPTION,
|
|
43859
|
+
required: false,
|
|
43860
|
+
enum: TASK_MODE_IDS
|
|
43861
|
+
},
|
|
43798
43862
|
"spawn-agent": {
|
|
43799
43863
|
type: "string",
|
|
43800
43864
|
description: "Replacement spawn-agent state",
|
|
@@ -43807,7 +43871,7 @@ registerSchema({
|
|
|
43807
43871
|
var updateCommand3 = defineCommand184({
|
|
43808
43872
|
meta: {
|
|
43809
43873
|
name: "update",
|
|
43810
|
-
description: "Stage a scheduled action update.
|
|
43874
|
+
description: "Stage a scheduled action update. Examples: baker scheduled-actions update <id> --enabled false | baker scheduled-actions update <id> --mode publish"
|
|
43811
43875
|
},
|
|
43812
43876
|
args: {
|
|
43813
43877
|
id: { type: "positional", description: "Scheduled action ID or temp_sched_* draft ID", required: false },
|
|
@@ -43822,6 +43886,7 @@ var updateCommand3 = defineCommand184({
|
|
|
43822
43886
|
"run-at": { type: "string", description: "ISO UTC timestamp ending in Z", required: false },
|
|
43823
43887
|
timezone: { type: "string", description: "IANA timezone override", required: false },
|
|
43824
43888
|
enabled: { type: "string", description: "true|false", required: false },
|
|
43889
|
+
mode: { type: "string", description: `One of: ${TASK_MODE_IDS.join(", ")}`, required: false },
|
|
43825
43890
|
"spawn-agent": { type: "string", description: "true|false", required: false },
|
|
43826
43891
|
prompt: { type: "string", description: "Replacement additional spawned-agent instructions", required: false }
|
|
43827
43892
|
},
|
|
@@ -43850,24 +43915,30 @@ var updateCommand3 = defineCommand184({
|
|
|
43850
43915
|
body.enabled = enabled;
|
|
43851
43916
|
hasPatch = true;
|
|
43852
43917
|
}
|
|
43918
|
+
const mode = parseModeFlag(args.mode);
|
|
43919
|
+
failIfModeAndSpawnAgent(args, mode);
|
|
43920
|
+
if (mode !== void 0) {
|
|
43921
|
+
body.mode = mode;
|
|
43922
|
+
hasPatch = true;
|
|
43923
|
+
}
|
|
43853
43924
|
const spawnAgent = parseBooleanFlag(args["spawn-agent"], "--spawn-agent");
|
|
43854
43925
|
if (spawnAgent !== void 0) {
|
|
43855
43926
|
body.spawnAgent = spawnAgent;
|
|
43856
43927
|
hasPatch = true;
|
|
43857
43928
|
}
|
|
43858
|
-
failIfPromptWithoutAgent(args, spawnAgent === false);
|
|
43929
|
+
failIfPromptWithoutAgent(args, spawnAgent === false || mode === "remind");
|
|
43859
43930
|
if (typeof args.prompt === "string") {
|
|
43860
43931
|
body.agentPrompt = args.prompt;
|
|
43861
43932
|
hasPatch = true;
|
|
43862
43933
|
}
|
|
43863
43934
|
if (!hasPatch) {
|
|
43864
43935
|
failValidation4(
|
|
43865
|
-
"Provide at least one of --name, --description, --cron, --run-at, --timezone, --enabled, --spawn-agent, --prompt."
|
|
43936
|
+
"Provide at least one of --name, --description, --cron, --run-at, --timezone, --enabled, --mode, --spawn-agent, --prompt."
|
|
43866
43937
|
);
|
|
43867
43938
|
}
|
|
43868
43939
|
body.chatId = requireChatId();
|
|
43869
|
-
await apiPost("/api/scheduled-actions/update", body);
|
|
43870
|
-
writeOk2();
|
|
43940
|
+
const response = await apiPost("/api/scheduled-actions/update", body);
|
|
43941
|
+
writeOk2(null, response.hints);
|
|
43871
43942
|
} catch (err) {
|
|
43872
43943
|
failApi2(err);
|
|
43873
43944
|
}
|
|
@@ -43887,6 +43958,7 @@ Examples:
|
|
|
43887
43958
|
baker scheduled-actions get <id-or-temp_sched_id>
|
|
43888
43959
|
baker scheduled-actions create --name "Weekly report" --description "Prepare weekly report" --cron "0 9 * * MON"
|
|
43889
43960
|
baker scheduled-actions update <id-or-temp_sched_id> --enabled false
|
|
43961
|
+
baker scheduled-actions update <id-or-temp_sched_id> --mode publish # what a run does on its own: remind|recommend|propose|publish
|
|
43890
43962
|
baker scheduled-actions delete <id-or-temp_sched_id>
|
|
43891
43963
|
baker scheduled-actions trigger <id>
|
|
43892
43964
|
Full guide: __tooling__/docs/tools/baker/scheduled-actions.md`
|