@synergenius/flow-weaver-pack-weaver 0.9.4 → 0.9.6

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 (35) hide show
  1. package/dist/bot/assistant-core.d.ts +2 -0
  2. package/dist/bot/assistant-core.d.ts.map +1 -1
  3. package/dist/bot/assistant-core.js +130 -17
  4. package/dist/bot/assistant-core.js.map +1 -1
  5. package/dist/bot/conversation-store.d.ts +1 -0
  6. package/dist/bot/conversation-store.d.ts.map +1 -1
  7. package/dist/bot/conversation-store.js +32 -0
  8. package/dist/bot/conversation-store.js.map +1 -1
  9. package/dist/bot/response-formatter.d.ts +15 -0
  10. package/dist/bot/response-formatter.d.ts.map +1 -0
  11. package/dist/bot/response-formatter.js +40 -0
  12. package/dist/bot/response-formatter.js.map +1 -0
  13. package/dist/bot/rich-input.d.ts +39 -0
  14. package/dist/bot/rich-input.d.ts.map +1 -0
  15. package/dist/bot/rich-input.js +308 -0
  16. package/dist/bot/rich-input.js.map +1 -0
  17. package/dist/bot/slash-commands.d.ts +20 -0
  18. package/dist/bot/slash-commands.d.ts.map +1 -0
  19. package/dist/bot/slash-commands.js +93 -0
  20. package/dist/bot/slash-commands.js.map +1 -0
  21. package/dist/cli-handlers.d.ts +1 -0
  22. package/dist/cli-handlers.d.ts.map +1 -1
  23. package/dist/cli-handlers.js +103 -2
  24. package/dist/cli-handlers.js.map +1 -1
  25. package/dist/node-types/agent-execute.js +15 -3
  26. package/dist/node-types/agent-execute.js.map +1 -1
  27. package/flowweaver.manifest.json +1 -1
  28. package/package.json +1 -1
  29. package/src/bot/assistant-core.ts +131 -19
  30. package/src/bot/conversation-store.ts +32 -0
  31. package/src/bot/response-formatter.ts +42 -0
  32. package/src/bot/rich-input.ts +307 -0
  33. package/src/bot/slash-commands.ts +114 -0
  34. package/src/cli-handlers.ts +105 -3
  35. package/src/node-types/agent-execute.ts +17 -4
@@ -0,0 +1,114 @@
1
+ import type { ToolExecutor } from '@synergenius/flow-weaver/agent';
2
+ import { c } from './ansi.js';
3
+
4
+ export interface SlashContext {
5
+ executor: ToolExecutor;
6
+ out: (s: string) => void;
7
+ projectDir: string;
8
+ conversationId?: string;
9
+ onClear?: () => void;
10
+ onExit?: () => void;
11
+ onNew?: () => void;
12
+ onVerbose?: () => void;
13
+ }
14
+
15
+ export interface SlashCommand {
16
+ name: string;
17
+ description: string;
18
+ handler: (ctx: SlashContext, args: string) => Promise<void>;
19
+ }
20
+
21
+ export const SLASH_COMMANDS: SlashCommand[] = [
22
+ {
23
+ name: '/help',
24
+ description: 'Show available commands',
25
+ handler: async (ctx) => {
26
+ ctx.out('\n Available commands:\n');
27
+ for (const cmd of SLASH_COMMANDS) {
28
+ ctx.out(` ${c.cyan(cmd.name.padEnd(12))} ${c.dim(cmd.description)}\n`);
29
+ }
30
+ ctx.out('\n');
31
+ },
32
+ },
33
+ {
34
+ name: '/status',
35
+ description: 'Show bots, queue, and conversation summary',
36
+ handler: async (ctx) => {
37
+ const bots = await ctx.executor('bot_list', {});
38
+ const summary = await ctx.executor('conversation_summary', {});
39
+ ctx.out(`\n ${bots.result}\n ${summary.result}\n\n`);
40
+ },
41
+ },
42
+ {
43
+ name: '/bots',
44
+ description: 'List running bots',
45
+ handler: async (ctx) => {
46
+ const result = await ctx.executor('bot_list', {});
47
+ ctx.out(`\n ${result.result}\n\n`);
48
+ },
49
+ },
50
+ {
51
+ name: '/clear',
52
+ description: 'Clear screen and start new conversation',
53
+ handler: async (ctx) => {
54
+ process.stderr.write('\x1b[2J\x1b[H');
55
+ ctx.onClear?.();
56
+ },
57
+ },
58
+ {
59
+ name: '/exit',
60
+ description: 'Exit the assistant',
61
+ handler: async (ctx) => {
62
+ ctx.onExit?.();
63
+ },
64
+ },
65
+ {
66
+ name: '/new',
67
+ description: 'Start a new conversation',
68
+ handler: async (ctx) => {
69
+ ctx.onNew?.();
70
+ ctx.out(`\n ${c.dim('New conversation started.')}\n\n`);
71
+ },
72
+ },
73
+ {
74
+ name: '/list',
75
+ description: 'List saved conversations',
76
+ handler: async (ctx) => {
77
+ const result = await ctx.executor('conversation_list', {});
78
+ ctx.out(`\n ${result.result}\n\n`);
79
+ },
80
+ },
81
+ {
82
+ name: '/verbose',
83
+ description: 'Toggle verbose mode (show AI thinking)',
84
+ handler: async (ctx) => {
85
+ ctx.onVerbose?.();
86
+ },
87
+ },
88
+ {
89
+ name: '/history',
90
+ description: 'Show conversation history summary',
91
+ handler: async (ctx) => {
92
+ const result = await ctx.executor('conversation_summary', {});
93
+ ctx.out(`\n ${result.result}\n\n`);
94
+ },
95
+ },
96
+ ];
97
+
98
+ export function getSlashCompletions(partial: string): string[] {
99
+ if (!partial.startsWith('/')) return [];
100
+ return SLASH_COMMANDS
101
+ .filter(cmd => cmd.name.startsWith(partial))
102
+ .map(cmd => cmd.name);
103
+ }
104
+
105
+ export async function handleSlashCommand(
106
+ input: string,
107
+ ctx: SlashContext,
108
+ ): Promise<boolean> {
109
+ const [name, ...rest] = input.split(' ');
110
+ const cmd = SLASH_COMMANDS.find(c => c.name === name);
111
+ if (!cmd) return false;
112
+ await cmd.handler(ctx, rest.join(' '));
113
+ return true;
114
+ }
@@ -1,5 +1,6 @@
1
1
  import * as path from 'node:path';
