@craftsamo/github-tools 0.6.1 → 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.
- package/.antigravity-plugin/plugin.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/dist/cli.js +194 -72
- package/dist/mcp.js +195 -73
- package/dist/opencode.js +194 -72
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/plugin.yaml +1 -1
- package/skills/github-projects/SKILL.md +16 -1
package/dist/opencode.js
CHANGED
|
@@ -20736,7 +20736,7 @@ function issueFieldValues(connection) {
|
|
|
20736
20736
|
}
|
|
20737
20737
|
var SCHEMA_ERROR_PATTERNS = [
|
|
20738
20738
|
/issueFieldValues.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Issue['"`]?/i,
|
|
20739
|
-
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?Repository['"`]?/i,
|
|
20739
|
+
/issueFields.*(?:doesn['’]t exist on type|is not defined on type).*['"`]?(?:Repository|Organization)['"`]?/i,
|
|
20740
20740
|
/(?:isIssueField|issueField).*(?:doesn['’]t exist on type|is not defined on type).*['"`]?ProjectV2/i,
|
|
20741
20741
|
/(?:No such type\s+['"`]?(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues)['"`]?|(?:ProjectV2ItemIssueFieldValue|ProjectV2IssueFieldValues).*(?:doesn['’]t exist|is not defined))/i
|
|
20742
20742
|
];
|
|
@@ -20755,7 +20755,9 @@ function isIssueFieldsMissingError(error62) {
|
|
|
20755
20755
|
var issueFieldInputSchema = issueFieldValuesSchema;
|
|
20756
20756
|
var ISSUE_FIELDS_ARGUMENT = `The organization's issue fields to set, by field name (without regard to case): {"Priority": "High", "Target date": "2026-12-01"}. Each takes a text, a number, a date as YYYY-MM-DD, an option name, or a list of option names for a multi-select field.`;
|
|
20757
20757
|
var MAX_ISSUE_FIELDS = 25;
|
|
20758
|
-
var
|
|
20758
|
+
var DEFINITION_SELECTION = "__typename ... on IssueFieldCommon{name dataType} ... on IssueFieldText{id} ... on IssueFieldNumber{id} ... on IssueFieldDate{id} ... on IssueFieldSingleSelect{id options{id name}} ... on IssueFieldMultiSelect{id options{id name}}";
|
|
20759
|
+
var DEFINITIONS_QUERY = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issueFields(first:${MAX_ISSUE_FIELDS}){nodes{${DEFINITION_SELECTION}}}}}`;
|
|
20760
|
+
var ORGANIZATION_DEFINITIONS_QUERY = `query($login:String!){repositoryOwner(login:$login){__typename ... on Organization{issueFields(first:${MAX_ISSUE_FIELDS}){nodes{${DEFINITION_SELECTION}}}}}}`;
|
|
20759
20761
|
var VALUES_QUERY = `query($id:ID!){node(id:$id){... on Issue{${ISSUE_FIELD_VALUES_FRAGMENT}}}}`;
|
|
20760
20762
|
var issueFieldTypes = {
|
|
20761
20763
|
TEXT: "text",
|
|
@@ -20791,6 +20793,20 @@ async function readIssueFieldDefinitions(repo, ctx) {
|
|
|
20791
20793
|
}
|
|
20792
20794
|
return (data?.repository?.issueFields?.nodes ?? []).flatMap((raw) => definitionOf(raw) ?? []);
|
|
20793
20795
|
}
|
|
20796
|
+
async function readOrganizationIssueFields(login, ctx) {
|
|
20797
|
+
let data;
|
|
20798
|
+
try {
|
|
20799
|
+
data = await graphql(ORGANIZATION_DEFINITIONS_QUERY, { login }, ctx);
|
|
20800
|
+
} catch (error62) {
|
|
20801
|
+
if (isIssueFieldsMissingError(error62)) return null;
|
|
20802
|
+
throw error62;
|
|
20803
|
+
}
|
|
20804
|
+
if (data?.repositoryOwner?.__typename !== "Organization") return null;
|
|
20805
|
+
return (data.repositoryOwner.issueFields?.nodes ?? []).flatMap((raw) => definitionOf(raw) ?? []);
|
|
20806
|
+
}
|
|
20807
|
+
function findIssueField(definitions, name) {
|
|
20808
|
+
return definitions.find((candidate) => sameTitle(candidate.name, name));
|
|
20809
|
+
}
|
|
20794
20810
|
function invalid4(message) {
|
|
20795
20811
|
return new ToolError("invalid_argument", message);
|
|
20796
20812
|
}
|
|
@@ -20798,7 +20814,7 @@ function fieldNames(definitions) {
|
|
|
20798
20814
|
return listOf(definitions.map((field) => field.name));
|
|
20799
20815
|
}
|
|
20800
20816
|
function fieldOf(repo, definitions, name) {
|
|
20801
|
-
const field = definitions
|
|
20817
|
+
const field = findIssueField(definitions, name);
|
|
20802
20818
|
if (!field) throw invalid4(`${repo.full} has no issue field "${name}". Fields: ${fieldNames(definitions)}.`);
|
|
20803
20819
|
return field;
|
|
20804
20820
|
}
|
|
@@ -21956,6 +21972,7 @@ var STATUS_FIELD = "Status";
|
|
|
21956
21972
|
var FIELD_REPLY = `projectV2Field{${FIELD_SELECTION}}`;
|
|
21957
21973
|
var createFieldMutation = (type, options) => `mutation($project:ID!,$name:String!){createProjectV2Field(input:{projectId:$project,dataType:${type.toUpperCase()},name:$name${options}}){${FIELD_REPLY}}}`;
|
|
21958
21974
|
var updateOptionsMutation = (options) => `mutation($field:ID!){updateProjectV2Field(input:{fieldId:$field,singleSelectOptions:${options}}){${FIELD_REPLY}}}`;
|
|
21975
|
+
var createIssueFieldColumnMutation = `mutation($project:ID!,$field:ID!){createProjectV2IssueField(input:{projectId:$project,issueFieldId:$field}){projectV2Field{${READ_FIELD_SELECTION}}}}`;
|
|
21959
21976
|
function invalid7(message) {
|
|
21960
21977
|
return new ToolError("invalid_argument", message);
|
|
21961
21978
|
}
|
|
@@ -21968,6 +21985,29 @@ function fieldRequest(name, type, options) {
|
|
|
21968
21985
|
}
|
|
21969
21986
|
return { name: trimmed, type, options: parsed };
|
|
21970
21987
|
}
|
|
21988
|
+
function issueFieldColumnRequest(name, type) {
|
|
21989
|
+
const trimmed = name.trim();
|
|
21990
|
+
if (!trimmed) throw invalid7("name cannot be blank.");
|
|
21991
|
+
return { name: trimmed, type };
|
|
21992
|
+
}
|
|
21993
|
+
async function readOwnerIssueFields(owner, ctx) {
|
|
21994
|
+
return owner.owner_type === "organization" ? readOrganizationIssueFields(owner.owner, ctx) : null;
|
|
21995
|
+
}
|
|
21996
|
+
function requireIssueField(owner, definitions, request2) {
|
|
21997
|
+
if (owner.owner_type !== "organization") {
|
|
21998
|
+
throw invalid7(`Issue fields belong to an organization; ${owner.owner} is a user.`);
|
|
21999
|
+
}
|
|
22000
|
+
if (!definitions) throw invalid7("This GitHub has no issue fields.");
|
|
22001
|
+
const definition = findIssueField(definitions, request2.name);
|
|
22002
|
+
if (!definition) {
|
|
22003
|
+
const names = listOf(definitions.map((field) => field.name));
|
|
22004
|
+
throw invalid7(`${owner.owner} has no issue field "${request2.name}". Issue fields: ${names}.`);
|
|
22005
|
+
}
|
|
22006
|
+
if (request2.type && request2.type !== definition.type) {
|
|
22007
|
+
throw invalid7(`"${definition.name}" is ${definition.type}, not ${request2.type}.`);
|
|
22008
|
+
}
|
|
22009
|
+
return definition;
|
|
22010
|
+
}
|
|
21971
22011
|
function optionsLiteral(options) {
|
|
21972
22012
|
const items = options.map((option) => {
|
|
21973
22013
|
if (!OPTION_COLORS.some((color) => color === option.color)) {
|
|
@@ -22025,6 +22065,23 @@ async function ensureField(board, fields, request2, policy, ctx) {
|
|
|
22025
22065
|
const field = fieldOf2(data?.updateProjectV2Field ?? null, existing.name);
|
|
22026
22066
|
return { field, created: false, added_options: merge2.added, updated_options: merge2.updated };
|
|
22027
22067
|
}
|
|
22068
|
+
async function ensureIssueFieldColumn(board, fields, definitions, request2, ctx) {
|
|
22069
|
+
const definition = requireIssueField(board, definitions, request2);
|
|
22070
|
+
const existing = fields.find((field2) => sameTitle(field2.name, definition.name));
|
|
22071
|
+
if (existing?.issue_field) return { field: existing, created: false, added_options: [], updated_options: [] };
|
|
22072
|
+
if (existing) {
|
|
22073
|
+
throw invalid7(
|
|
22074
|
+
`The board has its own field "${existing.name}"; an issue field column cannot take the same name. Rename or delete the board's field in the web UI, or use the board's field.`
|
|
22075
|
+
);
|
|
22076
|
+
}
|
|
22077
|
+
const data = await graphql(
|
|
22078
|
+
createIssueFieldColumnMutation,
|
|
22079
|
+
{ project: board.id, field: definition.id },
|
|
22080
|
+
ctx
|
|
22081
|
+
);
|
|
22082
|
+
const field = fieldOf2(data?.createProjectV2IssueField ?? null, definition.name);
|
|
22083
|
+
return { field, created: true, added_options: [], updated_options: [] };
|
|
22084
|
+
}
|
|
22028
22085
|
|
|
22029
22086
|
// src/core/projects/support.ts
|
|
22030
22087
|
var BOARD_SELECTION = {
|
|
@@ -22048,60 +22105,65 @@ function boardSummary(board) {
|
|
|
22048
22105
|
// src/core/projects/template.ts
|
|
22049
22106
|
var STATUS_TODO = "Todo";
|
|
22050
22107
|
var STATUS_WAITING = "Waiting";
|
|
22051
|
-
var
|
|
22052
|
-
var
|
|
22053
|
-
var
|
|
22108
|
+
var DATE_MODES = ["board", "issue_fields"];
|
|
22109
|
+
var BOARD_DATE_FIELDS = { start: "Start", target: "Target" };
|
|
22110
|
+
var ISSUE_DATE_FIELDS = { start: "Start date", target: "Target date" };
|
|
22054
22111
|
var DEFAULT_VIEW_NAME = "View 1";
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22112
|
+
function roadmapNote(dates) {
|
|
22113
|
+
return `Set the Roadmap view's date fields to ${dates.start} and ${dates.target} in the web UI; the API cannot set them.`;
|
|
22114
|
+
}
|
|
22115
|
+
var ROADMAP_NOTE = roadmapNote(BOARD_DATE_FIELDS);
|
|
22116
|
+
var STATUS_REQUEST = {
|
|
22117
|
+
name: STATUS_FIELD,
|
|
22118
|
+
type: "single_select",
|
|
22119
|
+
options: [
|
|
22120
|
+
{ name: STATUS_TODO, color: "BLUE", description: "Ready to start; nothing is in the way." },
|
|
22121
|
+
{ name: "In Progress", color: "YELLOW", description: "Being worked on now." },
|
|
22122
|
+
{
|
|
22123
|
+
name: STATUS_WAITING,
|
|
22124
|
+
color: "GRAY",
|
|
22125
|
+
description: "Cannot move until something else clears: an open blocking issue, a review or a reply."
|
|
22126
|
+
},
|
|
22127
|
+
{ name: "Done", color: "PURPLE", description: "Finished." },
|
|
22128
|
+
{ name: "Cancelled", color: "RED", description: "Will not be done; kept for the record." }
|
|
22129
|
+
]
|
|
22130
|
+
};
|
|
22131
|
+
function standardTemplate(dates) {
|
|
22132
|
+
const notDone = "-status:Done -status:Cancelled";
|
|
22133
|
+
return {
|
|
22134
|
+
roadmapNote: roadmapNote(dates),
|
|
22135
|
+
views: [
|
|
22136
|
+
{ name: "Roadmap", layout: "roadmap", filter: "-status:Cancelled", sort_by: dates.start },
|
|
22063
22137
|
{
|
|
22064
|
-
name:
|
|
22065
|
-
|
|
22066
|
-
|
|
22138
|
+
name: "Kanban",
|
|
22139
|
+
layout: "board",
|
|
22140
|
+
filter: notDone,
|
|
22141
|
+
column_by: STATUS_FIELD,
|
|
22142
|
+
fields: ["Title", "Assignees", "Repository", "Parent issue", dates.target],
|
|
22143
|
+
sort_by: dates.target
|
|
22067
22144
|
},
|
|
22068
|
-
{
|
|
22069
|
-
|
|
22145
|
+
{
|
|
22146
|
+
name: "Backlog",
|
|
22147
|
+
layout: "table",
|
|
22148
|
+
filter: notDone,
|
|
22149
|
+
fields: [
|
|
22150
|
+
"Title",
|
|
22151
|
+
STATUS_FIELD,
|
|
22152
|
+
"Assignees",
|
|
22153
|
+
"Repository",
|
|
22154
|
+
"Milestone",
|
|
22155
|
+
"Parent issue",
|
|
22156
|
+
"Sub-issues progress",
|
|
22157
|
+
dates.start,
|
|
22158
|
+
dates.target,
|
|
22159
|
+
"Linked pull requests"
|
|
22160
|
+
],
|
|
22161
|
+
group_by: STATUS_FIELD,
|
|
22162
|
+
sort_by: dates.target
|
|
22163
|
+
}
|
|
22070
22164
|
]
|
|
22071
|
-
}
|
|
22072
|
-
|
|
22073
|
-
{ name: TARGET_FIELD, type: "date", options: [] }
|
|
22074
|
-
];
|
|
22075
|
-
var STANDARD_VIEWS = [
|
|
22076
|
-
{ name: "Roadmap", layout: "roadmap", filter: "-status:Cancelled", sort_by: START_FIELD },
|
|
22077
|
-
{
|
|
22078
|
-
name: "Kanban",
|
|
22079
|
-
layout: "board",
|
|
22080
|
-
filter: NOT_DONE,
|
|
22081
|
-
column_by: STATUS_FIELD,
|
|
22082
|
-
fields: ["Title", "Assignees", "Repository", "Parent issue", TARGET_FIELD],
|
|
22083
|
-
sort_by: TARGET_FIELD
|
|
22084
|
-
},
|
|
22085
|
-
{
|
|
22086
|
-
name: "Backlog",
|
|
22087
|
-
layout: "table",
|
|
22088
|
-
filter: NOT_DONE,
|
|
22089
|
-
fields: [
|
|
22090
|
-
"Title",
|
|
22091
|
-
STATUS_FIELD,
|
|
22092
|
-
"Assignees",
|
|
22093
|
-
"Repository",
|
|
22094
|
-
"Milestone",
|
|
22095
|
-
"Parent issue",
|
|
22096
|
-
"Sub-issues progress",
|
|
22097
|
-
START_FIELD,
|
|
22098
|
-
TARGET_FIELD,
|
|
22099
|
-
"Linked pull requests"
|
|
22100
|
-
],
|
|
22101
|
-
group_by: STATUS_FIELD,
|
|
22102
|
-
sort_by: TARGET_FIELD
|
|
22103
|
-
}
|
|
22104
|
-
];
|
|
22165
|
+
};
|
|
22166
|
+
}
|
|
22105
22167
|
|
|
22106
22168
|
// src/core/projects/item-support.ts
|
|
22107
22169
|
var ITEM_BOARD_SELECTION = {
|
|
@@ -23336,6 +23398,9 @@ var input10 = external_exports.object({
|
|
|
23336
23398
|
"A board to copy, without its draft items, when the board has to be made: its number, owner/number or URL (a bare number or a title is looked up among owner's boards)."
|
|
23337
23399
|
),
|
|
23338
23400
|
standard: external_exports.boolean().optional().describe("Bring the board to the standard template: its fields and views (default true)."),
|
|
23401
|
+
dates: external_exports.enum(DATE_MODES).optional().describe(
|
|
23402
|
+
`"board" (default) makes the board's own date fields Start and Target; "issue_fields" uses the organization's issue fields Start date and Target date instead, so an issue carries the same dates on every board and they are set with the issue tools (a pull request or draft has none). Organization boards only.`
|
|
23403
|
+
),
|
|
23339
23404
|
link_repo: external_exports.string().optional().describe("owner/name of a repository to link the board to; needs write permission.")
|
|
23340
23405
|
});
|
|
23341
23406
|
var output10 = external_exports.object({
|
|
@@ -23367,23 +23432,41 @@ function openBoardTitled(boards, title) {
|
|
|
23367
23432
|
}
|
|
23368
23433
|
return matches[0];
|
|
23369
23434
|
}
|
|
23370
|
-
async function applyFields(board, fresh, report, ctx) {
|
|
23435
|
+
async function applyFields(board, fresh, mode, dates, definitions, report, ctx) {
|
|
23371
23436
|
const fields = await readBoardFields(board.id, ctx);
|
|
23372
|
-
|
|
23373
|
-
|
|
23374
|
-
|
|
23375
|
-
|
|
23376
|
-
|
|
23377
|
-
|
|
23378
|
-
|
|
23379
|
-
|
|
23380
|
-
|
|
23437
|
+
const status = await ensureField(board, fields, STATUS_REQUEST, fresh ? "replace" : "append", ctx);
|
|
23438
|
+
report.push({
|
|
23439
|
+
field: status.field.name,
|
|
23440
|
+
created: status.created,
|
|
23441
|
+
added_options: status.added_options,
|
|
23442
|
+
updated_options: status.updated_options
|
|
23443
|
+
});
|
|
23444
|
+
if (mode === "issue_fields") {
|
|
23445
|
+
for (const name of [dates.start, dates.target]) {
|
|
23446
|
+
const outcome = await ensureIssueFieldColumn(board, fields, definitions, { name, type: "date" }, ctx);
|
|
23447
|
+
report.push({
|
|
23448
|
+
field: outcome.field.name,
|
|
23449
|
+
created: outcome.created,
|
|
23450
|
+
added_options: outcome.added_options,
|
|
23451
|
+
updated_options: outcome.updated_options
|
|
23452
|
+
});
|
|
23453
|
+
}
|
|
23454
|
+
} else {
|
|
23455
|
+
for (const name of [dates.start, dates.target]) {
|
|
23456
|
+
const outcome = await ensureField(board, fields, { name, type: "date", options: [] }, "append", ctx);
|
|
23457
|
+
report.push({
|
|
23458
|
+
field: outcome.field.name,
|
|
23459
|
+
created: outcome.created,
|
|
23460
|
+
added_options: outcome.added_options,
|
|
23461
|
+
updated_options: outcome.updated_options
|
|
23462
|
+
});
|
|
23463
|
+
}
|
|
23381
23464
|
}
|
|
23382
23465
|
}
|
|
23383
|
-
async function applyViews(board, report, notes, ctx) {
|
|
23466
|
+
async function applyViews(board, templateViews, roadmapNote2, report, notes, ctx) {
|
|
23384
23467
|
const before = await readBoardViewNodes(board.id, ctx);
|
|
23385
23468
|
const readers = viewFieldReaders(board, ctx);
|
|
23386
|
-
for (const request2 of
|
|
23469
|
+
for (const request2 of templateViews) {
|
|
23387
23470
|
if (before.some((view5) => sameTitle(view5.name, request2.name))) {
|
|
23388
23471
|
report.push({ view: request2.name, created: false });
|
|
23389
23472
|
continue;
|
|
@@ -23395,7 +23478,7 @@ async function applyViews(board, report, notes, ctx) {
|
|
|
23395
23478
|
throw error62;
|
|
23396
23479
|
}
|
|
23397
23480
|
report.push({ view: request2.name, created: true });
|
|
23398
|
-
if (request2.layout === "roadmap") notes.push(
|
|
23481
|
+
if (request2.layout === "roadmap") notes.push(roadmapNote2);
|
|
23399
23482
|
}
|
|
23400
23483
|
return before;
|
|
23401
23484
|
}
|
|
@@ -23405,6 +23488,7 @@ var projectCreate = defineTool({
|
|
|
23405
23488
|
description: [
|
|
23406
23489
|
`Create a project board for a user or organization, or reuse the open board of that owner and exact title (case-insensitive; several are invalid_argument), and bring it to the standard template. The title defaults to ${DEFAULT_BOARD_TITLE}. A board that has to be made is empty, or a copy of template without its draft items.`,
|
|
23407
23490
|
"With standard (the default): the fields Status (Todo, In Progress, Waiting, Done, Cancelled, each with a color and a description), Start and Target (dates), and the views Roadmap, Kanban and Backlog. On a board this call made, Status gets exactly those options, keeping the ids of GitHub's own Todo, In Progress and Done, and GitHub's default view View 1 is deleted once the three views exist. On a reused or copied board only the missing fields, options and views are added, and nothing is deleted. A reused board must be one the viewer may update.",
|
|
23491
|
+
`dates: "issue_fields" uses the organization's issue fields Start date and Target date as the board's dates, refused before the board is made when the owner is a user or lacks them.`,
|
|
23408
23492
|
"link_repo links the board to a repository (needs write permission there) unless it is already linked. GitHub links only boards of the repository's own owner, so a repository of another owner is invalid_argument, before the board is made.",
|
|
23409
23493
|
"Once the board exists, a failed step does not fail the call: it is listed in errors and the board is still returned.",
|
|
23410
23494
|
`notes lists what the API cannot do, such as setting the Roadmap view's date fields. ${PROJECT_SCOPE_NOTE}`
|
|
@@ -23430,6 +23514,23 @@ var projectCreate = defineTool({
|
|
|
23430
23514
|
linkRepo = await writableContext(repo, ctx);
|
|
23431
23515
|
}
|
|
23432
23516
|
const owner = await readBoardOwner(login, ctx);
|
|
23517
|
+
const dateMode = args.dates ?? "board";
|
|
23518
|
+
let dateNames = BOARD_DATE_FIELDS;
|
|
23519
|
+
let definitions = null;
|
|
23520
|
+
if (dateMode === "issue_fields") {
|
|
23521
|
+
if (!standard) throw invalid5("dates applies only with standard.");
|
|
23522
|
+
definitions = await readOwnerIssueFields({ owner: owner.login, owner_type: owner.type }, ctx);
|
|
23523
|
+
const startDef = requireIssueField({ owner: owner.login, owner_type: owner.type }, definitions, {
|
|
23524
|
+
name: ISSUE_DATE_FIELDS.start,
|
|
23525
|
+
type: "date"
|
|
23526
|
+
});
|
|
23527
|
+
const targetDef = requireIssueField({ owner: owner.login, owner_type: owner.type }, definitions, {
|
|
23528
|
+
name: ISSUE_DATE_FIELDS.target,
|
|
23529
|
+
type: "date"
|
|
23530
|
+
});
|
|
23531
|
+
dateNames = { start: startDef.name, target: targetDef.name };
|
|
23532
|
+
}
|
|
23533
|
+
const template = standardTemplate(dateNames);
|
|
23433
23534
|
const reused = openBoardTitled((await ownerBoards(owner.login, ctx, owner.type)).boards, title);
|
|
23434
23535
|
if (reused) assertBoardWritable(reused);
|
|
23435
23536
|
const source = reused || !templateRef ? void 0 : (await resolveBoard({ project: templateRef, owner: ownerArg }, ctx)).project;
|
|
@@ -23442,8 +23543,11 @@ var projectCreate = defineTool({
|
|
|
23442
23543
|
const notes = [];
|
|
23443
23544
|
let deletedDefaultView = false;
|
|
23444
23545
|
if (standard) {
|
|
23445
|
-
await steps.record("fields", () => applyFields(board, fresh, fields, ctx));
|
|
23446
|
-
const before = await steps.record(
|
|
23546
|
+
await steps.record("fields", () => applyFields(board, fresh, dateMode, dateNames, definitions, fields, ctx));
|
|
23547
|
+
const before = await steps.record(
|
|
23548
|
+
"views",
|
|
23549
|
+
() => applyViews(board, template.views, template.roadmapNote, views, notes, ctx)
|
|
23550
|
+
);
|
|
23447
23551
|
const defaultView = fresh ? before?.find((view5) => view5.name === DEFAULT_VIEW_NAME) : void 0;
|
|
23448
23552
|
if (defaultView) {
|
|
23449
23553
|
deletedDefaultView = await steps.record("default_view", async () => {
|
|
@@ -23474,9 +23578,14 @@ var projectCreate = defineTool({
|
|
|
23474
23578
|
var input11 = external_exports.object({
|
|
23475
23579
|
...BOARD_SELECTION,
|
|
23476
23580
|
name: external_exports.string().describe("The field's name, matched without regard to case."),
|
|
23477
|
-
type: external_exports.enum(FIELD_TYPES).describe(
|
|
23581
|
+
type: external_exports.enum(FIELD_TYPES).optional().describe(
|
|
23582
|
+
"The field's type; a field of that name with another type is invalid_argument. Required unless issue_field is true, where it only checks the issue field's type."
|
|
23583
|
+
),
|
|
23478
23584
|
options: external_exports.array(external_exports.string()).optional().describe(
|
|
23479
|
-
`single_select only: Name, Name:COLOR or Name:COLOR:Description (the description is everything after the second colon). COLOR is one of ${OPTION_COLORS.join(", ")}, in any case; the default is GRAY.`
|
|
23585
|
+
`single_select only, not with issue_field: Name, Name:COLOR or Name:COLOR:Description (the description is everything after the second colon). COLOR is one of ${OPTION_COLORS.join(", ")}, in any case; the default is GRAY.`
|
|
23586
|
+
),
|
|
23587
|
+
issue_field: external_exports.boolean().optional().describe(
|
|
23588
|
+
"Add a column that mirrors the organization's issue field of that name instead of a field of the board's own; its options and values are the organization's and the issue's."
|
|
23480
23589
|
)
|
|
23481
23590
|
});
|
|
23482
23591
|
var output11 = external_exports.object({
|
|
@@ -23485,12 +23594,25 @@ var output11 = external_exports.object({
|
|
|
23485
23594
|
created: external_exports.boolean().describe("Whether this call made the field."),
|
|
23486
23595
|
added_options: external_exports.array(external_exports.string()).describe("The names of the options this call added; [] when none.")
|
|
23487
23596
|
});
|
|
23597
|
+
function planEnsure(args) {
|
|
23598
|
+
if (args.issue_field) {
|
|
23599
|
+
if (args.options && args.options.length > 0) {
|
|
23600
|
+
throw new ToolError("invalid_argument", "options are the organization's with issue_field; leave them out.");
|
|
23601
|
+
}
|
|
23602
|
+
const request3 = issueFieldColumnRequest(args.name, args.type);
|
|
23603
|
+
return async (board, fields, ctx) => ensureIssueFieldColumn(board, fields, await readOwnerIssueFields(board, ctx), request3, ctx);
|
|
23604
|
+
}
|
|
23605
|
+
if (!args.type) throw new ToolError("invalid_argument", "type is required unless issue_field is true.");
|
|
23606
|
+
const request2 = fieldRequest(args.name, args.type, args.options);
|
|
23607
|
+
return (board, fields, ctx) => ensureField(board, fields, request2, "append", ctx);
|
|
23608
|
+
}
|
|
23488
23609
|
var projectFieldEnsure = defineTool({
|
|
23489
23610
|
id: "github_project_field_ensure",
|
|
23490
23611
|
title: "Ensure a GitHub project field",
|
|
23491
23612
|
description: [
|
|
23492
23613
|
"Make sure a field exists on a project board the viewer may update, without ever making a second one: a missing field is created, and the missing options of an existing single select are appended after its own, which keep their id, name, color, description and order, so nothing already set moves.",
|
|
23493
23614
|
"A new single_select field needs options. Status can be extended but not created. A field of that name with another type, options on a field that is not single_select, a repeated option name and a color GitHub does not list are invalid_argument, before anything is written.",
|
|
23615
|
+
"With issue_field: true (organization boards only) the column mirrors the organization's issue field of that name, such as Priority, Start date or Target date; it is added when the board lacks it, and a field of the board's own with that name is invalid_argument.",
|
|
23494
23616
|
BOARD_LOOKUP_NOTE,
|
|
23495
23617
|
PROJECT_SCOPE_NOTE
|
|
23496
23618
|
].join(" "),
|
|
@@ -23500,11 +23622,11 @@ var projectFieldEnsure = defineTool({
|
|
|
23500
23622
|
input: input11,
|
|
23501
23623
|
output: output11,
|
|
23502
23624
|
async execute(args, ctx) {
|
|
23503
|
-
const
|
|
23625
|
+
const ensure = planEnsure(args);
|
|
23504
23626
|
const { project } = await resolveBoard(args, ctx);
|
|
23505
23627
|
assertBoardWritable(project);
|
|
23506
23628
|
const fields = await readBoardFields(project.id, ctx);
|
|
23507
|
-
const { field, created, added_options } = await
|
|
23629
|
+
const { field, created, added_options } = await ensure(project, fields, ctx);
|
|
23508
23630
|
return { project: boardSummary(project), field, created, added_options };
|
|
23509
23631
|
}
|
|
23510
23632
|
});
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "github-tools",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "GitHub tools for coding agents: issues, project boards, pull requests, repositories, releases and Actions.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "craftsamo",
|
package/plugin.yaml
CHANGED
|
@@ -83,9 +83,17 @@ open board of that owner and title, so calling it again is safe.
|
|
|
83
83
|
- `link_repo` links the board to a repository of the **same owner**; GitHub
|
|
84
84
|
links no other. A personal board cannot be linked to an organization's
|
|
85
85
|
repository.
|
|
86
|
+
- `dates: "issue_fields"` (an organization's board only) makes the dates the
|
|
87
|
+
organization's issue fields **Start date** and **Target date** instead of
|
|
88
|
+
the board's own Start and Target: an issue then carries the same dates on
|
|
89
|
+
every board, they are set with `issue_fields` in `github_issue_edit` (the
|
|
90
|
+
board tools refuse them), and a pull request or draft has none. It is
|
|
91
|
+
refused before a board is made when the owner is a user or the organization
|
|
92
|
+
lacks either field. Use it when the user wants dates shared across boards.
|
|
86
93
|
- **Tell the user to set the Roadmap view's date fields.** No API can set
|
|
87
94
|
them, so the roadmap shows no bars until, in the web UI, the view's
|
|
88
|
-
"Date fields" are set to Start and Target
|
|
95
|
+
"Date fields" are set to Start and Target (or Start date and Target date).
|
|
96
|
+
`notes` in the result says so;
|
|
89
97
|
pass it on.
|
|
90
98
|
|
|
91
99
|
`github_project_field_ensure` and `github_project_view_ensure` add one field or
|
|
@@ -94,6 +102,13 @@ view and leave an existing one of that name as it is. Options are written
|
|
|
94
102
|
and sort can only be set when it is created; to change an existing view, the
|
|
95
103
|
user does it in the web UI.
|
|
96
104
|
|
|
105
|
+
On an organization's board, `github_project_field_ensure` with
|
|
106
|
+
`issue_field: true` adds a column that mirrors one of the organization's issue
|
|
107
|
+
fields (Priority, Start date, Target date or any other it defines) by name,
|
|
108
|
+
without `type` or `options`: the options and the values are the organization's
|
|
109
|
+
and the issue's. Views can show, group and sort by such a column like any
|
|
110
|
+
other. A field of the board's own with the same name blocks it.
|
|
111
|
+
|
|
97
112
|
## Items
|
|
98
113
|
|
|
99
114
|
An item is an issue, a pull request or a draft on a board.
|