epiq 0.0.3 → 0.0.5

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 (55) hide show
  1. package/dist/app.js +4 -5
  2. package/dist/board/components/Board.js +2 -2
  3. package/dist/board/components/CommandLine.js +11 -4
  4. package/dist/board/components/ContextBar.js +12 -3
  5. package/dist/board/components/Help.js +4 -4
  6. package/dist/board/components/Swimlane.d.ts +1 -0
  7. package/dist/board/components/Swimlane.js +3 -4
  8. package/dist/board/components/Swimlanes.js +8 -11
  9. package/dist/board/components/TicketListItem.js +3 -3
  10. package/dist/board/components/TicketUI.js +1 -1
  11. package/dist/board/hints/hints.d.ts +4 -4
  12. package/dist/board/hints/hints.js +7 -15
  13. package/dist/board/mock/board.js +500 -523
  14. package/dist/cli.d.ts +1 -1
  15. package/dist/cli.js +20 -61
  16. package/dist/debug-logger.d.ts +1 -1
  17. package/dist/debug-logger.js +5 -4
  18. package/dist/init-project.d.ts +1 -0
  19. package/dist/init-project.js +1 -0
  20. package/dist/navigation/actions/add-item/add-item-actions.js +21 -11
  21. package/dist/navigation/actions/board-action-map.js +3 -3
  22. package/dist/navigation/actions/default/default-action-utils.d.ts +8 -7
  23. package/dist/navigation/actions/default/default-action-utils.js +67 -17
  24. package/dist/navigation/actions/default/default-actions.js +30 -44
  25. package/dist/navigation/actions/default/navigation-action-utils.d.ts +16 -0
  26. package/dist/navigation/actions/default/navigation-action-utils.js +109 -0
  27. package/dist/navigation/actions/input/command-line-input.js +4 -4
  28. package/dist/navigation/actions/input/input-actions.js +13 -12
  29. package/dist/navigation/actions/move/move-actions-utils.d.ts +4 -5
  30. package/dist/navigation/actions/move/move-actions-utils.js +44 -31
  31. package/dist/navigation/actions/move/move-actions.js +17 -11
  32. package/dist/navigation/command-line/command-line-sequence-actions.d.ts +1 -1
  33. package/dist/navigation/command-line/command-line-sequence-actions.js +24 -11
  34. package/dist/navigation/command-line/command-line-sequence-intent.d.ts +3 -2
  35. package/dist/navigation/command-line/command-line-sequence-intent.js +13 -10
  36. package/dist/navigation/command-line/commands.d.ts +2 -0
  37. package/dist/navigation/command-line/commands.js +58 -0
  38. package/dist/navigation/keypress-listener.d.ts +1 -0
  39. package/dist/navigation/keypress-listener.js +33 -0
  40. package/dist/navigation/model/action-map.model.d.ts +4 -4
  41. package/dist/navigation/model/navigation-ctx.model.d.ts +1 -16
  42. package/dist/navigation/model/navigation-ctx.model.js +0 -1
  43. package/dist/navigation/model/navigation-tree.model.d.ts +2 -1
  44. package/dist/navigation/navigation.d.ts +3 -10
  45. package/dist/navigation/navigation.js +73 -79
  46. package/dist/navigation/state/command-line.state.d.ts +5 -0
  47. package/dist/navigation/state/command-line.state.js +21 -4
  48. package/dist/navigation/state/state.d.ts +17 -9
  49. package/dist/navigation/state/state.js +34 -35
  50. package/dist/navigation/utils/get-command-line-intent.d.ts +2 -2
  51. package/dist/navigation/utils/get-command-line-intent.js +7 -7
  52. package/dist/navigation/utils/key-intent.d.ts +5 -5
  53. package/dist/navigation/utils/key-intent.js +68 -74
  54. package/package.json +2 -2
  55. package/readme.md +54 -17
@@ -1,11 +1,23 @@
1
+ export const commandDelimiter = ' ';
1
2
  export let commandLineState = {
2
3
  commandHistory: [],
3
4
  value: '',
4
5
  commandHistoryIndex: -1,
5
6
  };
