@causyne/agent 1.10.18 → 1.10.20
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/docs/ARCHITECTURE.md +20 -0
- package/native-skills/media/creative-studio-pipeline/SKILL.md +52 -0
- package/native-skills/media/photo-studio-automation/SKILL.md +53 -0
- package/native-skills/media/video-timeline-assembly/SKILL.md +52 -0
- package/package.json +1 -1
- package/src/agentLoop.js +4 -0
- package/src/appChannel.js +7 -3
- package/src/cliMode.js +33 -67
- package/src/commands.js +56 -8
- package/src/compiledSkills.js +15 -0
- package/src/config.js +80 -11
- package/src/creative/apps.js +69 -0
- package/src/creative/apps.ts +58 -0
- package/src/creative/blenderBatch.js +233 -0
- package/src/creative/blenderBatch.ts +217 -0
- package/src/creative/pipeline.js +56 -0
- package/src/creative/pipeline.ts +82 -0
- package/src/creative/studioScripts.js +374 -0
- package/src/creative/studioScripts.ts +378 -0
- package/src/decisionPolicy.js +2 -0
- package/src/decisionPolicy.ts +1 -0
- package/src/dynamicCatalogue.js +270 -0
- package/src/dynamicCatalogue.ts +283 -0
- package/src/entrypoint.js +2 -1
- package/src/modelCatalogue.d.ts +5 -0
- package/src/modelCatalogue.js +49 -18
- package/src/providers/anthropic.js +4 -0
- package/src/sandbox.js +4 -0
- package/src/toolPacks.js +1 -0
- package/src/tools/creativeApps.js +106 -0
- package/src/tools/creativeApps.ts +99 -0
- package/src/tools/registry.js +2 -1
- package/src/tui/banner.js +391 -190
- package/src/tui/banner.ts +409 -0
- package/src/tui/chatRenderer.js +1 -1
- package/src/tui/dialogs.js +10 -0
- package/src/tui/home.js +29 -72
- package/src/tui/setup.js +104 -0
- package/src/tui/setup.ts +98 -0
package/src/tui/setup.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const string = (value) => typeof value === 'string' ? value : '';
|
|
2
|
+
const customProviders = new Set(['openai-compatible', 'dashscope-byok', 'ollama']);
|
|
3
|
+
const defaults = {
|
|
4
|
+
ollama: 'http://localhost:11434/api/chat',
|
|
5
|
+
'dashscope-byok': 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions'
|
|
6
|
+
};
|
|
7
|
+
export function validateEndpoint(value) {
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(value);
|
|
10
|
+
if (url.username || url.password || url.search || url.hash)
|
|
11
|
+
return 'Use an endpoint without credentials, query parameters, or a fragment.';
|
|
12
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
13
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback))
|
|
14
|
+
return 'Use HTTPS, or HTTP on localhost for a local model.';
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return 'Enter a complete endpoint URL.';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function runSetup(ui, providers, store, workspace = process.cwd()) {
|
|
22
|
+
const original = store.loadConfig();
|
|
23
|
+
let draft = { ...original };
|
|
24
|
+
async function text(title, lines, value = '', mask = false, validate = () => null) {
|
|
25
|
+
let error = null;
|
|
26
|
+
for (;;) {
|
|
27
|
+
const answer = await ui.text({ title, lines: error ? [...lines, error] : lines, value,
|
|
28
|
+
placeholder: mask ? 'paste key' : '', mask });
|
|
29
|
+
if (answer === null)
|
|
30
|
+
return null;
|
|
31
|
+
const cleaned = answer.trim();
|
|
32
|
+
error = /[\x00-\x1f\x7f]/.test(cleaned) ? 'Use a single line without control characters.' : validate(cleaned);
|
|
33
|
+
if (!error)
|
|
34
|
+
return cleaned;
|
|
35
|
+
value = mask ? '' : cleaned;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (;;) {
|
|
39
|
+
const selected = await ui.select({ title: 'Setup 1/5 · provider', items: providers.map(p => ({ id: p.id, label: p.label, description: p.hint })) });
|
|
40
|
+
if (!selected)
|
|
41
|
+
return null;
|
|
42
|
+
const provider = providers.find(p => p.id === selected.id);
|
|
43
|
+
if (!provider)
|
|
44
|
+
return null;
|
|
45
|
+
const sameProvider = draft.provider === provider.id;
|
|
46
|
+
let baseUrl = '';
|
|
47
|
+
if (customProviders.has(provider.id)) {
|
|
48
|
+
const answer = await text('Setup 2/5 · endpoint', ['Full chat endpoint URL, including /v1/chat/completions or /api/chat.',
|
|
49
|
+
'Credentials are sent to this endpoint when you use the model.'], (sameProvider ? string(draft.baseUrl) : '') || defaults[provider.id] || '', false, validateEndpoint);
|
|
50
|
+
// No network request is made by setup.
|
|
51
|
+
if (answer === null)
|
|
52
|
+
return null;
|
|
53
|
+
baseUrl = answer;
|
|
54
|
+
}
|
|
55
|
+
const model = await text('Setup 3/5 · model', ['Enter the model ID supported by your provider.'], (sameProvider ? string(draft.model) : '') || provider.model, false, value => value ? null : 'A model ID is required.');
|
|
56
|
+
if (model === null)
|
|
57
|
+
return null;
|
|
58
|
+
const unchangedEndpoint = sameProvider && (string(draft.baseUrl) || defaults[provider.id] || '') === baseUrl;
|
|
59
|
+
// Fixed endpoints have no editable URL. A changed endpoint never inherits a key.
|
|
60
|
+
const keepKey = sameProvider && (!customProviders.has(provider.id) || unchangedEndpoint);
|
|
61
|
+
let apiKey = '';
|
|
62
|
+
if (provider.id !== 'ollama') {
|
|
63
|
+
const oldKey = keepKey ? string(draft.apiKey) : '';
|
|
64
|
+
const answer = await text('Setup 3/5 · credentials', ['Stored encrypted locally; used to authenticate with your selected provider.',
|
|
65
|
+
oldKey ? 'Blank keeps the key for this same provider and endpoint.' : 'Enter a key for this provider.',
|
|
66
|
+
provider.id === 'openai-compatible' ? 'Blank is allowed for an endpoint that needs no authentication.' : ''], '', true, value => value || oldKey || provider.id === 'openai-compatible' ? null : 'An API key is required.');
|
|
67
|
+
if (answer === null)
|
|
68
|
+
return null;
|
|
69
|
+
apiKey = answer || oldKey;
|
|
70
|
+
}
|
|
71
|
+
const mode = await ui.select({ title: 'Setup 4/5 · autonomy', items: [
|
|
72
|
+
{ id: 'B', label: 'B · Supervised (Standard)', description: 'Recommended. Approval for dangerous actions; routine work continues.' },
|
|
73
|
+
{ id: 'A', label: 'A · Ask first (Strict)', description: 'Explicit confirmation before every tool call.' },
|
|
74
|
+
{ id: 'C', label: 'C · Autonomous (High Velocity)', description: 'Fewer interruptions; mandatory safety and decision gates remain.' }
|
|
75
|
+
] });
|
|
76
|
+
if (!mode || !['A', 'B', 'C'].includes(mode.id))
|
|
77
|
+
return null;
|
|
78
|
+
draft = { ...draft, provider: provider.id, model, apiKey, autonomyMode: mode.id };
|
|
79
|
+
if (baseUrl)
|
|
80
|
+
draft.baseUrl = baseUrl;
|
|
81
|
+
else
|
|
82
|
+
delete draft.baseUrl;
|
|
83
|
+
const review = await ui.select({ title: 'Setup 5/5 · review', items: [
|
|
84
|
+
{ id: 'save', label: 'Save configuration', description: `${provider.label} · ${model} · Mode ${mode.id}` },
|
|
85
|
+
{ id: 'edit', label: 'Edit choices', description: baseUrl || 'Provider default endpoint' },
|
|
86
|
+
{ id: 'cancel', label: 'Cancel without saving', description: `Workspace: ${workspace}` }
|
|
87
|
+
] });
|
|
88
|
+
if (!review || review.id === 'cancel')
|
|
89
|
+
return null;
|
|
90
|
+
if (review.id === 'edit')
|
|
91
|
+
continue;
|
|
92
|
+
if (review.id !== 'save')
|
|
93
|
+
return null;
|
|
94
|
+
// Preserve unrelated settings changed while the dialog was open.
|
|
95
|
+
const next = { ...store.loadConfig(), provider: draft.provider, model, apiKey, autonomyMode: mode.id };
|
|
96
|
+
const saved = next;
|
|
97
|
+
if (baseUrl)
|
|
98
|
+
saved.baseUrl = baseUrl;
|
|
99
|
+
else
|
|
100
|
+
delete saved.baseUrl;
|
|
101
|
+
store.saveConfig(saved);
|
|
102
|
+
return { provider: provider.id, model };
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/tui/setup.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** Shared setup transaction for the home panel and standalone CLI. */
|
|
2
|
+
export interface SetupChoice { id: string; label: string; description?: string }
|
|
3
|
+
export interface SetupProvider { id: string; label: string; hint: string; model: string }
|
|
4
|
+
export interface SetupUI {
|
|
5
|
+
select(options: { title: string; items: SetupChoice[] }): Promise<SetupChoice | null>;
|
|
6
|
+
text(options: { title: string; lines: string[]; value: string; placeholder: string; mask: boolean }): Promise<string | null>;
|
|
7
|
+
}
|
|
8
|
+
type Config = Record<string, unknown>;
|
|
9
|
+
const string = (value: unknown): string => typeof value === 'string' ? value : '';
|
|
10
|
+
const customProviders = new Set(['openai-compatible', 'dashscope-byok', 'ollama']);
|
|
11
|
+
const defaults: Record<string, string> = {
|
|
12
|
+
ollama: 'http://localhost:11434/api/chat',
|
|
13
|
+
'dashscope-byok': 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function validateEndpoint(value: string): string | null {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(value);
|
|
19
|
+
if (url.username || url.password || url.search || url.hash) return 'Use an endpoint without credentials, query parameters, or a fragment.';
|
|
20
|
+
const loopback = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
|
|
21
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) return 'Use HTTPS, or HTTP on localhost for a local model.';
|
|
22
|
+
return null;
|
|
23
|
+
} catch { return 'Enter a complete endpoint URL.'; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function runSetup(ui: SetupUI, providers: SetupProvider[], store: {
|
|
27
|
+
loadConfig(): Config; saveConfig(config: Config): void;
|
|
28
|
+
}, workspace = process.cwd()): Promise<{ provider: string; model: string } | null> {
|
|
29
|
+
const original = store.loadConfig();
|
|
30
|
+
let draft: Config = { ...original };
|
|
31
|
+
async function text(title: string, lines: string[], value = '', mask = false,
|
|
32
|
+
validate: (answer: string) => string | null = () => null): Promise<string | null> {
|
|
33
|
+
let error: string | null = null;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const answer = await ui.text({ title, lines: error ? [...lines, error] : lines, value,
|
|
36
|
+
placeholder: mask ? 'paste key' : '', mask });
|
|
37
|
+
if (answer === null) return null;
|
|
38
|
+
const cleaned = answer.trim();
|
|
39
|
+
error = /[\x00-\x1f\x7f]/.test(cleaned) ? 'Use a single line without control characters.' : validate(cleaned);
|
|
40
|
+
if (!error) return cleaned;
|
|
41
|
+
value = mask ? '' : cleaned;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (;;) {
|
|
45
|
+
const selected = await ui.select({ title: 'Setup 1/5 · provider', items: providers.map(p => ({ id: p.id, label: p.label, description: p.hint })) });
|
|
46
|
+
if (!selected) return null;
|
|
47
|
+
const provider = providers.find(p => p.id === selected.id);
|
|
48
|
+
if (!provider) return null;
|
|
49
|
+
const sameProvider = draft.provider === provider.id;
|
|
50
|
+
let baseUrl = '';
|
|
51
|
+
if (customProviders.has(provider.id)) {
|
|
52
|
+
const answer = await text('Setup 2/5 · endpoint', ['Full chat endpoint URL, including /v1/chat/completions or /api/chat.',
|
|
53
|
+
'Credentials are sent to this endpoint when you use the model.'],
|
|
54
|
+
(sameProvider ? string(draft.baseUrl) : '') || defaults[provider.id] || '', false, validateEndpoint);
|
|
55
|
+
// No network request is made by setup.
|
|
56
|
+
if (answer === null) return null;
|
|
57
|
+
baseUrl = answer;
|
|
58
|
+
}
|
|
59
|
+
const model = await text('Setup 3/5 · model', ['Enter the model ID supported by your provider.'],
|
|
60
|
+
(sameProvider ? string(draft.model) : '') || provider.model, false, value => value ? null : 'A model ID is required.');
|
|
61
|
+
if (model === null) return null;
|
|
62
|
+
const unchangedEndpoint = sameProvider && (string(draft.baseUrl) || defaults[provider.id] || '') === baseUrl;
|
|
63
|
+
// Fixed endpoints have no editable URL. A changed endpoint never inherits a key.
|
|
64
|
+
const keepKey = sameProvider && (!customProviders.has(provider.id) || unchangedEndpoint);
|
|
65
|
+
let apiKey = '';
|
|
66
|
+
if (provider.id !== 'ollama') {
|
|
67
|
+
const oldKey = keepKey ? string(draft.apiKey) : '';
|
|
68
|
+
const answer = await text('Setup 3/5 · credentials', ['Stored encrypted locally; used to authenticate with your selected provider.',
|
|
69
|
+
oldKey ? 'Blank keeps the key for this same provider and endpoint.' : 'Enter a key for this provider.',
|
|
70
|
+
provider.id === 'openai-compatible' ? 'Blank is allowed for an endpoint that needs no authentication.' : ''], '', true,
|
|
71
|
+
value => value || oldKey || provider.id === 'openai-compatible' ? null : 'An API key is required.');
|
|
72
|
+
if (answer === null) return null;
|
|
73
|
+
apiKey = answer || oldKey;
|
|
74
|
+
}
|
|
75
|
+
const mode = await ui.select({ title: 'Setup 4/5 · autonomy', items: [
|
|
76
|
+
{ id: 'B', label: 'B · Supervised (Standard)', description: 'Recommended. Approval for dangerous actions; routine work continues.' },
|
|
77
|
+
{ id: 'A', label: 'A · Ask first (Strict)', description: 'Explicit confirmation before every tool call.' },
|
|
78
|
+
{ id: 'C', label: 'C · Autonomous (High Velocity)', description: 'Fewer interruptions; mandatory safety and decision gates remain.' }
|
|
79
|
+
] });
|
|
80
|
+
if (!mode || !['A', 'B', 'C'].includes(mode.id)) return null;
|
|
81
|
+
draft = { ...draft, provider: provider.id, model, apiKey, autonomyMode: mode.id };
|
|
82
|
+
if (baseUrl) draft.baseUrl = baseUrl; else delete draft.baseUrl;
|
|
83
|
+
const review = await ui.select({ title: 'Setup 5/5 · review', items: [
|
|
84
|
+
{ id: 'save', label: 'Save configuration', description: `${provider.label} · ${model} · Mode ${mode.id}` },
|
|
85
|
+
{ id: 'edit', label: 'Edit choices', description: baseUrl || 'Provider default endpoint' },
|
|
86
|
+
{ id: 'cancel', label: 'Cancel without saving', description: `Workspace: ${workspace}` }
|
|
87
|
+
] });
|
|
88
|
+
if (!review || review.id === 'cancel') return null;
|
|
89
|
+
if (review.id === 'edit') continue;
|
|
90
|
+
if (review.id !== 'save') return null;
|
|
91
|
+
// Preserve unrelated settings changed while the dialog was open.
|
|
92
|
+
const next = { ...store.loadConfig(), provider: draft.provider, model, apiKey, autonomyMode: mode.id };
|
|
93
|
+
const saved: Config = next;
|
|
94
|
+
if (baseUrl) saved.baseUrl = baseUrl; else delete saved.baseUrl;
|
|
95
|
+
store.saveConfig(saved);
|
|
96
|
+
return { provider: provider.id, model };
|
|
97
|
+
}
|
|
98
|
+
}
|