@compilr-dev/sdk 0.9.15 → 0.9.17
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/dist/capabilities/packs.js +1 -1
- package/dist/entitlements/cache.d.ts +84 -0
- package/dist/entitlements/cache.js +185 -0
- package/dist/entitlements/index.d.ts +7 -0
- package/dist/entitlements/index.js +5 -0
- package/dist/entitlements/types.d.ts +45 -0
- package/dist/entitlements/types.js +23 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -1
- package/dist/system-prompt/modules.js +1 -1
- package/dist/team/skill-requirements.js +2 -2
- package/dist/team/tool-config.js +2 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/propose-alternatives-tool.d.ts +64 -0
- package/dist/tools/propose-alternatives-tool.js +128 -0
- package/package.json +1 -1
|
@@ -53,7 +53,7 @@ export const CAPABILITY_PACKS = {
|
|
|
53
53
|
interaction: {
|
|
54
54
|
id: 'interaction',
|
|
55
55
|
label: 'User Interaction',
|
|
56
|
-
tools: ['ask_user', 'ask_user_simple', 'suggest'],
|
|
56
|
+
tools: ['ask_user', 'ask_user_simple', 'propose_alternatives', 'suggest'],
|
|
57
57
|
readOnly: true,
|
|
58
58
|
promptModules: [],
|
|
59
59
|
estimatedPromptTokens: 0,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntitlementCache — In-memory cache for server-driven entitlements.
|
|
3
|
+
*
|
|
4
|
+
* The server is the single source of truth. This cache:
|
|
5
|
+
* 1. Fetches entitlements from the server on first access
|
|
6
|
+
* 2. Caches in memory with a configurable TTL (default: 1 hour)
|
|
7
|
+
* 3. Falls back to encrypted offline backup if server is unreachable
|
|
8
|
+
* 4. Degrades to minimal fallback limits after offline grace period expires
|
|
9
|
+
*
|
|
10
|
+
* The host app (Desktop/CLI) provides:
|
|
11
|
+
* - fetchFn: how to call the server endpoint
|
|
12
|
+
* - store (optional): encrypted persistence for offline grace (IEntitlementStore)
|
|
13
|
+
*/
|
|
14
|
+
import type { TierLimits, EntitlementResponse, LimitCheckResult } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Persistence adapter for offline grace period.
|
|
17
|
+
* Desktop: Electron safeStorage. CLI: encrypted file.
|
|
18
|
+
*/
|
|
19
|
+
export interface IEntitlementStore {
|
|
20
|
+
/** Save encrypted entitlement data */
|
|
21
|
+
save(data: string): Promise<void>;
|
|
22
|
+
/** Load encrypted entitlement data (null if not found) */
|
|
23
|
+
load(): Promise<string | null>;
|
|
24
|
+
}
|
|
25
|
+
export interface EntitlementCacheConfig {
|
|
26
|
+
/** How to fetch entitlements from the server */
|
|
27
|
+
fetchFn: () => Promise<EntitlementResponse>;
|
|
28
|
+
/** Optional encrypted persistence for offline grace */
|
|
29
|
+
store?: IEntitlementStore;
|
|
30
|
+
/** Cache TTL in milliseconds (default: 1 hour) */
|
|
31
|
+
ttlMs?: number;
|
|
32
|
+
/** Offline grace period in milliseconds (default: 24 hours) */
|
|
33
|
+
offlineGraceMs?: number;
|
|
34
|
+
/** Called when entitlements are refreshed (for logging, UI updates) */
|
|
35
|
+
onRefresh?: (response: EntitlementResponse) => void;
|
|
36
|
+
}
|
|
37
|
+
export declare class EntitlementCache {
|
|
38
|
+
private entitlements;
|
|
39
|
+
private fetchedAtMonotonic;
|
|
40
|
+
private fetchedAtWall;
|
|
41
|
+
private readonly fetchFn;
|
|
42
|
+
private readonly store?;
|
|
43
|
+
private readonly ttlMs;
|
|
44
|
+
private readonly offlineGraceMs;
|
|
45
|
+
private readonly onRefresh?;
|
|
46
|
+
private fetchPromise;
|
|
47
|
+
constructor(config: EntitlementCacheConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Get current tier limits. Fetches from server if stale, falls back to cache/store/defaults.
|
|
50
|
+
*/
|
|
51
|
+
get(): Promise<TierLimits>;
|
|
52
|
+
/**
|
|
53
|
+
* Get the full entitlement response (tier, beta, features, etc.)
|
|
54
|
+
*/
|
|
55
|
+
getResponse(): Promise<EntitlementResponse | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Check if a numeric limit is exceeded.
|
|
58
|
+
*/
|
|
59
|
+
checkLimit(field: keyof TierLimits, currentCount: number): LimitCheckResult;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a feature flag is enabled for the current user.
|
|
62
|
+
*/
|
|
63
|
+
isFeatureEnabled(featureSlug: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the user is in beta mode (all features unlocked).
|
|
66
|
+
*/
|
|
67
|
+
isBeta(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Current tier name.
|
|
70
|
+
*/
|
|
71
|
+
getTier(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Force a refresh from the server (e.g., after login, after subscription change).
|
|
74
|
+
*/
|
|
75
|
+
refresh(): Promise<TierLimits>;
|
|
76
|
+
/**
|
|
77
|
+
* Clear the cache (e.g., on logout).
|
|
78
|
+
*/
|
|
79
|
+
clear(): void;
|
|
80
|
+
private isFresh;
|
|
81
|
+
private isWithinOfflineGrace;
|
|
82
|
+
private fetchAndCache;
|
|
83
|
+
private fallback;
|
|
84
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntitlementCache — In-memory cache for server-driven entitlements.
|
|
3
|
+
*
|
|
4
|
+
* The server is the single source of truth. This cache:
|
|
5
|
+
* 1. Fetches entitlements from the server on first access
|
|
6
|
+
* 2. Caches in memory with a configurable TTL (default: 1 hour)
|
|
7
|
+
* 3. Falls back to encrypted offline backup if server is unreachable
|
|
8
|
+
* 4. Degrades to minimal fallback limits after offline grace period expires
|
|
9
|
+
*
|
|
10
|
+
* The host app (Desktop/CLI) provides:
|
|
11
|
+
* - fetchFn: how to call the server endpoint
|
|
12
|
+
* - store (optional): encrypted persistence for offline grace (IEntitlementStore)
|
|
13
|
+
*/
|
|
14
|
+
import { UNLIMITED, OFFLINE_FALLBACK_LIMITS } from './types.js';
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Cache Implementation
|
|
17
|
+
// =============================================================================
|
|
18
|
+
const DEFAULT_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
19
|
+
const DEFAULT_OFFLINE_GRACE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
20
|
+
export class EntitlementCache {
|
|
21
|
+
entitlements = null;
|
|
22
|
+
fetchedAtMonotonic = 0n; // process.hrtime.bigint() — monotonic, not clockable
|
|
23
|
+
fetchedAtWall = 0; // Date.now() — for serialization only
|
|
24
|
+
fetchFn;
|
|
25
|
+
store;
|
|
26
|
+
ttlMs;
|
|
27
|
+
offlineGraceMs;
|
|
28
|
+
onRefresh;
|
|
29
|
+
fetchPromise = null; // Dedup concurrent fetches
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.fetchFn = config.fetchFn;
|
|
32
|
+
this.store = config.store;
|
|
33
|
+
this.ttlMs = config.ttlMs ?? DEFAULT_TTL_MS;
|
|
34
|
+
this.offlineGraceMs = config.offlineGraceMs ?? DEFAULT_OFFLINE_GRACE_MS;
|
|
35
|
+
this.onRefresh = config.onRefresh;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get current tier limits. Fetches from server if stale, falls back to cache/store/defaults.
|
|
39
|
+
*/
|
|
40
|
+
async get() {
|
|
41
|
+
// Fresh cache → return immediately
|
|
42
|
+
if (this.entitlements && this.isFresh()) {
|
|
43
|
+
return this.entitlements.limits;
|
|
44
|
+
}
|
|
45
|
+
// Dedup concurrent fetch calls
|
|
46
|
+
if (this.fetchPromise)
|
|
47
|
+
return this.fetchPromise;
|
|
48
|
+
this.fetchPromise = this.fetchAndCache();
|
|
49
|
+
try {
|
|
50
|
+
return await this.fetchPromise;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
this.fetchPromise = null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the full entitlement response (tier, beta, features, etc.)
|
|
58
|
+
*/
|
|
59
|
+
async getResponse() {
|
|
60
|
+
await this.get(); // Ensure cache is populated
|
|
61
|
+
return this.entitlements;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check if a numeric limit is exceeded.
|
|
65
|
+
*/
|
|
66
|
+
checkLimit(field, currentCount) {
|
|
67
|
+
if (!this.entitlements) {
|
|
68
|
+
return { allowed: true }; // No entitlements loaded yet — allow (fail open during init)
|
|
69
|
+
}
|
|
70
|
+
const limit = this.entitlements.limits[field];
|
|
71
|
+
// Unlimited
|
|
72
|
+
if (limit === UNLIMITED) {
|
|
73
|
+
return { allowed: true };
|
|
74
|
+
}
|
|
75
|
+
if (currentCount >= limit) {
|
|
76
|
+
return {
|
|
77
|
+
allowed: false,
|
|
78
|
+
reason: `Limit reached (${String(currentCount)}/${String(limit)})`,
|
|
79
|
+
upgradeHint: this.entitlements.beta
|
|
80
|
+
? undefined // No upgrade prompt during beta
|
|
81
|
+
: 'Upgrade to Pro for unlimited access',
|
|
82
|
+
current: currentCount,
|
|
83
|
+
limit,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { allowed: true, current: currentCount, limit };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check if a feature flag is enabled for the current user.
|
|
90
|
+
*/
|
|
91
|
+
isFeatureEnabled(featureSlug) {
|
|
92
|
+
if (!this.entitlements)
|
|
93
|
+
return true; // Fail open during init
|
|
94
|
+
return featureSlug in this.entitlements.features
|
|
95
|
+
? this.entitlements.features[featureSlug]
|
|
96
|
+
: true; // Default: enabled if not explicitly listed
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Whether the user is in beta mode (all features unlocked).
|
|
100
|
+
*/
|
|
101
|
+
isBeta() {
|
|
102
|
+
return this.entitlements?.beta ?? false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Current tier name.
|
|
106
|
+
*/
|
|
107
|
+
getTier() {
|
|
108
|
+
return this.entitlements?.tier ?? 'free';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Force a refresh from the server (e.g., after login, after subscription change).
|
|
112
|
+
*/
|
|
113
|
+
async refresh() {
|
|
114
|
+
this.fetchPromise = null; // Clear any pending dedup
|
|
115
|
+
return this.fetchAndCache();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Clear the cache (e.g., on logout).
|
|
119
|
+
*/
|
|
120
|
+
clear() {
|
|
121
|
+
this.entitlements = null;
|
|
122
|
+
this.fetchedAtMonotonic = 0n;
|
|
123
|
+
this.fetchedAtWall = 0;
|
|
124
|
+
this.fetchPromise = null;
|
|
125
|
+
}
|
|
126
|
+
// ─── Private ───────────────────────────────────────────────────────────────
|
|
127
|
+
isFresh() {
|
|
128
|
+
if (this.fetchedAtMonotonic === 0n)
|
|
129
|
+
return false;
|
|
130
|
+
const elapsed = process.hrtime.bigint() - this.fetchedAtMonotonic;
|
|
131
|
+
return elapsed < BigInt(this.ttlMs) * 1000000n; // Convert ms to ns
|
|
132
|
+
}
|
|
133
|
+
isWithinOfflineGrace() {
|
|
134
|
+
if (this.fetchedAtMonotonic === 0n)
|
|
135
|
+
return false;
|
|
136
|
+
const elapsed = process.hrtime.bigint() - this.fetchedAtMonotonic;
|
|
137
|
+
return elapsed < BigInt(this.offlineGraceMs) * 1000000n;
|
|
138
|
+
}
|
|
139
|
+
async fetchAndCache() {
|
|
140
|
+
try {
|
|
141
|
+
const response = await this.fetchFn();
|
|
142
|
+
this.entitlements = response;
|
|
143
|
+
this.fetchedAtMonotonic = process.hrtime.bigint();
|
|
144
|
+
this.fetchedAtWall = Date.now();
|
|
145
|
+
// Persist encrypted for offline grace
|
|
146
|
+
if (this.store) {
|
|
147
|
+
const serialized = JSON.stringify({ response, fetchedAtWall: this.fetchedAtWall });
|
|
148
|
+
await this.store.save(serialized).catch(() => { }); // Best effort
|
|
149
|
+
}
|
|
150
|
+
this.onRefresh?.(response);
|
|
151
|
+
return response.limits;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Server unreachable — try fallbacks
|
|
155
|
+
return this.fallback();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async fallback() {
|
|
159
|
+
// 1. Stale in-memory cache within offline grace
|
|
160
|
+
if (this.entitlements && this.isWithinOfflineGrace()) {
|
|
161
|
+
return this.entitlements.limits;
|
|
162
|
+
}
|
|
163
|
+
// 2. Encrypted store backup
|
|
164
|
+
if (this.store) {
|
|
165
|
+
try {
|
|
166
|
+
const stored = await this.store.load();
|
|
167
|
+
if (stored) {
|
|
168
|
+
const parsed = JSON.parse(stored);
|
|
169
|
+
const age = Date.now() - parsed.fetchedAtWall;
|
|
170
|
+
if (age < this.offlineGraceMs) {
|
|
171
|
+
this.entitlements = parsed.response;
|
|
172
|
+
this.fetchedAtMonotonic = process.hrtime.bigint(); // Reset monotonic to now
|
|
173
|
+
this.fetchedAtWall = parsed.fetchedAtWall;
|
|
174
|
+
return this.entitlements.limits;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Corrupted store — fall through to defaults
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 3. Offline grace expired or no store — degrade to minimal fallback
|
|
183
|
+
return OFFLINE_FALLBACK_LIMITS;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlements — Server-driven tier management for pricing.
|
|
3
|
+
*/
|
|
4
|
+
export type { TierLimits, EntitlementResponse, LimitCheckResult, } from './types.js';
|
|
5
|
+
export { UNLIMITED, OFFLINE_FALLBACK_LIMITS, } from './types.js';
|
|
6
|
+
export type { IEntitlementStore, EntitlementCacheConfig, } from './cache.js';
|
|
7
|
+
export { EntitlementCache } from './cache.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlement Types — Shared between server, SDK, Desktop, and CLI.
|
|
3
|
+
*
|
|
4
|
+
* TierLimits values: -1 = unlimited, 0+ = actual limit.
|
|
5
|
+
*/
|
|
6
|
+
/** Numeric limits for a tier. All values server-driven — never hardcoded in app logic. */
|
|
7
|
+
export interface TierLimits {
|
|
8
|
+
maxProjects: number;
|
|
9
|
+
maxTeamAgents: number;
|
|
10
|
+
maxCustomAgents: number;
|
|
11
|
+
maxTemplates: number;
|
|
12
|
+
maxBackgroundTasks: number;
|
|
13
|
+
maxMcpServers: number;
|
|
14
|
+
maxKnowledgeDocs: number;
|
|
15
|
+
maxConversationsPerDay: number;
|
|
16
|
+
maxAgentMessagesPerDay: number;
|
|
17
|
+
maxScaffoldToolkits: number;
|
|
18
|
+
}
|
|
19
|
+
/** Response from the cli-entitlements endpoint */
|
|
20
|
+
export interface EntitlementResponse {
|
|
21
|
+
tier: string;
|
|
22
|
+
beta: boolean;
|
|
23
|
+
limits: TierLimits;
|
|
24
|
+
pendingPayment: boolean;
|
|
25
|
+
trialEndsAt: string | null;
|
|
26
|
+
features: Record<string, boolean>;
|
|
27
|
+
issuedAt: string;
|
|
28
|
+
userId: string;
|
|
29
|
+
signature: string;
|
|
30
|
+
}
|
|
31
|
+
/** Result of checking a limit */
|
|
32
|
+
export interface LimitCheckResult {
|
|
33
|
+
allowed: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
upgradeHint?: string;
|
|
36
|
+
current?: number;
|
|
37
|
+
limit?: number;
|
|
38
|
+
}
|
|
39
|
+
/** Sentinel value for unlimited */
|
|
40
|
+
export declare const UNLIMITED = -1;
|
|
41
|
+
/**
|
|
42
|
+
* Minimal fallback limits when server is unreachable AND no cached entitlements exist.
|
|
43
|
+
* Intentionally restrictive to push the user to connect.
|
|
44
|
+
*/
|
|
45
|
+
export declare const OFFLINE_FALLBACK_LIMITS: TierLimits;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlement Types — Shared between server, SDK, Desktop, and CLI.
|
|
3
|
+
*
|
|
4
|
+
* TierLimits values: -1 = unlimited, 0+ = actual limit.
|
|
5
|
+
*/
|
|
6
|
+
/** Sentinel value for unlimited */
|
|
7
|
+
export const UNLIMITED = -1;
|
|
8
|
+
/**
|
|
9
|
+
* Minimal fallback limits when server is unreachable AND no cached entitlements exist.
|
|
10
|
+
* Intentionally restrictive to push the user to connect.
|
|
11
|
+
*/
|
|
12
|
+
export const OFFLINE_FALLBACK_LIMITS = {
|
|
13
|
+
maxProjects: 1,
|
|
14
|
+
maxTeamAgents: 1,
|
|
15
|
+
maxCustomAgents: 0,
|
|
16
|
+
maxTemplates: 0,
|
|
17
|
+
maxBackgroundTasks: 0,
|
|
18
|
+
maxMcpServers: 0,
|
|
19
|
+
maxKnowledgeDocs: 0,
|
|
20
|
+
maxConversationsPerDay: 5,
|
|
21
|
+
maxAgentMessagesPerDay: 20,
|
|
22
|
+
maxScaffoldToolkits: 0,
|
|
23
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -56,8 +56,10 @@ export type { SystemPromptContext, BuildResult, SystemPromptModule, ModuleCondit
|
|
|
56
56
|
export type { ProjectType, ProjectStatus, RepoPattern, WorkflowMode, LifecycleState, WorkItemType, WorkItemStatus, WorkItemPriority, GuidedStep, DocumentType, PlanStatus, Project, WorkItem, ProjectDocument, Plan, PlanSummary, PlanWithWorkItem, HistoryEntry, CreateProjectInput, UpdateProjectInput, ProjectListOptions, CreateWorkItemInput, UpdateWorkItemInput, QueryWorkItemsInput, CreateDocumentInput, UpdateDocumentInput, CreatePlanInput, UpdatePlanInput, ListPlansOptions, WorkItemQueryResult, ProjectListResult, BulkCreateItem, WorkItemComment, CreateCommentInput, UpdateCommentInput, IProjectRepository, IWorkItemRepository, IDocumentRepository, IPlanRepository, ICommentRepository, IAnchorService, IArtifactService, IEpisodeService, AnchorData, ArtifactType, ArtifactData, ArtifactSummaryData, WorkEpisode, ProjectWorkSummary, PlatformContext, PlatformToolsConfig, PlatformHooks, StepCriteria, } from './platform/index.js';
|
|
57
57
|
export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './platform/index.js';
|
|
58
58
|
export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './platform/index.js';
|
|
59
|
-
export { createAskUserTool, createAskUserSimpleTool } from './tools/index.js';
|
|
60
|
-
export type { AskUserQuestion, AskUserInput, AskUserResult, AskUserHandler, AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleHandler, } from './tools/index.js';
|
|
59
|
+
export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTool } from './tools/index.js';
|
|
60
|
+
export type { AskUserQuestion, AskUserInput, AskUserResult, AskUserHandler, AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleHandler, Alternative, ProposeAlternativesInput, ProposeAlternativesResult, ProposeAlternativesHandler, } from './tools/index.js';
|
|
61
|
+
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS } from './entitlements/index.js';
|
|
62
|
+
export type { TierLimits, EntitlementResponse, LimitCheckResult, IEntitlementStore, EntitlementCacheConfig, } from './entitlements/index.js';
|
|
61
63
|
export { detectProject, suggestProjectType, detectCommon } from './detection/index.js';
|
|
62
64
|
export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
|
|
63
65
|
export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
|
package/dist/index.js
CHANGED
|
@@ -131,7 +131,11 @@ export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemReposi
|
|
|
131
131
|
// =============================================================================
|
|
132
132
|
// User Interaction Tools (ask_user, ask_user_simple)
|
|
133
133
|
// =============================================================================
|
|
134
|
-
export { createAskUserTool, createAskUserSimpleTool } from './tools/index.js';
|
|
134
|
+
export { createAskUserTool, createAskUserSimpleTool, createProposeAlternativesTool } from './tools/index.js';
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Entitlements (server-driven tier management)
|
|
137
|
+
// =============================================================================
|
|
138
|
+
export { EntitlementCache, UNLIMITED, OFFLINE_FALLBACK_LIMITS } from './entitlements/index.js';
|
|
135
139
|
// =============================================================================
|
|
136
140
|
// Project Detection (universal project content detection)
|
|
137
141
|
// =============================================================================
|
|
@@ -140,7 +140,7 @@ IMPORTANT: Tool names are lowercase with underscores.
|
|
|
140
140
|
- **File operations**: read_file, write_file, edit, glob, grep
|
|
141
141
|
- **Shell**: bash (use run_in_background=true for long-running), bash_output, kill_shell
|
|
142
142
|
- **Task management**: todo_write, todo_read
|
|
143
|
-
- **User interaction**: ask_user, ask_user_simple
|
|
143
|
+
- **User interaction**: ask_user, ask_user_simple, propose_alternatives (present 2-3 options for comparison)
|
|
144
144
|
- **Sub-agents**: task (types: explore, code-review, plan, debug, test-runner, refactor, security-audit, general)`,
|
|
145
145
|
};
|
|
146
146
|
/**
|
|
@@ -51,7 +51,7 @@ export const SKILL_REQUIREMENTS = {
|
|
|
51
51
|
},
|
|
52
52
|
design: {
|
|
53
53
|
required: ['ask_user', 'workitem_add'],
|
|
54
|
-
optional: ['detect_project', 'document_save', 'edit'],
|
|
54
|
+
optional: ['propose_alternatives', 'detect_project', 'document_save', 'edit'],
|
|
55
55
|
reason: 'Needs to gather requirements and create backlog',
|
|
56
56
|
},
|
|
57
57
|
refine: {
|
|
@@ -72,7 +72,7 @@ export const SKILL_REQUIREMENTS = {
|
|
|
72
72
|
// Documentation skills
|
|
73
73
|
architecture: {
|
|
74
74
|
required: ['read_file', 'write_file', 'edit'],
|
|
75
|
-
optional: ['backlog_read', 'ask_user', 'glob'],
|
|
75
|
+
optional: ['backlog_read', 'ask_user', 'propose_alternatives', 'glob'],
|
|
76
76
|
reason: 'Creates architecture documents',
|
|
77
77
|
},
|
|
78
78
|
prd: {
|
package/dist/team/tool-config.js
CHANGED
|
@@ -21,6 +21,7 @@ const TOOL_NAMES = {
|
|
|
21
21
|
// User interaction
|
|
22
22
|
ASK_USER: 'ask_user',
|
|
23
23
|
ASK_USER_SIMPLE: 'ask_user_simple',
|
|
24
|
+
PROPOSE_ALTERNATIVES: 'propose_alternatives',
|
|
24
25
|
// Delegation
|
|
25
26
|
DELEGATE: 'delegate',
|
|
26
27
|
DELEGATE_BACKGROUND: 'delegate_background',
|
|
@@ -147,7 +148,7 @@ export const TOOL_GROUPS = {
|
|
|
147
148
|
},
|
|
148
149
|
interaction: {
|
|
149
150
|
label: 'User Interaction',
|
|
150
|
-
tools: [TOOL_NAMES.ASK_USER, TOOL_NAMES.ASK_USER_SIMPLE, TOOL_NAMES.SUGGEST],
|
|
151
|
+
tools: [TOOL_NAMES.ASK_USER, TOOL_NAMES.ASK_USER_SIMPLE, TOOL_NAMES.PROPOSE_ALTERNATIVES, TOOL_NAMES.SUGGEST],
|
|
151
152
|
readOnly: true,
|
|
152
153
|
tier: 'direct',
|
|
153
154
|
},
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
* The SDK defines schemas and types; consumers provide the UI.
|
|
6
6
|
*/
|
|
7
7
|
export { createAskUserTool, createAskUserSimpleTool } from './ask-user-tools.js';
|
|
8
|
+
export { createProposeAlternativesTool } from './propose-alternatives-tool.js';
|
|
8
9
|
export type { AskUserQuestion, AskUserInput, AskUserResult, AskUserHandler, AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleHandler, } from './ask-user-tools.js';
|
|
10
|
+
export type { Alternative, ProposeAlternativesInput, ProposeAlternativesResult, ProposeAlternativesHandler, } from './propose-alternatives-tool.js';
|
package/dist/tools/index.js
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Propose Alternatives Tool — Present 2-3 options for the user to compare and choose.
|
|
3
|
+
*
|
|
4
|
+
* Unlike ask_user (open-ended questions), this tool presents concrete alternatives
|
|
5
|
+
* with rich previews (code, markdown, mermaid) for side-by-side comparison.
|
|
6
|
+
*
|
|
7
|
+
* Factory pattern: the consumer (CLI, desktop) provides a handler callback that
|
|
8
|
+
* implements the UI. The SDK defines the tool schema and types.
|
|
9
|
+
*/
|
|
10
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
11
|
+
/** A single alternative to present */
|
|
12
|
+
export interface Alternative {
|
|
13
|
+
/** Short title: "Card Layout", "REST API" */
|
|
14
|
+
title: string;
|
|
15
|
+
/** 1-2 sentence explanation */
|
|
16
|
+
description: string;
|
|
17
|
+
/** Optional rich content for preview */
|
|
18
|
+
content?: string;
|
|
19
|
+
/** Content format for rendering */
|
|
20
|
+
contentType?: 'markdown' | 'code' | 'mermaid';
|
|
21
|
+
/** Language hint for code syntax highlighting (e.g., 'typescript', 'html') */
|
|
22
|
+
language?: string;
|
|
23
|
+
/** Pros of this alternative */
|
|
24
|
+
pros?: string[];
|
|
25
|
+
/** Cons of this alternative */
|
|
26
|
+
cons?: string[];
|
|
27
|
+
}
|
|
28
|
+
/** Input parameters for propose_alternatives tool */
|
|
29
|
+
export interface ProposeAlternativesInput {
|
|
30
|
+
/** What decision is being made */
|
|
31
|
+
question: string;
|
|
32
|
+
/** Optional context paragraph */
|
|
33
|
+
context?: string;
|
|
34
|
+
/** 2-3 alternatives to present */
|
|
35
|
+
alternatives: Alternative[];
|
|
36
|
+
}
|
|
37
|
+
/** Result from propose_alternatives tool */
|
|
38
|
+
export interface ProposeAlternativesResult {
|
|
39
|
+
/** 0-based index of chosen alternative */
|
|
40
|
+
chosenIndex: number;
|
|
41
|
+
/** Title of chosen alternative */
|
|
42
|
+
chosenTitle: string;
|
|
43
|
+
/** Optional user notes */
|
|
44
|
+
notes?: string;
|
|
45
|
+
/** User rejected all alternatives */
|
|
46
|
+
rejected: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Handler callback for propose_alternatives tool.
|
|
50
|
+
* The consumer provides the UI implementation.
|
|
51
|
+
*/
|
|
52
|
+
export type ProposeAlternativesHandler = (input: ProposeAlternativesInput) => Promise<ProposeAlternativesResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Create a propose_alternatives tool with a custom UI handler.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const tool = createProposeAlternativesTool(async (input) => {
|
|
59
|
+
* // Show side-by-side dialog, wait for user choice
|
|
60
|
+
* return { chosenIndex: 0, chosenTitle: input.alternatives[0].title, rejected: false };
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function createProposeAlternativesTool(handler: ProposeAlternativesHandler): Tool<ProposeAlternativesInput>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Propose Alternatives Tool — Present 2-3 options for the user to compare and choose.
|
|
3
|
+
*
|
|
4
|
+
* Unlike ask_user (open-ended questions), this tool presents concrete alternatives
|
|
5
|
+
* with rich previews (code, markdown, mermaid) for side-by-side comparison.
|
|
6
|
+
*
|
|
7
|
+
* Factory pattern: the consumer (CLI, desktop) provides a handler callback that
|
|
8
|
+
* implements the UI. The SDK defines the tool schema and types.
|
|
9
|
+
*/
|
|
10
|
+
import { defineTool } from '@compilr-dev/agents';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Factory
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Create a propose_alternatives tool with a custom UI handler.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const tool = createProposeAlternativesTool(async (input) => {
|
|
20
|
+
* // Show side-by-side dialog, wait for user choice
|
|
21
|
+
* return { chosenIndex: 0, chosenTitle: input.alternatives[0].title, rejected: false };
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function createProposeAlternativesTool(handler) {
|
|
26
|
+
return defineTool({
|
|
27
|
+
name: 'propose_alternatives',
|
|
28
|
+
description: 'Present 2-3 alternatives for the user to compare and choose from. ' +
|
|
29
|
+
'Use when there are multiple valid approaches and the user\'s preference matters. ' +
|
|
30
|
+
'Each alternative can include rich content (code, markdown, mermaid diagrams) for visual comparison. ' +
|
|
31
|
+
'Include pros and cons to help the user decide. The user picks one or provides feedback.',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
question: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'What decision is being made (e.g., "Dashboard layout approach")',
|
|
38
|
+
},
|
|
39
|
+
context: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Optional background context for the decision',
|
|
42
|
+
},
|
|
43
|
+
alternatives: {
|
|
44
|
+
type: 'array',
|
|
45
|
+
description: '2-3 alternatives to present',
|
|
46
|
+
minItems: 2,
|
|
47
|
+
maxItems: 3,
|
|
48
|
+
items: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
title: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Short title (e.g., "Card Grid", "Data Table")',
|
|
54
|
+
},
|
|
55
|
+
description: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: '1-2 sentence explanation of this approach',
|
|
58
|
+
},
|
|
59
|
+
content: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Optional rich content for preview (code, markdown, or mermaid source)',
|
|
62
|
+
},
|
|
63
|
+
contentType: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
enum: ['markdown', 'code', 'mermaid'],
|
|
66
|
+
description: 'Content format for rendering',
|
|
67
|
+
},
|
|
68
|
+
language: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Language hint for code syntax highlighting (e.g., "typescript", "html")',
|
|
71
|
+
},
|
|
72
|
+
pros: {
|
|
73
|
+
type: 'array',
|
|
74
|
+
items: { type: 'string' },
|
|
75
|
+
description: 'Advantages of this alternative',
|
|
76
|
+
},
|
|
77
|
+
cons: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
items: { type: 'string' },
|
|
80
|
+
description: 'Disadvantages of this alternative',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
required: ['title', 'description'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['question', 'alternatives'],
|
|
88
|
+
},
|
|
89
|
+
execute: async (input) => {
|
|
90
|
+
try {
|
|
91
|
+
if (input.alternatives.length < 2) {
|
|
92
|
+
return { success: false, error: 'At least 2 alternatives are required' };
|
|
93
|
+
}
|
|
94
|
+
if (input.alternatives.length > 3) {
|
|
95
|
+
return { success: false, error: 'Maximum 3 alternatives allowed' };
|
|
96
|
+
}
|
|
97
|
+
const result = await handler(input);
|
|
98
|
+
if (result.rejected) {
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
result: {
|
|
102
|
+
chosen: null,
|
|
103
|
+
rejected: true,
|
|
104
|
+
feedback: result.notes ?? 'User rejected all alternatives',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
result: {
|
|
111
|
+
chosen: result.chosenTitle,
|
|
112
|
+
chosenIndex: result.chosenIndex,
|
|
113
|
+
notes: result.notes,
|
|
114
|
+
rejected: false,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: `Failed to propose alternatives: ${err instanceof Error ? err.message : String(err)}`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
// Not silent — show in tool call history (this is substantive, not meta)
|
|
126
|
+
silent: false,
|
|
127
|
+
});
|
|
128
|
+
}
|