whose-turn 0.0.0-stage → 1.0.0-alpha.2
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/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +241 -2
- package/config.example.json +144 -0
- package/connectors/demo.mjs +146 -0
- package/connectors/github.mjs +316 -0
- package/connectors/index.mjs +38 -0
- package/connectors/jira.mjs +155 -0
- package/connectors/linear.mjs +113 -0
- package/dist/assets/_plugin-vue_export-helper-BKveAJb2.css +1 -0
- package/dist/assets/_plugin-vue_export-helper-DHr5RHTx.js +18 -0
- package/dist/assets/bem-puYdOas_.js +1 -0
- package/dist/assets/index-DR8StBp8.js +3 -0
- package/dist/assets/index-d0wlPvUz.css +1 -0
- package/dist/assets/report-Xynr6yGL.js +1 -0
- package/dist/assets/report-xVOo7qip.css +1 -0
- package/dist/assets/scheme-DJ3WNw60.js +1 -0
- package/dist/assets/sheet-CBYGM-FG.js +1 -0
- package/dist/assets/sheet-DYpaJaXO.css +1 -0
- package/dist/index.html +19 -0
- package/dist/report.html +16 -0
- package/dist/sheet.html +16 -0
- package/dist/vendor/popper.min.js +6 -0
- package/dist/vendor/tippy-bundle.umd.min.js +2 -0
- package/install.sh +69 -0
- package/lib/api.mjs +399 -0
- package/lib/backoff.mjs +2 -0
- package/lib/compose.mjs +67 -0
- package/lib/config.mjs +290 -0
- package/lib/default-table.mjs +58 -0
- package/lib/errors.mjs +53 -0
- package/lib/facts.mjs +128 -0
- package/lib/feedback.mjs +145 -0
- package/lib/handoff.mjs +127 -0
- package/lib/health.mjs +35 -0
- package/lib/http-client.mjs +44 -0
- package/lib/inbox.mjs +60 -0
- package/lib/linear-api.mjs +12 -0
- package/lib/notify.mjs +36 -0
- package/lib/palette.mjs +213 -0
- package/lib/platform/credential.ps1 +84 -0
- package/lib/registry.mjs +61 -0
- package/lib/rules.mjs +241 -0
- package/lib/secrets.mjs +69 -0
- package/lib/static.mjs +36 -0
- package/lib/store.mjs +61 -0
- package/lib/table.mjs +148 -0
- package/lib/text.mjs +46 -0
- package/lib/token-cache.mjs +12 -0
- package/lib/window.mjs +33 -0
- package/lib/workspace.mjs +294 -0
- package/open.sh +40 -0
- package/package.json +67 -4
- package/server.mjs +80 -0
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whose-turn",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/aquivalabs/whose-turn.git"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"server.mjs",
|
|
11
|
+
"lib/",
|
|
12
|
+
"connectors/",
|
|
13
|
+
"dist/",
|
|
14
|
+
"install.sh",
|
|
15
|
+
"open.sh",
|
|
16
|
+
"config.example.json",
|
|
17
|
+
"CHANGELOG.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"description": "Local monitor that answers what is waiting on you across Jira, GitHub and Linear.",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"prepare": "git config core.hooksPath .githooks 2>/dev/null || true",
|
|
29
|
+
"start": "node server.mjs",
|
|
30
|
+
"dev": "WHOSE_TURN_DATA=.users-files/dev WHOSE_TURN_PORT=8766 node server.mjs",
|
|
31
|
+
"test": "node --experimental-strip-types --test \"test/**/*.test.mjs\"",
|
|
32
|
+
"dev:web": "vite",
|
|
33
|
+
"build": "vue-tsc --noEmit && vite build",
|
|
34
|
+
"typecheck": "vue-tsc --noEmit && tsc -p tsconfig.server.json",
|
|
35
|
+
"lint": "eslint web/src lib connectors server.mjs test",
|
|
36
|
+
"lint:style": "stylelint \"web/src/**/*.{vue,css}\"",
|
|
37
|
+
"format": "prettier --write web/src",
|
|
38
|
+
"check": "npm run typecheck && npm run lint && npm run lint:style && npm test && npm run test:web",
|
|
39
|
+
"test:web": "vitest run"
|
|
40
|
+
},
|
|
41
|
+
"bin": {
|
|
42
|
+
"whose-turn": "server.mjs"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "10.0.1",
|
|
46
|
+
"@testing-library/dom": "10.4.2",
|
|
47
|
+
"@testing-library/vue": "8.1.0",
|
|
48
|
+
"@types/node": "22.19.19",
|
|
49
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
50
|
+
"@vue/test-utils": "2.5.1",
|
|
51
|
+
"@vueuse/core": "15.0.0",
|
|
52
|
+
"eslint": "10.13.0",
|
|
53
|
+
"eslint-config-prettier": "10.1.8",
|
|
54
|
+
"eslint-plugin-vue": "10.11.1",
|
|
55
|
+
"happy-dom": "21.0.4",
|
|
56
|
+
"postcss-html": "2.0.0",
|
|
57
|
+
"prettier": "3.9.10",
|
|
58
|
+
"stylelint": "17.16.0",
|
|
59
|
+
"stylelint-config-html": "2.0.0",
|
|
60
|
+
"stylelint-config-standard": "40.0.0",
|
|
61
|
+
"typescript": "6.0.3",
|
|
62
|
+
"typescript-eslint": "8.71.2",
|
|
63
|
+
"vite": "^6.0.0",
|
|
64
|
+
"vitest": "5.1.0",
|
|
65
|
+
"vue": "^3.5.0",
|
|
66
|
+
"vue-eslint-parser": "10.4.1",
|
|
67
|
+
"vue-tsc": "3.3.13"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/server.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Whose Turn server: loads the workspace config, polls every workspace on one loop with rate-limit backoff, checks data health on a timer, serves the window and the local API. */
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { loadConfig, saveWorkspaces, normaliseWorkspaces } from './lib/config.mjs';
|
|
6
|
+
import { createHttpClient } from './lib/http-client.mjs';
|
|
7
|
+
import { nextDelay } from './lib/backoff.mjs';
|
|
8
|
+
import { createSecrets } from './lib/secrets.mjs';
|
|
9
|
+
import { createWorkspace } from './lib/workspace.mjs';
|
|
10
|
+
import { createRegistry } from './lib/registry.mjs';
|
|
11
|
+
import { openInbox } from './lib/inbox.mjs';
|
|
12
|
+
import { openReports } from './lib/feedback.mjs';
|
|
13
|
+
import { startApi } from './lib/api.mjs';
|
|
14
|
+
|
|
15
|
+
const appDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const http = createHttpClient();
|
|
17
|
+
const first = loadConfig();
|
|
18
|
+
const secrets = createSecrets(first.dataDir);
|
|
19
|
+
const inbox = openInbox(first.dataDir);
|
|
20
|
+
const reports = openReports(first.dataDir);
|
|
21
|
+
let pollSeconds = first.pollSeconds;
|
|
22
|
+
/** @type {{ stale_minutes: number, failing_polls: number }} */
|
|
23
|
+
let healthLimits = /** @type {any} */ (first.health);
|
|
24
|
+
|
|
25
|
+
const registry = createRegistry({
|
|
26
|
+
load: () => {
|
|
27
|
+
const config = loadConfig();
|
|
28
|
+
pollSeconds = config.pollSeconds;
|
|
29
|
+
healthLimits = /** @type {any} */ (config.health);
|
|
30
|
+
return config;
|
|
31
|
+
},
|
|
32
|
+
save: (rawWorkspaces) => saveWorkspaces(first.configFile, rawWorkspaces),
|
|
33
|
+
normalise: (rawWorkspaces) => normaliseWorkspaces(rawWorkspaces).workspaces,
|
|
34
|
+
build: (definition, config) => createWorkspace(definition, { dataDir: config.dataDir, historyDays: config.historyDays, http, secrets, limits: () => healthLimits }),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let wake = null;
|
|
38
|
+
const requestPoll = () => wake?.();
|
|
39
|
+
const sleep = (seconds) => new Promise((resolve) => {
|
|
40
|
+
const timer = setTimeout(resolve, seconds * 1000);
|
|
41
|
+
wake = () => { clearTimeout(timer); resolve(); };
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const pollLoop = async () => {
|
|
45
|
+
let delay = pollSeconds;
|
|
46
|
+
for (;;) {
|
|
47
|
+
const outcomes = await Promise.all(registry.workspaces.map((workspace) => workspace.poll()));
|
|
48
|
+
delay = nextDelay(outcomes, delay, pollSeconds);
|
|
49
|
+
await sleep(delay);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const saveConfig = async (rawWorkspaces) => {
|
|
54
|
+
const created = await registry.save(rawWorkspaces);
|
|
55
|
+
await Promise.all(created.map((workspace) => workspace.poll()));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await startApi({ port: first.port, appDir, registry, requestPoll, saveConfig, secrets, inbox, http, reports, dataDir: first.dataDir });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
console.log(`whose-turn on http://127.0.0.1:${first.port} · data ${first.dataDir} · workspaces ${registry.workspaces.map((workspace) => workspace.id).join(', ') || 'none'}`);
|
|
65
|
+
for (const problem of registry.problems) {
|
|
66
|
+
console.error(problem);
|
|
67
|
+
}
|
|
68
|
+
pollLoop();
|
|
69
|
+
|
|
70
|
+
const HEALTH_TICK = 30000;
|
|
71
|
+
let lastTick = Date.now();
|
|
72
|
+
setInterval(() => {
|
|
73
|
+
const slept = Date.now() - lastTick > 3 * HEALTH_TICK;
|
|
74
|
+
lastTick = Date.now();
|
|
75
|
+
if (slept) {
|
|
76
|
+
requestPoll();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
registry.workspaces.forEach((workspace) => workspace.checkHealth());
|
|
80
|
+
}, HEALTH_TICK).unref();
|