@compilr-dev/cli 0.5.17 → 0.6.1
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/.tsbuildinfo.app +1 -1
- package/dist/.tsbuildinfo.data +1 -1
- package/dist/.tsbuildinfo.domain +1 -1
- package/dist/agent.d.ts +41 -111
- package/dist/agent.js +238 -390
- package/dist/commands-v2/handlers/project.d.ts +1 -0
- package/dist/commands-v2/handlers/project.js +36 -2
- package/dist/commands-v2/handlers/team.js +23 -3
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/entitlements/index.d.ts +23 -0
- package/dist/entitlements/index.js +110 -0
- package/dist/guide/cli-guide-entries.d.ts +15 -0
- package/dist/guide/cli-guide-entries.js +99 -0
- package/dist/guide/index.d.ts +5 -4
- package/dist/guide/index.js +4 -3
- package/dist/guide/shared-content.js +188 -21
- package/dist/handlers/permission-handler.js +10 -3
- package/dist/index.js +18 -0
- package/dist/repl-v2.d.ts +16 -0
- package/dist/repl-v2.js +51 -17
- package/dist/tools/db-tools.d.ts +1 -1
- package/dist/tools/platform-adapter.d.ts +1 -1
- package/dist/tools/platform-adapter.js +6 -1
- package/dist/tools.js +6 -1
- package/dist/ui/overlay/impl/app-model-overlay-v2.d.ts +57 -0
- package/dist/ui/overlay/impl/app-model-overlay-v2.js +232 -0
- package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.d.ts +23 -1
- package/dist/ui/overlay/impl/custom-agent-form-overlay-v2.js +203 -47
- package/dist/ui/overlay/impl/model-overlay-v2.js +2 -2
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +2 -2
- package/dist/ui/overlay/impl/new-overlay-v2.js +10 -17
- package/dist/ui/overlay/impl/team-overlay-v2.js +2 -2
- package/dist/ui/overlay/index.d.ts +1 -0
- package/dist/ui/overlay/index.js +1 -0
- package/package.json +4 -4
|
@@ -27,10 +27,17 @@ export function createPermissionHandler(deps) {
|
|
|
27
27
|
if (permissionMode === 'auto-accept') {
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
|
-
// "Plan" mode:
|
|
30
|
+
// "Plan" mode: block write/execute tools, allow read/plan tools
|
|
31
|
+
if (permissionMode === 'plan') {
|
|
32
|
+
const { isToolAllowedInPlanMode } = await import('@compilr-dev/sdk');
|
|
33
|
+
if (!isToolAllowedInPlanMode(request.toolName)) {
|
|
34
|
+
return false; // Blocked — agent gets denial in tool result
|
|
35
|
+
}
|
|
36
|
+
// Read/plan tools pass through without prompting
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
31
39
|
// "Default" mode: use rule-based checking
|
|
32
|
-
|
|
33
|
-
if (permissionMode !== 'plan') {
|
|
40
|
+
{
|
|
34
41
|
const rule = findMatchingRule(request.toolName);
|
|
35
42
|
if (rule) {
|
|
36
43
|
if (rule.level === 'always') {
|
package/dist/index.js
CHANGED
|
@@ -560,6 +560,19 @@ async function main() {
|
|
|
560
560
|
// MCP tools injected later via agent.registerTools() when background init completes
|
|
561
561
|
// Episode recorder for work history tracking
|
|
562
562
|
episodeRecorder: defaultEpisodeRecorder,
|
|
563
|
+
// Plan mode callbacks — wired to ReplV2's handlePlanApproval
|
|
564
|
+
planModeCallbacks: {
|
|
565
|
+
onPlanSubmit: async (info) => {
|
|
566
|
+
// Delegate to ReplV2's existing plan approval flow
|
|
567
|
+
if (sharedState.onPlanSubmit)
|
|
568
|
+
return sharedState.onPlanSubmit(info);
|
|
569
|
+
return { action: 'reject' }; // Fallback if repl not ready
|
|
570
|
+
},
|
|
571
|
+
onPlanModeExit: async (info) => {
|
|
572
|
+
if (sharedState.onPlanModeExit)
|
|
573
|
+
return sharedState.onPlanModeExit(info);
|
|
574
|
+
},
|
|
575
|
+
},
|
|
563
576
|
});
|
|
564
577
|
perf('create-agent-done');
|
|
565
578
|
// Mutable ref so grantSession and onDefaultAgentSwapped closures track the current default agent
|
|
@@ -731,6 +744,11 @@ async function main() {
|
|
|
731
744
|
onBackgroundDelegationReady: (runDelegation) => {
|
|
732
745
|
sharedState.runBackgroundDelegation = runDelegation;
|
|
733
746
|
},
|
|
747
|
+
// Plan mode: wire approval and exit callbacks to ReplV2
|
|
748
|
+
onPlanModeReady: (callbacks) => {
|
|
749
|
+
sharedState.onPlanSubmit = callbacks.onPlanSubmit;
|
|
750
|
+
sharedState.onPlanModeExit = callbacks.onPlanModeExit;
|
|
751
|
+
},
|
|
734
752
|
// Update sharedState.team when session restore replaces the team object
|
|
735
753
|
// (so delegation handlers see restored agents, not just 'default')
|
|
736
754
|
onTeamReplaced: (newTeam) => {
|
package/dist/repl-v2.d.ts
CHANGED
|
@@ -149,6 +149,21 @@ export interface ReplV2Options {
|
|
|
149
149
|
* Phase 3d-beta: Coordinator Mode
|
|
150
150
|
*/
|
|
151
151
|
onBackgroundDelegationReady?: (runDelegation: (agentId: string, message: string) => 'running' | 'queued' | null) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Plan mode: wire plan_submit and plan_mode_exit callbacks to the approval overlay.
|
|
154
|
+
*/
|
|
155
|
+
onPlanModeReady?: (callbacks: {
|
|
156
|
+
onPlanSubmit: (info: {
|
|
157
|
+
planId: number;
|
|
158
|
+
summary?: string;
|
|
159
|
+
}) => Promise<{
|
|
160
|
+
action: 'approve-auto' | 'approve' | 'revise' | 'reject';
|
|
161
|
+
feedback?: string;
|
|
162
|
+
}>;
|
|
163
|
+
onPlanModeExit: (info: {
|
|
164
|
+
reason: string;
|
|
165
|
+
}) => Promise<void>;
|
|
166
|
+
}) => void;
|
|
152
167
|
/**
|
|
153
168
|
* Callback invoked when terminal session is registered.
|
|
154
169
|
* Passes the session ID so index.ts can set sessionPrefix for IPC.
|
|
@@ -234,6 +249,7 @@ export declare class ReplV2 {
|
|
|
234
249
|
private readonly onPendingDelegation?;
|
|
235
250
|
private readonly onBackgroundAgentReady?;
|
|
236
251
|
private readonly onBackgroundDelegationReady?;
|
|
252
|
+
private readonly onPlanModeReady?;
|
|
237
253
|
private readonly onSessionRegistered?;
|
|
238
254
|
private readonly onTeamReplaced?;
|
|
239
255
|
private readonly onProjectStateUpdate?;
|
package/dist/repl-v2.js
CHANGED
|
@@ -31,7 +31,7 @@ import { renderMascotWithLogo } from './ui/mascot/renderer.js';
|
|
|
31
31
|
import { getStartupHighlights } from './changelog/index.js';
|
|
32
32
|
import { registerCommands, executeCommand, allCommands, getAutocompleteCommands, saveCurrentSession, saveCurrentTeam, loadProjectSession, archiveCurrentSession, convertMessagesToItems, } from './commands-v2/index.js';
|
|
33
33
|
import { getCustomCommandRegistry } from './commands/custom-registry.js';
|
|
34
|
-
import { getPlanModePrompt } from '
|
|
34
|
+
import { getPlanModePrompt } from '@compilr-dev/sdk';
|
|
35
35
|
import { planRepository } from './db/repositories/index.js';
|
|
36
36
|
import { PlanApprovalOverlayV2, } from './ui/overlay/impl/plan-approval-overlay-v2.js';
|
|
37
37
|
import { PermissionOverlayV2, } from './ui/overlay/impl/permission-overlay-v2.js';
|
|
@@ -197,6 +197,7 @@ export class ReplV2 {
|
|
|
197
197
|
onPendingDelegation;
|
|
198
198
|
onBackgroundAgentReady;
|
|
199
199
|
onBackgroundDelegationReady;
|
|
200
|
+
onPlanModeReady;
|
|
200
201
|
onSessionRegistered;
|
|
201
202
|
onTeamReplaced;
|
|
202
203
|
onProjectStateUpdate;
|
|
@@ -245,6 +246,7 @@ export class ReplV2 {
|
|
|
245
246
|
this.onPendingDelegation = options.onPendingDelegation;
|
|
246
247
|
this.onBackgroundAgentReady = options.onBackgroundAgentReady;
|
|
247
248
|
this.onBackgroundDelegationReady = options.onBackgroundDelegationReady;
|
|
249
|
+
this.onPlanModeReady = options.onPlanModeReady;
|
|
248
250
|
this.onSessionRegistered = options.onSessionRegistered;
|
|
249
251
|
this.onTeamReplaced = options.onTeamReplaced;
|
|
250
252
|
this.onProjectStateUpdate = options.onProjectStateUpdate;
|
|
@@ -987,6 +989,26 @@ export class ReplV2 {
|
|
|
987
989
|
return 'running';
|
|
988
990
|
});
|
|
989
991
|
}
|
|
992
|
+
// Plan mode: wire plan_submit and plan_mode_exit to handlePlanApproval
|
|
993
|
+
if (this.onPlanModeReady) {
|
|
994
|
+
this.onPlanModeReady({
|
|
995
|
+
onPlanSubmit: async (info) => {
|
|
996
|
+
await this.handlePlanApproval(info.planId, info.summary ?? 'Plan');
|
|
997
|
+
// Map the internal action to PlanSubmitResult
|
|
998
|
+
// handlePlanApproval already updates DB and mode — return a simplified result
|
|
999
|
+
const mode = this.ui.getMode();
|
|
1000
|
+
if (mode === 'auto-accept')
|
|
1001
|
+
return { action: 'approve-auto' };
|
|
1002
|
+
return { action: 'approve' };
|
|
1003
|
+
},
|
|
1004
|
+
onPlanModeExit: (info) => {
|
|
1005
|
+
this.ui.setMode('normal');
|
|
1006
|
+
this.ui.clearActivePlan();
|
|
1007
|
+
this.ui.print({ type: 'info', message: `Agent exited plan mode: ${info.reason}` });
|
|
1008
|
+
return Promise.resolve();
|
|
1009
|
+
},
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
990
1012
|
// Event handlers
|
|
991
1013
|
this.ui.on('submit', (input) => {
|
|
992
1014
|
void this.processInput(input);
|
|
@@ -1547,6 +1569,12 @@ export class ReplV2 {
|
|
|
1547
1569
|
// Start session heartbeat in background (don't block UI)
|
|
1548
1570
|
const auth = getAuthManager();
|
|
1549
1571
|
void auth.startSession();
|
|
1572
|
+
// Warm entitlement cache in background (non-blocking)
|
|
1573
|
+
if (await auth.isAuthenticated()) {
|
|
1574
|
+
import('./entitlements/index.js').then(({ warmEntitlementCache }) => {
|
|
1575
|
+
void warmEntitlementCache(() => auth.getAccessToken());
|
|
1576
|
+
}).catch(() => { });
|
|
1577
|
+
}
|
|
1550
1578
|
const startupMode = getStartupMode();
|
|
1551
1579
|
if (startupMode === 'menu') {
|
|
1552
1580
|
await this.handleCommand('menu', '');
|
|
@@ -2209,6 +2237,25 @@ export class ReplV2 {
|
|
|
2209
2237
|
async runAgentReal(userMessage) {
|
|
2210
2238
|
if (!this.agent)
|
|
2211
2239
|
return;
|
|
2240
|
+
// Entitlement check: daily message limit
|
|
2241
|
+
try {
|
|
2242
|
+
const { dailyMessageCounter, getEntitlementCache } = await import('./entitlements/index.js');
|
|
2243
|
+
const auth = (await import('./auth/index.js')).getAuthManager();
|
|
2244
|
+
if (await auth.isAuthenticated()) {
|
|
2245
|
+
const cache = getEntitlementCache(() => auth.getAccessToken());
|
|
2246
|
+
const msgCount = dailyMessageCounter.increment();
|
|
2247
|
+
const check = cache.checkLimit('maxAgentMessagesPerDay', msgCount);
|
|
2248
|
+
if (!check.allowed) {
|
|
2249
|
+
const { formatTimeUntilReset } = await import('@compilr-dev/sdk');
|
|
2250
|
+
this.ui.print({
|
|
2251
|
+
type: 'warning',
|
|
2252
|
+
message: `Daily message limit reached (${String(check.current)}/${String(check.limit)}). ${formatTimeUntilReset()}.`,
|
|
2253
|
+
});
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
catch { /* allow if entitlements unavailable */ }
|
|
2212
2259
|
const abortController = new AbortController();
|
|
2213
2260
|
const signal = abortController.signal;
|
|
2214
2261
|
const setAction = (action) => {
|
|
@@ -2252,7 +2299,7 @@ export class ReplV2 {
|
|
|
2252
2299
|
let messageToSend = userMessage;
|
|
2253
2300
|
if (this.ui.getMode() === 'plan') {
|
|
2254
2301
|
const activePlan = this.ui.getActivePlan();
|
|
2255
|
-
const planModePrompt = getPlanModePrompt(activePlan.id
|
|
2302
|
+
const planModePrompt = getPlanModePrompt(activePlan.id && activePlan.name ? { id: activePlan.id, name: activePlan.name } : undefined);
|
|
2256
2303
|
messageToSend = `${planModePrompt}\n\n---\n\nUser request: ${userMessage}`;
|
|
2257
2304
|
}
|
|
2258
2305
|
// Inject delegation completion notifications (deferred delivery)
|
|
@@ -2687,22 +2734,9 @@ export class ReplV2 {
|
|
|
2687
2734
|
});
|
|
2688
2735
|
}
|
|
2689
2736
|
}
|
|
2690
|
-
// Flush remaining text
|
|
2737
|
+
// Flush remaining text (PLAN_READY marker detection removed — plan_submit tool handles approval)
|
|
2691
2738
|
if (this.textAccumulator.trim()) {
|
|
2692
|
-
|
|
2693
|
-
// Check for PLAN_READY marker
|
|
2694
|
-
const planReadyInfo = this.detectPlanReady(text);
|
|
2695
|
-
// Print the text (remove the marker from display if present)
|
|
2696
|
-
const displayText = planReadyInfo
|
|
2697
|
-
? text.replace(ReplV2.PLAN_READY_REGEX, '').trim()
|
|
2698
|
-
: text;
|
|
2699
|
-
if (displayText) {
|
|
2700
|
-
this.ui.print({ type: 'agent-text', text: displayText, expression: this.getActiveAgentMascot(), agentId: activeAgentId });
|
|
2701
|
-
}
|
|
2702
|
-
// If PLAN_READY marker found, show approval overlay
|
|
2703
|
-
if (planReadyInfo) {
|
|
2704
|
-
await this.handlePlanApproval(planReadyInfo.planId, planReadyInfo.planName);
|
|
2705
|
-
}
|
|
2739
|
+
this.ui.print({ type: 'agent-text', text: this.textAccumulator.trim(), expression: this.getActiveAgentMascot(), agentId: activeAgentId });
|
|
2706
2740
|
}
|
|
2707
2741
|
// Print per-turn summary before stopping agent
|
|
2708
2742
|
this.printTurnSummary();
|
package/dist/tools/db-tools.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { getActiveProject, setActiveProject } from './project-db.js';
|
|
|
10
10
|
/**
|
|
11
11
|
* All database tools combined (32 tools from SDK)
|
|
12
12
|
*/
|
|
13
|
-
export declare const allDbTools: import("@compilr-dev/
|
|
13
|
+
export declare const allDbTools: import("@compilr-dev/agents").Tool<never>[];
|
|
14
14
|
/**
|
|
15
15
|
* All factory tools (5 tools from @compilr-dev/factory)
|
|
16
16
|
*/
|
|
@@ -22,4 +22,4 @@ export declare const allPlatformTools: import("@compilr-dev/sdk").Tool<never>[];
|
|
|
22
22
|
* - 3 model tools (app_model_get, app_model_update, app_model_validate)
|
|
23
23
|
* - 2 factory tools (factory_scaffold, factory_list_toolkits)
|
|
24
24
|
*/
|
|
25
|
-
export declare const allFactoryTools: import(
|
|
25
|
+
export declare const allFactoryTools: import('@compilr-dev/agents').Tool<never>[];
|
|
@@ -9,6 +9,7 @@ import { createPlatformTools, createSQLiteRepositories, ProjectAnchorStore, } fr
|
|
|
9
9
|
import { createFactoryTools } from '@compilr-dev/factory';
|
|
10
10
|
import { getDb } from '../db/index.js';
|
|
11
11
|
import { getDataPath, getSessionsPath } from '../settings/paths.js';
|
|
12
|
+
import { getPermissionMode } from '../settings/index.js';
|
|
12
13
|
import { existsSync, rmSync } from 'fs';
|
|
13
14
|
import { join } from 'path';
|
|
14
15
|
import { getCurrentProject, setCurrentProject } from './project-db.js';
|
|
@@ -273,7 +274,11 @@ const config = {
|
|
|
273
274
|
* - 4 artifact tools (artifact_save, artifact_get, artifact_list, artifact_delete)
|
|
274
275
|
* - 1 episode tool (recall_work)
|
|
275
276
|
*/
|
|
276
|
-
export const allPlatformTools = createPlatformTools(config
|
|
277
|
+
export const allPlatformTools = createPlatformTools(config, {
|
|
278
|
+
planModeCallbacks: {
|
|
279
|
+
isPlanModeActive: () => getPermissionMode() === 'plan',
|
|
280
|
+
},
|
|
281
|
+
});
|
|
277
282
|
/**
|
|
278
283
|
* All 5 factory tools from @compilr-dev/factory:
|
|
279
284
|
* - 3 model tools (app_model_get, app_model_update, app_model_validate)
|
package/dist/tools.js
CHANGED
|
@@ -125,7 +125,12 @@ import { delegateTool } from './tools/delegate.js';
|
|
|
125
125
|
import { delegateBackgroundTool } from './tools/delegate-background.js';
|
|
126
126
|
import { delegationStatusTool } from './tools/delegation-status.js';
|
|
127
127
|
import { handoffTool } from './tools/handoff.js';
|
|
128
|
-
import {
|
|
128
|
+
import { createGuideTool } from '@compilr-dev/sdk';
|
|
129
|
+
import { CLI_GUIDE_ENTRIES } from './guide/cli-guide-entries.js';
|
|
130
|
+
const guideTool = createGuideTool({
|
|
131
|
+
environment: 'cli',
|
|
132
|
+
additionalEntries: CLI_GUIDE_ENTRIES,
|
|
133
|
+
});
|
|
129
134
|
// DB tools for project, work item, document, plan, backlog, anchor, artifact, and episode management
|
|
130
135
|
import { allDbTools, allFactoryTools } from './tools/db-tools.js';
|
|
131
136
|
// Meta-tools for dynamic tool loading
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Model Overlay V2
|
|
3
|
+
*
|
|
4
|
+
* Terminal visualization of the ApplicationModel (from @compilr-dev/factory).
|
|
5
|
+
* Three tabs: Entities (list with detail), Relationships (map), Summary (stats).
|
|
6
|
+
*
|
|
7
|
+
* Not a replacement for the desktop SVG viewer — text-based tables and lists
|
|
8
|
+
* for CLI users who want to inspect the model structure.
|
|
9
|
+
*/
|
|
10
|
+
import { TabbedListOverlayV2, BaseScreen } from '../../base/index.js';
|
|
11
|
+
interface ModelField {
|
|
12
|
+
name: string;
|
|
13
|
+
label: string;
|
|
14
|
+
type: string;
|
|
15
|
+
required: boolean;
|
|
16
|
+
enumValues?: readonly string[];
|
|
17
|
+
defaultValue?: string | number | boolean;
|
|
18
|
+
}
|
|
19
|
+
interface ModelRelationship {
|
|
20
|
+
type: 'belongsTo' | 'hasMany';
|
|
21
|
+
target: string;
|
|
22
|
+
fieldName?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ModelEntity {
|
|
25
|
+
name: string;
|
|
26
|
+
pluralName: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
icon: string;
|
|
29
|
+
fields: readonly ModelField[];
|
|
30
|
+
views: readonly string[];
|
|
31
|
+
relationships: readonly ModelRelationship[];
|
|
32
|
+
}
|
|
33
|
+
export interface AppModelOverlayV2Options {
|
|
34
|
+
/** Raw JSON content from the document store */
|
|
35
|
+
modelJson: string;
|
|
36
|
+
/** Project display name */
|
|
37
|
+
projectName: string;
|
|
38
|
+
}
|
|
39
|
+
export interface AppModelOverlayV2Result {
|
|
40
|
+
dismissed: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface ModelListItem {
|
|
43
|
+
id: string;
|
|
44
|
+
tab: string;
|
|
45
|
+
label: string;
|
|
46
|
+
detail: string;
|
|
47
|
+
entity?: ModelEntity;
|
|
48
|
+
}
|
|
49
|
+
export declare class AppModelOverlayV2 extends TabbedListOverlayV2<ModelListItem, AppModelOverlayV2Result> {
|
|
50
|
+
readonly type: "inline";
|
|
51
|
+
readonly id = "app-model-viewer";
|
|
52
|
+
private readonly model;
|
|
53
|
+
private readonly parseError;
|
|
54
|
+
constructor(options: AppModelOverlayV2Options);
|
|
55
|
+
protected createDetailScreen(item: ModelListItem): BaseScreen | null;
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Model Overlay V2
|
|
3
|
+
*
|
|
4
|
+
* Terminal visualization of the ApplicationModel (from @compilr-dev/factory).
|
|
5
|
+
* Three tabs: Entities (list with detail), Relationships (map), Summary (stats).
|
|
6
|
+
*
|
|
7
|
+
* Not a replacement for the desktop SVG viewer — text-based tables and lists
|
|
8
|
+
* for CLI users who want to inspect the model structure.
|
|
9
|
+
*/
|
|
10
|
+
import * as terminal from '../../terminal.js';
|
|
11
|
+
import { TabbedListOverlayV2, BaseScreen, stay, popScreen, isEscape, isCtrlC, isClose, renderBorder, wrapText, truncate, } from '../../base/index.js';
|
|
12
|
+
import { parseKeyEvent } from '../key-utils.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Tab Definitions
|
|
15
|
+
// =============================================================================
|
|
16
|
+
const MODEL_TABS = [
|
|
17
|
+
{ id: 'entities', label: 'Entities' },
|
|
18
|
+
{ id: 'relationships', label: 'Relationships' },
|
|
19
|
+
{ id: 'summary', label: 'Summary' },
|
|
20
|
+
];
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Main Overlay
|
|
23
|
+
// =============================================================================
|
|
24
|
+
export class AppModelOverlayV2 extends TabbedListOverlayV2 {
|
|
25
|
+
type = 'inline';
|
|
26
|
+
id = 'app-model-viewer';
|
|
27
|
+
model = null;
|
|
28
|
+
parseError = null;
|
|
29
|
+
constructor(options) {
|
|
30
|
+
// Parse model and build list items
|
|
31
|
+
let model = null;
|
|
32
|
+
let parseError = null;
|
|
33
|
+
let items = [];
|
|
34
|
+
try {
|
|
35
|
+
model = JSON.parse(options.modelJson);
|
|
36
|
+
items = buildListItems(model);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
parseError = `Failed to parse model: ${e instanceof Error ? e.message : String(e)}`;
|
|
40
|
+
}
|
|
41
|
+
// Compute column widths for aligned rendering
|
|
42
|
+
const maxEntityName = model
|
|
43
|
+
? Math.max(8, ...model.entities.map((e) => e.name.length))
|
|
44
|
+
: 12;
|
|
45
|
+
const maxRelLabel = items
|
|
46
|
+
.filter((i) => i.tab === 'relationships')
|
|
47
|
+
.reduce((max, i) => Math.max(max, i.label.length), 12);
|
|
48
|
+
const maxSummaryLabel = items
|
|
49
|
+
.filter((i) => i.tab === 'summary')
|
|
50
|
+
.reduce((max, i) => Math.max(max, i.label.length), 12);
|
|
51
|
+
super({
|
|
52
|
+
title: `Application Model — ${options.projectName}`,
|
|
53
|
+
tabs: MODEL_TABS,
|
|
54
|
+
items,
|
|
55
|
+
pageSize: 15,
|
|
56
|
+
filterByTab: (item, tabId) => tabId === 'all' || item.tab === tabId,
|
|
57
|
+
getSearchText: (item) => `${item.label} ${item.detail}`,
|
|
58
|
+
renderItem: (item, isSelected, s) => {
|
|
59
|
+
const prefix = isSelected ? s.primary('›') : ' ';
|
|
60
|
+
if (item.tab === 'entities') {
|
|
61
|
+
const entity = item.entity;
|
|
62
|
+
const fields = entity ? String(entity.fields.length).padStart(2) : ' 0';
|
|
63
|
+
const rels = entity ? String(entity.relationships.length) : '0';
|
|
64
|
+
const name = item.label.padEnd(maxEntityName + 2);
|
|
65
|
+
const desc = entity?.description ? s.muted(truncate(entity.description, 30)) : '';
|
|
66
|
+
return `${prefix} ${isSelected ? s.primary(name) : name} ${s.secondary(`${fields} fields · ${rels} rels`)} ${desc}`;
|
|
67
|
+
}
|
|
68
|
+
if (item.tab === 'relationships') {
|
|
69
|
+
const label = item.label.padEnd(maxRelLabel + 2);
|
|
70
|
+
return `${prefix} ${isSelected ? s.primary(label) : label} ${s.secondary(item.detail)}`;
|
|
71
|
+
}
|
|
72
|
+
// summary
|
|
73
|
+
const label = (item.label + ':').padEnd(maxSummaryLabel + 3);
|
|
74
|
+
return `${prefix} ${s.secondary(label)} ${isSelected ? s.primary(item.detail) : item.detail}`;
|
|
75
|
+
},
|
|
76
|
+
footerHints: () => '↑↓ Navigate · Enter Detail · Tab/← → Tabs · / Search · q Close',
|
|
77
|
+
});
|
|
78
|
+
this.model = model;
|
|
79
|
+
this.parseError = parseError;
|
|
80
|
+
}
|
|
81
|
+
createDetailScreen(item) {
|
|
82
|
+
if (item.entity) {
|
|
83
|
+
return new EntityDetailScreen(item.entity, this.getStyles());
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// =============================================================================
|
|
89
|
+
// Entity Detail Screen
|
|
90
|
+
// =============================================================================
|
|
91
|
+
class EntityDetailScreen extends BaseScreen {
|
|
92
|
+
entity;
|
|
93
|
+
styles;
|
|
94
|
+
scrollOffset = 0;
|
|
95
|
+
constructor(entity, styles) {
|
|
96
|
+
super();
|
|
97
|
+
this.entity = entity;
|
|
98
|
+
this.styles = styles;
|
|
99
|
+
}
|
|
100
|
+
render() {
|
|
101
|
+
const s = this.styles;
|
|
102
|
+
const cols = terminal.getTerminalWidth();
|
|
103
|
+
const e = this.entity;
|
|
104
|
+
const lines = [];
|
|
105
|
+
// Header
|
|
106
|
+
lines.push(renderBorder(cols, s));
|
|
107
|
+
lines.push(` ${s.primaryBold(e.name)}`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
if (e.description) {
|
|
110
|
+
for (const wrapped of wrapText(e.description, cols - 6)) {
|
|
111
|
+
lines.push(` ${s.secondary(wrapped)}`);
|
|
112
|
+
}
|
|
113
|
+
lines.push('');
|
|
114
|
+
}
|
|
115
|
+
// Info line
|
|
116
|
+
lines.push(` ${s.muted('Plural:')} ${e.pluralName} ${s.muted('Views:')} ${e.views.join(', ')}`);
|
|
117
|
+
lines.push('');
|
|
118
|
+
// Fields table
|
|
119
|
+
lines.push(` ${s.primaryBold('Fields')} ${s.muted(`(${String(e.fields.length)})`)}`);
|
|
120
|
+
lines.push(` ${s.muted('─'.repeat(Math.min(cols - 4, 60)))}`);
|
|
121
|
+
const nameWidth = Math.max(12, ...e.fields.map((f) => f.name.length)) + 1;
|
|
122
|
+
for (const field of e.fields) {
|
|
123
|
+
const name = field.name.padEnd(nameWidth);
|
|
124
|
+
const type = field.type.padEnd(10);
|
|
125
|
+
const req = field.required ? s.primary('*') : ' ';
|
|
126
|
+
const enums = field.enumValues ? s.muted(` [${field.enumValues.join(', ')}]`) : '';
|
|
127
|
+
const def = field.defaultValue !== undefined ? s.muted(` = ${String(field.defaultValue)}`) : '';
|
|
128
|
+
lines.push(` ${req} ${name} ${s.secondary(type)}${enums}${def}`);
|
|
129
|
+
}
|
|
130
|
+
// Relationships
|
|
131
|
+
if (e.relationships.length > 0) {
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push(` ${s.primaryBold('Relationships')} ${s.muted(`(${String(e.relationships.length)})`)}`);
|
|
134
|
+
lines.push(` ${s.muted('─'.repeat(Math.min(cols - 4, 60)))}`);
|
|
135
|
+
for (const rel of e.relationships) {
|
|
136
|
+
const arrow = rel.type === 'hasMany' ? '──▸' : '◂──';
|
|
137
|
+
const label = rel.type === 'hasMany' ? 'has many' : 'belongs to';
|
|
138
|
+
const via = rel.fieldName ? s.muted(` via ${rel.fieldName}`) : '';
|
|
139
|
+
lines.push(` ${e.name} ${arrow} ${s.primary(rel.target)} ${s.muted(label)}${via}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(` ${s.muted('↑↓ Scroll · Esc Back')}`);
|
|
144
|
+
lines.push(renderBorder(cols, s));
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
handleKey(data) {
|
|
148
|
+
if (isEscape(data) || isCtrlC(data) || isClose(data)) {
|
|
149
|
+
return popScreen();
|
|
150
|
+
}
|
|
151
|
+
const key = parseKeyEvent(data);
|
|
152
|
+
if (key.name === 'up' && this.scrollOffset > 0) {
|
|
153
|
+
this.scrollOffset--;
|
|
154
|
+
return stay();
|
|
155
|
+
}
|
|
156
|
+
if (key.name === 'down') {
|
|
157
|
+
this.scrollOffset++;
|
|
158
|
+
return stay();
|
|
159
|
+
}
|
|
160
|
+
return stay();
|
|
161
|
+
}
|
|
162
|
+
getMinHeight() {
|
|
163
|
+
return 15;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// =============================================================================
|
|
167
|
+
// Build list items from model
|
|
168
|
+
// =============================================================================
|
|
169
|
+
function buildListItems(model) {
|
|
170
|
+
const items = [];
|
|
171
|
+
// Entity items
|
|
172
|
+
for (const entity of model.entities) {
|
|
173
|
+
items.push({
|
|
174
|
+
id: `entity-${entity.name}`,
|
|
175
|
+
tab: 'entities',
|
|
176
|
+
label: entity.name,
|
|
177
|
+
detail: `${String(entity.fields.length)} fields, ${String(entity.relationships.length)} relationships`,
|
|
178
|
+
entity,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Relationship items (flattened from all entities)
|
|
182
|
+
for (const entity of model.entities) {
|
|
183
|
+
for (const rel of entity.relationships) {
|
|
184
|
+
const arrow = rel.type === 'hasMany' ? '→' : '←';
|
|
185
|
+
items.push({
|
|
186
|
+
id: `rel-${entity.name}-${rel.target}-${rel.type}`,
|
|
187
|
+
tab: 'relationships',
|
|
188
|
+
label: `${entity.name} ${arrow} ${rel.target}`,
|
|
189
|
+
detail: rel.type === 'hasMany' ? `has many ${rel.target}` : `belongs to ${rel.target}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Summary items
|
|
194
|
+
const totalFields = model.entities.reduce((sum, e) => sum + e.fields.length, 0);
|
|
195
|
+
const totalRels = model.entities.reduce((sum, e) => sum + e.relationships.length, 0);
|
|
196
|
+
const requiredFields = model.entities.reduce((sum, e) => sum + e.fields.filter((f) => f.required).length, 0);
|
|
197
|
+
const enumFields = model.entities.reduce((sum, e) => sum + e.fields.filter((f) => f.type === 'enum').length, 0);
|
|
198
|
+
// Identity
|
|
199
|
+
items.push({ id: 'sum-name', tab: 'summary', label: 'Application', detail: model.identity.name }, { id: 'sum-desc', tab: 'summary', label: 'Description', detail: truncate(model.identity.description, 60) }, { id: 'sum-version', tab: 'summary', label: 'Version', detail: model.identity.version });
|
|
200
|
+
// Data model stats
|
|
201
|
+
items.push({ id: 'sum-sep1', tab: 'summary', label: '── Data Model', detail: '' }, { id: 'sum-entities', tab: 'summary', label: 'Entities', detail: String(model.entities.length) }, { id: 'sum-fields', tab: 'summary', label: 'Total Fields', detail: `${String(totalFields)} (${String(requiredFields)} required, ${String(enumFields)} enums)` }, { id: 'sum-rels', tab: 'summary', label: 'Relationships', detail: String(totalRels) });
|
|
202
|
+
// Configuration
|
|
203
|
+
const configItems = [];
|
|
204
|
+
if (model.layout?.shell) {
|
|
205
|
+
configItems.push({ id: 'sum-layout', tab: 'summary', label: 'Layout', detail: model.layout.shell });
|
|
206
|
+
}
|
|
207
|
+
if (model.techStack?.toolkit) {
|
|
208
|
+
configItems.push({ id: 'sum-toolkit', tab: 'summary', label: 'Toolkit', detail: model.techStack.toolkit });
|
|
209
|
+
}
|
|
210
|
+
if (model.theme?.primaryColor) {
|
|
211
|
+
configItems.push({ id: 'sum-color', tab: 'summary', label: 'Primary Color', detail: model.theme.primaryColor });
|
|
212
|
+
}
|
|
213
|
+
const featureList = [];
|
|
214
|
+
if (model.features?.dashboard)
|
|
215
|
+
featureList.push('Dashboard');
|
|
216
|
+
if (model.features?.auth)
|
|
217
|
+
featureList.push('Auth');
|
|
218
|
+
if (model.features?.userProfiles)
|
|
219
|
+
featureList.push('Profiles');
|
|
220
|
+
if (model.features?.settings)
|
|
221
|
+
featureList.push('Settings');
|
|
222
|
+
if (model.features?.darkMode)
|
|
223
|
+
featureList.push('Dark Mode');
|
|
224
|
+
if (featureList.length > 0) {
|
|
225
|
+
configItems.push({ id: 'sum-features', tab: 'summary', label: 'Features', detail: featureList.join(', ') });
|
|
226
|
+
}
|
|
227
|
+
if (configItems.length > 0) {
|
|
228
|
+
items.push({ id: 'sum-sep2', tab: 'summary', label: '── Configuration', detail: '' });
|
|
229
|
+
items.push(...configItems);
|
|
230
|
+
}
|
|
231
|
+
return items;
|
|
232
|
+
}
|
|
@@ -20,7 +20,7 @@ interface FormField {
|
|
|
20
20
|
value: string;
|
|
21
21
|
error?: string;
|
|
22
22
|
}
|
|
23
|
-
type WizardStep = 'basic' | 'tools' | 'skills' | 'tier';
|
|
23
|
+
type WizardStep = 'template-select' | 'basic' | 'tools' | 'skills' | 'tier' | 'prompt';
|
|
24
24
|
interface FormState {
|
|
25
25
|
fields: FormField[];
|
|
26
26
|
activeFieldIndex: number;
|
|
@@ -35,20 +35,37 @@ interface FormState {
|
|
|
35
35
|
skillListIndex: number;
|
|
36
36
|
selectedModelTier: ModelTier;
|
|
37
37
|
tierListIndex: number;
|
|
38
|
+
systemPromptAddition: string;
|
|
39
|
+
templateListIndex: number;
|
|
40
|
+
saveAsTemplate: boolean;
|
|
41
|
+
templateName: string;
|
|
42
|
+
}
|
|
43
|
+
export interface TemplateOption {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
displayName: string;
|
|
47
|
+
specialty: string;
|
|
48
|
+
personality?: string;
|
|
49
|
+
systemPromptAddition?: string;
|
|
38
50
|
}
|
|
39
51
|
export interface CustomAgentFormOptions {
|
|
40
52
|
/** Existing custom agents (for duplicate check) */
|
|
41
53
|
existingCustomAgents: CustomAgentDefinition[];
|
|
42
54
|
/** Current team agent IDs (for duplicate check) */
|
|
43
55
|
teamAgentIds: string[];
|
|
56
|
+
/** Available templates to load from */
|
|
57
|
+
templates?: TemplateOption[];
|
|
44
58
|
}
|
|
45
59
|
export interface CustomAgentFormResult {
|
|
46
60
|
cancelled: boolean;
|
|
61
|
+
/** Template name to save as (if user requested) */
|
|
62
|
+
saveAsTemplate?: string;
|
|
47
63
|
agent?: {
|
|
48
64
|
id: string;
|
|
49
65
|
displayName: string;
|
|
50
66
|
specialty: string;
|
|
51
67
|
personality?: string;
|
|
68
|
+
systemPromptAddition?: string;
|
|
52
69
|
toolConfig: ToolConfig;
|
|
53
70
|
enabledSkills: string[];
|
|
54
71
|
modelTier: ModelTier;
|
|
@@ -59,8 +76,11 @@ export declare class CustomAgentFormOverlayV2 extends BaseOverlayV2<FormState, C
|
|
|
59
76
|
readonly id = "custom-agent-form-overlay-v2";
|
|
60
77
|
private readonly existingCustomAgents;
|
|
61
78
|
private readonly teamAgentIds;
|
|
79
|
+
private readonly templates;
|
|
62
80
|
constructor(options: CustomAgentFormOptions);
|
|
63
81
|
protected renderContent(context: RenderContext): string[];
|
|
82
|
+
private renderTemplateSelectStep;
|
|
83
|
+
private handleTemplateSelectKey;
|
|
64
84
|
private renderBasicStep;
|
|
65
85
|
private renderToolsStep;
|
|
66
86
|
private renderGroupItem;
|
|
@@ -72,6 +92,8 @@ export declare class CustomAgentFormOverlayV2 extends BaseOverlayV2<FormState, C
|
|
|
72
92
|
private handleSkillsStepKey;
|
|
73
93
|
private renderTierStep;
|
|
74
94
|
private handleTierStepKey;
|
|
95
|
+
private renderPromptStep;
|
|
96
|
+
private handlePromptStepKey;
|
|
75
97
|
private nextField;
|
|
76
98
|
private prevField;
|
|
77
99
|
private validateBasicInfo;
|