@sayknow-cli/coding-agent 0.5.11 → 0.5.14
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/CHANGELOG.md +35 -0
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/config/file-lock.d.ts +8 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/defaults/skc-ui-skills.d.ts +12 -0
- package/dist/types/hooks/ui-skill-keywords.d.ts +84 -0
- package/dist/types/session-import/redact.d.ts +1 -1
- package/dist/types/setup/external-ui-skills.d.ts +37 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +13 -1
- package/dist/types/skc-runtime/ultragoal-succession.d.ts +251 -0
- package/dist/types/skc-runtime/workflow-placeholder.d.ts +6 -0
- package/package.json +7 -7
- package/src/cli/setup-cli.ts +22 -1
- package/src/cli/skills-cli.ts +29 -10
- package/src/commands/setup.ts +1 -0
- package/src/config/file-lock-gc.ts +39 -2
- package/src/config/file-lock.ts +22 -1
- package/src/config/model-registry.ts +116 -40
- package/src/config/models-config-schema.ts +1 -0
- package/src/defaults/skc/skills/ultragoal/SKILL.md +3 -0
- package/src/defaults/skc/ui-skills/LICENSE.appllama +21 -0
- package/src/defaults/skc/ui-skills/LICENSE.emilkowalski +21 -0
- package/src/defaults/skc/ui-skills/NOTICE.md +43 -0
- package/src/defaults/skc/ui-skills/animate/SKILL.md +536 -0
- package/src/defaults/skc/ui-skills/animation-vocabulary/SKILL.md +178 -0
- package/src/defaults/skc/ui-skills/apple-design/SKILL.md +285 -0
- package/src/defaults/skc/ui-skills/appllama-app-design-skill/SKILL.md +821 -0
- package/src/defaults/skc/ui-skills/ask-sonner/SKILL.md +157 -0
- package/src/defaults/skc/ui-skills/emil-design-eng/SKILL.md +671 -0
- package/src/defaults/skc/ui-skills/find-animation-opportunities/SKILL.md +137 -0
- package/src/defaults/skc/ui-skills/improve-animations/SKILL.md +305 -0
- package/src/defaults/skc/ui-skills/mobile-native/SKILL.md +308 -0
- package/src/defaults/skc/ui-skills/pick-ui-library/SKILL.md +82 -0
- package/src/defaults/skc/ui-skills/prototype/SKILL.md +300 -0
- package/src/defaults/skc/ui-skills/react-bits/SKILL.md +113 -0
- package/src/defaults/skc/ui-skills/review-animations/SKILL.md +312 -0
- package/src/defaults/skc-ui-skills.ts +108 -0
- package/src/extensibility/runtime-skill-discovery.ts +8 -2
- package/src/hooks/native-skill-hook.ts +14 -0
- package/src/hooks/ui-skill-keywords.ts +312 -0
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/agents/executor.md +1 -0
- package/src/prompts/agents/planner.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/skill.md +2 -2
- package/src/sdk/session.ts +7 -5
- package/src/session-import/redact.ts +11 -3
- package/src/setup/external-ui-skills.ts +132 -0
- package/src/skc-runtime/state-writer.ts +24 -27
- package/src/skc-runtime/ultragoal-runtime.ts +125 -45
- package/src/skc-runtime/ultragoal-succession.ts +1667 -0
- package/src/skc-runtime/workflow-placeholder.ts +33 -0
package/src/cli/skills-cli.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Handles `skc skills` for inspecting bundled workflow skill definitions.
|
|
2
|
+
* Handles `skc skills` for inspecting bundled workflow and UI skill definitions.
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_SKC_DEFINITION_NAMES,
|
|
6
6
|
type EmbeddedDefaultSkcSkill,
|
|
7
7
|
getEmbeddedDefaultSkcSkills,
|
|
8
8
|
} from "../defaults/skc-defaults";
|
|
9
|
+
import { getEmbeddedSkcUiSkills } from "../defaults/skc-ui-skills";
|
|
10
|
+
import type { Skill } from "../extensibility/skills";
|
|
9
11
|
|
|
10
12
|
export type SkillsAction = "list" | "read";
|
|
11
13
|
|
|
@@ -28,17 +30,21 @@ interface SkillsReadEntry extends SkillsListEntry {
|
|
|
28
30
|
content: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function getEmbeddedSkill(name: string): EmbeddedDefaultSkcSkill | undefined {
|
|
32
|
-
return
|
|
33
|
+
function getEmbeddedSkill(name: string): EmbeddedDefaultSkcSkill | Skill | undefined {
|
|
34
|
+
return (
|
|
35
|
+
getEmbeddedDefaultSkcSkills().find(skill => skill.name === name) ??
|
|
36
|
+
getEmbeddedSkcUiSkills().find(skill => skill.name === name)
|
|
37
|
+
);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
function listEmbeddedSkills(): SkillsListEntry[] {
|
|
36
|
-
|
|
41
|
+
const toEntry = (skill: EmbeddedDefaultSkcSkill | Skill): SkillsListEntry => ({
|
|
37
42
|
name: skill.name,
|
|
38
43
|
description: skill.description,
|
|
39
44
|
path: skill.filePath,
|
|
40
45
|
source: skill.source,
|
|
41
|
-
})
|
|
46
|
+
});
|
|
47
|
+
return [...getEmbeddedDefaultSkcSkills().map(toEntry), ...getEmbeddedSkcUiSkills().map(toEntry)];
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
function writeJson(value: unknown): void {
|
|
@@ -60,14 +66,27 @@ export async function runSkillsCommand(cmd: SkillsCommandArgs): Promise<void> {
|
|
|
60
66
|
|
|
61
67
|
const name = cmd.name?.trim();
|
|
62
68
|
if (!name) {
|
|
63
|
-
process.stderr.write(
|
|
69
|
+
process.stderr.write(
|
|
70
|
+
`error: skill name is required for read (${DEFAULT_SKC_DEFINITION_NAMES.join(", ")}, or a bundled UI skill such as emil-design-eng)\n`,
|
|
71
|
+
);
|
|
64
72
|
process.exitCode = 1;
|
|
65
73
|
return;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
const skill = getEmbeddedSkill(name);
|
|
69
77
|
if (!skill) {
|
|
70
|
-
process.stderr.write(
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
`error: unknown embedded skill "${name}" (${DEFAULT_SKC_DEFINITION_NAMES.join(", ")}, or a bundled UI skill such as emil-design-eng)\n`,
|
|
80
|
+
);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Every embedded skill carries inline content; a missing body means the
|
|
86
|
+
// bundle was built wrong, so fail loudly instead of printing "undefined".
|
|
87
|
+
const content = skill.content;
|
|
88
|
+
if (typeof content !== "string" || content.length === 0) {
|
|
89
|
+
process.stderr.write(`error: embedded skill "${name}" has no bundled content\n`);
|
|
71
90
|
process.exitCode = 1;
|
|
72
91
|
return;
|
|
73
92
|
}
|
|
@@ -77,12 +96,12 @@ export async function runSkillsCommand(cmd: SkillsCommandArgs): Promise<void> {
|
|
|
77
96
|
description: skill.description,
|
|
78
97
|
path: skill.filePath,
|
|
79
98
|
source: skill.source,
|
|
80
|
-
content
|
|
99
|
+
content,
|
|
81
100
|
};
|
|
82
101
|
if (cmd.flags?.json) {
|
|
83
102
|
writeJson(entry);
|
|
84
103
|
return;
|
|
85
104
|
}
|
|
86
|
-
process.stdout.write(
|
|
87
|
-
if (!
|
|
105
|
+
process.stdout.write(content);
|
|
106
|
+
if (!content.endsWith("\n")) process.stdout.write("\n");
|
|
88
107
|
}
|
package/src/commands/setup.ts
CHANGED
|
@@ -58,9 +58,33 @@ function keptMalformedRecord(lockDir: string): GcRecord {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function emptyLockDirRecord(lockDir: string): GcRecord {
|
|
62
|
+
return {
|
|
63
|
+
store: "file_locks",
|
|
64
|
+
id: lockDir,
|
|
65
|
+
path: lockDir,
|
|
66
|
+
pid_status: "none",
|
|
67
|
+
status: "stale",
|
|
68
|
+
stale: true,
|
|
69
|
+
removable: true,
|
|
70
|
+
action: "none",
|
|
71
|
+
reason: "empty_file_lock_dir",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
async function collectLockRecord(lockDir: string, ctx: GcContext): Promise<GcRecord> {
|
|
62
76
|
const info = await readFileLockInfoForGc(lockDir);
|
|
63
|
-
if (!info)
|
|
77
|
+
if (!info) {
|
|
78
|
+
// mkdir-before-info crash leftover: an empty `.lock` dir has no owner token
|
|
79
|
+
// and is safe to reclaim. Any other info-less shape stays fail-closed.
|
|
80
|
+
try {
|
|
81
|
+
const entries = await fs.readdir(lockDir);
|
|
82
|
+
if (entries.length === 0) return emptyLockDirRecord(lockDir);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (!isEnoent(error)) throw error;
|
|
85
|
+
}
|
|
86
|
+
return keptMalformedRecord(lockDir);
|
|
87
|
+
}
|
|
64
88
|
|
|
65
89
|
const probeResult = ctx.probe(info.pid);
|
|
66
90
|
const pidStatus = gcPidStatusLabel(probeResult);
|
|
@@ -165,7 +189,20 @@ export const fileLocksGcAdapter: GcStoreAdapter = {
|
|
|
165
189
|
async prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome> {
|
|
166
190
|
const lockDir = record.path ?? record.id;
|
|
167
191
|
const info = await readFileLockInfoForGc(lockDir);
|
|
168
|
-
if (!info)
|
|
192
|
+
if (!info) {
|
|
193
|
+
if (record.reason !== "empty_file_lock_dir") {
|
|
194
|
+
return { removed: false, skipped: "lock_no_longer_dead_or_missing" };
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const entries = await fs.readdir(lockDir);
|
|
198
|
+
if (entries.length !== 0) return { removed: false, skipped: "lock_no_longer_dead_or_missing" };
|
|
199
|
+
await fs.rmdir(lockDir);
|
|
200
|
+
return { removed: true };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (isEnoent(error)) return { removed: false, skipped: "lock_no_longer_dead_or_missing" };
|
|
203
|
+
return { removed: false, error: errorMessage(error) };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
169
206
|
|
|
170
207
|
const probeResult = ctx.probe(info.pid);
|
|
171
208
|
if (probeResult.status !== "dead") {
|
package/src/config/file-lock.ts
CHANGED
|
@@ -14,6 +14,21 @@ const DEFAULT_OPTIONS: Required<FileLockOptions> = {
|
|
|
14
14
|
retries: 50,
|
|
15
15
|
retryDelayMs: 100,
|
|
16
16
|
};
|
|
17
|
+
export class FileLockAcquireError extends Error {
|
|
18
|
+
readonly code = "acquire_timeout";
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
readonly filePath: string,
|
|
22
|
+
readonly lockPath: string,
|
|
23
|
+
readonly attempts: number,
|
|
24
|
+
readonly holder: string,
|
|
25
|
+
) {
|
|
26
|
+
super(
|
|
27
|
+
`Failed to acquire lock for ${filePath} after ${attempts} attempts: ${holder} (${lockPath}); a live owner is never displaced`,
|
|
28
|
+
);
|
|
29
|
+
this.name = "FileLockAcquireError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
17
32
|
|
|
18
33
|
type LockInfo = FileLockOwnerToken;
|
|
19
34
|
|
|
@@ -243,6 +258,12 @@ async function releaseLock(lockPath: string, owner: FileLockOwnerToken): Promise
|
|
|
243
258
|
const outcome = await removeFileLockDirForGc(lockPath, owner);
|
|
244
259
|
if (outcome !== "removed") throw new Error(`Failed to release file lock: ${outcome}.`);
|
|
245
260
|
}
|
|
261
|
+
async function lockHolderDescription(lockPath: string): Promise<string> {
|
|
262
|
+
const info = await readLockInfo(lockPath);
|
|
263
|
+
if (!info) return "unknown holder";
|
|
264
|
+
return `pid ${info.pid}`;
|
|
265
|
+
}
|
|
266
|
+
|
|
246
267
|
async function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<() => Promise<void>> {
|
|
247
268
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
248
269
|
const lockPath = getLockPath(filePath);
|
|
@@ -255,7 +276,7 @@ async function acquireLock(filePath: string, options: FileLockOptions = {}): Pro
|
|
|
255
276
|
if (await removeStaleLockForAcquire(lockPath, stale)) continue;
|
|
256
277
|
await Bun.sleep(opts.retryDelayMs);
|
|
257
278
|
}
|
|
258
|
-
throw new
|
|
279
|
+
throw new FileLockAcquireError(filePath, lockPath, opts.retries, await lockHolderDescription(lockPath));
|
|
259
280
|
}
|
|
260
281
|
|
|
261
282
|
/**
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
UNK_MAX_TOKENS,
|
|
30
30
|
unregisterCustomApis,
|
|
31
31
|
} from "@sayknow-cli/ai";
|
|
32
|
+
import { detectDiscoveredApiFamily } from "@sayknow-cli/ai/utils/discovery/openai-compatible";
|
|
32
33
|
|
|
33
34
|
// Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
|
|
34
35
|
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
@@ -212,6 +213,36 @@ type ProviderValidationMode = "models-config" | "runtime-register";
|
|
|
212
213
|
|
|
213
214
|
const OPENAI_REQUEST_TRANSFORM_APIS = new Set<Api>(["openai-completions", "openai-responses"]);
|
|
214
215
|
|
|
216
|
+
const OPENAI_FAMILY_APIS = new Set<Api>([
|
|
217
|
+
"openai-completions",
|
|
218
|
+
"openai-responses",
|
|
219
|
+
"openai-codex-responses",
|
|
220
|
+
"azure-openai-responses",
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
function isOpenAIFamilyApi(api: Api): boolean {
|
|
224
|
+
return OPENAI_FAMILY_APIS.has(api);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function dominantOpenAIFamilyModelApi(models: readonly { api?: string }[] | undefined): Api | undefined {
|
|
228
|
+
if (!models || models.length === 0) return undefined;
|
|
229
|
+
const counts = new Map<Api, number>();
|
|
230
|
+
for (const model of models) {
|
|
231
|
+
const api = model.api as Api | undefined;
|
|
232
|
+
if (api === undefined || !isOpenAIFamilyApi(api)) continue;
|
|
233
|
+
counts.set(api, (counts.get(api) ?? 0) + 1);
|
|
234
|
+
}
|
|
235
|
+
let best: Api | undefined;
|
|
236
|
+
let bestCount = 0;
|
|
237
|
+
for (const [api, count] of counts) {
|
|
238
|
+
if (count > bestCount) {
|
|
239
|
+
best = api;
|
|
240
|
+
bestCount = count;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return best;
|
|
244
|
+
}
|
|
245
|
+
|
|
215
246
|
function getKnownProviderApis(providerName: string): Set<Api> {
|
|
216
247
|
const apis = new Set<Api>();
|
|
217
248
|
for (const model of getBundledModels(providerName as Parameters<typeof getBundledModels>[0])) {
|
|
@@ -1563,17 +1594,30 @@ export class ModelRegistry {
|
|
|
1563
1594
|
keylessProviders.add(providerName);
|
|
1564
1595
|
}
|
|
1565
1596
|
|
|
1566
|
-
|
|
1597
|
+
const effectiveDiscoveryBaseUrl = providerConfig.baseUrl ?? resolveProviderBaseUrlFromEnv(providerName);
|
|
1598
|
+
const providerApi =
|
|
1599
|
+
(providerConfig.api as Api | undefined) ??
|
|
1600
|
+
dominantOpenAIFamilyModelApi(providerConfig.models as { api?: string }[] | undefined);
|
|
1601
|
+
const autoDiscovery: ProviderDiscovery | undefined =
|
|
1602
|
+
!providerConfig.discovery &&
|
|
1603
|
+
!localOpenAICompat &&
|
|
1604
|
+
providerApi !== undefined &&
|
|
1605
|
+
isOpenAIFamilyApi(providerApi) &&
|
|
1606
|
+
effectiveDiscoveryBaseUrl !== undefined
|
|
1607
|
+
? { type: "openai-models-list" }
|
|
1608
|
+
: undefined;
|
|
1609
|
+
const effectiveDiscovery = providerConfig.discovery ?? autoDiscovery;
|
|
1610
|
+
if (effectiveDiscovery && providerApi) {
|
|
1567
1611
|
discoverableProviders.push({
|
|
1568
1612
|
provider: providerName,
|
|
1569
|
-
api:
|
|
1570
|
-
baseUrl:
|
|
1613
|
+
api: providerApi,
|
|
1614
|
+
baseUrl: effectiveDiscoveryBaseUrl,
|
|
1571
1615
|
headers: providerConfig.headers,
|
|
1572
1616
|
compat: providerConfig.compat,
|
|
1573
1617
|
requestTransform: providerConfig.requestTransform,
|
|
1574
1618
|
cacheRetention: providerConfig.cacheRetention,
|
|
1575
|
-
discovery:
|
|
1576
|
-
optional:
|
|
1619
|
+
discovery: effectiveDiscovery,
|
|
1620
|
+
optional: !providerConfig.discovery,
|
|
1577
1621
|
});
|
|
1578
1622
|
}
|
|
1579
1623
|
|
|
@@ -2280,50 +2324,82 @@ export class ModelRegistry {
|
|
|
2280
2324
|
}
|
|
2281
2325
|
return new Error(`HTTP ${response.status} from ${modelsUrl}`);
|
|
2282
2326
|
},
|
|
2283
|
-
mapModel: (item, defaults) =>
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
levels: [Effort.Low, Effort.Medium, Effort.High],
|
|
2294
|
-
},
|
|
2295
|
-
}
|
|
2296
|
-
: {}),
|
|
2297
|
-
contextWindow:
|
|
2298
|
-
parseDiscoveryLimit(item.max_model_len) ??
|
|
2299
|
-
parseDiscoveryLimit(item.context_length) ??
|
|
2300
|
-
parseDiscoveryLimit(item.context_window) ??
|
|
2301
|
-
parseDiscoveryLimit(item.max_context_length) ??
|
|
2302
|
-
UNK_CONTEXT_WINDOW,
|
|
2303
|
-
maxTokens:
|
|
2304
|
-
parseDiscoveryLimit(item.max_completion_tokens) ??
|
|
2305
|
-
parseDiscoveryLimit(item.max_tokens) ??
|
|
2306
|
-
parseDiscoveryLimit(item.max_output_tokens) ??
|
|
2307
|
-
UNK_MAX_TOKENS,
|
|
2308
|
-
headers,
|
|
2309
|
-
compat: {
|
|
2310
|
-
...(providerConfig.compat ?? {}),
|
|
2311
|
-
supportsStore: false,
|
|
2312
|
-
supportsDeveloperRole: false,
|
|
2313
|
-
supportsReasoningEffort: isOmlx,
|
|
2327
|
+
mapModel: (item, defaults) => {
|
|
2328
|
+
const api = this.#resolveDiscoveredModelApi(
|
|
2329
|
+
providerConfig,
|
|
2330
|
+
typeof item.id === "string" ? item.id : "",
|
|
2331
|
+
item,
|
|
2332
|
+
);
|
|
2333
|
+
return {
|
|
2334
|
+
...defaults,
|
|
2335
|
+
api,
|
|
2336
|
+
reasoning: isOmlx,
|
|
2314
2337
|
...(isOmlx
|
|
2315
2338
|
? {
|
|
2316
|
-
|
|
2317
|
-
|
|
2339
|
+
thinking: {
|
|
2340
|
+
mode: "effort" as const,
|
|
2341
|
+
minLevel: Effort.Low,
|
|
2342
|
+
maxLevel: Effort.High,
|
|
2343
|
+
defaultLevel: Effort.Medium,
|
|
2344
|
+
levels: [Effort.Low, Effort.Medium, Effort.High],
|
|
2345
|
+
},
|
|
2318
2346
|
}
|
|
2319
2347
|
: {}),
|
|
2320
|
-
|
|
2321
|
-
|
|
2348
|
+
contextWindow:
|
|
2349
|
+
parseDiscoveryLimit(item.max_model_len) ??
|
|
2350
|
+
parseDiscoveryLimit(item.context_length) ??
|
|
2351
|
+
parseDiscoveryLimit(item.context_window) ??
|
|
2352
|
+
parseDiscoveryLimit(item.max_context_length) ??
|
|
2353
|
+
UNK_CONTEXT_WINDOW,
|
|
2354
|
+
maxTokens:
|
|
2355
|
+
parseDiscoveryLimit(item.max_completion_tokens) ??
|
|
2356
|
+
parseDiscoveryLimit(item.max_tokens) ??
|
|
2357
|
+
parseDiscoveryLimit(item.max_output_tokens) ??
|
|
2358
|
+
UNK_MAX_TOKENS,
|
|
2359
|
+
headers,
|
|
2360
|
+
compat: {
|
|
2361
|
+
...(providerConfig.compat ?? {}),
|
|
2362
|
+
supportsStore: false,
|
|
2363
|
+
supportsDeveloperRole: false,
|
|
2364
|
+
supportsReasoningEffort: isOmlx,
|
|
2365
|
+
...(isOmlx
|
|
2366
|
+
? {
|
|
2367
|
+
thinkingFormat: "qwen-chat-template" as const,
|
|
2368
|
+
reasoningContentField: "reasoning_content" as const,
|
|
2369
|
+
}
|
|
2370
|
+
: {}),
|
|
2371
|
+
},
|
|
2372
|
+
};
|
|
2373
|
+
},
|
|
2322
2374
|
});
|
|
2323
2375
|
if (discovered === null) throw new Error(`Invalid OpenAI-compatible model catalog from ${baseUrl}`);
|
|
2324
2376
|
return this.#applyProviderModelOverrides(providerConfig.provider, discovered);
|
|
2325
2377
|
}
|
|
2326
2378
|
|
|
2379
|
+
#resolveDiscoveredModelApi(
|
|
2380
|
+
providerConfig: DiscoveryProviderConfig,
|
|
2381
|
+
modelId: string,
|
|
2382
|
+
entry?: { id?: unknown; owned_by?: unknown },
|
|
2383
|
+
): Api {
|
|
2384
|
+
let matchedPrefixLength = -1;
|
|
2385
|
+
let prefixApi: Api | undefined;
|
|
2386
|
+
for (const [prefix, routedApi] of Object.entries(providerConfig.discovery.apiByModelPrefix ?? {})) {
|
|
2387
|
+
if (modelId.startsWith(prefix) && prefix.length > matchedPrefixLength) {
|
|
2388
|
+
prefixApi = routedApi as Api;
|
|
2389
|
+
matchedPrefixLength = prefix.length;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
if (prefixApi !== undefined) return prefixApi;
|
|
2393
|
+
if (providerConfig.discovery.type === "openai-models-list") {
|
|
2394
|
+
const detected = detectDiscoveredApiFamily(entry ?? { id: modelId });
|
|
2395
|
+
if (detected === "anthropic-messages") return "anthropic-messages";
|
|
2396
|
+
if (detected === "openai-completions") {
|
|
2397
|
+
return isOpenAIFamilyApi(providerConfig.api) ? providerConfig.api : "openai-completions";
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return providerConfig.api;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2327
2403
|
#normalizeLlamaCppBaseUrl(baseUrl?: string): string {
|
|
2328
2404
|
const defaultBaseUrl = "http://127.0.0.1:8080";
|
|
2329
2405
|
const raw = baseUrl || defaultBaseUrl;
|
|
@@ -185,6 +185,7 @@ export type ModelOverride = z.infer<typeof ModelOverrideSchema>;
|
|
|
185
185
|
|
|
186
186
|
export const ProviderDiscoverySchema = z.object({
|
|
187
187
|
type: z.enum(["ollama", "llama.cpp", "lm-studio", "omlx", "sglang", "openai-models-list"]),
|
|
188
|
+
apiByModelPrefix: z.record(z.string().min(1), z.string().min(1)).optional(),
|
|
188
189
|
});
|
|
189
190
|
|
|
190
191
|
const LocalOpenAICompatSchema = z
|
|
@@ -37,6 +37,9 @@ skc ultragoal complete-goals --retry-failed
|
|
|
37
37
|
skc ultragoal checkpoint --goal-id <id> --status complete --evidence "<evidence>" --quality-gate-json <quality-gate-json-or-path>
|
|
38
38
|
skc ultragoal checkpoint --goal-id <id> --status failed --evidence "<blocker/evidence>"
|
|
39
39
|
skc ultragoal record-review-blockers --goal-id <id> --title "Resolve final review blockers" --objective "<blocker-resolution objective>" --evidence "<review findings>"
|
|
40
|
+
skc ultragoal succession offer --target-repo <path> --goal-id <id> --authorize "<statement>" --authorized-by <identity>
|
|
41
|
+
skc ultragoal succession adopt --offer <path>
|
|
42
|
+
skc ultragoal succession status --json
|
|
40
43
|
```
|
|
41
44
|
|
|
42
45
|
Use these exact goal-tool calls for the inline goal state:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antmind Ventures Private Limited (appllama.io)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emil Kowalski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Bundled frontend UI/UX skills
|
|
2
|
+
|
|
3
|
+
These `SKILL.md` files are vendored from upstream skill repositories so SKC
|
|
4
|
+
sessions can invoke frontend UI/UX craft skills without a separate user
|
|
5
|
+
install. They are **not** SKC public workflow skills: the four public
|
|
6
|
+
workflows remain `deep-interview`, `ralplan`, `ultragoal`, and `team`.
|
|
7
|
+
|
|
8
|
+
Regenerate with:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
bun scripts/build-ui-skill-bundle.ts \
|
|
12
|
+
--source emilkowalski=<checkout> \
|
|
13
|
+
--source appllama=<checkout>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Only `SKILL.md` is compiled into the binary, and a bundled skill's `baseDir`
|
|
17
|
+
is a virtual `embedded:skc/...` path. Companion reference files are therefore
|
|
18
|
+
inlined into each `SKILL.md` as appendices by the generator; do not add
|
|
19
|
+
loose companion files here expecting them to be readable at runtime.
|
|
20
|
+
|
|
21
|
+
## Sources
|
|
22
|
+
|
|
23
|
+
| Upstream | License | Vendored skills |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| [emilkowalski/skills](https://github.com/emilkowalski/skills) | MIT (`LICENSE.emilkowalski`), Copyright (c) 2026 Emil Kowalski | `emil-design-eng`, `animate`, `review-animations`, `improve-animations`, `find-animation-opportunities`, `pick-ui-library`, `prototype`, `mobile-native`, `animation-vocabulary`, `apple-design`, `ask-sonner` |
|
|
26
|
+
| [Appllama/appllama-skills](https://github.com/Appllama/appllama-skills) | MIT (`LICENSE.appllama`) | `appllama-app-design-skill` |
|
|
27
|
+
|
|
28
|
+
## Deliberate exclusions
|
|
29
|
+
|
|
30
|
+
- `write-swift`, `animate-expo` (emilkowalski) — not web frontend craft.
|
|
31
|
+
- `appllama-usage` (Appllama) — it exists only to drive the paid Appllama MCP
|
|
32
|
+
(`mcp.appllama.io`). SKC neither ships nor enables that connector, so
|
|
33
|
+
bundling it would instruct the agent to call tools that do not exist.
|
|
34
|
+
`appllama-app-design-skill` is upstream-documented as standalone.
|
|
35
|
+
|
|
36
|
+
- [`Jakubantalik/transitions.dev`](https://github.com/Jakubantalik/transitions.dev) — **not vendored.** Its terms license only the `transitions-dev` CLI and Refine tooling under MIT; the transition collection is governed by a separate "Using the transitions" clause that permits unlimited use in your own projects but forbids repackaging or publishing the collection (or a substantial part of it). SKC therefore never carries the content: the agent installs it into the user's own project on demand via `npx -y skills@latest add Jakubantalik/transitions.dev --skill transitions-dev -a universal -y`. See `hooks/ui-skill-keywords.ts`.
|
|
37
|
+
- [`DavidHDev/react-bits`](https://github.com/DavidHDev/react-bits) — **components not vendored.** React Bits is a shadcn component registry, not an agent skill repo (it ships no `SKILL.md`), and its MIT + Commons Clause license forbids selling, sublicensing, or redistributing the components "alone, in a bundle, or as a ported version". The bundled `react-bits` skill is therefore **SKC-authored guidance**: when to reach for the registry, how to install one component (`npx shadcn@latest add @react-bits/<Component>-<Variant>`), the `components.json` precondition, and a factual catalog of component names. No upstream component source is included; components are installed from the registry into the user's own project.
|
|
38
|
+
|
|
39
|
+
## Trademarks
|
|
40
|
+
|
|
41
|
+
The Appllama name, llama, and logo are trademarks of Antmind Ventures Private
|
|
42
|
+
Limited. The MIT license does not grant rights to use them; they appear here
|
|
43
|
+
only to attribute the source of the vendored skill.
|