@takemo101/mikan 0.0.5 → 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 +354 -138
  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,
@@ -107386,27 +107399,38 @@ function coreError(code, message) {
107386
107399
 
107387
107400
  // ../tui/src/index.ts
107388
107401
  var import_react20 = __toESM(require_react(), 1);
107389
- // ../tui/package.json
107402
+ // package.json
107390
107403
  var package_default = {
107391
- name: "@mikan/tui",
107392
- version: "0.0.3",
107393
- private: true,
107404
+ name: "@takemo101/mikan",
107405
+ version: "0.0.7",
107406
+ private: false,
107394
107407
  type: "module",
107395
- exports: {
107396
- ".": "./src/index.ts"
107408
+ bin: {
107409
+ mikan: "dist/bin.js"
107410
+ },
107411
+ repository: {
107412
+ type: "git",
107413
+ url: "https://github.com/takemo101/mikan"
107414
+ },
107415
+ files: [
107416
+ "dist"
107417
+ ],
107418
+ publishConfig: {
107419
+ access: "public"
107420
+ },
107421
+ optionalDependencies: {
107422
+ "@opentui/core-darwin-arm64": "0.3.0",
107423
+ "@opentui/core-darwin-x64": "0.3.0",
107424
+ "@opentui/core-linux-arm64": "0.3.0",
107425
+ "@opentui/core-linux-x64": "0.3.0",
107426
+ "@opentui/core-win32-arm64": "0.3.0",
107427
+ "@opentui/core-win32-x64": "0.3.0"
107397
107428
  },
107398
107429
  scripts: {
107399
- build: "tsc -p ../../tsconfig.json --noEmit",
107430
+ build: "bun run build:dist && tsc -p ../../tsconfig.json --noEmit",
107431
+ "build:dist": "bun build ./src/bin.ts --target=bun --outdir=./dist --entry-naming=bin.js",
107400
107432
  typecheck: "tsc -p ../../tsconfig.json --noEmit",
107401
107433
  test: "bun test"
107402
- },
107403
- dependencies: {
107404
- "@mikan/core": "workspace:*",
107405
- "@mikan/github": "workspace:*",
107406
- "@mikan/project-config": "workspace:*",
107407
- "@opentui/core": "latest",
107408
- "@opentui/react": "latest",
107409
- react: "latest"
107410
107434
  }
107411
107435
  };
107412
107436
 
@@ -107446,9 +107470,9 @@ function footerText(mode) {
107446
107470
  return "Modal | enter confirm | esc cancel | ? keys";
107447
107471
  }
107448
107472
  if (mode === "detail") {
107449
- return "Detail | \u2191\u2193 scroll | g github | esc board | ? keys";
107473
+ return "Detail | \u2191\u2193 scroll | esc board | ? keys";
107450
107474
  }
107451
- 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";
107452
107476
  }
107453
107477
 
107454
107478
  // ../tui/src/selection.ts
@@ -107555,6 +107579,7 @@ function buildTuiModel(board, labels = [], githubRepo) {
107555
107579
  })),
107556
107580
  warnings: board.warnings.map(formatWarning),
107557
107581
  ...board.warnings.length > 0 ? { warningDetails: board.warnings.map(formatTuiWarning) } : {},
107582
+ labels: labels.map((label) => ({ id: label.id, title: label.title })),
107558
107583
  labelTitles: Object.fromEntries(labels.map((label) => [label.id, label.title])),
107559
107584
  githubRepo
107560
107585
  };
@@ -107929,7 +107954,7 @@ function moveSelection(model, selection, direction, options = {}) {
107929
107954
  message: undefined
107930
107955
  };
107931
107956
  }