7
+ const listeners = new Set();
8
+ const notify = () => {
9
+ for (const l of listeners)
10
+ l();
11
+ };
12
+ export const subscribeCommandLineState = (listener) => {
13
+ listeners.add(listener);
14
+ return () => listeners.delete(listener);
15
+ };
16
+ // ===== Updates =====
6
17
  export const updateCommandLineState = (cb) => {
7
- bug(commandLineState);
8
- commandLineState = cb(structuredClone(commandLineState));
18
+ const next = cb(structuredClone(commandLineState));
19
+ commandLineState = next;
20
+ notify();
9
21
  };
10
22
  export const updateCommandLineInput = (cb) => {
11
23
  updateCommandLineState(state => ({
@@ -31,13 +43,18 @@ export const getPrevCommand = () => {
31
43
  };
32
44
  export const getNextCommand = () => {
33
45
  updateCommandLineState(s => {
34
- const nextIndex = Math.min(s.commandHistoryIndex - 1, s.commandHistory.length - 1);
46
+ // typically you want to clamp down to -1 (meaning "no selection")
47
+ const nextIndex = Math.max(s.commandHistoryIndex - 1, -1);
35
48
  return {
36
49
  ...s,
37
50
  commandHistoryIndex: nextIndex,
38
- value: s.commandHistory[nextIndex] ?? '',
51
+ value: nextIndex === -1 ? '' : s.commandHistory[nextIndex] ?? '',
39
52
  };
40
53
  });
41
54
  };
42
55
  export const clearCommandLine = () => updateCommandLineInput(() => '');
43
56
  export const getCommandLineInput = () => commandLineState.value;
57
+ export const getCommandLineArgumentValue = () => {
58
+ const [_, ...rest] = commandLineState.value.split(commandDelimiter);
59
+ return rest.join(commandDelimiter);
60
+ };
@@ -1,12 +1,20 @@
1
+ import { Board } from '../../board/model/board.model.js';
1
2
  import { ActionEntry, ModeUnion } from '../model/action-map.model.js';
2
3
  import { NavigationTree } from '../model/navigation-tree.model.js';
3
- export declare let navigationState: {
4
- mode: ModeUnion;
5
- availableActions: ActionEntry[];
6
- availableHints: readonly string[];
7
- currentNode: NavigationTree<NavigationTree> | null;
8
- breadCrumb: NavigationTree<NavigationTree>[];
4
+ export type AppState = {
5
+ readonly selectedIndex: number;
6
+ readonly mode: ModeUnion;
7
+ readonly availableActions: ActionEntry[];
8
+ readonly availableHints: readonly string[];
9
+ readonly currentNode: NavigationTree<NavigationTree>;
10
+ readonly breadCrumb: NavigationTree<NavigationTree>[];
11
+ readonly rootNode: Board;
9
12
  };
10
- export declare const updateState: (cb: (oldState: typeof navigationState) => typeof navigationState) => void;
11
- export declare const patchState: (patch: Partial<typeof navigationState>) => void;
12
- export declare const flashHint: (hints: string[]) => Promise<void>;
13
+ export declare let appState: AppState;
14
+ export declare const initAppState: (board: Board) => void;
15
+ export declare const updateState: (cb: (oldState: typeof appState) => typeof appState, opts?: {
16
+ render?: boolean;
17
+ }) => void;
18
+ export declare const patchState: (patch: Partial<typeof appState>, opts?: {
19
+ render?: boolean;
20
+ }) => void;
@@ -1,39 +1,38 @@
1
1
  import { Hints } from '../../board/hints/hints.js';
2
- import { triggerRender } from '../../cli.js';
3
- export let navigationState = {
4
- mode: 'default',
5
- availableActions: [],
6
- availableHints: [],
7
- currentNode: null,
8
- breadCrumb: [],
2
+ import { renderBoard } from '../../cli.js';
3
+ import { ContextualActionMap } from '../actions/board-action-map.js';
4
+ import { DefaultActions } from '../actions/default/default-actions.js';
5
+ import { inputActions } from '../actions/input/input-actions.js';
6
+ export let appState;
7
+ const derived = (state) => {
8
+ const { currentNode, mode } = state;
9
+ const availableHints = Hints[currentNode.actionContext + mode] ?? Hints[currentNode.actionContext];
10
+ const actionContext = currentNode?.actionContext;
11
+ const availableActions = [
12
+ ...DefaultActions,
13
+ ...ContextualActionMap[actionContext],
14
+ ...inputActions,
15
+ ];
16
+ return {
17
+ ...state,
18
+ availableHints,
19
+ availableActions,
20
+ };
9
21
  };
10
- export const updateState = (cb) => {
11
- navigationState = cb(navigationState);
12
- };
13
- export const patchState = (patch) => updateState(oldState => ({
14
- ...oldState,
15
- ...patch,
16
- }));
17
- // Utilities for updating specific parts of the state
18
- export const flashHint = async (hints) => {
19
- await new Promise(resolve => {
20
- setTimeout(() => {
21
- updateState(state => {
22
- const { currentNode, mode } = state;
23
- const contextualHints = (currentNode &&
24
- Hints[currentNode.actionContext + mode]) ??
25
- (currentNode && Hints[currentNode.actionContext]) ?? [''];
26
- return {
27
- ...state,
28
- availableHints: contextualHints,
29
- };
30
- });
31
- triggerRender();
32
- resolve();
33
- }, 3000);
34
- patchState({
35
- availableHints: hints,
36
- });
37
- triggerRender();
22
+ export const initAppState = (board) => {
23
+ appState = derived({
24
+ ...appState,
25
+ rootNode: board,
26
+ breadCrumb: [board],
27
+ selectedIndex: 0,
28
+ mode: 'default',
29
+ currentNode: board,
38
30
  });
31
+ renderBoard();
32
+ };
33
+ export const updateState = (cb, opts = { render: true }) => {
34
+ appState = derived(cb(appState));
35
+ if (opts.render)
36
+ renderBoard();
39
37
  };
38
+ export const patchState = (patch, opts = { render: true }) => updateState(old => ({ ...old, ...patch }), opts);
@@ -1,3 +1,3 @@
1
- import { KeyIntent } from './key-intent.js';
1
+ import { Intent } from './key-intent.js';
2
2
  import readline from 'readline';
3
- export declare const getCommandLineIntent: (key: readline.Key) => KeyIntent.Confirm | KeyIntent.ToggleCommandLine | KeyIntent.CaptureInput | KeyIntent.EraseInput | KeyIntent.GetLastCommandFromHistory | KeyIntent.GetNextCommandFromHistory;
3
+ export declare const getCommandLineIntent: (key: readline.Key) => Intent.Confirm | Intent.ToggleCommandLine | Intent.CaptureInput | Intent.EraseInput | Intent.GetLastCommandFromHistory | Intent.GetNextCommandFromHistory;
@@ -1,17 +1,17 @@
1
- import { KeyIntent } from './key-intent.js';
1
+ import { Intent } from './key-intent.js';
2
2
  export const getCommandLineIntent = (key) => {
3
3
  switch (key.name) {
4
4
  case 'up':
5
- return KeyIntent.GetLastCommandFromHistory;
5
+ return Intent.GetLastCommandFromHistory;
6
6
  case 'down':
7
- return KeyIntent.GetNextCommandFromHistory;
7
+ return Intent.GetNextCommandFromHistory;
8
8
  case 'return':
9
- return KeyIntent.Confirm;
9
+ return Intent.Confirm;
10
10
  case 'backspace':
11
- return KeyIntent.EraseInput;
11
+ return Intent.EraseInput;
12
12
  case 'escape':
13
- return KeyIntent.ToggleCommandLine;
13
+ return Intent.ToggleCommandLine;
14
14
  default:
15
- return KeyIntent.CaptureInput;
15
+ return Intent.CaptureInput;
16
16
  }
17
17
  };
@@ -1,7 +1,6 @@
1
1
  import readline from 'readline';
2
2
  import { ModeUnion } from '../model/action-map.model.js';
3
- import { NavigateCtx } from '../model/navigation-ctx.model.js';
4
- export declare enum KeyIntent {
3
+ export declare enum Intent {
5
4
  NavPreviousItem = "navPreviousItem",
6
5
  NavNextItem = "navNextItem",
7
6
  NavToPreviousContainer = "navToPreviousContainer",
@@ -13,8 +12,9 @@ export declare enum KeyIntent {
13
12
  Confirm = "confirm",
14
13
  Edit = "edit",
15
14
  Exit = "exit",
16
- ToggleHelp = "toggleHelp",
17
- ToggleMove = "toggleMove",
15
+ ViewHelp = "viewHelp",
16
+ HideHelp = "hideHelp",
17
+ InitMove = "initMove",
18
18
  ToggleCommandLine = "toggleCommandLine",
19
19
  CaptureInput = "captureInput",
20
20
  EraseInput = "eraseInput",
@@ -22,4 +22,4 @@ export declare enum KeyIntent {
22
22
  GetLastCommandFromHistory = "getLastCommandFromHistory",
23
23
  GetNextCommandFromHistory = "getNextCommandFromHistory"
24
24
  }
25
- export declare function getKeyIntent(key: readline.Key, ctx: NavigateCtx, mode: ModeUnion): KeyIntent | null;
25
+ export declare function getKeyIntent(key: readline.Key, mode: ModeUnion): Intent | null;
@@ -1,27 +1,30 @@
1
1
  import { Mode } from '../model/action-map.model.js';
2
+ import { appState } from '../state/state.js';
2
3
  import { getCommandLineIntent } from './get-command-line-intent.js';
3
- export var KeyIntent;
4
- (function (KeyIntent) {
5
- KeyIntent["NavPreviousItem"] = "navPreviousItem";
6
- KeyIntent["NavNextItem"] = "navNextItem";
7
- KeyIntent["NavToPreviousContainer"] = "navToPreviousContainer";
8
- KeyIntent["NavToNextContainer"] = "navToNextContainer";
9
- KeyIntent["MovePreviousItem"] = "movePreviousItem";
10
- KeyIntent["MoveNextItem"] = "moveNextItem";
11
- KeyIntent["MoveToPreviousContainer"] = "moveToPreviousContainer";
12
- KeyIntent["MoveToNextContainer"] = "moveToNextContainer";
13
- KeyIntent["Confirm"] = "confirm";
14
- KeyIntent["Edit"] = "edit";
15
- KeyIntent["Exit"] = "exit";
16
- KeyIntent["ToggleHelp"] = "toggleHelp";
17
- KeyIntent["ToggleMove"] = "toggleMove";
18
- KeyIntent["ToggleCommandLine"] = "toggleCommandLine";
19
- KeyIntent["CaptureInput"] = "captureInput";
20
- KeyIntent["EraseInput"] = "eraseInput";
21
- KeyIntent["AddItem"] = "addItem";
22
- KeyIntent["GetLastCommandFromHistory"] = "getLastCommandFromHistory";
23
- KeyIntent["GetNextCommandFromHistory"] = "getNextCommandFromHistory";
24
- })(KeyIntent || (KeyIntent = {}));
4
+ export var Intent;
5
+ (function (Intent) {
6
+ Intent["NavPreviousItem"] = "navPreviousItem";
7
+ Intent["NavNextItem"] = "navNextItem";
8
+ Intent["NavToPreviousContainer"] = "navToPreviousContainer";
9
+ Intent["NavToNextContainer"] = "navToNextContainer";
10
+ Intent["MovePreviousItem"] = "movePreviousItem";
11
+ Intent["MoveNextItem"] = "moveNextItem";
12
+ Intent["MoveToPreviousContainer"] = "moveToPreviousContainer";
13
+ Intent["MoveToNextContainer"] = "moveToNextContainer";
14
+ Intent["Confirm"] = "confirm";
15
+ Intent["Edit"] = "edit";
16
+ Intent["Exit"] = "exit";
17
+ Intent["ViewHelp"] = "viewHelp";
18
+ Intent["HideHelp"] = "hideHelp";
19
+ Intent["InitMove"] = "initMove";
20
+ // Command line
21
+ Intent["ToggleCommandLine"] = "toggleCommandLine";
22
+ Intent["CaptureInput"] = "captureInput";
23
+ Intent["EraseInput"] = "eraseInput";
24
+ Intent["AddItem"] = "addItem";
25
+ Intent["GetLastCommandFromHistory"] = "getLastCommandFromHistory";
26
+ Intent["GetNextCommandFromHistory"] = "getNextCommandFromHistory";
27
+ })(Intent || (Intent = {}));
25
28
  function getDir(key) {
26
29
  switch (key.name) {
27
30
  // arrows
@@ -43,12 +46,21 @@ function getDir(key) {
43
46
  return null;
44
47
  }
45
48
  }
46
- function mapDirectionalIntent(dir, axis, enableAcrossContainers, intents) {
49
+ function mapDirectionalIntent(axis, dir, intents) {
50
+ const enableAcrossContainers = appState.currentNode.enableChildNavigationAcrossContainers;
47
51
  switch (dir) {
48
52
  case 'up':
49
- return axis === 'vertical' ? intents.prevItem : null;
53
+ return axis === 'vertical'
54
+ ? intents.prevItem
55
+ : enableAcrossContainers
56
+ ? intents.prevContainer
57
+ : null;
50
58
  case 'down':
51
- return axis === 'vertical' ? intents.nextItem : null;
59
+ return axis === 'vertical'
60
+ ? intents.nextItem
61
+ : enableAcrossContainers
62
+ ? intents.nextContainer
63
+ : null;
52
64
  case 'left':
53
65
  return axis === 'horizontal'
54
66
  ? intents.prevItem
@@ -63,62 +75,44 @@ function mapDirectionalIntent(dir, axis, enableAcrossContainers, intents) {
63
75
  : null;
64
76
  }
65
77
  }
66
- export function getKeyIntent(key, ctx, mode) {
67
- if (key.sequence === ':') {
68
- return KeyIntent.ToggleCommandLine;
69
- }
70
- if (mode === Mode.COMMAND_LINE) {
78
+ export function getKeyIntent(key, mode) {
79
+ if (key.sequence === ':')
80
+ return Intent.ToggleCommandLine;
81
+ if (mode === Mode.COMMAND_LINE)
71
82
  return getCommandLineIntent(key);
72
- }
73
- const axis = ctx.navigationNode.childrenRenderAxis;
74
- const enableAcrossContainers = ctx.navigationNode.enableChildNavigationAcrossContainers;
75
- if (key.name === 'escape' && mode === Mode.HELP) {
76
- return KeyIntent.ToggleHelp;
77
- }
78
- // Hard exits
79
- if (key.ctrl && key.name === 'c')
80
- return KeyIntent.Exit;
81
- if (key.name === 'escape')
82
- return KeyIntent.Exit;
83
- // Move mode
84
- if (mode === Mode.MOVE) {
85
- if (key.name === 'y')
86
- return KeyIntent.ToggleMove;
87
- const dir = getDir(key);
88
- if (!dir)
89
- return null;
90
- return mapDirectionalIntent(dir, axis, Boolean(enableAcrossContainers), {
91
- prevItem: KeyIntent.MovePreviousItem,
92
- nextItem: KeyIntent.MoveNextItem,
93
- prevContainer: KeyIntent.MoveToPreviousContainer,
94
- nextContainer: KeyIntent.MoveToNextContainer,
95
- });
96
- }
97
- // Normal mode
98
- if (key.name === 'y')
99
- return KeyIntent.ToggleMove;
100
- // Edit (vim-ish)
101
- if (key.name === 'i')
102
- return KeyIntent.Edit;
103
- // Navigation (arrows + vim hjkl)
83
+ // Navigation keys
104
84
  const dir = getDir(key);
105
85
  if (dir) {
106
- return mapDirectionalIntent(dir, axis, Boolean(enableAcrossContainers), {
107
- prevItem: KeyIntent.NavPreviousItem,
108
- nextItem: KeyIntent.NavNextItem,
109
- prevContainer: KeyIntent.NavToPreviousContainer,
110
- nextContainer: KeyIntent.NavToNextContainer,
111
- });
86
+ let dirMap = mode === Mode.MOVE
87
+ ? {
88
+ prevItem: Intent.MovePreviousItem,
89
+ nextItem: Intent.MoveNextItem,
90
+ prevContainer: Intent.MoveToPreviousContainer,
91
+ nextContainer: Intent.MoveToNextContainer,
92
+ }
93
+ : {
94
+ prevItem: Intent.NavPreviousItem,
95
+ nextItem: Intent.NavNextItem,
96
+ prevContainer: Intent.NavToPreviousContainer,
97
+ nextContainer: Intent.NavToNextContainer,
98
+ };
99
+ return mapDirectionalIntent(appState.currentNode.childrenRenderAxis, dir, dirMap);
112
100
  }
113
- // Actions
101
+ // Hard exit
102
+ if (key.ctrl && key.name === 'c')
103
+ return Intent.Exit;
104
+ // Default actions
114
105
  switch (key.name) {
115
- case 'a':
116
- return KeyIntent.AddItem;
117
- case 'return':
106
+ case 'i':
107
+ return Intent.Edit;
108
+ case 'y':
109
+ return Intent.InitMove;
118
110
  case 'e':
119
- return KeyIntent.Confirm;
111
+ case 'return':
112
+ return Intent.Confirm;
120
113
  case 'q':
121
- return KeyIntent.Exit;
114
+ case 'escape':
115
+ return Intent.Exit;
122
116
  default:
123
117
  return null;
124
118
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "epiq",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "MIT",
5
5
  "bin": "dist/cli.js",
6
6
  "type": "module",
7
- "description": "CLI based issue tracking manager",
7
+ "description": "EPIQ - CLI based issue tracker",
8
8
  "engines": {
9
9
  "node": ">=16"
10
10
  },
package/readme.md CHANGED
@@ -1,37 +1,74 @@
1
1
  # epiq
2
2
 
3
- ## Local dev
3
+ > **The ultimate productivity tool** — a CLI-based issue tracking client powered by Git as its backend, living directly inside the repository you work in.
4
+ > No external services. No context switching. Just tickets, versioned and colocated with your code.
4
5
 
5
- Start dev server in background terminal session:
6
+ ---
7
+
8
+ ## Why epiq?
9
+
10
+ - **Simplicity** — Skip the additional tooling complexity
11
+ - **Repo-native** — Lives inside your project directory
12
+ - **Offline-ready** — Works wherever Git works
13
+ - **Team-friendly** — Collaborate through normal Git workflows
14
+ - **Shareable** — ASCII board exported to `.md`, viewable in the CLI, on GitHub/GitLab, or as your project README.
15
+ - **CLI-first** — Fast, scriptable, and developer/agent-friendly
16
+
17
+ ---
18
+
19
+ ## 📦 Installation
20
+
21
+ Install globally using npm:
6
22
 
7
23
  ```bash
8
- $ npm run dev
24
+ npm install --global epiq
9
25
  ```
10
26
 
11
- Run app with:
27
+ Verify installation:
12
28
 
13
29
  ```bash
14
- $ npm run execute
30
+ epiq --version
15
31
  ```
16
32
 
17
- ## Install (WIP!)
33
+ ---
34
+
35
+ ## 🚀 Getting Started
36
+
37
+ ### Initialize a project
38
+
39
+ Create a new epiq workspace inside your current directory:
18
40
 
19
41
  ```bash
20
- $ npm install --global epiq
42
+ epiq --init "Project Name"
21
43
  ```
22
44
 
23
- ## CLI (WIP!)
45
+ This sets up epiq in your repository and prepares it for issue tracking.
24
46
 
25
- ```
26
- $ epiq --help
47
+ ---
27
48
 
28
- Usage
29
- $ epiq
49
+ ### Open the workspace
30
50
 
31
- Options
32
- --name Your name
51
+ Run epiq inside any initialized repository:
33
52
 
34
- Examples
35
- $ epiq --name=Jane
36
- Hello, Jane
53
+ ```bash
54
+ epiq
37
55
  ```
56
+
57
+ This opens the interactive CLI workspace.
58
+
59
+ ---
60
+
61
+ ## 🛠 Philosophy
62
+
63
+ epiq keeps issue tracking:
64
+
65
+ - Close to your code
66
+ - Versioned with your commits
67
+ - Simple and transparent
68
+ - Independent from external platforms
69
+
70
+ If you use Git, you already have everything you need.
71
+
72
+ ---
73
+
74
+ Built for developers who live in the terminal.