@takemo101/mikan 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/bin.js +329 -124
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  mikan is a tiny local-first Issue board for AI-assisted development. It stores Issues as Markdown files under `.mikan/` and exposes the same board through a CLI, keyboard-first TUI, stdio MCP server, and polling watcher.
4
4
 
5
+ Manual: <https://takemo101.github.io/mikan/>
6
+
5
7
  ## Install
6
8
 
7
9
  ```sh
@@ -17,7 +19,7 @@ mikan is currently built for Bun-based execution. The npm package installs the `
17
19
 
18
20
  - **Markdown source of truth**: each Issue is a file such as `.mikan/ready/MIK-001.md`.
19
21
  - **Primitive CLI commands**: `init`, `add`, `list`, `show`, `update`, `move`, `append`, `github`, `tui`, `watch`, `mcp`, `skills`.
20
- - **Keyboard TUI**: board-first flow with detail view, Note modal, Move shortcuts, and Archive confirmation.
22
+ - **Keyboard TUI**: board-first flow with detail view, Label/Note/Warning modals, Move shortcuts, and Archive confirmation.
21
23
  - **MCP server**: stdio tools for agents: `get_board`, `list_issues`, `get_issue`, `create_issue`, `update_issue`, `move_issue`, `append_issue`, `mirror_issue_to_github`.
22
24
  - **GitHub Mirror**: explicit one-way publication from local Markdown Issues to GitHub Issues.
23
25
  - **Agent setup**: register the MCP server or install agent guidance for common AI agents.
package/dist/bin.js CHANGED
@@ -100301,8 +100301,12 @@ function updateIssue(options) {
100301
100301
  const target = findIssueById(options);
100302
100302
  if (!target.ok)
100303
100303
  return target;
100304
- const labels = options.labels ?? target.value.issue.labels.map(String);
100305
- const labelsValidation = validateLabels(options.config, labels);
100304
+ const existingLabels = target.value.issue.labels.map(String);
100305
+ const configuredLabelIds = new Set(options.config.labels.map((label) => label.id));
100306
+ const existingUnknownLabels = new Set(existingLabels.filter((label) => !configuredLabelIds.has(label)));
100307
+ const labels = options.labels ?? existingLabels;
100308
+ const labelsToValidate = options.preserveUnknownLabels ? labels.filter((label) => !existingUnknownLabels.has(label)) : labels;
100309
+ const labelsValidation = validateLabels(options.config, labelsToValidate);
100306
100310
  if (!labelsValidation.ok)
100307
100311
  return labelsValidation;
100308
100312
  const dependenciesValidation = options.dependencies ? validateDependencies(options.dependencies) : undefined;
@@ -100798,9 +100802,18 @@ var labelSchema = exports_external.object({
100798
100802
  id: nonEmptyString,
100799
100803
  title: nonEmptyString
100800
100804
  });
100805
+ var hookEntrySchema = exports_external.union([
100806
+ nonEmptyString,
100807
+ exports_external.object({
100808
+ command: nonEmptyString,
100809
+ when: exports_external.object({
100810
+ labels_include: exports_external.array(nonEmptyString).min(1).optional()
100811
+ }).optional()
100812
+ })
100813
+ ]);
100801
100814
  var hookSchema = exports_external.object({
100802
- on_enter: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).optional(),
100803
- on_transition: exports_external.record(exports_external.string(), exports_external.array(exports_external.string())).optional()
100815
+ on_enter: exports_external.record(exports_external.string(), exports_external.array(hookEntrySchema)).optional(),
100816
+ on_transition: exports_external.record(exports_external.string(), exports_external.array(hookEntrySchema)).optional()
100804
100817
  });