107932
- if (selection.detailOpen && !selection.moveOpen && !selection.noteOpen) {
107957
+ if (selection.detailOpen && !selection.moveOpen && !selection.noteOpen && !selection.labelOpen) {
107933
107958
  if (direction === "up" || direction === "down") {
107934
107959
  return {
107935
107960
  ...selection,
@@ -107950,6 +107975,9 @@ function moveSelection(model, selection, direction, options = {}) {
107950
107975
  if (selection.githubConfirmOpen) {
107951
107976
  return { ...selection, githubConfirmOpen: false };
107952
107977
  }
107978
+ if (selection.labelOpen) {
107979
+ return { ...selection, labelOpen: false };
107980
+ }
107953
107981
  if (selection.warningsOpen) {
107954
107982
  return { ...selection, warningsOpen: false };
107955
107983
  }
@@ -107957,7 +107985,8 @@ function moveSelection(model, selection, direction, options = {}) {
107957
107985
  ...selection,
107958
107986
  detailOpen: false,
107959
107987
  moveOpen: false,
107960
- noteOpen: false
107988
+ noteOpen: false,
107989
+ labelOpen: false
107961
107990
  };
107962
107991
  }
107963
107992
  if (direction === "move") {
@@ -107966,6 +107995,7 @@ function moveSelection(model, selection, direction, options = {}) {
107966
107995
  archiveOpen: false,
107967
107996
  detailOpen: false,
107968
107997
  noteOpen: false,
107998
+ labelOpen: false,
107969
107999
  moveOpen: true,
107970
108000
  moveTargetIndex: 0
107971
108001
  };
@@ -107976,15 +108006,31 @@ function moveSelection(model, selection, direction, options = {}) {
107976
108006
  archiveOpen: false,
107977
108007
  detailOpen: false,
107978
108008
  moveOpen: false,
108009
+ labelOpen: false,
107979
108010
  noteOpen: true
107980
108011
  };
107981
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
+ }
107982
108027
  if (direction === "archive") {
107983
108028
  return {
107984
108029
  ...selection,
107985
108030
  archiveOpen: true,
107986
108031
  moveOpen: false,
107987
108032
  noteOpen: false,
108033
+ labelOpen: false,
107988
108034
  githubConfirmOpen: false
107989
108035
  };
107990
108036
  }
@@ -107994,6 +108040,7 @@ function moveSelection(model, selection, direction, options = {}) {
107994
108040
  archiveOpen: false,
107995
108041
  moveOpen: false,
107996
108042
  noteOpen: false,
108043
+ labelOpen: false,
107997
108044
  githubConfirmOpen: true
107998
108045
  };
107999
108046
  }
@@ -108025,6 +108072,27 @@ function getAdjacentMoveTarget(model, selection, direction) {
108025
108072
  const column = model.columns[selection.columnIndex + offset];
108026
108073
  return column ? { id: column.id, title: column.title } : undefined;
108027
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
+ }
108028
108096
  function applyNoteInput(selection, keyName2, shift = false) {
108029
108097
  if (!selection.noteOpen || !keyName2)
108030
108098
  return selection;
@@ -108041,7 +108109,7 @@ function applyNoteInput(selection, keyName2, shift = false) {
108041
108109
  return { ...selection, noteDraft: `${selection.noteDraft ?? ""}${value}` };
108042
108110
  }
108043
108111
  function footerMode(selection) {
108044
- if (selection.moveOpen || selection.noteOpen || selection.archiveOpen || selection.githubConfirmOpen) {
108112
+ if (selection.moveOpen || selection.noteOpen || selection.labelOpen || selection.archiveOpen || selection.githubConfirmOpen) {
108045
108113
  return "modal";
108046
108114
  }
108047
108115
  return selection.detailOpen ? "detail" : "board";
@@ -108079,6 +108147,8 @@ function keyToTuiAction(keyName2, shift = false) {
108079
108147
  return "move";
108080
108148
  case "n":
108081
108149
  return "append-note";
108150
+ case "e":
108151
+ return "edit-labels";
108082
108152
  case "a":
108083
108153
  return "archive";
108084
108154
  case "g":
@@ -108129,6 +108199,29 @@ function buildNotePromptViewModel(model, selection) {
108129
108199
  hint: "enter append esc cancel"
108130
108200
  };
108131
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
+ }
108132
108225
  function buildArchivePromptViewModel(model, selection) {
108133
108226
  const card = model.columns[selection.columnIndex]?.cards[selection.cardIndex];
108134
108227
  if (!card)
@@ -108155,118 +108248,7 @@ Local Markdown remains the source of truth.`,
108155
108248
  };
108156
108249
  }
108157
108250
 
108158
- // ../tui/src/modals.ts
108159
- function MovePrompt(props) {
108160
- const theme = props.theme ?? buildTuiTheme();
108161
- return import_react3.default.createElement("box", {
108162
- id: "move-modal-backdrop",
108163
- style: modalBackdropStyle(theme)
108164
- }, import_react3.default.createElement("box", {
108165
- id: "move-prompt",
108166
- title: "Move Issue",
108167
- border: true,
108168
- style: modalStyle(theme)
108169
- }, import_react3.default.createElement("text", {
108170
- content: renderMoveInteraction(props.model, props.selection).join(`
108171
- `)
108172
- })));
108173
- }
108174
- function NotePrompt(props) {
108175
- const theme = props.theme ?? buildTuiTheme();
108176
- return import_react3.default.createElement("box", {
108177
- id: "note-modal-backdrop",
108178
- style: modalBackdropStyle(theme)
108179
- }, import_react3.default.createElement("box", {
108180
- id: "note-prompt",
108181
- title: "Append Note",
108182
- border: true,
108183
- style: modalStyle(theme)
108184
- }, import_react3.default.createElement("text", {
108185
- content: renderNoteInteraction(props.model, props.selection).join(`
108186
- `)
108187
- })));
108188
- }
108189
- function ArchivePrompt(props) {
108190
- const theme = props.theme ?? buildTuiTheme();
108191
- return import_react3.default.createElement("box", {
108192
- id: "archive-modal-backdrop",
108193
- style: modalBackdropStyle(theme)
108194
- }, import_react3.default.createElement("box", {
108195
- id: "archive-prompt",
108196
- title: "Archive Issue",
108197
- border: true,
108198
- style: modalStyle(theme)
108199
- }, import_react3.default.createElement("text", {
108200
- content: renderArchiveInteraction(props.model, props.selection).join(`
108201
- `)
108202
- })));
108203
- }
108204
- function GitHubMirrorPrompt(props) {
108205
- const theme = props.theme ?? buildTuiTheme();
108206
- return import_react3.default.createElement("box", {
108207
- id: "github-mirror-modal-backdrop",
108208
- style: modalBackdropStyle(theme)
108209
- }, import_react3.default.createElement("box", {
108210
- id: "github-mirror-prompt",
108211
- title: "GitHub Mirror",
108212
- border: true,
108213
- style: modalStyle(theme)
108214
- }, import_react3.default.createElement("text", {
108215
- content: renderGitHubMirrorInteraction(props.model, props.selection).join(`
108216
- `)
108217
- })));
108218
- }
108219
- function modalBackdropStyle(_theme) {
108220
- return {
108221
- alignItems: "center",
108222
- flexDirection: "column",
108223
- height: "100%",
108224
- justifyContent: "center",
108225
- left: 0,
108226
- position: "absolute",
108227
- top: 0,
108228
- width: "100%",
108229
- zIndex: 10
108230
- };
108231
- }
108232
- function modalStyle(theme) {
108233
- return {
108234
- backgroundColor: theme.base.surface,
108235
- borderColor: theme.interactive.focus,
108236
- flexDirection: "column",
108237
- padding: 1,
108238
- width: "70%"
108239
- };
108240
- }
108241
- function HelpPanel(props) {
108242
- const theme = props.theme ?? buildTuiTheme();
108243
- return import_react3.default.createElement("box", {
108244
- id: "help-panel-backdrop",
108245
- style: modalBackdropStyle(theme)
108246
- }, import_react3.default.createElement("box", {
108247
- id: "help-panel",
108248
- title: "Key help",
108249
- border: true,
108250
- style: modalStyle(theme)
108251
- }, import_react3.default.createElement("text", { content: renderKeyHelp().join(`
108252
- `) })));
108253
- }
108254
- function WarningPanel(props) {
108255
- const theme = props.theme ?? buildTuiTheme();
108256
- return import_react3.default.createElement("box", {
108257
- id: "warning-panel",
108258
- title: "Warning details",
108259
- border: true,
108260
- style: {
108261
- backgroundColor: theme.base.surface,
108262
- borderColor: theme.feedback.warning,
108263
- flexDirection: "column"
108264
- }
108265
- }, import_react3.default.createElement("text", {
108266
- content: props.model.warnings.length > 0 ? props.model.warnings.map((warning) => `! ${warning}`).join(`
108267
- `) : "No warnings"
108268
- }));
108269
- }
108251
+ // ../tui/src/prompt-text.ts
108270
108252
  function renderMoveInteraction(model, selection) {
108271
108253
  const view = buildMovePromptViewModel(model, selection);
108272
108254
  if (!view)
@@ -108288,6 +108270,22 @@ function renderNoteInteraction(model, selection) {
108288
108270
  view.hint
108289
108271
  ];
108290
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
+ }
108291
108289
  function renderArchiveInteraction(model, selection) {
108292
108290
  const view = buildArchivePromptViewModel(model, selection);
108293
108291
  if (!view)
@@ -108300,6 +108298,12 @@ function renderGitHubMirrorInteraction(model, selection) {
108300
108298
  return ["GitHub Mirror", "No Issue selected"];
108301
108299
  return [view.title, view.body, view.hint];
108302
108300
  }
108301
+ function renderWarningDetails(model) {
108302
+ return [
108303
+ "Warning details",
108304
+ ...model.warnings.length > 0 ? model.warnings.map((warning) => `! ${warning}`) : ["No warnings"]
108305
+ ];
108306
+ }
108303
108307
  function renderKeyHelp() {
108304
108308
  return [
108305
108309
  "Key help",
@@ -108310,6 +108314,7 @@ function renderKeyHelp() {
108310
108314
  "H/L move Issue",
108311
108315
  "m move menu",
108312
108316
  "n append Note",
108317
+ "e edit Labels",
108313
108318
  "a archive Issue",
108314
108319
  "g GitHub Mirror",
108315
108320
  "w warning details",
@@ -108318,6 +108323,113 @@ function renderKeyHelp() {
108318
108323
  ];
108319
108324
  }
108320
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
+
108321
108433
  // ../tui/src/mutations.ts
108322
108434
  function refreshTuiModel(options) {
108323
108435
  const selectedCard = options.model.columns[options.selection.columnIndex]?.cards[options.selection.cardIndex];
@@ -108336,6 +108448,9 @@ function refreshTuiModel(options) {
108336
108448
  moveTargetIndex: stillSelected ? options.selection.moveTargetIndex : undefined,
108337
108449
  noteOpen: stillSelected ? options.selection.noteOpen : false,
108338
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,
108339
108454
  message: options.selection.message,
108340
108455
  archiveOpen: stillSelected ? options.selection.archiveOpen : false,
108341
108456
  githubConfirmOpen: stillSelected ? options.selection.githubConfirmOpen : false,
@@ -108428,6 +108543,59 @@ function archiveSelectedIssue(options) {
108428
108543
  selection: { ...result.selection, archiveOpen: false }
108429
108544
  } : result;
108430
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
+ }
108431
108599
  async function beginSelectedIssueGitHubMirror(options) {
108432
108600
  if (options.selection.githubBusy)
108433
108601
  return githubAlreadyRunning(options);
@@ -108687,7 +108855,7 @@ function TuiAppView({
108687
108855
  viewportHeight,
108688
108856
  viewportWidth,
108689
108857
  columns
108690
- })), 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, {
108691
108859
  message: selection.message,
108692
108860
  mode: footerMode(selection),
108693
108861
  theme
@@ -108820,6 +108988,35 @@ async function launchTui(options = {}) {
108820
108988
  }
108821
108989
  return;
108822
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
+ }
108823
109020
  if (!action)
108824
109021
  return;
108825
109022
  if (action === "quit") {
@@ -109180,7 +109377,10 @@ function fireHooks(loaded, issue2, fromStatus, toStatus, options) {
109180
109377
  ...hooks?.on_enter?.[toStatus] ?? [],
109181
109378
  ...hooks?.on_transition?.[`${fromStatus}->${toStatus}`] ?? []
109182
109379
  ];
109183
- for (const command of commands2) {
109380
+ for (const entry of commands2) {
109381
+ const command = hookCommandForIssue(entry, loaded, issue2, options);
109382
+ if (!command)
109383
+ continue;
109184
109384
  const rendered = renderHookCommand(command, {
109185
109385
  project_root: loaded.projectRoot,
109186
109386
  issue_path: issue2.path,
@@ -109206,6 +109406,22 @@ function fireHooks(loaded, issue2, fromStatus, toStatus, options) {
109206
109406
  }
109207
109407
  }
109208
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
+ }
109209
109425
  function recordGitHubMirrorPushFailure(projectRoot, issueId, message, options) {
109210
109426
  appendHookFailure(projectRoot, {
109211
109427
  timestamp: utcNow3(options.now),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takemo101/mikan",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {