@askthew/mcp-plugin 0.2.8 → 0.4.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.
Files changed (60) hide show
  1. package/README.md +65 -16
  2. package/dist/auth-pending.test.d.ts +1 -0
  3. package/dist/auth-pending.test.js +56 -0
  4. package/dist/cli-actions.test.d.ts +1 -0
  5. package/dist/cli-actions.test.js +71 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.js +412 -18
  8. package/dist/cli.test.d.ts +1 -0
  9. package/dist/cli.test.js +274 -0
  10. package/dist/free-tier-policy.test.d.ts +1 -0
  11. package/dist/free-tier-policy.test.js +57 -0
  12. package/dist/index.d.ts +59 -13
  13. package/dist/index.js +1736 -103
  14. package/dist/index.test.d.ts +1 -0
  15. package/dist/index.test.js +952 -0
  16. package/dist/install.d.ts +56 -1
  17. package/dist/install.js +171 -26
  18. package/dist/install.test.d.ts +1 -0
  19. package/dist/install.test.js +297 -0
  20. package/dist/lib/auth-magic-link.d.ts +22 -0
  21. package/dist/lib/auth-magic-link.js +43 -0
  22. package/dist/lib/auth-pending.d.ts +23 -0
  23. package/dist/lib/auth-pending.js +36 -0
  24. package/dist/lib/cli-actions.d.ts +28 -0
  25. package/dist/lib/cli-actions.js +104 -0
  26. package/dist/lib/free-install-registration.d.ts +27 -0
  27. package/dist/lib/free-install-registration.js +52 -0
  28. package/dist/lib/free-tier-policy.d.ts +23 -0
  29. package/dist/lib/free-tier-policy.js +68 -0
  30. package/dist/lib/local-identity.d.ts +44 -0
  31. package/dist/lib/local-identity.js +81 -0
  32. package/dist/lib/local-store.d.ts +130 -0
  33. package/dist/lib/local-store.js +595 -0
  34. package/dist/lib/loopback-auth.d.ts +8 -0
  35. package/dist/lib/loopback-auth.js +30 -0
  36. package/dist/lib/paths.d.ts +9 -0
  37. package/dist/lib/paths.js +50 -0
  38. package/dist/lib/telemetry.d.ts +25 -0
  39. package/dist/lib/telemetry.js +159 -0
  40. package/dist/lib/timeline-insights.d.ts +23 -0
  41. package/dist/lib/timeline-insights.js +115 -0
  42. package/dist/lib/tip-engine.d.ts +18 -0
  43. package/dist/lib/tip-engine.js +237 -0
  44. package/dist/lib/upgrade-nudge.d.ts +19 -0
  45. package/dist/lib/upgrade-nudge.js +37 -0
  46. package/dist/lib/upgrade-sync.d.ts +38 -0
  47. package/dist/lib/upgrade-sync.js +60 -0
  48. package/dist/local-identity.test.d.ts +1 -0
  49. package/dist/local-identity.test.js +29 -0
  50. package/dist/local-store.test.d.ts +1 -0
  51. package/dist/local-store.test.js +71 -0
  52. package/dist/scope.d.ts +1 -2
  53. package/dist/scope.js +56 -8
  54. package/dist/scope.test.d.ts +1 -0
  55. package/dist/scope.test.js +49 -0
  56. package/dist/timeline-insights.test.d.ts +1 -0
  57. package/dist/timeline-insights.test.js +85 -0
  58. package/dist/tip-engine.test.d.ts +1 -0
  59. package/dist/tip-engine.test.js +51 -0
  60. package/package.json +7 -10
