@voidlabs.gg/voidshift-agents 0.4.2 → 0.4.4
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/README.md +18 -6
- package/dist/config.d.ts +12 -2
- package/dist/config.js +14 -1
- package/dist/control.d.ts +4 -2
- package/dist/control.js +79 -52
- package/dist/cron.d.ts +9 -0
- package/dist/cron.js +66 -0
- package/dist/index.js +77 -44
- package/dist/launchd.d.ts +1 -0
- package/dist/launchd.js +6 -0
- package/dist/linear.d.ts +1 -1
- package/dist/linear.js +3 -3
- package/dist/manage.d.ts +1 -0
- package/dist/manage.js +35 -13
- package/dist/runner.d.ts +12 -5
- package/dist/runner.js +130 -47
- package/dist/secrets.d.ts +9 -0
- package/dist/secrets.js +55 -0
- package/dist/service.d.ts +9 -0
- package/dist/service.js +18 -0
- package/dist/state.d.ts +6 -1
- package/dist/state.js +15 -0
- package/dist/systemd.d.ts +14 -0
- package/dist/systemd.js +85 -0
- package/dist/tracker.d.ts +24 -0
- package/dist/tracker.js +36 -0
- package/dist/voidtrack.d.ts +18 -0
- package/dist/voidtrack.js +89 -0
- package/package.json +3 -5
- package/dist/keychain.d.ts +0 -3
- package/dist/keychain.js +0 -19
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// VoidTrack over its REST API with a bearer key. Self hosted installs set
|
|
2
|
+
// VOIDTRACK_API_URL; the web app lives at the same address without /api.
|
|
3
|
+
const apiBase = () => (process.env.VOIDTRACK_API_URL ?? 'https://web.voidtrack.app/api').replace(/\/$/, '');
|
|
4
|
+
// One branch per card that survives a retitle: GitHub closes a pull request
|
|
5
|
+
// whose branch is renamed.
|
|
6
|
+
export const branchFor = (identifier) => identifier.toLowerCase();
|
|
7
|
+
export function mapIssue(i, web) {
|
|
8
|
+
return {
|
|
9
|
+
id: i.id,
|
|
10
|
+
identifier: i.identifier,
|
|
11
|
+
title: i.title,
|
|
12
|
+
description: i.description ?? undefined,
|
|
13
|
+
url: `${web}/issue/${i.identifier}`,
|
|
14
|
+
branchName: branchFor(i.identifier),
|
|
15
|
+
teamId: i.team?.id ?? '',
|
|
16
|
+
teamKey: i.team?.key ?? i.identifier.split('-')[0],
|
|
17
|
+
projectName: i.project?.name ?? undefined,
|
|
18
|
+
labels: (i.labels ?? []).map((l) => (typeof l === 'string' ? l : l.name)),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function createVoidTrack(apiKey, fetcher = fetch) {
|
|
22
|
+
const base = apiBase();
|
|
23
|
+
const web = base.replace(/\/api$/, '');
|
|
24
|
+
async function call(method, path, body) {
|
|
25
|
+
let res;
|
|
26
|
+
try {
|
|
27
|
+
res = await fetcher(`${base}${path}`, {
|
|
28
|
+
method,
|
|
29
|
+
signal: AbortSignal.timeout(30_000),
|
|
30
|
+
headers: { Authorization: `Bearer ${apiKey}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
|
|
31
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const cause = err.cause;
|
|
36
|
+
throw new Error(`could not reach VoidTrack (${cause?.code ?? cause?.message ?? err.message})`);
|
|
37
|
+
}
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
throw Object.assign(new Error(`voidtrack ${method} ${path.split('?')[0]} ${res.status}: ${text.slice(0, 200)}`), { status: res.status });
|
|
41
|
+
return text ? JSON.parse(text) : {};
|
|
42
|
+
}
|
|
43
|
+
const enc = encodeURIComponent;
|
|
44
|
+
return {
|
|
45
|
+
async viewer() {
|
|
46
|
+
const d = await call('GET', '/v1/users');
|
|
47
|
+
const me = (d.users ?? []).find((u) => u.isMe);
|
|
48
|
+
if (!me)
|
|
49
|
+
throw new Error('voidtrack did not say which user this key belongs to');
|
|
50
|
+
return { id: me.id, name: me.name };
|
|
51
|
+
},
|
|
52
|
+
// My open cards in the team carrying the label. The API cannot filter on
|
|
53
|
+
// labels, so that part happens here.
|
|
54
|
+
async myCardsWithLabel(label, team) {
|
|
55
|
+
const d = await call('GET', `/v1/issues?team=${enc(team)}&assignee=me&stateType=backlog,unstarted,started&sort=created&limit=200`);
|
|
56
|
+
return (d.issues ?? []).map((i) => mapIssue(i, web)).filter((c) => c.labels.includes(label));
|
|
57
|
+
},
|
|
58
|
+
async card(identifier) {
|
|
59
|
+
const d = await call('GET', `/v1/issues/${enc(identifier)}`);
|
|
60
|
+
if (!d.issue)
|
|
61
|
+
throw new Error(`voidtrack has no card ${identifier}`);
|
|
62
|
+
return mapIssue(d.issue, web);
|
|
63
|
+
},
|
|
64
|
+
async comments(identifier) {
|
|
65
|
+
const d = await call('GET', `/v1/issues/${enc(identifier)}/comments`);
|
|
66
|
+
return (d.comments ?? []).map((c) => ({ body: c.body, createdAt: c.createdAt }));
|
|
67
|
+
},
|
|
68
|
+
async comment(card, body) {
|
|
69
|
+
await call('POST', `/v1/issues/${enc(card.identifier)}/comments`, { body });
|
|
70
|
+
},
|
|
71
|
+
// Labels are free text on VoidTrack and the list replaces the whole set.
|
|
72
|
+
async setLabels(card, next) {
|
|
73
|
+
await call('PATCH', `/v1/issues/${enc(card.identifier)}`, { labels: next });
|
|
74
|
+
card.labels = next;
|
|
75
|
+
},
|
|
76
|
+
// Move to a named state; false if the team has no such state.
|
|
77
|
+
async moveTo(card, stateName) {
|
|
78
|
+
try {
|
|
79
|
+
await call('PATCH', `/v1/issues/${enc(card.identifier)}`, { state: stateName });
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
if (err.status === 400 || err.status === 404)
|
|
84
|
+
return false;
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidlabs.gg/voidshift-agents",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Agents for Claude Code and Codex that plan, build and QA Linear cards, chained and run in the background.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"agents",
|
|
19
19
|
"linear",
|
|
20
20
|
"cli",
|
|
21
|
-
"macos"
|
|
21
|
+
"macos",
|
|
22
|
+
"linux"
|
|
22
23
|
],
|
|
23
24
|
"type": "module",
|
|
24
25
|
"main": "dist/index.js",
|
|
@@ -29,9 +30,6 @@
|
|
|
29
30
|
"LICENSE",
|
|
30
31
|
"README.md"
|
|
31
32
|
],
|
|
32
|
-
"os": [
|
|
33
|
-
"darwin"
|
|
34
|
-
],
|
|
35
33
|
"engines": {
|
|
36
34
|
"node": ">=22.18"
|
|
37
35
|
},
|
package/dist/keychain.d.ts
DELETED
package/dist/keychain.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { userInfo } from 'node:os';
|
|
3
|
-
const SERVICE = 'voidshift-linear';
|
|
4
|
-
const KEY_SHAPE = /^lin_api_[A-Za-z0-9]+$/;
|
|
5
|
-
export function readLinearKey() {
|
|
6
|
-
const r = spawnSync('/usr/bin/security', ['find-generic-password', '-s', SERVICE, '-w'], { encoding: 'utf8' });
|
|
7
|
-
const key = r.status === 0 ? r.stdout.trim() : '';
|
|
8
|
-
return key || null;
|
|
9
|
-
}
|
|
10
|
-
export const looksLikeLinearKey = (key) => KEY_SHAPE.test(key);
|
|
11
|
-
// Stores the key through `security -i` on stdin, so it never appears in a
|
|
12
|
-
// process list. The shape check above rules out anything that could break
|
|
13
|
-
// out of the command line.
|
|
14
|
-
export function storeLinearKey(key) {
|
|
15
|
-
if (!looksLikeLinearKey(key))
|
|
16
|
-
return false;
|
|
17
|
-
const command = `add-generic-password -U -s ${SERVICE} -a ${userInfo().username} -w ${key}\n`;
|
|
18
|
-
return spawnSync('/usr/bin/security', ['-i'], { input: command, encoding: 'utf8' }).status === 0;
|
|
19
|
-
}
|