100805
100818
  var githubSchema = exports_external.object({
100806
100819
  repo: githubRepoSchema,
@@ -107389,7 +107402,7 @@ var import_react20 = __toESM(require_react(), 1);
107389
107402
  // package.json
107390
107403
  var package_default = {
107391
107404
  name: "@takemo101/mikan",
107392
- version: "0.0.6",
107405
+ version: "0.0.7",
107393
107406
  private: false,
107394
107407
  type: "module",
107395
107408
  bin: {
@@ -107457,9 +107470,9 @@ function footerText(mode) {
107457
107470
  return "Modal | enter confirm | esc cancel | ? keys";
107458
107471
  }
107459
107472
  if (mode === "detail") {
107460
- return "Detail | \u2191\u2193 scroll | g github | esc board | ? keys";
107473
+ return "Detail | \u2191\u2193 scroll | esc board | ? keys";
107461
107474
  }
107462
- return "Board | \u2191\u2193 card | \u2190\u2192 column | enter detail | g github | ? keys";
107475
+ return "Board | \u2191\u2193 card | \u2190\u2192 column | enter detail | ? keys";
107463
107476
  }
107464
107477
 
107465
107478
  // ../tui/src/selection.ts
@@ -107566,6 +107579,7 @@ function buildTuiModel(board, labels = [], githubRepo) {
107566
107579
  })),
107567
107580
  warnings: board.warnings.map(formatWarning),
107568
107581
  ...board.warnings.length > 0 ? { warningDetails: board.warnings.map(formatTuiWarning) } : {},
107582
+ labels: labels.map((label) => ({ id: label.id, title: label.title })),
107569
107583
  labelTitles: Object.fromEntries(labels.map((label) => [label.id, label.title])),
107570
107584
  githubRepo
107571
107585
  };
@@ -107940,7 +107954,7 @@ function moveSelection(model, selection, direction, options = {}) {
107940
107954
  message: undefined
107941
107955
  };
107942
107956
  }