@@ -0,0 +1,68 @@
1
+ import fs from "node:fs";
2
+ import { credentialsPath, readJsonFile } from "./paths.js";
3
+ import { loadLocalIdentity } from "./local-identity.js";
4
+ function clean(value) {
5
+ return String(value ?? "").trim().replace(/^['"]/, "").replace(/['"]$/, "");
6
+ }
7
+ export function loadCliCredentials(env = process.env) {
8
+ const explicitToken = clean(env.ASKTHEW_CLI_TOKEN);
9
+ if (explicitToken) {
10
+ return {
11
+ userId: clean(env.ASKTHEW_USER_ID) || "local",
12
+ cliToken: explicitToken,
13
+ cliTokenId: clean(env.ASKTHEW_CLI_TOKEN_ID) || "env",
14
+ apiUrl: clean(env.ASKTHEW_API_URL) || undefined,
15
+ telemetryOptOut: env.ASKTHEW_TELEMETRY === "off",
16
+ };
17
+ }
18
+ const localIdentity = loadLocalIdentity(env);
19
+ if (localIdentity) {
20
+ return {
21
+ email: localIdentity.emailClaim,
22
+ userId: localIdentity.installId,
23
+ cliToken: localIdentity.installId,
24
+ cliTokenId: localIdentity.installId,
25
+ apiUrl: localIdentity.apiUrl,
26
+ telemetryOptOut: localIdentity.telemetryOptOut,
27
+ identityKind: "local_install",
28
+ installId: localIdentity.installId,
29
+ localIdentity,
30
+ };
31
+ }
32
+ const creds = readJsonFile(credentialsPath(env));
33
+ if (!creds?.cliToken || !creds.userId || !creds.cliTokenId) {
34
+ return null;
35
+ }
36
+ return creds;
37
+ }
38
+ export function resolveMcpMode(env = process.env) {
39
+ const installToken = clean(env.ASKTHEW_INSTALL_TOKEN);
40
+ if (installToken) {
41
+ return {
42
+ mode: "paid",
43
+ installToken,
44
+ reason: "workspace_install_token",
45
+ };
46
+ }
47
+ const credentials = loadCliCredentials(env);
48
+ if (credentials?.cliToken) {
49
+ return {
50
+ mode: "free",
51
+ cliCredentials: credentials,
52
+ reason: "cli_free_tier_credentials",
53
+ };
54
+ }
55
+ if (clean(env.ASKTHEW_FREE_MODE) === "1" || clean(env.ASKTHEW_FREE_MODE).toLowerCase() === "true") {
56
+ return {
57
+ mode: "free_pending_auth",
58
+ reason: fs.existsSync(credentialsPath(env)) ? "invalid_cli_credentials" : "free_mode_no_credentials",
59
+ };
60
+ }
61
+ return {
62
+ mode: "unauthenticated",
63
+ reason: "no_identity",
64
+ };
65
+ }
66
+ export function isTelemetryOptedOut(env = process.env, credentials = loadCliCredentials(env)) {
67
+ return env.ASKTHEW_TELEMETRY === "off" || credentials?.telemetryOptOut === true;
68
+ }
@@ -0,0 +1,44 @@
1
+ export interface LocalInstallIdentity {
2
+ installId: string;
3
+ privateKey: string;
4
+ publicKey: string;
5
+ claimCode: string;
6
+ emailClaim?: string;
7
+ createdAt: string;
8
+ registeredAt?: string;
9
+ registrationError?: string;
10
+ apiUrl?: string;
11
+ telemetryOptOut?: boolean;
12
+ }
13
+ export interface PublicInstallIdentity {
14
+ installId: string;
15
+ publicKey: string;
16
+ claimCode: string;
17
+ emailClaim?: string;
18
+ createdAt: string;
19
+ registeredAt?: string;
20
+ registrationError?: string;
21
+ apiUrl?: string;
22
+ telemetryOptOut?: boolean;
23
+ }
24
+ export declare function loadLocalIdentity(env?: NodeJS.ProcessEnv): LocalInstallIdentity | null;
25
+ export declare function publicIdentity(identity: LocalInstallIdentity): PublicInstallIdentity;
26
+ export declare function ensureLocalIdentity(input?: {
27
+ emailClaim?: string | null;
28
+ apiUrl?: string;
29
+ telemetryOptOut?: boolean;
30
+ env?: NodeJS.ProcessEnv;
31
+ }): LocalInstallIdentity;
32
+ export declare function markLocalIdentityRegistered(input: {
33
+ registeredAt?: string;
34
+ registrationError?: string;
35
+ env?: NodeJS.ProcessEnv;
36
+ }): LocalInstallIdentity | null;
37
+ export declare function signLocalIdentityPayload(input: {
38
+ identity: LocalInstallIdentity;
39
+ body: string;
40
+ timestamp?: string;
41
+ }): {
42
+ timestamp: string;
43
+ signature: string;
44
+ };
@@ -0,0 +1,81 @@
1
+ import crypto from "node:crypto";
2
+ import { identityPath, readJsonFile, writePrivateJson } from "./paths.js";
3
+ function normalizeEmail(email) {
4
+ const value = String(email ?? "").trim().toLowerCase();
5
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value) ? value : undefined;
6
+ }
7
+ function generateClaimCode() {
8
+ return crypto.randomBytes(6).toString("base64url").toUpperCase();
9
+ }
10
+ export function loadLocalIdentity(env = process.env) {
11
+ const identity = readJsonFile(identityPath(env));
12
+ if (!identity?.installId || !identity.privateKey || !identity.publicKey || !identity.claimCode) {
13
+ return null;
14
+ }
15
+ return identity;
16
+ }
17
+ export function publicIdentity(identity) {
18
+ return {
19
+ installId: identity.installId,
20
+ publicKey: identity.publicKey,
21
+ claimCode: identity.claimCode,
22
+ emailClaim: identity.emailClaim,
23
+ createdAt: identity.createdAt,
24
+ registeredAt: identity.registeredAt,
25
+ registrationError: identity.registrationError,
26
+ apiUrl: identity.apiUrl,
27
+ telemetryOptOut: identity.telemetryOptOut,
28
+ };
29
+ }
30
+ export function ensureLocalIdentity(input = {}) {
31
+ const env = input.env ?? process.env;
32
+ const existing = loadLocalIdentity(env);
33
+ const emailClaim = normalizeEmail(input.emailClaim);
34
+ if (existing) {
35
+ const next = {
36
+ ...existing,
37
+ ...(emailClaim ? { emailClaim } : {}),
38
+ ...(input.apiUrl ? { apiUrl: input.apiUrl } : {}),
39
+ ...(typeof input.telemetryOptOut === "boolean" ? { telemetryOptOut: input.telemetryOptOut } : {}),
40
+ };
41
+ writePrivateJson(identityPath(env), next);
42
+ return next;
43
+ }
44
+ const keyPair = crypto.generateKeyPairSync("ed25519", {
45
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
46
+ publicKeyEncoding: { type: "spki", format: "pem" },
47
+ });
48
+ const identity = {
49
+ installId: crypto.randomUUID(),
50
+ privateKey: keyPair.privateKey,
51
+ publicKey: keyPair.publicKey,
52
+ claimCode: generateClaimCode(),
53
+ ...(emailClaim ? { emailClaim } : {}),
54
+ ...(input.apiUrl ? { apiUrl: input.apiUrl } : {}),
55
+ ...(typeof input.telemetryOptOut === "boolean" ? { telemetryOptOut: input.telemetryOptOut } : {}),
56
+ createdAt: new Date().toISOString(),
57
+ };
58
+ writePrivateJson(identityPath(env), identity);
59
+ return identity;
60
+ }
61
+ export function markLocalIdentityRegistered(input) {
62
+ const env = input.env ?? process.env;
63
+ const identity = loadLocalIdentity(env);
64
+ if (!identity)
65
+ return null;
66
+ const next = {
67
+ ...identity,
68
+ registeredAt: input.registrationError ? identity.registeredAt : (input.registeredAt ?? new Date().toISOString()),
69
+ registrationError: input.registrationError,
70
+ };
71
+ writePrivateJson(identityPath(env), next);
72
+ return next;
73
+ }
74
+ export function signLocalIdentityPayload(input) {
75
+ const timestamp = input.timestamp ?? new Date().toISOString();
76
+ const signature = crypto.sign(null, Buffer.from(`${timestamp}.${input.body}`), input.identity.privateKey).toString("base64url");
77
+ return {
78
+ timestamp,
79
+ signature,
80
+ };
81
+ }
@@ -0,0 +1,130 @@
1
+ export type SignalKind = "setup_complete" | "session_checkpoint" | "direction_change" | "implementation_update" | "verification_result" | "final_summary";
2
+ export type DecisionStatus = "proposed" | "committed" | "shipped" | "abandoned";
3
+ export interface LocalSignalInput {
4
+ sessionId: string;
5
+ sequence: number;
6
+ kind: SignalKind;
7
+ summary: string;
8
+ evidence?: unknown[];
9
+ filesTouched?: string[];
10
+ commandsRun?: string[];
11
+ metadata?: Record<string, unknown>;
12
+ scopeKey?: string | null;
13
+ }
14
+ export interface LocalSignal {
15
+ id: number;
16
+ sessionId: string;
17
+ sequence: number;
18
+ kind: SignalKind;
19
+ summary: string;
20
+ evidence: unknown[];
21
+ filesTouched: string[];
22
+ commandsRun: string[];
23
+ metadata: Record<string, unknown>;
24
+ capturedAt: string;
25
+ scopeKey?: string | null;
26
+ }
27
+ export interface LocalDecision {
28
+ id: string;
29
+ sessionId: string | null;
30
+ headline: string;
31
+ why: string | null;
32
+ status: DecisionStatus;
33
+ alignment: "aligned" | "orthogonal" | "conflicts" | "ambiguous" | null;
34
+ files: string[];
35
+ sourceSignalIds: number[];
36
+ rawContent: string;
37
+ createdAt: string;
38
+ updatedAt: string;
39
+ uploadedAt: string | null;
40
+ scopeKey?: string | null;
41
+ proposedAt?: string | null;
42
+ committedAt?: string | null;
43
+ shippedAt?: string | null;
44
+ abandonedAt?: string | null;
45
+ }
46
+ export interface LocalDecisionInput {
47
+ id?: string;
48
+ sessionId?: string | null;
49
+ headline?: string;
50
+ why?: string | null;
51
+ status?: DecisionStatus;
52
+ alignment?: LocalDecision["alignment"];
53
+ files?: string[];
54
+ sourceSignalIds?: number[];
55
+ rawContent: string;
56
+ scopeKey?: string | null;
57
+ }
58
+ export interface TelemetryOutboxRow {
59
+ id: number;
60
+ payload: Record<string, unknown>;
61
+ createdAt: string;
62
+ attempts: number;
63
+ lastAttemptAt: string | null;
64
+ deliveredAt: string | null;
65
+ }
66
+ export declare class LocalStore {
67
+ private readonly storePath;
68
+ private data;
69
+ private db;
70
+ private jsonMode;
71
+ private jsonPath;
72
+ private constructor();
73
+ static open(input?: {
74
+ path?: string;
75
+ }): LocalStore;
76
+ get usingJsonFallback(): boolean;
77
+ close(): void;
78
+ migrate(): void;
79
+ getMeta(key: string): string;
80
+ setMeta(key: string, value: string): void;
81
+ insertSignal(input: LocalSignalInput): LocalSignal;
82
+ listSignals(input?: {
83
+ sessionId?: string;
84
+ scopeKey?: string | null;
85
+ since?: string;
86
+ limit?: number;
87
+ cursor?: string;
88
+ uploaded?: boolean;
89
+ }): LocalSignal[];
90
+ getSignal(id: number): LocalSignal | null;
91
+ mostRecentSessionId(input?: {
92
+ scopeKey?: string | null;
93
+ }): any;
94
+ createDecision(input: LocalDecisionInput): LocalDecision;
95
+ updateDecision(id: string, patch: Partial<Omit<LocalDecision, "id" | "createdAt">>): LocalDecision | null;
96
+ deleteDecision(id: string): boolean;
97
+ getDecision(id: string): LocalDecision | null;
98
+ listDecisions(input?: {
99
+ status?: DecisionStatus;
100
+ limit?: number;
101
+ since?: string;
102
+ cursor?: string;
103
+ sessionId?: string;
104
+ scopeKey?: string | null;
105
+ pendingUploadOnly?: boolean;
106
+ }): LocalDecision[];
107
+ listSessionIds(input?: {
108
+ limit?: number;
109
+ scopeKey?: string | null;
110
+ }): string[];
111
+ listSignalsByIds(ids: number[]): LocalSignal[];
112
+ getDecisionForSignal(signalId: number): LocalDecision | null;
113
+ enqueueTelemetry(payload: Record<string, unknown>): number;
114
+ listTelemetryOutbox(input?: {
115
+ undeliveredOnly?: boolean;
116
+ limit?: number;
117
+ }): TelemetryOutboxRow[];
118
+ markTelemetryAttempt(id: number, delivered: boolean): void;
119
+ stats(): {
120
+ signals: number;
121
+ decisions: number;
122
+ decisionsByStatus: Record<string, number>;
123
+ lastSessionId: string | null;
124
+ jsonFallback: boolean;
125
+ };
126
+ private openDatabase;
127
+ private persistJson;
128
+ private nextJsonId;
129
+ private addColumnIfMissing;
130
+ }