@dahrk/linear 0.1.0 → 0.2.0
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 +24 -12
- package/dist/batch-source.d.ts +63 -0
- package/dist/batch-source.d.ts.map +1 -0
- package/dist/batch-source.js +149 -0
- package/dist/batch-source.js.map +1 -0
- package/dist/comments.d.ts +36 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +104 -0
- package/dist/comments.js.map +1 -0
- package/dist/documents.d.ts +1 -15
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +37 -27
- package/dist/documents.js.map +1 -1
- package/dist/format-action.d.ts +25 -0
- package/dist/format-action.d.ts.map +1 -0
- package/dist/format-action.js +250 -0
- package/dist/format-action.js.map +1 -0
- package/dist/index.d.ts +119 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +231 -59
- package/dist/index.js.map +1 -1
- package/dist/issue-graph.d.ts +37 -0
- package/dist/issue-graph.d.ts.map +1 -0
- package/dist/issue-graph.js +125 -0
- package/dist/issue-graph.js.map +1 -0
- package/dist/issues.d.ts +27 -2
- package/dist/issues.d.ts.map +1 -1
- package/dist/issues.js +33 -10
- package/dist/issues.js.map +1 -1
- package/dist/labels.d.ts +48 -1
- package/dist/labels.d.ts.map +1 -1
- package/dist/labels.js +72 -24
- package/dist/labels.js.map +1 -1
- package/dist/linear-client.d.ts +51 -0
- package/dist/linear-client.d.ts.map +1 -1
- package/dist/linear-client.js +233 -34
- package/dist/linear-client.js.map +1 -1
- package/dist/oauth.d.ts +52 -12
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +91 -34
- package/dist/oauth.js.map +1 -1
- package/dist/recording-client.d.ts +20 -2
- package/dist/recording-client.d.ts.map +1 -1
- package/dist/recording-client.js +39 -1
- package/dist/recording-client.js.map +1 -1
- package/dist/responding-client.d.ts +49 -0
- package/dist/responding-client.d.ts.map +1 -0
- package/dist/responding-client.js +47 -0
- package/dist/responding-client.js.map +1 -0
- package/dist/teams.d.ts +20 -0
- package/dist/teams.d.ts.map +1 -0
- package/dist/teams.js +32 -0
- package/dist/teams.js.map +1 -0
- package/package.json +8 -10
- package/src/batch-source.ts +208 -0
- package/src/comments.ts +126 -0
- package/src/documents.ts +162 -0
- package/src/format-action.ts +279 -0
- package/src/index.ts +617 -0
- package/src/issue-graph.ts +169 -0
- package/src/issues.ts +142 -0
- package/src/labels.ts +254 -0
- package/src/linear-client.ts +448 -0
- package/src/oauth.ts +255 -0
- package/src/recording-client.ts +141 -0
- package/src/responding-client.ts +106 -0
- package/src/teams.ts +44 -0
package/dist/oauth.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* the authorization-code flow with `actor=app` and the `app:assignable` / `app:mentionable`
|
|
6
6
|
* scopes (admin consent). That flow also yields the access + refresh tokens the hub posts with.
|
|
7
7
|
* Access tokens last ~24h, so the hub refreshes them with the refresh token (see the hub's
|
|
8
|
-
* `resolveClient`).
|
|
8
|
+
* `resolveClient`). Token grants use plain `fetch`; post-token reads use the typed LinearClient.
|
|
9
9
|
*/
|
|
10
|
+
import { LinearClient } from "@linear/sdk";
|
|
10
11
|
const AUTHORIZE_URL = "https://linear.app/oauth/authorize";
|
|
11
12
|
const TOKEN_URL = "https://api.linear.app/oauth/token";
|
|
12
|
-
const
|
|
13
|
+
const REVOKE_URL = "https://api.linear.app/oauth/revoke";
|
|
13
14
|
/** The agent scopes: read/write plus the two that make the app assignable + mentionable. */
|
|
14
15
|
export const DEFAULT_AGENT_SCOPES = ["read", "write", "app:assignable", "app:mentionable"];
|
|
15
16
|
/**
|
|
@@ -25,8 +26,8 @@ export function authorizeUrl(params) {
|
|
|
25
26
|
u.searchParams.set("scope", (params.scopes ?? DEFAULT_AGENT_SCOPES).join(","));
|
|
26
27
|
u.searchParams.set("state", params.state);
|
|
27
28
|
u.searchParams.set("actor", params.actor ?? "app");
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if ((params.prompt ?? "consent") === "consent")
|
|
30
|
+
u.searchParams.set("prompt", "consent");
|
|
30
31
|
return u.toString();
|
|
31
32
|
}
|
|
32
33
|
async function postToken(body, now) {
|
|
@@ -58,24 +59,24 @@ export function exchangeCode(params, now = () => new Date()) {
|
|
|
58
59
|
redirect_uri: params.redirectUri,
|
|
59
60
|
}, now);
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Personal API keys start with `lin_api_` and must be sent as `apiKey` (not Bearer accessToken).
|
|
64
|
+
* OAuth access tokens use `accessToken` so LinearClient adds the Bearer prefix automatically.
|
|
65
|
+
*/
|
|
66
|
+
function makeLinearClient(token) {
|
|
67
|
+
return token.startsWith("lin_api_")
|
|
68
|
+
? new LinearClient({ apiKey: token })
|
|
69
|
+
: new LinearClient({ accessToken: token });
|
|
70
|
+
}
|
|
61
71
|
/**
|
|
62
72
|
* Resolve the workspace (organization) id the access token belongs to. Used at install to backfill
|
|
63
73
|
* a connection's `workspaceIds` from the token, so an operator need not paste the org id by hand and
|
|
64
|
-
* intake can match the workspace on the very first webhook.
|
|
74
|
+
* intake can match the workspace on the very first webhook.
|
|
65
75
|
*/
|
|
66
76
|
export async function fetchOrganizationId(accessToken) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
body: JSON.stringify({ query: "query { organization { id } }" }),
|
|
71
|
-
});
|
|
72
|
-
const json = (await res.json().catch(() => ({})));
|
|
73
|
-
const id = json.data?.organization?.id;
|
|
74
|
-
if (!res.ok || !id) {
|
|
75
|
-
const detail = json.errors?.[0]?.message || `${res.status}`;
|
|
76
|
-
throw new Error(`linear organization query failed: ${detail}`);
|
|
77
|
-
}
|
|
78
|
-
return id;
|
|
77
|
+
const client = makeLinearClient(accessToken);
|
|
78
|
+
const org = await client.organization;
|
|
79
|
+
return org.id;
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
82
|
* Probe a stored access token: query `viewer` (the app/agent user) and `organization` (the bound
|
|
@@ -83,31 +84,87 @@ export async function fetchOrganizationId(accessToken) {
|
|
|
83
84
|
* binds to. Throws on an auth failure / GraphQL error, which the caller renders as "token invalid".
|
|
84
85
|
*/
|
|
85
86
|
export async function probeToken(accessToken) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
const client = makeLinearClient(accessToken);
|
|
88
|
+
const [viewer, org] = await Promise.all([client.viewer, client.organization]);
|
|
89
|
+
const probe = {};
|
|
90
|
+
if (viewer?.id) {
|
|
91
|
+
probe.viewer = {
|
|
92
|
+
id: viewer.id,
|
|
93
|
+
...(viewer.name ? { name: viewer.name } : {}),
|
|
94
|
+
...(viewer.email ? { email: viewer.email } : {}),
|
|
95
|
+
...(typeof viewer.admin === "boolean" ? { admin: viewer.admin } : {}),
|
|
96
|
+
};
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
if (org?.id) {
|
|
99
|
+
probe.organization = {
|
|
100
|
+
id: org.id,
|
|
101
|
+
...(org.name ? { name: org.name } : {}),
|
|
102
|
+
...(org.urlKey ? { urlKey: org.urlKey } : {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return probe;
|
|
102
106
|
}
|
|
103
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* Rotate the access token using the stored refresh token (the hub calls this near expiry / on 401).
|
|
109
|
+
*
|
|
110
|
+
* Linear rotates refresh tokens **single-use**: the request consumes the one presented and the response
|
|
111
|
+
* carries "a new valid access token and a new refresh token" (oauth-2-0-authentication.md, "Refresh an
|
|
112
|
+
* access token"). So the replacement is mandatory, and a response without one means the old token has
|
|
113
|
+
* been consumed while we learned nothing - the caller MUST NOT carry on with the previous value, which
|
|
114
|
+
* is now dead. Throwing here is what makes that loud instead of silently arming a connection to fail
|
|
115
|
+
* on its next refresh, forever (DHK-1306).
|
|
116
|
+
*
|
|
117
|
+
* Linear gives a 30-minute grace period for exactly this case: the original request can be replayed to
|
|
118
|
+
* retrieve the new refresh token. Recovery is the caller's to attempt; this function's job is to refuse
|
|
119
|
+
* to report success when the rotation is unaccounted for.
|
|
120
|
+
*/
|
|
104
121
|
export function refreshTokens(params, now = () => new Date()) {
|
|
105
122
|
return postToken({
|
|
106
123
|
grant_type: "refresh_token",
|
|
107
124
|
client_id: params.clientId,
|
|
108
125
|
client_secret: params.clientSecret,
|
|
109
126
|
refresh_token: params.refreshToken,
|
|
110
|
-
}, now)
|
|
127
|
+
}, now).then((t) => {
|
|
128
|
+
if (!t.refreshToken) {
|
|
129
|
+
throw new Error("linear refresh returned no replacement refresh token: the presented token is now consumed. " +
|
|
130
|
+
"Replay the same request within 30 minutes to recover it.");
|
|
131
|
+
}
|
|
132
|
+
return t;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Revoke an access token at Linear, de-authorising the app for the workspace that token belongs to.
|
|
137
|
+
*
|
|
138
|
+
* This is the ONLY app-callable lever Linear gives us over an install: there is no uninstall mutation
|
|
139
|
+
* and nothing in the GraphQL schema that names or targets an install (`revokeOauthToken`,
|
|
140
|
+
* `userAuthorizedApplications`, `applicationWithAuthorization` are all absent). De-authorisation is
|
|
141
|
+
* per organisation - Linear signals it with an `OAuthApp revoked` webhook carrying `organizationId`.
|
|
142
|
+
*
|
|
143
|
+
* Why it matters beyond hygiene: while an install exists, Linear's authorize hop resolves against it
|
|
144
|
+
* and shows "Dahrk already installed - Continue" rather than a consent screen for the workspace the
|
|
145
|
+
* user is actually in. So a user who wanted to move the connection to another workspace got the old
|
|
146
|
+
* one back, every time, with no way out from inside the product. Forgetting a token locally is not
|
|
147
|
+
* enough; Linear has to be told.
|
|
148
|
+
*
|
|
149
|
+
* A `400` counts as revoked. The endpoint returns it for an already-revoked token, and the caller's
|
|
150
|
+
* question is "is this token dead", to which "Linear dropped it earlier" is a yes. A `401` does not:
|
|
151
|
+
* it means we could not authenticate the revocation at all, so the install may well still be live and
|
|
152
|
+
* the caller must say so rather than report a clean disconnect.
|
|
153
|
+
*/
|
|
154
|
+
export async function revokeToken(params) {
|
|
155
|
+
const body = { token: params.token };
|
|
156
|
+
// Documented as optional but helpful; must not be combined with the legacy access_token/refresh_token
|
|
157
|
+
// form fields, which we never send.
|
|
158
|
+
if (params.tokenTypeHint)
|
|
159
|
+
body.token_type_hint = params.tokenTypeHint;
|
|
160
|
+
const res = await fetch(REVOKE_URL, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
163
|
+
body: new URLSearchParams(body).toString(),
|
|
164
|
+
});
|
|
165
|
+
if (res.ok || res.status === 400)
|
|
166
|
+
return;
|
|
167
|
+
throw new Error(`linear revoke endpoint failed: ${res.status}`);
|
|
111
168
|
}
|
|
112
169
|
/**
|
|
113
170
|
* Mint an `app` actor token via the `client_credentials` grant: headless (no user, no redirect, no
|
package/dist/oauth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* externalUrls) is verified with no Linear credentials. The real `@linear/sdk` client is
|
|
7
7
|
* the live path (./linear-client.ts).
|
|
8
8
|
*/
|
|
9
|
-
import type { Activity, AgentSessionClient, AuthRequest, DocumentInput, ElicitOption, ExternalUrl, PlanItem, PrAttachment, RepoCandidate, SessionState } from "./index.js";
|
|
9
|
+
import type { Activity, AgentSessionClient, AuthRequest, DocumentInput, ElicitOption, ExternalUrl, IssueEngagement, PlanItem, PrAttachment, RepoCandidate, SessionState } from "./index.js";
|
|
10
10
|
export type RecordedCall = {
|
|
11
11
|
call: "postActivity";
|
|
12
12
|
sessionId: string;
|
|
@@ -59,10 +59,28 @@ export type RecordedCall = {
|
|
|
59
59
|
call: "setState";
|
|
60
60
|
sessionId: string;
|
|
61
61
|
state: SessionState;
|
|
62
|
+
} | {
|
|
63
|
+
call: "readIssueEngagement";
|
|
64
|
+
issueIds: string[];
|
|
65
|
+
} | {
|
|
66
|
+
call: "createSessionOnIssue";
|
|
67
|
+
issueId: string;
|
|
68
|
+
} | {
|
|
69
|
+
call: "createSessionOnComment";
|
|
70
|
+
commentId: string;
|
|
62
71
|
};
|
|
63
72
|
export interface RecordingClient extends AgentSessionClient {
|
|
64
73
|
/** Every call made, in order. */
|
|
65
74
|
readonly calls: RecordedCall[];
|
|
66
75
|
}
|
|
67
|
-
|
|
76
|
+
/** Drive the read-only ground-truth methods so a test can simulate a disengaged issue. */
|
|
77
|
+
export interface RecordingClientOptions {
|
|
78
|
+
/** The app user id `appUserId()` returns (default "app-user"). */
|
|
79
|
+
appUserId?: string;
|
|
80
|
+
/** Per-issue engagement `readIssueEngagement` returns. Any requested id NOT present here defaults
|
|
81
|
+
* to a fully-engaged issue delegated + assigned to `appUserId` (so an unconfigured sweep never
|
|
82
|
+
* false-cancels). Set an id to `null` to simulate a hard-deleted issue (omitted from the result). */
|
|
83
|
+
engagement?: Record<string, IssueEngagement | null>;
|
|
84
|
+
}
|
|
85
|
+
export declare function createRecordingClient(opts?: RecordingClientOptions): RecordingClient;
|
|
68
86
|
//# sourceMappingURL=recording-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recording-client.d.ts","sourceRoot":"","sources":["../src/recording-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,GACzF;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,aAAa,EAAE,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,YAAY,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"recording-client.d.ts","sourceRoot":"","sources":["../src/recording-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,WAAW,EACX,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,GACzF;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,aAAa,EAAE,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAC7E;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,YAAY,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,aAAa,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,EAAE,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC;CAChC;AAED,0FAA0F;AAC1F,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;0GAEsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC;CACrD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,sBAA2B,GAAG,eAAe,CAqFxF"}
|
package/dist/recording-client.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export function createRecordingClient() {
|
|
1
|
+
export function createRecordingClient(opts = {}) {
|
|
2
2
|
const calls = [];
|
|
3
|
+
const appUserId = opts.appUserId ?? "app-user";
|
|
4
|
+
const engagement = opts.engagement ?? {};
|
|
3
5
|
return {
|
|
4
6
|
calls,
|
|
5
7
|
async postActivity(sessionId, activity) {
|
|
@@ -25,9 +27,13 @@ export function createRecordingClient() {
|
|
|
25
27
|
},
|
|
26
28
|
async startIssue(sessionId) {
|
|
27
29
|
calls.push({ call: "startIssue", sessionId });
|
|
30
|
+
// Offline stub: report a successful move so the reporter's no-op surfacing stays quiet.
|
|
31
|
+
return { moved: true, stateName: "In Progress" };
|
|
28
32
|
},
|
|
29
33
|
async moveIssueToReview(sessionId) {
|
|
30
34
|
calls.push({ call: "moveIssueToReview", sessionId });
|
|
35
|
+
// Offline stub: report a successful move so the reporter's no-op surfacing stays quiet.
|
|
36
|
+
return { moved: true, stateName: "In Review" };
|
|
31
37
|
},
|
|
32
38
|
async commentOnIssue(sessionId, body) {
|
|
33
39
|
calls.push({ call: "commentOnIssue", sessionId, body });
|
|
@@ -47,6 +53,38 @@ export function createRecordingClient() {
|
|
|
47
53
|
async setState(sessionId, state) {
|
|
48
54
|
calls.push({ call: "setState", sessionId, state });
|
|
49
55
|
},
|
|
56
|
+
async readIssueEngagement(issueIds) {
|
|
57
|
+
calls.push({ call: "readIssueEngagement", issueIds });
|
|
58
|
+
const out = new Map();
|
|
59
|
+
for (const id of issueIds) {
|
|
60
|
+
if (!id)
|
|
61
|
+
continue;
|
|
62
|
+
if (Object.prototype.hasOwnProperty.call(engagement, id)) {
|
|
63
|
+
const e = engagement[id];
|
|
64
|
+
// null => hard-deleted: leave it out of the result entirely.
|
|
65
|
+
if (e)
|
|
66
|
+
out.set(id, e);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Default: fully engaged (delegated + assigned to the app user), so a sweep against an
|
|
70
|
+
// unconfigured recording client reads every issue as still-engaged.
|
|
71
|
+
out.set(id, { present: true, archived: false, stateType: "started", delegateId: appUserId, assigneeId: appUserId });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
},
|
|
76
|
+
async appUserId() {
|
|
77
|
+
return appUserId;
|
|
78
|
+
},
|
|
79
|
+
async createSessionOnIssue(issueId) {
|
|
80
|
+
calls.push({ call: "createSessionOnIssue", issueId });
|
|
81
|
+
// Deterministic synthetic session id so offline runs and tests have a stable reference.
|
|
82
|
+
return `sess-${issueId}`;
|
|
83
|
+
},
|
|
84
|
+
async createSessionOnComment(commentId) {
|
|
85
|
+
calls.push({ call: "createSessionOnComment", commentId });
|
|
86
|
+
return `sess-${commentId}`;
|
|
87
|
+
},
|
|
50
88
|
};
|
|
51
89
|
}
|
|
52
90
|
//# sourceMappingURL=recording-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recording-client.js","sourceRoot":"","sources":["../src/recording-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recording-client.js","sourceRoot":"","sources":["../src/recording-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An actively-responding, credential-free AgentSessionClient for the `pnpm dev` offline
|
|
3
|
+
* environment. It is a superset of the recording client (./recording-client.ts): it keeps
|
|
4
|
+
* the ordered `calls` log so it is a drop-in wherever the recording client is injected, but
|
|
5
|
+
* it additionally
|
|
6
|
+
*
|
|
7
|
+
* 1. maintains a small, queryable per-session projection of what the hub last pushed
|
|
8
|
+
* (latest state, latest plan, appended activities, set external URLs), so a dev can
|
|
9
|
+
* observe a run without touching Linear, and
|
|
10
|
+
* 2. exposes a pure, pre-declared gate-decision policy (`decide`) a dev CLI reads to know
|
|
11
|
+
* which answer to fire for a stage.
|
|
12
|
+
*
|
|
13
|
+
* The gate policy is a value the dev supplies up front; it NEVER chooses the next stage
|
|
14
|
+
* (that would cross the determinism boundary). Gate answers remain human-drive: the CLI
|
|
15
|
+
* uses `decide` only to pick which `prompted` webhook to send.
|
|
16
|
+
*/
|
|
17
|
+
import type { Activity, ElicitOption, ExternalUrl, PlanItem, SessionState } from "./index.js";
|
|
18
|
+
import { type RecordingClient, type RecordingClientOptions } from "./recording-client.js";
|
|
19
|
+
/** A pre-declared gate answer for a stage. Never a control-flow decision (see file header). */
|
|
20
|
+
export type GateDecision = "allow" | "deny";
|
|
21
|
+
/** What the hub last pushed for one session, observable offline without touching Linear. */
|
|
22
|
+
export interface SessionProjection {
|
|
23
|
+
/** The latest session state the hub mirrored, or undefined if none was set yet. */
|
|
24
|
+
state?: SessionState;
|
|
25
|
+
/** The latest plan (agent-plan checklist); replaced wholesale on each `setPlan`. */
|
|
26
|
+
plan: PlanItem[];
|
|
27
|
+
/** Every activity posted to the session, in order. */
|
|
28
|
+
activities: Activity[];
|
|
29
|
+
/** The latest external-URL set; replaced wholesale on each `setExternalUrls`. */
|
|
30
|
+
externalUrls: ExternalUrl[];
|
|
31
|
+
/** The latest elicitation raised on the session, if any. */
|
|
32
|
+
elicitation?: {
|
|
33
|
+
prompt: string;
|
|
34
|
+
options?: ElicitOption[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface RespondingClientOptions extends RecordingClientOptions {
|
|
38
|
+
/** Per-stage gate answers the dev pre-declares. A stage absent here defaults to "allow". */
|
|
39
|
+
gateDecisions?: Record<string, GateDecision>;
|
|
40
|
+
}
|
|
41
|
+
export interface RespondingClient extends RecordingClient {
|
|
42
|
+
/** The queryable projection of what the hub last pushed for a session, or undefined if the
|
|
43
|
+
* hub has not touched that session. */
|
|
44
|
+
session(sessionId: string): SessionProjection | undefined;
|
|
45
|
+
/** The dev's pre-declared gate answer for a stage (default "allow"). Pure. */
|
|
46
|
+
decide(stageId: string): GateDecision;
|
|
47
|
+
}
|
|
48
|
+
export declare function createRespondingLinearClient(opts?: RespondingClientOptions): RespondingClient;
|
|
49
|
+
//# sourceMappingURL=responding-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responding-client.d.ts","sourceRoot":"","sources":["../src/responding-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,YAAY,EACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B,+FAA+F;AAC/F,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;AAE5C,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,oFAAoF;IACpF,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,sDAAsD;IACtD,UAAU,EAAE,QAAQ,EAAE,CAAC;IACvB,iFAAiF;IACjF,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,4DAA4D;IAC5D,WAAW,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;CAC5D;AAED,MAAM,WAAW,uBAAwB,SAAQ,sBAAsB;IACrE,4FAA4F;IAC5F,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD;4CACwC;IACxC,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAAC;IAC1D,8EAA8E;IAC9E,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC;CACvC;AAED,wBAAgB,4BAA4B,CAAC,IAAI,GAAE,uBAA4B,GAAG,gBAAgB,CA8CjG"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createRecordingClient, } from "./recording-client.js";
|
|
2
|
+
export function createRespondingLinearClient(opts = {}) {
|
|
3
|
+
// Reuse the recording client wholesale for the ordered call log and the seeded read-side
|
|
4
|
+
// methods (engagement, appUserId, createDocument), then wrap the mutating methods to also
|
|
5
|
+
// update a per-session projection.
|
|
6
|
+
const base = createRecordingClient(opts);
|
|
7
|
+
const gateDecisions = opts.gateDecisions ?? {};
|
|
8
|
+
const projections = new Map();
|
|
9
|
+
const projectionFor = (sessionId) => {
|
|
10
|
+
let p = projections.get(sessionId);
|
|
11
|
+
if (!p) {
|
|
12
|
+
p = { plan: [], activities: [], externalUrls: [] };
|
|
13
|
+
projections.set(sessionId, p);
|
|
14
|
+
}
|
|
15
|
+
return p;
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
...base,
|
|
19
|
+
async setPlan(sessionId, items) {
|
|
20
|
+
await base.setPlan(sessionId, items);
|
|
21
|
+
projectionFor(sessionId).plan = items;
|
|
22
|
+
},
|
|
23
|
+
async postActivity(sessionId, activity) {
|
|
24
|
+
await base.postActivity(sessionId, activity);
|
|
25
|
+
projectionFor(sessionId).activities.push(activity);
|
|
26
|
+
},
|
|
27
|
+
async raiseElicitation(sessionId, prompt, options) {
|
|
28
|
+
await base.raiseElicitation(sessionId, prompt, options);
|
|
29
|
+
projectionFor(sessionId).elicitation = { prompt, ...(options ? { options } : {}) };
|
|
30
|
+
},
|
|
31
|
+
async setExternalUrls(sessionId, urls) {
|
|
32
|
+
await base.setExternalUrls(sessionId, urls);
|
|
33
|
+
projectionFor(sessionId).externalUrls = urls;
|
|
34
|
+
},
|
|
35
|
+
async setState(sessionId, state) {
|
|
36
|
+
await base.setState(sessionId, state);
|
|
37
|
+
projectionFor(sessionId).state = state;
|
|
38
|
+
},
|
|
39
|
+
session(sessionId) {
|
|
40
|
+
return projections.get(sessionId);
|
|
41
|
+
},
|
|
42
|
+
decide(stageId) {
|
|
43
|
+
return gateDecisions[stageId] ?? "allow";
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=responding-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responding-client.js","sourceRoot":"","sources":["../src/responding-client.ts"],"names":[],"mappings":"AAuBA,OAAO,EACL,qBAAqB,GAGtB,MAAM,uBAAuB,CAAC;AAgC/B,MAAM,UAAU,4BAA4B,CAAC,OAAgC,EAAE;IAC7E,yFAAyF;IACzF,0FAA0F;IAC1F,mCAAmC;IACnC,MAAM,IAAI,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,GAAG,EAA6B,CAAC;IAEzD,MAAM,aAAa,GAAG,CAAC,SAAiB,EAAqB,EAAE;QAC7D,IAAI,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;YACnD,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC;IAEF,OAAO;QACL,GAAG,IAAI;QACP,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK;YAC5B,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACrC,aAAa,CAAC,SAAS,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;QACxC,CAAC;QACD,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ;YACpC,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC7C,aAAa,CAAC,SAAS,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO;YAC/C,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACxD,aAAa,CAAC,SAAS,CAAC,CAAC,WAAW,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QACrF,CAAC;QACD,KAAK,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI;YACnC,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC5C,aAAa,CAAC,SAAS,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC;QAC/C,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK;YAC7B,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACtC,aAAa,CAAC,SAAS,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC;QACzC,CAAC;QACD,OAAO,CAAC,SAAS;YACf,OAAO,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,CAAC,OAAO;YACZ,OAAO,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC;QAC3C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/teams.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A workspace team, reduced to just what the picker needs. */
|
|
2
|
+
export interface Team {
|
|
3
|
+
id: string;
|
|
4
|
+
key: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/** The injectable seam: list the workspace's teams. */
|
|
8
|
+
export interface TeamsApi {
|
|
9
|
+
listTeams(): Promise<Team[]>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* List the workspace's teams via the `TeamsApi` seam, normalised to `{id,key,name}` and sorted by
|
|
13
|
+
* key so the picker renders in a stable, deterministic order. A thin deterministic transform - no
|
|
14
|
+
* control flow, no network - so it feeds the portal picker directly.
|
|
15
|
+
*/
|
|
16
|
+
export declare function listWorkspaceTeams(api: TeamsApi): Promise<Team[]>;
|
|
17
|
+
/** The live `TeamsApi` backed by a Linear bearer token (workspace-scoped teams). Paginates
|
|
18
|
+
* `client.teams()` exactly as `linearLabelApi` paginates `issueLabels()`. */
|
|
19
|
+
export declare function linearTeamsApi(token: string): TeamsApi;
|
|
20
|
+
//# sourceMappingURL=teams.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams.d.ts","sourceRoot":"","sources":["../src/teams.ts"],"names":[],"mappings":"AAQA,+DAA+D;AAC/D,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uDAAuD;AACvD,MAAM,WAAW,QAAQ;IACvB,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAKvE;AAED;8EAC8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAStD"}
|
package/dist/teams.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-team listing (DHK-777). A repository routes issues to itself partly by team key
|
|
3
|
+
* (`Repository.routing.teamKeys`); the portal offers the real keys instead of free text by reading
|
|
4
|
+
* the linked workspace's teams. The `TeamsApi` seam keeps this pure and unit-testable: the live
|
|
5
|
+
* adapter (`linearTeamsApi`) wraps `@linear/sdk`, while tests inject a fake - mirroring `LabelApi`.
|
|
6
|
+
*/
|
|
7
|
+
import { LinearClient } from "@linear/sdk";
|
|
8
|
+
/**
|
|
9
|
+
* List the workspace's teams via the `TeamsApi` seam, normalised to `{id,key,name}` and sorted by
|
|
10
|
+
* key so the picker renders in a stable, deterministic order. A thin deterministic transform - no
|
|
11
|
+
* control flow, no network - so it feeds the portal picker directly.
|
|
12
|
+
*/
|
|
13
|
+
export async function listWorkspaceTeams(api) {
|
|
14
|
+
const teams = await api.listTeams();
|
|
15
|
+
return teams
|
|
16
|
+
.map((t) => ({ id: t.id, key: t.key, name: t.name }))
|
|
17
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
18
|
+
}
|
|
19
|
+
/** The live `TeamsApi` backed by a Linear bearer token (workspace-scoped teams). Paginates
|
|
20
|
+
* `client.teams()` exactly as `linearLabelApi` paginates `issueLabels()`. */
|
|
21
|
+
export function linearTeamsApi(token) {
|
|
22
|
+
const client = new LinearClient({ accessToken: token });
|
|
23
|
+
return {
|
|
24
|
+
async listTeams() {
|
|
25
|
+
const conn = await client.teams();
|
|
26
|
+
while (conn.pageInfo.hasNextPage)
|
|
27
|
+
await conn.fetchNext();
|
|
28
|
+
return conn.nodes.map((t) => ({ id: t.id, key: t.key, name: t.name }));
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=teams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams.js","sourceRoot":"","sources":["../src/teams.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAc3C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAa;IACpD,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC;IACpC,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SACpD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;8EAC8E;AAC9E,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,OAAO;QACL,KAAK,CAAC,SAAS;YACb,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW;gBAAE,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzE,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dahrk/linear",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Linear Agent Session API client and webhook verification. The native control surface: activities, agent-plan checklist, elicitation gates, prompted-drive, stop signal, externalUrls.",
|
|
7
|
-
"
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/skakel/dahrk.git"
|
|
10
|
-
},
|
|
11
|
-
"homepage": "https://github.com/skakel/dahrk#readme",
|
|
7
|
+
"homepage": "https://dahrk.ai",
|
|
12
8
|
"bugs": {
|
|
13
|
-
"url": "https://github.com/
|
|
9
|
+
"url": "https://github.com/dahrkai/dahrk-node/issues"
|
|
14
10
|
},
|
|
15
11
|
"exports": {
|
|
16
12
|
".": {
|
|
@@ -19,14 +15,15 @@
|
|
|
19
15
|
}
|
|
20
16
|
},
|
|
21
17
|
"files": [
|
|
22
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"src"
|
|
23
20
|
],
|
|
24
21
|
"publishConfig": {
|
|
25
22
|
"access": "public"
|
|
26
23
|
},
|
|
27
24
|
"dependencies": {
|
|
28
25
|
"@linear/sdk": "^86.0.0",
|
|
29
|
-
"@dahrk/contracts": "0.
|
|
26
|
+
"@dahrk/contracts": "0.29.0"
|
|
30
27
|
},
|
|
31
28
|
"devDependencies": {
|
|
32
29
|
"tsx": "^4.19.0"
|
|
@@ -35,7 +32,8 @@
|
|
|
35
32
|
"build": "tsc -p tsconfig.json",
|
|
36
33
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
37
34
|
"test": "node --import tsx --test test/*.test.ts",
|
|
38
|
-
"spike/live": "tsx spike/run.ts"
|
|
35
|
+
"spike/live": "tsx spike/run.ts",
|
|
36
|
+
"spike/filter": "tsx spike/filter-probe.ts"
|
|
39
37
|
},
|
|
40
38
|
"main": "./dist/index.js",
|
|
41
39
|
"types": "./dist/index.d.ts"
|