107943
- if (selection.detailOpen && !selection.moveOpen && !selection.noteOpen) {
107957
+ if (selection.detailOpen && !selection.moveOpen && !selection.noteOpen && !selection.labelOpen) {
107944
107958
  if (direction === "up" || direction === "down") {
107945
107959
  return {
107946
107960
  ...selection,
@@ -107961,6 +107975,9 @@ function moveSelection(model, selection, direction, options = {}) {
107961
107975
  if (selection.githubConfirmOpen) {
107962
107976
  return { ...selection, githubConfirmOpen: false };
107963
107977
  }
107978
+ if (selection.labelOpen) {
107979
+ return { ...selection, labelOpen: false };
107980
+ }
107964
107981
  if (selection.warningsOpen) {
107965
107982
  return { ...selection, warningsOpen: false };
107966
107983
  }
@@ -107968,7 +107985,8 @@ function moveSelection(model, selection, direction, options = {}) {
107968
107985
  ...selection,
107969
107986
  detailOpen: false,
107970
107987
  moveOpen: false,
107971
- noteOpen: false
107988
+ noteOpen: false,
107989
+ labelOpen: false
107972
107990
  };
107973
107991
  }
107974
107992
  if (direction === "move") {
@@ -107977,6 +107995,7 @@ function moveSelection(model, selection, direction, options = {}) {
107977
107995
  archiveOpen: false,
107978
107996
  detailOpen: false,
107979
107997
  noteOpen: false,
107998
+ labelOpen: false,
107980
107999
  moveOpen: true,
107981
108000
  moveTargetIndex: 0
107982
108001
  };
@@ -107987,15 +108006,31 @@ function moveSelection(model, selection, direction, options = {}) {
107987
108006
  archiveOpen: false,
107988
108007
  detailOpen: false,
107989
108008
  moveOpen: false,
108009
+ labelOpen: false,
107990
108010
  noteOpen: true
107991
108011
  };
107992
108012
  }
108013
+ if (direction === "edit-labels") {
108014
+ const card = model.columns[selection.columnIndex]?.cards[selection.cardIndex];
108015
+ const knownLabelIds = new Set((model.labels ?? []).map((label) => label.id));
108016
+ return {
108017
+ ...selection,
108018
+ archiveOpen: false,
108019
+ githubConfirmOpen: false,
108020
+ moveOpen: false,
108021
+ noteOpen: false,
108022
+ labelOpen: true,
108023
+ labelFocusIndex: 0,
108024
+ labelDraftIds: card?.labels.filter((label) => knownLabelIds.has(label)) ?? []
108025
+ };
108026
+ }
107993
108027
  if (direction === "archive") {
107994
108028
  return {
107995
108029
  ...selection,
107996
108030
  archiveOpen: true,
107997
108031
  moveOpen: false,
107998
108032
  noteOpen: false,
108033
+ labelOpen: false,
107999
108034
  githubConfirmOpen: false
108000
108035
  };
108001
108036
  }
@@ -108005,6 +108040,7 @@ function moveSelection(model, selection, direction, options = {}) {
108005
108040
  archiveOpen: false,
108006
108041
  moveOpen: false,
108007
108042
  noteOpen: false,
108043
+ labelOpen: false,
108008
108044
  githubConfirmOpen: true
108009
108045
  };
108010
108046
  }
@@ -108036,6 +108072,27 @@ function getAdjacentMoveTarget(model, selection, direction) {
108036
108072
  const column = model.columns[selection.columnIndex + offset];
108037
108073
  return column ? { id: column.id, title: column.title } : undefined;
108038
108074
  }
108075
+ function moveLabelFocus(model, selection, direction) {
108076
+ if (!selection.labelOpen)
108077
+ return selection;
108078
+ return {
108079
+ ...selection,
108080
+ labelFocusIndex: clamp((selection.labelFocusIndex ?? 0) + (direction === "down" ? 1 : -1), 0, Math.max(0, (model.labels ?? []).length - 1))
108081
+ };
108082
+ }
108083
+ function toggleFocusedLabel(model, selection) {
108084
+ if (!selection.labelOpen)
108085
+ return selection;
108086
+ const label = (model.labels ?? [])[selection.labelFocusIndex ?? 0];
108087
+ if (!label)
108088
+ return selection;
108089
+ const current = new Set(selection.labelDraftIds ?? []);
108090
+ if (current.has(label.id))
108091
+ current.delete(label.id);
108092
+ else
108093
+ current.add(label.id);
108094
+ return { ...selection, labelDraftIds: [...current] };
108095
+ }
108039
108096
  function applyNoteInput(selection, keyName2, shift = false) {
108040
108097
  if (!selection.noteOpen || !keyName2)
108041
108098
  return selection;
@@ -108052,7 +108109,7 @@ function applyNoteInput(selection, keyName2, shift = false) {
108052
108109
  return { ...selection, noteDraft: `${selection.noteDraft ?? ""}${value}` };
108053
108110
  }
108054
108111
  function footerMode(selection) {
108055
- if (selection.moveOpen || selection.noteOpen || selection.archiveOpen || selection.githubConfirmOpen) {
108112
+ if (selection.moveOpen || selection.noteOpen || selection.labelOpen || selection.archiveOpen || selection.githubConfirmOpen) {
108056
108113
  return "modal";
108057
108114
  }
108058
108115
  return selection.detailOpen ? "detail" : "board";
@@ -108090,6 +108147,8 @@ function keyToTuiAction(keyName2, shift = false) {
108090
108147
  return "move";
108091
108148
  case "n":
108092
108149
  return "append-note";
108150
+ case "e":
108151
+ return "edit-labels";
108093
108152
  case "a":
108094
108153
  return "archive";
108095
108154
  case "g":
@@ -108140,6 +108199,29 @@ function buildNotePromptViewModel(model, selection) {
108140
108199
  hint: "enter append esc cancel"
108141
108200
  };
108142
108201
  }
108202
+ function buildLabelPromptViewModel(model, selection) {
108203
+ const card = model.columns[selection.columnIndex]?.cards[selection.cardIndex];
108204
+ if (!card)
108205
+ return;
108206
+ const configuredLabels = model.labels ?? [];
108207
+ const draft = new Set(selection.labelDraftIds ?? card.labels);
108208
+ const known = new Set(configuredLabels.map((label) => label.id));
108209
+ return {
108210
+ title: `Edit Labels for ${card.id}`,
108211
+ focused: Boolean(selection.labelOpen),
108212
+ labels: configuredLabels.map((label, index2) => ({
108213
+ id: label.id,
108214
+ title: label.title,
108215
+ checked: draft.has(label.id),
108216
+ focused: index2 === (selection.labelFocusIndex ?? 0)
108217
+ })),
108218
+ unknownLabels: card.labels.filter((label) => !known.has(label)),
108219
+ ...configuredLabels.length === 0 ? {
108220
+ emptyMessage: "No Labels configured. Add Labels in .mikan/config.yaml."
108221
+ } : {},
108222
+ hint: configuredLabels.length === 0 ? "esc close" : "space toggle enter save esc cancel"
108223
+ };
108224
+ }
108143
108225
  function buildArchivePromptViewModel(model, selection) {
108144
108226
  const card = model.columns[selection.columnIndex]?.cards[selection.cardIndex];
108145
108227
  if (!card)
@@ -108166,118 +108248,7 @@ Local Markdown remains the source of truth.`,
108166
108248
  };
108167
108249
  }
108168
108250
 
108169
- // ../tui/src/modals.ts
108170
- function MovePrompt(props) {
108171
- const theme = props.theme ?? buildTuiTheme();
108172
- return import_react3.default.createElement("box", {
108173
- id: "move-modal-backdrop",
108174
- style: modalBackdropStyle(theme)
108175
- }, import_react3.default.createElement("box", {
108176
- id: "move-prompt",
108177
- title: "Move Issue",
108178
- border: true,
108179
- style: modalStyle(theme)
108180
- }, import_react3.default.createElement("text", {
108181
- content: renderMoveInteraction(props.model, props.selection).join(`
108182
- `)
108183
- })));
108184
- }
108185
- function NotePrompt(props) {
108186
- const theme = props.theme ?? buildTuiTheme();
108187
- return import_react3.default.createElement("box", {
108188
- id: "note-modal-backdrop",
108189
- style: modalBackdropStyle(theme)
108190
- }, import_react3.default.createElement("box", {
108191
- id: "note-prompt",
108192
- title: "Append Note",
108193
- border: true,
108194
- style: modalStyle(theme)
108195
- }, import_react3.default.createElement("text", {
108196
- content: renderNoteInteraction(props.model, props.selection).join(`
108197
- `)
108198
- })));
108199
- }
108200
- function ArchivePrompt(props) {
108201
- const theme = props.theme ?? buildTuiTheme();
108202
- return import_react3.default.createElement("box", {
108203
- id: "archive-modal-backdrop",
108204
- style: modalBackdropStyle(theme)
108205
- }, import_react3.default.createElement("box", {
108206
- id: "archive-prompt",
108207
- title: "Archive Issue",
108208
- border: true,
108209
- style: modalStyle(theme)
108210
- }, import_react3.default.createElement("text", {
108211
- content: renderArchiveInteraction(props.model, props.selection).join(`
108212
- `)
108213
- })));
108214
- }
108215
- function GitHubMirrorPrompt(props) {
108216
- const theme = props.theme ?? buildTuiTheme();
108217
- return import_react3.default.createElement("box", {
108218
- id: "github-mirror-modal-backdrop",
108219
- style: modalBackdropStyle(theme)
108220
- }, import_react3.default.createElement("box", {
108221
- id: "github-mirror-prompt",
108222
- title: "GitHub Mirror",
108223
- border: true,
108224
- style: modalStyle(theme)
108225
- }, import_react3.default.createElement("text", {
108226
- content: renderGitHubMirrorInteraction(props.model, props.selection).join(`
108227
- `)
108228
- })));
108229
- }
108230
- function modalBackdropStyle(_theme) {
108231
- return {
108232
- alignItems: "center",
108233
- flexDirection: "column",
108234
- height: "100%",
108235
- justifyContent: "center",
108236
- left: 0,
108237
- position: "absolute",
108238
- top: 0,
108239
- width: "100%",
108240
- zIndex: 10
108241
- };
108242
- }
108243
- function modalStyle(theme) {
108244
- return {
108245
- backgroundColor: theme.base.surface,
108246
- borderColor: theme.interactive.focus,
108247
- flexDirection: "column",
108248
- padding: 1,
108249
- width: "70%"
108250
- };
108251
- }
108252
- function HelpPanel(props) {
108253
- const theme = props.theme ?? buildTuiTheme();
108254
- return import_react3.default.createElement("box", {
108255
- id: "help-panel-backdrop",
108256
- style: modalBackdropStyle(theme)
108257
- }, import_react3.default.createElement("box", {
108258
- id: "help-panel",
108259
- title: "Key help",
108260
- border: true,
108261
- style: modalStyle(theme)
108262
- }, import_react3.default.createElement("text", { content: renderKeyHelp().join(`
108263
- `) })));
108264
- }
108265
- function WarningPanel(props) {
108266
- const theme = props.theme ?? buildTuiTheme();
108267
- return import_react3.default.createElement("box", {
108268
- id: "warning-panel",
108269
- title: "Warning details",
108270
- border: true,
108271
- style: {
108272
- backgroundColor: theme.base.surface,
108273
- borderColor: theme.feedback.warning,
108274
- flexDirection: "column"
108275
- }
108276
- }, import_react3.default.createElement("text", {
108277
- content: props.model.warnings.length > 0 ? props.model.warnings.map((warning) => `! ${warning}`).join(`
108278
- `) : "No warnings"
108279
- }));
108280
- }
108251
+ // ../tui/src/prompt-text.ts
108281
108252
  function renderMoveInteraction(model, selection) {
108282
108253
  const view = buildMovePromptViewModel(model, selection);
108283
108254
  if (!view)
@@ -108299,6 +108270,22 @@ function renderNoteInteraction(model, selection) {
108299
108270
  view.hint
108300
108271
  ];
108301
108272
  }
108273
+ function renderLabelInteraction(model, selection) {
108274
+ const view = buildLabelPromptViewModel(model, selection);
108275
+ if (!view)
108276
+ return ["Edit Labels", "No Issue selected"];
108277
+ if (view.emptyMessage) {
108278
+ return [view.title, "", view.emptyMessage, "", view.hint];
108279
+ }
108280
+ return [
108281
+ view.title,
108282
+ "",
108283
+ ...view.labels.map((label) => `${label.focused ? "\u25B6" : " "} [${label.checked ? "x" : " "}] ${label.title}`),
108284
+ ...view.unknownLabels.length > 0 ? ["", `Unknown Labels (read-only): ${view.unknownLabels.join(", ")}`] : [],
108285
+ "",
108286
+ view.hint
108287
+ ];
108288
+ }
108302
108289
  function renderArchiveInteraction(model, selection) {
108303
108290
  const view = buildArchivePromptViewModel(model, selection);
108304
108291
  if (!view)
@@ -108311,6 +108298,12 @@ function renderGitHubMirrorInteraction(model, selection) {
108311
108298
  return ["GitHub Mirror", "No Issue selected"];
108312
108299
  return [view.title, view.body, view.hint];
108313
108300
  }
108301
+ function renderWarningDetails(model) {
108302
+ return [
108303
+ "Warning details",
108304
+ ...model.warnings.length > 0 ? model.warnings.map((warning) => `! ${warning}`) : ["No warnings"]
108305
+ ];
108306
+ }
108314
108307
  function renderKeyHelp() {
108315
108308
  return [
108316
108309
  "Key help",
@@ -108321,6 +108314,7 @@ function renderKeyHelp() {
108321
108314
  "H/L move Issue",
108322
108315
  "m move menu",
108323
108316
  "n append Note",
108317
+ "e edit Labels",
108324
108318
  "a archive Issue",
108325
108319
  "g GitHub Mirror",
108326
108320
  "w warning details",
@@ -108329,6 +108323,113 @@ function renderKeyHelp() {
108329
108323
  ];
108330
108324
  }
108331
108325
 
108326
+ // ../tui/src/modals.ts
108327
+ function MovePrompt(props) {
108328
+ return renderModalText({
108329
+ theme: props.theme,
108330
+ backdropId: "move-modal-backdrop",
108331
+ panelId: "move-prompt",
108332
+ title: "Move Issue",
108333
+ content: renderMoveInteraction(props.model, props.selection)
108334
+ });
108335
+ }
108336
+ function NotePrompt(props) {
108337
+ return renderModalText({
108338
+ theme: props.theme,
108339
+ backdropId: "note-modal-backdrop",
108340
+ panelId: "note-prompt",
108341
+ title: "Append Note",
108342
+ content: renderNoteInteraction(props.model, props.selection)
108343
+ });
108344
+ }
108345
+ function LabelPrompt(props) {
108346
+ return renderModalText({
108347
+ theme: props.theme,
108348
+ backdropId: "label-modal-backdrop",
108349
+ panelId: "label-prompt",
108350
+ title: "Edit Labels",
108351
+ content: renderLabelInteraction(props.model, props.selection)
108352
+ });
108353
+ }
108354
+ function ArchivePrompt(props) {
108355
+ return renderModalText({
108356
+ theme: props.theme,
108357
+ backdropId: "archive-modal-backdrop",
108358
+ panelId: "archive-prompt",
108359
+ title: "Archive Issue",
108360
+ content: renderArchiveInteraction(props.model, props.selection)
108361
+ });
108362
+ }
108363
+ function GitHubMirrorPrompt(props) {
108364
+ return renderModalText({
108365
+ theme: props.theme,
108366
+ backdropId: "github-mirror-modal-backdrop",
108367
+ panelId: "github-mirror-prompt",
108368
+ title: "GitHub Mirror",
108369
+ content: renderGitHubMirrorInteraction(props.model, props.selection)
108370
+ });
108371
+ }
108372
+ function renderModalText(options) {
108373
+ const theme = options.theme ?? buildTuiTheme();
108374
+ return import_react3.default.createElement("box", {
108375
+ id: options.backdropId,
108376
+ style: modalBackdropStyle(theme)
108377
+ }, import_react3.default.createElement("box", {
108378
+ id: options.panelId,
108379
+ title: options.title,
108380
+ border: true,
108381
+ style: {
108382
+ ...modalStyle(theme),
108383
+ ...options.panelStyle ?? {}
108384
+ }
108385
+ }, import_react3.default.createElement("text", {
108386
+ content: options.content.join(`
108387
+ `)
108388
+ })));
108389
+ }
108390
+ function modalBackdropStyle(_theme) {
108391
+ return {
108392
+ alignItems: "center",
108393
+ flexDirection: "column",
108394
+ height: "100%",
108395
+ justifyContent: "center",
108396
+ left: 0,
108397
+ position: "absolute",
108398
+ top: 0,
108399
+ width: "100%",
108400
+ zIndex: 10
108401
+ };
108402
+ }
108403
+ function modalStyle(theme) {
108404
+ return {
108405
+ backgroundColor: theme.base.surface,
108406
+ borderColor: theme.interactive.focus,
108407
+ flexDirection: "column",
108408
+ padding: 1,
108409
+ width: "70%"
108410
+ };
108411
+ }
108412
+ function HelpPanel(props) {
108413
+ return renderModalText({
108414
+ theme: props.theme,
108415
+ backdropId: "help-panel-backdrop",
108416
+ panelId: "help-panel",
108417
+ title: "Key help",
108418
+ content: renderKeyHelp()
108419
+ });
108420
+ }
108421
+ function WarningPanel(props) {
108422
+ const theme = props.theme ?? buildTuiTheme();
108423
+ return renderModalText({
108424
+ theme,
108425
+ backdropId: "warning-panel-backdrop",
108426
+ panelId: "warning-panel",
108427
+ title: "Warning details",
108428
+ content: renderWarningDetails(props.model).slice(1),
108429
+ panelStyle: { borderColor: theme.feedback.warning }
108430
+ });
108431
+ }
108432
+
108332
108433
  // ../tui/src/mutations.ts
108333
108434
  function refreshTuiModel(options) {
108334
108435
  const selectedCard = options.model.columns[options.selection.columnIndex]?.cards[options.selection.cardIndex];
@@ -108347,6 +108448,9 @@ function refreshTuiModel(options) {
108347
108448
  moveTargetIndex: stillSelected ? options.selection.moveTargetIndex : undefined,
108348
108449
  noteOpen: stillSelected ? options.selection.noteOpen : false,
108349
108450
  noteDraft: stillSelected ? options.selection.noteDraft : undefined,
108451
+ labelOpen: stillSelected ? options.selection.labelOpen : false,
108452
+ labelFocusIndex: stillSelected ? options.selection.labelFocusIndex : undefined,
108453
+ labelDraftIds: stillSelected ? options.selection.labelDraftIds : undefined,
108350
108454
  message: options.selection.message,
108351
108455
  archiveOpen: stillSelected ? options.selection.archiveOpen : false,
108352
108456
  githubConfirmOpen: stillSelected ? options.selection.githubConfirmOpen : false,
@@ -108439,6 +108543,59 @@ function archiveSelectedIssue(options) {
108439
108543
  selection: { ...result.selection, archiveOpen: false }
108440
108544
  } : result;
108441
108545
  }
108546
+ function updateSelectedIssueLabels(options) {
108547
+ const card = selectedCard(options.model, options.selection);
108548
+ if (!card) {
108549
+ return {
108550
+ ok: false,
108551
+ model: options.model,
108552
+ selection: { ...options.selection, labelOpen: false },
108553
+ message: "No Issue selected"
108554
+ };
108555
+ }
108556
+ const loaded = loadProjectConfig(options.cwd ?? process.cwd());
108557
+ if (!loaded.ok) {
108558
+ return {
108559
+ ok: false,
108560
+ model: options.model,
108561
+ selection: { ...options.selection, labelOpen: false },
108562
+ message: loaded.error.message
108563
+ };
108564
+ }
108565
+ const selectedKnown = new Set(options.selection.labelDraftIds ?? []);
108566
+ const configuredIds = loaded.value.config.labels.map((label) => label.id);
108567
+ const configuredSet = new Set(configuredIds);
108568
+ const knownLabels = configuredIds.filter((label) => selectedKnown.has(label));
108569
+ const unknownLabels = card.labels.filter((label) => !configuredSet.has(label));
108570
+ const updated = updateIssue({
108571
+ projectRoot: loaded.value.projectRoot,
108572
+ config: loaded.value.config,
108573
+ id: card.id,
108574
+ labels: [...knownLabels, ...unknownLabels],
108575
+ preserveUnknownLabels: true,
108576
+ now: options.now
108577
+ });
108578
+ if (!updated.ok) {
108579
+ return {
108580
+ ok: false,
108581
+ model: options.model,
108582
+ selection: { ...options.selection, labelOpen: false },
108583
+ message: updated.error.message
108584
+ };
108585
+ }
108586
+ const model = loadTuiModel(options.cwd);
108587
+ const selection = findSelectionByCardId(model, card.id) ?? clampSelection(model, options.selection);
108588
+ return {
108589
+ ok: true,
108590
+ model,
108591
+ selection: {
108592
+ ...selection,
108593
+ detailOpen: options.selection.detailOpen,
108594
+ labelOpen: false
108595
+ },
108596
+ message: `${card.id} Labels updated`
108597
+ };
108598
+ }
108442
108599
  async function beginSelectedIssueGitHubMirror(options) {
108443
108600
  if (options.selection.githubBusy)
108444
108601
  return githubAlreadyRunning(options);
@@ -108698,7 +108855,7 @@ function TuiAppView({
108698
108855
  viewportHeight,
108699
108856
  viewportWidth,
108700
108857
  columns
108701
- })), selection.moveOpen ? import_react20.default.createElement(MovePrompt, { model, selection, theme }) : undefined, selection.noteOpen ? import_react20.default.createElement(NotePrompt, { model, selection, theme }) : undefined, selection.archiveOpen ? import_react20.default.createElement(ArchivePrompt, { model, selection, theme }) : undefined, selection.githubConfirmOpen ? import_react20.default.createElement(GitHubMirrorPrompt, { model, selection, theme }) : undefined, selection.warningsOpen ? import_react20.default.createElement(WarningPanel, { model, theme }) : undefined, selection.helpOpen ? import_react20.default.createElement(HelpPanel, { theme }) : undefined, import_react20.default.createElement(Footer, {
108858
+ })), selection.moveOpen ? import_react20.default.createElement(MovePrompt, { model, selection, theme }) : undefined, selection.noteOpen ? import_react20.default.createElement(NotePrompt, { model, selection, theme }) : undefined, selection.labelOpen ? import_react20.default.createElement(LabelPrompt, { model, selection, theme }) : undefined, selection.archiveOpen ? import_react20.default.createElement(ArchivePrompt, { model, selection, theme }) : undefined, selection.githubConfirmOpen ? import_react20.default.createElement(GitHubMirrorPrompt, { model, selection, theme }) : undefined, selection.warningsOpen ? import_react20.default.createElement(WarningPanel, { model, theme }) : undefined, selection.helpOpen ? import_react20.default.createElement(HelpPanel, { theme }) : undefined, import_react20.default.createElement(Footer, {
108702
108859
  message: selection.message,
108703
108860
  mode: footerMode(selection),
108704
108861
  theme
@@ -108831,6 +108988,35 @@ async function launchTui(options = {}) {
108831
108988
  }
108832
108989
  return;
108833
108990
  }
108991
+ if (selection.labelOpen) {
108992
+ if (action === "help") {
108993
+ setSelection((current) => moveSelection(model, current, action));
108994
+ return;
108995
+ }
108996
+ if (action === "escape") {
108997
+ setSelection((current) => moveSelection(model, current, action));
108998
+ return;
108999
+ }
109000
+ if (action === "up" || action === "down") {
109001
+ setSelection((current) => moveLabelFocus(model, current, action));
109002
+ return;
109003
+ }
109004
+ if (key.name === "space") {
109005
+ setSelection((current) => toggleFocusedLabel(model, current));
109006
+ return;
109007
+ }
109008
+ if (action === "enter") {
109009
+ const result = updateSelectedIssueLabels({
109010
+ cwd: options.cwd,
109011
+ model,
109012
+ selection
109013
+ });
109014
+ setModel(result.model);
109015
+ setSelection({ ...result.selection, message: result.message });
109016
+ return;
109017
+ }
109018
+ return;
109019
+ }
108834
109020
  if (!action)
108835
109021
  return;
108836
109022
  if (action === "quit") {
@@ -109191,7 +109377,10 @@ function fireHooks(loaded, issue2, fromStatus, toStatus, options) {
109191
109377
  ...hooks?.on_enter?.[toStatus] ?? [],
109192
109378
  ...hooks?.on_transition?.[`${fromStatus}->${toStatus}`] ?? []
109193
109379
  ];
109194
- for (const command of commands2) {
109380
+ for (const entry of commands2) {
109381
+ const command = hookCommandForIssue(entry, loaded, issue2, options);
109382
+ if (!command)
109383
+ continue;
109195
109384
  const rendered = renderHookCommand(command, {
109196
109385
  project_root: loaded.projectRoot,
109197
109386
  issue_path: issue2.path,
@@ -109217,6 +109406,22 @@ function fireHooks(loaded, issue2, fromStatus, toStatus, options) {
109217
109406
  }
109218
109407
  }
109219
109408
  }
109409
+ function hookCommandForIssue(entry, loaded, issue2, options) {
109410
+ if (typeof entry === "string")
109411
+ return entry;
109412
+ const labelsInclude = entry.when?.labels_include;
109413
+ if (!labelsInclude)
109414
+ return entry.command;
109415
+ const knownLabels = new Set(loaded.config.labels.map((label) => label.id));
109416
+ for (const label of labelsInclude) {
109417
+ if (!knownLabels.has(label)) {
109418
+ emitError(options, `hook skipped: ${String(issue2.issue.id)} references unknown label in hook filter: ${label}`);
109419
+ return;
109420
+ }
109421
+ }
109422
+ const issueLabels = new Set(issue2.issue.labels.map(String));
109423
+ return labelsInclude.every((label) => issueLabels.has(label)) ? entry.command : undefined;
109424
+ }
109220
109425
  function recordGitHubMirrorPushFailure(projectRoot, issueId, message, options) {
109221
109426
  appendHookFailure(projectRoot, {
109222
109427
  timestamp: utcNow3(options.now),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takemo101/mikan",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {