howone 0.1.53 → 0.2.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/bin/index.mjs +362 -5
- package/package.json +1 -1
- package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +19 -16
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md +14 -24
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/02-workflow-contract-rules.md +0 -107
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md +26 -101
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +23 -17
- package/templates/vite/.howone/skills/howone/03-ai-capabilities/05-ai-feature-playbooks.md +1 -66
- package/templates/vite/.howone/skills/howone/SKILL.md +5 -4
- package/templates/vite/.howone/skills/web-clone/LICENSE +0 -21
- package/templates/vite/.howone/skills/web-clone/README.md +0 -179
- package/templates/vite/.howone/skills/web-clone/SKILL.md +0 -243
- package/templates/vite/.howone/skills/web-clone/references/assessment.md +0 -77
- package/templates/vite/.howone/skills/web-clone/references/complex-playbooks.md +0 -46
- package/templates/vite/.howone/skills/web-clone/references/deliverables.md +0 -144
- package/templates/vite/.howone/skills/web-clone/references/design-dna.md +0 -125
- package/templates/vite/.howone/skills/web-clone/references/effect-extraction.md +0 -73
- package/templates/vite/.howone/skills/web-clone/references/marbles-case.md +0 -31
- package/templates/vite/.howone/skills/web-clone/references/reverse-engineering.md +0 -34
- package/templates/vite/.howone/skills/web-clone/references/static-mirror.md +0 -72
- package/templates/vite/.howone/skills/web-clone/scripts/asset-harvest.mjs +0 -101
- package/templates/vite/.howone/skills/web-clone/scripts/audit-clone.mjs +0 -151
- package/templates/vite/.howone/skills/web-clone/scripts/compare-recon.mjs +0 -265
- package/templates/vite/.howone/skills/web-clone/scripts/dna-scaffold.mjs +0 -214
- package/templates/vite/.howone/skills/web-clone/scripts/init-clone.mjs +0 -136
- package/templates/vite/.howone/skills/web-clone/scripts/interaction-probe.mjs +0 -314
- package/templates/vite/.howone/skills/web-clone/scripts/lib/playwright-loader.mjs +0 -39
- package/templates/vite/.howone/skills/web-clone/scripts/mirror-site.mjs +0 -121
- package/templates/vite/.howone/skills/web-clone/scripts/network-capture.mjs +0 -127
- package/templates/vite/.howone/skills/web-clone/scripts/recon-site.mjs +0 -235
- package/templates/vite/.howone/skills/web-clone/scripts/route-crawl.mjs +0 -228
- package/templates/vite/.howone/skills/web-clone/scripts/sourcemap-hunt.mjs +0 -112
- package/templates/vite/.howone/skills/web-clone/scripts/visual-diff.mjs +0 -161
package/bin/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
|
-
import { readdir, rename, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { readFile, readdir, rename, stat, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { promisify } from "node:util";
|
|
@@ -62,6 +62,18 @@ async function main() {
|
|
|
62
62
|
await runDeploy(command.options);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
+
if (scope === "capabilities" || scope === "capability" || scope === "caps") {
|
|
66
|
+
await runCapabilitiesCommand(subcommand, rest, command.options);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (scope === "skills" || scope === "skill") {
|
|
70
|
+
await runSkillsCommand(subcommand, rest, command.options);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (scope === "run") {
|
|
74
|
+
await runCapabilityRun(compact([subcommand, ...rest]), command.options);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
65
77
|
if (scope === "ai" && subcommand === "run") {
|
|
66
78
|
await runAiRun(rest, command.options);
|
|
67
79
|
return;
|
|
@@ -87,9 +99,22 @@ function parseCommand(args) {
|
|
|
87
99
|
"next",
|
|
88
100
|
"force",
|
|
89
101
|
"yes",
|
|
90
|
-
"json"
|
|
102
|
+
"json",
|
|
103
|
+
"stream"
|
|
91
104
|
],
|
|
92
|
-
string: [
|
|
105
|
+
string: [
|
|
106
|
+
"template",
|
|
107
|
+
"cwd",
|
|
108
|
+
"scope",
|
|
109
|
+
"input",
|
|
110
|
+
"output",
|
|
111
|
+
"prompt",
|
|
112
|
+
"api-url",
|
|
113
|
+
"token",
|
|
114
|
+
"status",
|
|
115
|
+
"reason"
|
|
116
|
+
],
|
|
117
|
+
number: ["limit"],
|
|
93
118
|
configuration: { "camel-case-expansion": false }
|
|
94
119
|
});
|
|
95
120
|
return {
|
|
@@ -103,6 +128,16 @@ function parseCommand(args) {
|
|
|
103
128
|
yes: Boolean(parsed.yes),
|
|
104
129
|
json: Boolean(parsed.json),
|
|
105
130
|
cwd: typeof parsed.cwd === "string" ? parsed.cwd : void 0,
|
|
131
|
+
scope: typeof parsed.scope === "string" ? parsed.scope : void 0,
|
|
132
|
+
input: typeof parsed.input === "string" ? parsed.input : void 0,
|
|
133
|
+
output: typeof parsed.output === "string" ? parsed.output : void 0,
|
|
134
|
+
prompt: typeof parsed.prompt === "string" ? parsed.prompt : void 0,
|
|
135
|
+
apiUrl: typeof parsed["api-url"] === "string" ? parsed["api-url"] : void 0,
|
|
136
|
+
token: typeof parsed.token === "string" ? parsed.token : void 0,
|
|
137
|
+
stream: Boolean(parsed.stream),
|
|
138
|
+
status: typeof parsed.status === "string" ? parsed.status : void 0,
|
|
139
|
+
limit: typeof parsed.limit === "number" && Number.isFinite(parsed.limit) ? parsed.limit : void 0,
|
|
140
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
|
|
106
141
|
help: Boolean(parsed.help) || hasHelp,
|
|
107
142
|
version: Boolean(parsed.version) || hasVersion
|
|
108
143
|
}
|
|
@@ -182,6 +217,244 @@ async function runAiRun(pathArgs, options) {
|
|
|
182
217
|
}
|
|
183
218
|
printPending("howone ai run", result.message ?? "AI run is not implemented yet.");
|
|
184
219
|
}
|
|
220
|
+
async function runCapabilitiesCommand(subcommand, pathArgs, options) {
|
|
221
|
+
if (!subcommand || subcommand === "search") {
|
|
222
|
+
const query = pathArgs.join(" ").trim();
|
|
223
|
+
if (!query) throw new CliError("E_QUERY_REQUIRED", "Search query is required.");
|
|
224
|
+
await runCapabilitySearch(query, options);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (subcommand === "list") {
|
|
228
|
+
await runCapabilityList(options);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (subcommand === "inspect" || subcommand === "show") {
|
|
232
|
+
const ref = pathArgs[0];
|
|
233
|
+
if (!ref) throw new CliError("E_CAPABILITY_REF_REQUIRED", "Capability reference is required.");
|
|
234
|
+
await runCapabilityInspect(ref, options);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (subcommand === "proposals") {
|
|
238
|
+
await runCapabilityProposals(options);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (subcommand === "apply") {
|
|
242
|
+
const proposalId = pathArgs[0];
|
|
243
|
+
if (!proposalId) throw new CliError("E_PROPOSAL_ID_REQUIRED", "Capability proposal id is required.");
|
|
244
|
+
await runCapabilityProposalAction("apply", proposalId, options);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (subcommand === "reject") {
|
|
248
|
+
const proposalId = pathArgs[0];
|
|
249
|
+
if (!proposalId) throw new CliError("E_PROPOSAL_ID_REQUIRED", "Capability proposal id is required.");
|
|
250
|
+
await runCapabilityProposalAction("reject", proposalId, options);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
throw new CliError("E_COMMAND_UNKNOWN", `Unknown capabilities command: ${["capabilities", subcommand].join(" ")}`);
|
|
254
|
+
}
|
|
255
|
+
async function runSkillsCommand(subcommand, pathArgs, options) {
|
|
256
|
+
const ref = pathArgs[0];
|
|
257
|
+
if (subcommand === "export") {
|
|
258
|
+
if (!ref) throw new CliError("E_SKILL_REF_REQUIRED", "Skill reference is required.");
|
|
259
|
+
await runSkillProjection("export", ref, options);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (subcommand === "install") {
|
|
263
|
+
if (!ref) throw new CliError("E_SKILL_REF_REQUIRED", "Skill reference is required.");
|
|
264
|
+
await runSkillProjection("install", ref, options);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (subcommand === "publish") {
|
|
268
|
+
if (!ref) throw new CliError("E_SKILL_REF_REQUIRED", "Skill reference is required.");
|
|
269
|
+
await runSkillProjection("publish", ref, options);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
throw new CliError("E_COMMAND_UNKNOWN", `Unknown skills command: ${["skills", subcommand].filter(Boolean).join(" ")}`);
|
|
273
|
+
}
|
|
274
|
+
async function runCapabilitySearch(query, options) {
|
|
275
|
+
const params = new URLSearchParams({ q: query });
|
|
276
|
+
if (options.scope) params.set("scope", options.scope);
|
|
277
|
+
await printRegistryResult("capabilities search", await callHowoneRegistryApi(`/api/capabilities/search?${params}`, {
|
|
278
|
+
options,
|
|
279
|
+
requireAuth: options.scope !== "public"
|
|
280
|
+
}), options);
|
|
281
|
+
}
|
|
282
|
+
async function runCapabilityList(options) {
|
|
283
|
+
const params = new URLSearchParams();
|
|
284
|
+
if (options.scope) params.set("scope", options.scope);
|
|
285
|
+
await printRegistryResult("capabilities list", await callHowoneRegistryApi(`/api/capabilities${params.size ? `?${params}` : ""}`, {
|
|
286
|
+
options,
|
|
287
|
+
requireAuth: options.scope !== "public"
|
|
288
|
+
}), options);
|
|
289
|
+
}
|
|
290
|
+
async function runCapabilityInspect(ref, options) {
|
|
291
|
+
const result = await callHowoneRegistryApi(`/api/capabilities/${encodeURIComponent(ref)}`, {
|
|
292
|
+
options,
|
|
293
|
+
requireAuth: options.scope !== "public"
|
|
294
|
+
});
|
|
295
|
+
await printRegistryResult(`capabilities inspect ${ref}`, result, options);
|
|
296
|
+
}
|
|
297
|
+
async function runCapabilityProposals(options) {
|
|
298
|
+
const params = new URLSearchParams();
|
|
299
|
+
if (options.status) params.set("status", options.status);
|
|
300
|
+
if (options.limit) params.set("limit", String(options.limit));
|
|
301
|
+
await printRegistryResult("capabilities proposals", await callHowoneRegistryApi(`/api/capabilities/proposals${params.size ? `?${params}` : ""}`, {
|
|
302
|
+
options,
|
|
303
|
+
requireAuth: true
|
|
304
|
+
}), options);
|
|
305
|
+
}
|
|
306
|
+
async function runCapabilityProposalAction(action, proposalId, options) {
|
|
307
|
+
const result = await callHowoneRegistryApi(`/api/capabilities/proposals/${encodeURIComponent(proposalId)}/${action}`, {
|
|
308
|
+
options,
|
|
309
|
+
requireAuth: true,
|
|
310
|
+
init: {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: { "Content-Type": "application/json" },
|
|
313
|
+
body: JSON.stringify(action === "reject" ? { reason: options.reason } : {})
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
await printRegistryResult(`capabilities ${action} ${proposalId}`, result, options);
|
|
317
|
+
}
|
|
318
|
+
async function runCapabilityRun(pathArgs, options) {
|
|
319
|
+
const actionRef = pathArgs[0];
|
|
320
|
+
if (!actionRef && !options.prompt) throw new CliError("E_ACTION_REF_REQUIRED", "Capability action reference is required, or use --prompt for query-based routing.");
|
|
321
|
+
const input = await readCapabilityRunInput(options);
|
|
322
|
+
const body = {
|
|
323
|
+
...actionRef ? { actionRef } : {},
|
|
324
|
+
...options.prompt ? { prompt: options.prompt } : {},
|
|
325
|
+
input,
|
|
326
|
+
stream: Boolean(options.stream)
|
|
327
|
+
};
|
|
328
|
+
const result = await callHowoneRegistryApi("/api/capabilities/run", {
|
|
329
|
+
options,
|
|
330
|
+
requireAuth: true,
|
|
331
|
+
init: {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: { "Content-Type": "application/json" },
|
|
334
|
+
body: JSON.stringify(body)
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
await printRegistryResult(actionRef ? `run ${actionRef}` : "run query", result, options);
|
|
338
|
+
}
|
|
339
|
+
async function runSkillProjection(action, ref, options) {
|
|
340
|
+
const method = action === "export" ? "GET" : "POST";
|
|
341
|
+
const result = await callHowoneRegistryApi(`/api/capabilities/skills/${encodeURIComponent(ref)}/${action}`, {
|
|
342
|
+
options,
|
|
343
|
+
requireAuth: action !== "export",
|
|
344
|
+
init: { method }
|
|
345
|
+
});
|
|
346
|
+
await printRegistryResult(`skills ${action} ${ref}`, result, options);
|
|
347
|
+
}
|
|
348
|
+
async function readCapabilityRunInput(options) {
|
|
349
|
+
if (!options.input) return {};
|
|
350
|
+
const raw = options.input.trim();
|
|
351
|
+
const content = raw.startsWith("{") || raw.startsWith("[") ? raw : await readFile(path.resolve(options.cwd ?? process.cwd(), raw), "utf-8");
|
|
352
|
+
try {
|
|
353
|
+
const parsed = JSON.parse(content);
|
|
354
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CliError("E_INPUT_INVALID", "Capability input must be a JSON object.");
|
|
355
|
+
return parsed;
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error instanceof CliError) throw error;
|
|
358
|
+
throw new CliError("E_INPUT_INVALID", error instanceof Error ? error.message : "Invalid JSON input.");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function resolveHowoneApiUrl(options) {
|
|
362
|
+
return (options.apiUrl ?? "https://howone.dev").replace(/\/+$/, "");
|
|
363
|
+
}
|
|
364
|
+
function resolveHowoneApiToken(options) {
|
|
365
|
+
return options.token ?? process.env.HOWONE_API_TOKEN ?? process.env.HOWONE_AGENT_TOKEN;
|
|
366
|
+
}
|
|
367
|
+
async function callHowoneRegistryApi(apiPath, args) {
|
|
368
|
+
const token = resolveHowoneApiToken(args.options);
|
|
369
|
+
if (args.requireAuth && !token) throw new CliError("E_AUTH_REQUIRED", "Set HOWONE_API_TOKEN or pass --token to access private/workspace capabilities.");
|
|
370
|
+
const url = `${resolveHowoneApiUrl(args.options)}${apiPath.startsWith("/") ? apiPath : `/${apiPath}`}`;
|
|
371
|
+
const headers = new Headers(args.init?.headers);
|
|
372
|
+
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
373
|
+
headers.set("Accept", args.options.stream ? "text/event-stream, application/json" : "application/json");
|
|
374
|
+
const response = await fetch(url, {
|
|
375
|
+
...args.init,
|
|
376
|
+
headers
|
|
377
|
+
});
|
|
378
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
379
|
+
if (args.options.stream && contentType.includes("text/event-stream")) return readSseResponse(response);
|
|
380
|
+
const text = await response.text();
|
|
381
|
+
const body = text ? parseJsonOrText(text) : null;
|
|
382
|
+
if (!response.ok) throw new CliError("E_API_REQUEST_FAILED", `HowOne API request failed: HTTP ${response.status}`, {
|
|
383
|
+
status: response.status,
|
|
384
|
+
body
|
|
385
|
+
});
|
|
386
|
+
return body;
|
|
387
|
+
}
|
|
388
|
+
async function readSseResponse(response) {
|
|
389
|
+
if (!response.ok) throw new CliError("E_API_REQUEST_FAILED", `HowOne API request failed: HTTP ${response.status}`);
|
|
390
|
+
if (!response.body) return {
|
|
391
|
+
ok: true,
|
|
392
|
+
events: []
|
|
393
|
+
};
|
|
394
|
+
const reader = response.body.getReader();
|
|
395
|
+
const decoder = new TextDecoder();
|
|
396
|
+
const events = [];
|
|
397
|
+
let buffer = "";
|
|
398
|
+
while (true) {
|
|
399
|
+
const { value, done } = await reader.read();
|
|
400
|
+
if (done) break;
|
|
401
|
+
buffer += decoder.decode(value, { stream: true });
|
|
402
|
+
let boundary = buffer.indexOf("\n\n");
|
|
403
|
+
while (boundary >= 0) {
|
|
404
|
+
const chunk = buffer.slice(0, boundary).trim();
|
|
405
|
+
buffer = buffer.slice(boundary + 2);
|
|
406
|
+
if (chunk) {
|
|
407
|
+
events.push(chunk);
|
|
408
|
+
console.log(chunk);
|
|
409
|
+
}
|
|
410
|
+
boundary = buffer.indexOf("\n\n");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const tail = buffer.trim();
|
|
414
|
+
if (tail) {
|
|
415
|
+
events.push(tail);
|
|
416
|
+
console.log(tail);
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
ok: true,
|
|
420
|
+
events
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function parseJsonOrText(text) {
|
|
424
|
+
try {
|
|
425
|
+
return JSON.parse(text);
|
|
426
|
+
} catch {
|
|
427
|
+
return text;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function printRegistryResult(command, result, options) {
|
|
431
|
+
if (options.output) await writeFile(path.resolve(options.cwd ?? process.cwd(), options.output), `${JSON.stringify(result, null, 2)}\n`, "utf-8");
|
|
432
|
+
if (options.json || options.output) {
|
|
433
|
+
if (!options.output) console.log(JSON.stringify(result, null, 2));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
console.log(formatPanel(pc.bold(command), [formatRegistrySummary(result)]));
|
|
437
|
+
}
|
|
438
|
+
function formatRegistrySummary(result) {
|
|
439
|
+
if (typeof result === "string") return result;
|
|
440
|
+
if (!result || typeof result !== "object") return String(result);
|
|
441
|
+
const record = result;
|
|
442
|
+
const data = record.data ?? record.results ?? record.capabilities ?? record.actions;
|
|
443
|
+
if (Array.isArray(data)) {
|
|
444
|
+
if (!data.length) return "No capabilities found.";
|
|
445
|
+
return data.slice(0, 12).map((item) => {
|
|
446
|
+
if (!item || typeof item !== "object") return `- ${String(item)}`;
|
|
447
|
+
const row = item;
|
|
448
|
+
const name = row.name ?? row.title ?? row.slug ?? row.actionRef ?? row.proposalSlug ?? row.id ?? "capability";
|
|
449
|
+
const description = row.description ?? row.summary ?? row.reason ?? row.status;
|
|
450
|
+
return `- ${String(name)}${description ? `: ${String(description)}` : ""}`;
|
|
451
|
+
}).join("\n");
|
|
452
|
+
}
|
|
453
|
+
const name = record.name ?? record.title ?? record.slug ?? record.actionRef ?? record.proposalSlug ?? record.id;
|
|
454
|
+
const description = record.description ?? record.summary ?? record.reason ?? record.status;
|
|
455
|
+
if (name || description) return [name ? String(name) : void 0, description ? String(description) : void 0].filter(Boolean).join("\n");
|
|
456
|
+
return JSON.stringify(result, null, 2);
|
|
457
|
+
}
|
|
185
458
|
async function resolveCreatePlan(pathArgs, options, cwd, isInteractive) {
|
|
186
459
|
const template = resolveTemplateOption(options);
|
|
187
460
|
const projectName = pathArgs[0] === "template" ? pathArgs[1] : pathArgs[0];
|
|
@@ -417,6 +690,18 @@ function printHelp(pathArgs) {
|
|
|
417
690
|
printReservedHelp("howone deploy", "Deploy support is reserved and not implemented yet.");
|
|
418
691
|
return;
|
|
419
692
|
}
|
|
693
|
+
if (topic === "capabilities" || topic === "capabilities search") {
|
|
694
|
+
printCapabilitiesHelp();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (topic === "skills") {
|
|
698
|
+
printSkillsHelp();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (topic === "run") {
|
|
702
|
+
printRunHelp();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
420
705
|
if (topic === "ai" || topic === "ai run") {
|
|
421
706
|
printReservedHelp("howone ai run [aiId]", "AI run support is reserved and not implemented yet.");
|
|
422
707
|
return;
|
|
@@ -429,6 +714,11 @@ function printRootHelp() {
|
|
|
429
714
|
"",
|
|
430
715
|
pc.bold("Commands"),
|
|
431
716
|
formatCommand("init app [name]", "Create an app from a template"),
|
|
717
|
+
formatCommand("capabilities search <query>", "Search callable HowOne capabilities"),
|
|
718
|
+
formatCommand("capabilities inspect <ref>", "Inspect a capability package or action"),
|
|
719
|
+
formatCommand("capabilities proposals", "List generated capability proposals"),
|
|
720
|
+
formatCommand("run <package.action>", "Run a callable capability action"),
|
|
721
|
+
formatCommand("skills export <ref>", "Export a skill projection for a capability"),
|
|
432
722
|
formatCommand("create", "Reserved placeholder"),
|
|
433
723
|
formatCommand("deploy", "Reserved placeholder"),
|
|
434
724
|
formatCommand("ai run [aiId]", "Reserved placeholder"),
|
|
@@ -439,8 +729,10 @@ function printRootHelp() {
|
|
|
439
729
|
"",
|
|
440
730
|
pc.bold("Examples"),
|
|
441
731
|
` ${pc.cyan("howone init app my-app --template vite")}`,
|
|
442
|
-
` ${pc.cyan("howone
|
|
443
|
-
` ${pc.cyan("howone
|
|
732
|
+
` ${pc.cyan("howone capabilities search \"海报生成\" --scope public")}`,
|
|
733
|
+
` ${pc.cyan("howone capabilities proposals --status pending_review")}`,
|
|
734
|
+
` ${pc.cyan("howone run poster-generator.createPoster --input input.json --output result.json")}`,
|
|
735
|
+
` ${pc.cyan("howone run --prompt \"帮我生成一张 AI 发布会海报\" --stream")}`
|
|
444
736
|
]));
|
|
445
737
|
}
|
|
446
738
|
function printInitHelp() {
|
|
@@ -469,6 +761,71 @@ function printReservedHelp(command, description) {
|
|
|
469
761
|
`${pc.yellow("Pending")} This command is available as a stable placeholder.`
|
|
470
762
|
]));
|
|
471
763
|
}
|
|
764
|
+
function printCapabilitiesHelp() {
|
|
765
|
+
console.log(formatPanel(`${pc.bold("HowOne capabilities")}`, [
|
|
766
|
+
`${pc.dim("Usage")} howone capabilities <command> [options]`,
|
|
767
|
+
"",
|
|
768
|
+
pc.bold("Commands"),
|
|
769
|
+
formatCommand("search <query>", "Search private, workspace, installed, or public capabilities"),
|
|
770
|
+
formatCommand("list", "List capabilities in a scope"),
|
|
771
|
+
formatCommand("inspect <ref>", "Inspect package/action schemas and runtime bindings"),
|
|
772
|
+
formatCommand("proposals", "List generated proposals for the current user"),
|
|
773
|
+
formatCommand("apply <proposalId>", "Apply a generated proposal to the registry"),
|
|
774
|
+
formatCommand("reject <proposalId>", "Reject a generated proposal"),
|
|
775
|
+
"",
|
|
776
|
+
pc.bold("Options"),
|
|
777
|
+
formatCommand("--scope <scope>", "private, workspace, installed, public, or global"),
|
|
778
|
+
formatCommand("--status <status>", "Filter proposal list by status"),
|
|
779
|
+
formatCommand("--limit <number>", "Limit result count"),
|
|
780
|
+
formatCommand("--reason <text>", "Reason for rejecting a proposal"),
|
|
781
|
+
formatCommand("--api-url <url>", "HowOne registry API origin"),
|
|
782
|
+
formatCommand("--token <token>", "Agent API token; defaults to HOWONE_API_TOKEN"),
|
|
783
|
+
formatCommand("--json", "Print machine-readable JSON output"),
|
|
784
|
+
"",
|
|
785
|
+
pc.bold("Examples"),
|
|
786
|
+
` ${pc.cyan("howone capabilities search \"海报生成\" --scope public")}`,
|
|
787
|
+
` ${pc.cyan("howone capabilities inspect poster-generator.createPoster")}`,
|
|
788
|
+
` ${pc.cyan("howone capabilities proposals --status pending_review")}`,
|
|
789
|
+
` ${pc.cyan("howone capabilities apply 00000000-0000-0000-0000-000000000000")}`
|
|
790
|
+
]));
|
|
791
|
+
}
|
|
792
|
+
function printRunHelp() {
|
|
793
|
+
console.log(formatPanel(`${pc.bold("Run a capability")}`, [
|
|
794
|
+
`${pc.dim("Usage")} howone run <package.action> [options]`,
|
|
795
|
+
"",
|
|
796
|
+
pc.bold("Options"),
|
|
797
|
+
formatCommand("--input <json|file>", "JSON object or path to JSON input file"),
|
|
798
|
+
formatCommand("--prompt <text>", "Natural-language query routed through the registry"),
|
|
799
|
+
formatCommand("--output <file>", "Write JSON result to a file"),
|
|
800
|
+
formatCommand("--stream", "Request SSE and print events as they arrive"),
|
|
801
|
+
formatCommand("--api-url <url>", "HowOne registry API origin"),
|
|
802
|
+
formatCommand("--token <token>", "Agent API token; defaults to HOWONE_API_TOKEN"),
|
|
803
|
+
formatCommand("--json", "Print machine-readable JSON output"),
|
|
804
|
+
"",
|
|
805
|
+
pc.bold("Examples"),
|
|
806
|
+
` ${pc.cyan("howone run poster-generator.createPoster --input input.json")}`,
|
|
807
|
+
` ${pc.cyan("howone run --prompt \"帮我生成一张 AI 发布会海报\" --stream")}`
|
|
808
|
+
]));
|
|
809
|
+
}
|
|
810
|
+
function printSkillsHelp() {
|
|
811
|
+
console.log(formatPanel(`${pc.bold("HowOne skill projections")}`, [
|
|
812
|
+
`${pc.dim("Usage")} howone skills <command> <ref> [options]`,
|
|
813
|
+
"",
|
|
814
|
+
pc.bold("Commands"),
|
|
815
|
+
formatCommand("export <ref>", "Export an agent-facing skill artifact"),
|
|
816
|
+
formatCommand("install <ref>", "Install a public or workspace skill projection"),
|
|
817
|
+
formatCommand("publish <ref>", "Request publication of a capability skill projection"),
|
|
818
|
+
"",
|
|
819
|
+
pc.bold("Options"),
|
|
820
|
+
formatCommand("--api-url <url>", "HowOne registry API origin"),
|
|
821
|
+
formatCommand("--token <token>", "Agent API token; defaults to HOWONE_API_TOKEN"),
|
|
822
|
+
formatCommand("--json", "Print machine-readable JSON output"),
|
|
823
|
+
"",
|
|
824
|
+
pc.bold("Examples"),
|
|
825
|
+
` ${pc.cyan("howone skills export poster-generator")}`,
|
|
826
|
+
` ${pc.cyan("howone skills install poster-generator")}`
|
|
827
|
+
]));
|
|
828
|
+
}
|
|
472
829
|
function printPending(command, message) {
|
|
473
830
|
console.log(formatPanel(pc.bold(command), [`${pc.yellow("Pending")} ${message}`]));
|
|
474
831
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ Read immediately after `skill(name="howone")` and before platform design tools,
|
|
|
4
4
|
edits, or implementation guesses.
|
|
5
5
|
|
|
6
6
|
HowOne is a generated app platform. This file classifies **user scope**, separates **platform
|
|
7
|
-
contracts** from
|
|
7
|
+
contracts** from explicit user-owned integrations, and routes to backend, AI, or SDK tracks. Keep design
|
|
8
8
|
tracks separate: backend design does not require SDK references; AI design does not require SDK
|
|
9
9
|
references until the synced AI manifest is ready for code.
|
|
10
10
|
|
|
@@ -19,15 +19,14 @@ Map the user request to surfaces. Include only what they need.
|
|
|
19
19
|
| HowOne AI features | `03-ai-capabilities/` → sync → external workflow | Verify catalog before design |
|
|
20
20
|
| SDK wiring, auth, UI calls | `04-app-sdk/` | Only after manifests exist when contracts apply |
|
|
21
21
|
| UI only, no HowOne data/AI | App code under `{appRoot}` | No schema/AI design tools |
|
|
22
|
-
|
|
|
22
|
+
| Explicit user-owned integrations | App code + config | Only when the user explicitly asks to connect something outside HowOne |
|
|
23
23
|
|
|
24
24
|
**Mixed scope:** read at least one file per touched track (`SKILL.md` index) before writing.
|
|
25
25
|
|
|
26
26
|
## HowOne platform boundary
|
|
27
27
|
|
|
28
|
-
Use this decision model for **any** user request.
|
|
29
|
-
|
|
30
|
-
or **app-owned**.
|
|
28
|
+
Use this decision model for **any** user request. Ask whether the ask is a **platform contract
|
|
29
|
+
surface** or an explicit user-owned integration.
|
|
31
30
|
|
|
32
31
|
### Platform provides (evidence required)
|
|
33
32
|
|
|
@@ -42,15 +41,17 @@ If none of these surfaces can express the user's **platform** requirement after
|
|
|
42
41
|
catalog, and tool schemas, it is a **platform gap**—not an automatic ban on whatever technology the
|
|
43
42
|
user named.
|
|
44
43
|
|
|
45
|
-
###
|
|
44
|
+
### Explicit user-owned integration (not platform gap)
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
Use this path only when the user explicitly asks to connect something they run, host, or configure
|
|
47
|
+
outside HowOne contracts. Do not apply it to ordinary generated app requests such as "AI image
|
|
48
|
+
generation app", "AI story app", "login", "cloud sync", or "history"; first check the HowOne
|
|
49
|
+
platform tracks.
|
|
49
50
|
|
|
50
51
|
- Implement in application code and configuration under `{appRoot}`.
|
|
51
52
|
- **Do not refuse** because HowOne does not provision it.
|
|
52
53
|
- **Do not** call platform design tools to fake it as entities, AI capabilities, or manifest fields.
|
|
53
|
-
- **Do not** tell the user they cannot use their own stack—only clarify it is outside HowOne platform scope.
|
|
54
|
+
- **Do not** tell the user they cannot use their own stack—only clarify it is outside HowOne platform scope when they explicitly asked for that stack.
|
|
54
55
|
|
|
55
56
|
### Boundary decision (always)
|
|
56
57
|
|
|
@@ -58,9 +59,9 @@ custom APIs, message systems, identity products, analytics, payment gateways, et
|
|
|
58
59
|
1. What did the user ask for?
|
|
59
60
|
2. Does it require HowOne persisted data? → entity-schema path or skip
|
|
60
61
|
3. Does it require HowOne AI? → catalog + ai-capabilities path or skip
|
|
61
|
-
4.
|
|
62
|
+
4. Did they explicitly ask to connect something outside HowOne? → app-owned; wire in UI/config
|
|
62
63
|
5. Did they ask for a platform feature with no contract evidence? → platform stop (generic)
|
|
63
|
-
6. Mixed? → platform parts via tracks;
|
|
64
|
+
6. Mixed? → platform parts via tracks; explicit integrations in app code
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
### Platform scope rules
|
|
@@ -69,7 +70,8 @@ custom APIs, message systems, identity products, analytics, payment gateways, et
|
|
|
69
70
|
- **No invalid shortcuts:** Do not handwrite `.howone/` metadata or guess version/workflow identifiers.
|
|
70
71
|
- **Stop wording:** Name the **missing contract surface** (e.g. no catalog family, no manifest binding, no tool operation)—not the user's technology choice.
|
|
71
72
|
|
|
72
|
-
When stopping a platform path, separate what HowOne can provide from
|
|
73
|
+
When stopping a platform path, separate what HowOne can provide from any explicit integration the
|
|
74
|
+
user requested.
|
|
73
75
|
|
|
74
76
|
Inspect-only platform reads do not replace this file before the first **design write**.
|
|
75
77
|
|
|
@@ -149,9 +151,10 @@ without default) need explicit user alignment before applying the final patch.
|
|
|
149
151
|
1. Read architecture + **catalog** (feasibility) + contract rules; use playbooks when they match.
|
|
150
152
|
2. Apply one complete capability patch → `sync_ai_artifacts`.
|
|
151
153
|
3. External workflow create/update per workflow-operations reference; keep job/request IDs from tool results.
|
|
152
|
-
4.
|
|
153
|
-
5.
|
|
154
|
-
6.
|
|
154
|
+
4. Wait for the terminal result. A successful update promotes its new workflow ID in the backend capability version.
|
|
155
|
+
5. Run `sync_ai_artifacts` again, then read `{appRoot}/.howone/ai/manifest.json`.
|
|
156
|
+
6. Stop AI design. Read SDK references only if implementing app calls.
|
|
157
|
+
7. If persistence required: entity workflow after output contract is fixed.
|
|
155
158
|
|
|
156
159
|
Do not fake catalog-backed AI. Platform gap → stop AI design path, explain generically.
|
|
157
160
|
No AI capability dry-run step. Design the contract from the skill references, then apply the final
|
|
@@ -169,7 +172,7 @@ capability patch.
|
|
|
169
172
|
|
|
170
173
|
## Checklist before implementation
|
|
171
174
|
|
|
172
|
-
- [ ] Scope explicit: which tracks apply;
|
|
175
|
+
- [ ] Scope explicit: which tracks apply; explicit integration vs platform clear
|
|
173
176
|
- [ ] Data and auth posture chosen when data in scope
|
|
174
177
|
- [ ] AI requirements verified against catalog when AI in scope
|
|
175
178
|
- [ ] Manifests synced before SDK codegen
|
package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md
CHANGED
|
@@ -10,14 +10,6 @@ belongs?** For schema details read `02-workflow-contract-rules.md`. For workflow
|
|
|
10
10
|
|
|
11
11
|
## Platform Mental Model
|
|
12
12
|
|
|
13
|
-
HowOne workflow-service AI is a bounded catalog, not a general automation runtime. Before designing
|
|
14
|
-
an AI contract, verify the requested behavior matches the supported action families in
|
|
15
|
-
`03-service-capability-catalog.md`.
|
|
16
|
-
|
|
17
|
-
Streaming is not supported for AI workflow capability design. Do not design streaming execution,
|
|
18
|
-
streaming partial results, realtime tokens, or stream-based workflow contracts. Use non-streaming
|
|
19
|
-
status/result UI in app code when progress feedback is needed.
|
|
20
|
-
|
|
21
13
|
HowOne AI has five distinct layers:
|
|
22
14
|
|
|
23
15
|
| Layer | Owns | Does not own |
|
|
@@ -43,7 +35,8 @@ user request = intent
|
|
|
43
35
|
agent AI contract proposal = draft
|
|
44
36
|
applied AI capability version = validated contract
|
|
45
37
|
.howone/ai/manifest.json = local synced source for workflow submit and later SDK codegen
|
|
46
|
-
external-ai-capability
|
|
38
|
+
external-ai-capability submission = job/task/config mapping; update IDs remain pending
|
|
39
|
+
terminal background finalizer = promotes successful update IDs in the backend capability version
|
|
47
40
|
SDK/UI implementation = separate app-sdk track after AI design is complete
|
|
48
41
|
entity schema = persistence contract, separate from AI contract
|
|
49
42
|
```
|
|
@@ -62,10 +55,11 @@ Use this flow for new AI features:
|
|
|
62
55
|
6. Sync `.howone/ai/manifest.json`.
|
|
63
56
|
7. Submit workflow create/update through `external-ai-capability` from the synced manifest.
|
|
64
57
|
8. Store returned job/task IDs and submitted config IDs for polling/debugging.
|
|
65
|
-
9.
|
|
66
|
-
10.
|
|
67
|
-
11.
|
|
68
|
-
12. If
|
|
58
|
+
9. Let the host poll status until `completed` or `failed`.
|
|
59
|
+
10. On success, the background finalizer promotes successful update IDs in the backend capability version. Failed operations keep their previous IDs.
|
|
60
|
+
11. Run `sync_ai_artifacts` again, then re-read `.howone/ai/manifest.json`.
|
|
61
|
+
12. Leave AI design. If app code must call the workflow, read the SDK track and generate bindings.
|
|
62
|
+
13. If output must persist, design entity schema after the output contract is fixed.
|
|
69
63
|
|
|
70
64
|
Do not submit external workflow create/update from a hand-written schema. It should come from the
|
|
71
65
|
synced manifest.
|
|
@@ -105,11 +99,13 @@ create:
|
|
|
105
99
|
update:
|
|
106
100
|
previous config = current manifest capability.workflowId
|
|
107
101
|
new config = freshly generated UUID
|
|
108
|
-
|
|
102
|
+
submitted state = local manifest remains unchanged while EAX runs
|
|
103
|
+
completed state = backend capability version receives the fresh UUID
|
|
109
104
|
```
|
|
110
105
|
|
|
111
|
-
The SDK execution binding uses the manifest `workflowId`, which is the EAX config id. After
|
|
112
|
-
|
|
106
|
+
The SDK execution binding uses the synced manifest `workflowId`, which is the EAX config id. After
|
|
107
|
+
update completion, run `sync_ai_artifacts`; only the newly synced manifest value should be copied
|
|
108
|
+
into `src/lib/sdk.ts`.
|
|
113
109
|
Do not invent IDs; let the AI design/sync/external workflow tools generate and persist them.
|
|
114
110
|
|
|
115
111
|
## Workflow Count Rule
|
|
@@ -137,19 +133,15 @@ Workflow may do:
|
|
|
137
133
|
|
|
138
134
|
- generate, summarize, translate, classify, extract;
|
|
139
135
|
- search/crawl and synthesize;
|
|
140
|
-
-
|
|
141
|
-
- generate/edit/analyze images;
|
|
142
|
-
- generate video, concatenate videos, and extract first/last video frames;
|
|
143
|
-
- generate TTS audio, transcribe speech, and concatenate audio files;
|
|
136
|
+
- generate/edit/analyze images, video, and audio;
|
|
144
137
|
- retrieve financial or academic data;
|
|
145
|
-
-
|
|
138
|
+
- save/read generated files through URL-based storage.
|
|
146
139
|
|
|
147
140
|
Workflow must not do:
|
|
148
141
|
|
|
149
142
|
- database create/read/update/delete;
|
|
150
143
|
- authentication/session logic;
|
|
151
144
|
- file upload from browser raw bytes;
|
|
152
|
-
- streaming output or realtime event delivery;
|
|
153
145
|
- payment processing;
|
|
154
146
|
- owner assignment or permissions;
|
|
155
147
|
- app navigation, UI state, toast, or modal logic.
|
|
@@ -176,7 +168,6 @@ Do not:
|
|
|
176
168
|
- hide the unsupported part;
|
|
177
169
|
- build a UI that pretends the workflow exists;
|
|
178
170
|
- replace the requested capability with a different one without saying so;
|
|
179
|
-
- imply streaming/realtime AI behavior is available;
|
|
180
171
|
- assume private APIs, external datasets, or providers that are not listed.
|
|
181
172
|
|
|
182
173
|
Correct response:
|
|
@@ -213,7 +204,6 @@ The description can be human readable. The ID must be stable for codegen.
|
|
|
213
204
|
Before editing files:
|
|
214
205
|
|
|
215
206
|
- Feature maps to available workflow capabilities.
|
|
216
|
-
- Streaming/realtime output is not required.
|
|
217
207
|
- One workflow per feature unless RAG.
|
|
218
208
|
- Description says what the user gets, not how tools run.
|
|
219
209
|
- Input schema accepts URLs for files, not raw bytes.
|