2
2
  import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import { runWorkflow } from './bot/runner.js';
5
6
  import { RunStore } from './bot/run-store.js';
@@ -67,6 +68,7 @@ export interface ParsedArgs {
67
68
  assistantResume?: string;
68
69
  assistantList?: boolean;
69
70
  assistantDelete?: string;
71
+ assistantWatch?: string;
70
72
  }
71
73
 
72
74
  export function parseArgs(argv: string[]): ParsedArgs {
@@ -261,6 +263,9 @@ export function parseArgs(argv: string[]): ParsedArgs {
261
263
  result.genesisInit = true;
262
264
  } else if (arg === '--watch') {
263
265
  result.genesisWatch = true;
266
+ } else if (arg === '--watch-dir' && i + 1 < args.length) {
267
+ i++;
268
+ result.assistantWatch = args[i];
264
269
  } else if (arg === '--new') {
265
270
  result.assistantNew = true;
266
271
  } else if (arg === '--resume' && i + 1 < args.length) {
@@ -1093,6 +1098,43 @@ export async function handleEject(opts: ParsedArgs): Promise<void> {
1093
1098
  console.log(`[weaver] Metadata written to ${metaPath}`);
1094
1099
  console.log('[weaver] You can now customize the ejected workflow(s) freely.');
1095
1100
  console.log('[weaver] The bot will use local files when available.');
1101
+
1102
+ // Generate CUSTOMIZATION.md guide
1103
+ const customizationGuide = `# Customizing Your Weaver Bot
1104
+
1105
+ ## What You Got
1106
+ ${results.map(r => `- ${r.file} — ${r.workflow} workflow (${r.nodeTypes.length} node types)`).join('\n')}
1107
+
1108
+ You can edit these files freely. Weaver will use your local versions.
1109
+
1110
+ ## Safe Changes
1111
+ - Add a notification node (Slack, Discord, email) after git-ops
1112
+ - Change approval mode: edit the @node approve line
1113
+ - Add custom validation: insert a node between agent and gitOps
1114
+ - Modify the system prompt: edit system-prompt.ts
1115
+ - Add new tools: create a node type and wire it in
1116
+
1117
+ ## How to Add a Node
1118
+ 1. Create a new file: my-node.ts with @flowWeaver nodeType annotation
1119
+ 2. Import it in the workflow file
1120
+ 3. Add: @node myNode myNodeType [color: "blue"] [icon: "star"]
1121
+ 4. Wire it: @connect existingNode.output -> myNode.input
1122
+ 5. Compile: flow-weaver compile <workflow-file>
1123
+
1124
+ ## How to Test
1125
+ flow-weaver validate <workflow-file> # check for errors
1126
+ flow-weaver diagram <workflow-file> # visualize the DAG
1127
+ weaver bot "test task" --auto-approve # run a test task
1128
+
1129
+ ## Learn More
1130
+ weaver examples # see what's possible
1131
+ weaver doctor # check your setup
1132
+ flow-weaver docs concepts # Flow Weaver documentation
1133
+ `;
1134
+
1135
+ const customPath = path.resolve(destDir, 'CUSTOMIZATION.md');
1136
+ fs.writeFileSync(customPath, customizationGuide, 'utf-8');
1137
+ console.log(`[weaver] Generated ${customPath}`);
1096
1138
  }
1097
1139
 
1098
1140
  export async function handleRun(opts: ParsedArgs): Promise<void> {
@@ -1525,6 +1567,32 @@ export async function handleSession(opts: ParsedArgs): Promise<void> {
1525
1567
  sendDesktopNotification('Weaver Session Complete', `${sessionCompleted} done, ${sessionFailed} failed, ${sessionNoOp} no-op`);
1526
1568
  } catch { /* non-fatal */ }
1527
1569
  }
1570
+
1571
+ // Webhook notification if configured
1572
+ if (config?.notify) {
1573
+ try {
1574
+ const webhookUrl = typeof config.notify === 'string' ? config.notify
1575
+ : typeof config.notify === 'object' && 'webhook' in config.notify ? (config.notify as { webhook: string }).webhook
1576
+ : null;
1577
+ if (webhookUrl) {
1578
+ fetch(webhookUrl, {
1579
+ method: 'POST',
1580
+ headers: { 'Content-Type': 'application/json' },
1581
+ body: JSON.stringify({
1582
+ event: 'session.completed',
1583
+ timestamp: new Date().toISOString(),
1584
+ tasks: taskCount,
1585
+ completed: sessionCompleted,
1586
+ failed: sessionFailed,
1587
+ noOp: sessionNoOp,
1588
+ tokens: sessionInputTokens + sessionOutputTokens,
1589
+ cost: sessionCost,
1590
+ elapsed: Date.now() - sessionStartTime,
1591
+ }),
1592
+ }).catch(() => {}); // fire-and-forget
1593
+ }
1594
+ } catch { /* non-fatal */ }
1595
+ }
1528
1596
  }
1529
1597
 
1530
1598
  export async function handleAssistant(opts: ParsedArgs): Promise<void> {
@@ -1559,13 +1627,37 @@ export async function handleAssistant(opts: ParsedArgs): Promise<void> {
1559
1627
 
1560
1628
  const config = await loadConfig(opts.configPath);
1561
1629
 
1630
+ // Check platform login — override provider if logged in
1631
+ let providerOverride: string | undefined;
1632
+ try {
1633
+ const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
1634
+ if (fs.existsSync(credPath)) {
1635
+ const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
1636
+ if (creds.token && creds.platformUrl && creds.expiresAt > Date.now()) {
1637
+ providerOverride = 'platform';
1638
+ // Make credentials available to the provider
1639
+ process.env.FW_PLATFORM_TOKEN = creds.token;
1640
+ process.env.FW_PLATFORM_URL = creds.platformUrl;
1641
+ }
1642
+ }
1643
+ } catch { /* not available */ }
1644
+
1562
1645
  // Create provider
1563
- const { createAnthropicProvider, createClaudeCliProvider } = await import('@synergenius/flow-weaver/agent');
1646
+ const agentMod = await import('@synergenius/flow-weaver/agent');
1647
+ const { createAnthropicProvider, createClaudeCliProvider } = agentMod;
1648
+ const createPlatformProvider = 'createPlatformProvider' in agentMod
1649
+ ? (agentMod as Record<string, unknown>).createPlatformProvider as (opts: { token: string; platformUrl: string }) => unknown
1650
+ : null;
1564
1651
  const providerSetting = config?.provider ?? 'auto';
1565
1652
  const providerType = typeof providerSetting === 'object' ? providerSetting.name : String(providerSetting);
1566
1653
 
1567
- let provider;
1568
- if (providerType === 'anthropic' || (providerType === 'auto' && process.env.ANTHROPIC_API_KEY)) {
1654
+ let provider: import('@synergenius/flow-weaver/agent').AgentProvider;
1655
+ if ((providerOverride === 'platform' || providerType === 'platform') && createPlatformProvider) {
1656
+ provider = createPlatformProvider({
1657
+ token: process.env.FW_PLATFORM_TOKEN!,
1658
+ platformUrl: process.env.FW_PLATFORM_URL!,
1659
+ }) as import('@synergenius/flow-weaver/agent').AgentProvider;
1660
+ } else if (providerType === 'anthropic' || (providerType === 'auto' && process.env.ANTHROPIC_API_KEY)) {
1569
1661
  const apiKey = process.env.ANTHROPIC_API_KEY ?? (typeof providerSetting === 'object' ? (providerSetting as { apiKey?: string }).apiKey : undefined);
1570
1662
  if (!apiKey) { console.error('ANTHROPIC_API_KEY required for anthropic provider'); process.exit(1); }
1571
1663
  provider = createAnthropicProvider({ apiKey, model: typeof providerSetting === 'object' ? providerSetting.model : undefined });
@@ -1584,6 +1676,7 @@ export async function handleAssistant(opts: ParsedArgs): Promise<void> {
1584
1676
  projectDir,
1585
1677
  resumeId: opts.assistantResume,
1586
1678
  newConversation: opts.assistantNew,
1679
+ watchDir: opts.assistantWatch,
1587
1680
  });
1588
1681
  }
1589
1682
 
@@ -2125,6 +2218,15 @@ export async function handleDoctor(opts: ParsedArgs): Promise<void> {
2125
2218
  checks.push({ label: 'Connection', status: 'warn', detail: 'Not tested' });
2126
2219
  }
2127
2220
 
2221
+ // Weaver version (this pack)
2222
+ try {
2223
+ const url = await import('node:url');
2224
+ const packPkg = JSON.parse(fs.readFileSync(new url.URL('../package.json', import.meta.url) as unknown as string, 'utf-8'));
2225
+ checks.push({ label: 'Weaver', status: 'ok', detail: `v${packPkg.version}` });
2226
+ } catch {
2227
+ checks.push({ label: 'Weaver', status: 'ok', detail: 'unknown version' });
2228
+ }
2229
+
2128
2230
  // Flow Weaver version
2129
2231
  try {
2130
2232
  const { execFileSync: fwCheck } = await import('node:child_process');
@@ -140,7 +140,7 @@ export async function weaverAgentExecute(
140
140
  auditEmit('run-start', { task: task.instruction });
141
141
 
142
142
  try {
143
- const provider = createProvider(pInfo, projectDir);
143
+ const provider = await createProvider(pInfo, projectDir);
144
144
  const executor = createWeaverExecutor(projectDir);
145
145
 
146
146
  const filesModified: string[] = [];
@@ -253,10 +253,10 @@ export async function weaverAgentExecute(
253
253
  /**
254
254
  * Create an AgentProvider from pack-weaver's ProviderInfo.
255
255
  */
256
- function createProvider(
256
+ async function createProvider(
257
257
  pInfo: { type: string; apiKey?: string; model?: string; maxTokens?: number },
258
258
  projectDir?: string,
259
- ): AgentProvider {
259
+ ): Promise<AgentProvider> {
260
260
  const type = pInfo.type ?? 'auto';
261
261
 
262
262
  // Explicit Anthropic API
@@ -273,8 +273,21 @@ function createProvider(
273
273
  return createSessionProvider(pInfo.model, projectDir);
274
274
  }
275
275
 
276
- // Auto mode: try Anthropic API key from env, then fall back to Claude CLI
276
+ // Auto mode: try platform login, then Anthropic API key, then Claude CLI
277
277
  if (type === 'auto') {
278
+ // Check platform login first (no local key needed)
279
+ // Uses env vars set by handleSession/handleAssistant, or reads credentials directly
280
+ if (process.env.FW_PLATFORM_TOKEN && process.env.FW_PLATFORM_URL) {
281
+ try {
282
+ const agentMod = await import('@synergenius/flow-weaver/agent');
283
+ if ('createPlatformProvider' in agentMod) {
284
+ const factory = (agentMod as Record<string, unknown>).createPlatformProvider as
285
+ (opts: { token: string; platformUrl: string }) => AgentProvider;
286
+ return factory({ token: process.env.FW_PLATFORM_TOKEN, platformUrl: process.env.FW_PLATFORM_URL });
287
+ }
288
+ } catch { /* platform provider not available in this fw version */ }
289
+ }
290
+
278
291
  if (process.env.ANTHROPIC_API_KEY) {
279
292
  return createAnthropicProvider({
280
293
  apiKey: process.env.ANTHROPIC_API_KEY,