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.
Files changed (33) hide show
  1. package/bin/index.mjs +362 -5
  2. package/package.json +1 -1
  3. package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +19 -16
  4. package/templates/vite/.howone/skills/howone/03-ai-capabilities/01-ai-capability-architecture.md +14 -24
  5. package/templates/vite/.howone/skills/howone/03-ai-capabilities/02-workflow-contract-rules.md +0 -107
  6. package/templates/vite/.howone/skills/howone/03-ai-capabilities/03-service-capability-catalog.md +26 -101
  7. package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +23 -17
  8. package/templates/vite/.howone/skills/howone/03-ai-capabilities/05-ai-feature-playbooks.md +1 -66
  9. package/templates/vite/.howone/skills/howone/SKILL.md +5 -4
  10. package/templates/vite/.howone/skills/web-clone/LICENSE +0 -21
  11. package/templates/vite/.howone/skills/web-clone/README.md +0 -179
  12. package/templates/vite/.howone/skills/web-clone/SKILL.md +0 -243
  13. package/templates/vite/.howone/skills/web-clone/references/assessment.md +0 -77
  14. package/templates/vite/.howone/skills/web-clone/references/complex-playbooks.md +0 -46
  15. package/templates/vite/.howone/skills/web-clone/references/deliverables.md +0 -144
  16. package/templates/vite/.howone/skills/web-clone/references/design-dna.md +0 -125
  17. package/templates/vite/.howone/skills/web-clone/references/effect-extraction.md +0 -73
  18. package/templates/vite/.howone/skills/web-clone/references/marbles-case.md +0 -31
  19. package/templates/vite/.howone/skills/web-clone/references/reverse-engineering.md +0 -34
  20. package/templates/vite/.howone/skills/web-clone/references/static-mirror.md +0 -72
  21. package/templates/vite/.howone/skills/web-clone/scripts/asset-harvest.mjs +0 -101
  22. package/templates/vite/.howone/skills/web-clone/scripts/audit-clone.mjs +0 -151
  23. package/templates/vite/.howone/skills/web-clone/scripts/compare-recon.mjs +0 -265
  24. package/templates/vite/.howone/skills/web-clone/scripts/dna-scaffold.mjs +0 -214
  25. package/templates/vite/.howone/skills/web-clone/scripts/init-clone.mjs +0 -136
  26. package/templates/vite/.howone/skills/web-clone/scripts/interaction-probe.mjs +0 -314
  27. package/templates/vite/.howone/skills/web-clone/scripts/lib/playwright-loader.mjs +0 -39
  28. package/templates/vite/.howone/skills/web-clone/scripts/mirror-site.mjs +0 -121
  29. package/templates/vite/.howone/skills/web-clone/scripts/network-capture.mjs +0 -127
  30. package/templates/vite/.howone/skills/web-clone/scripts/recon-site.mjs +0 -235
  31. package/templates/vite/.howone/skills/web-clone/scripts/route-crawl.mjs +0 -228
  32. package/templates/vite/.howone/skills/web-clone/scripts/sourcemap-hunt.mjs +0 -112
  33. 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: ["template", "cwd"],
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 deploy")}`,
443
- ` ${pc.cyan("howone ai run generateImage")}`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "howone",
3
- "version": "0.1.53",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",
@@ -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 **app-owned integrations**, and routes to backend, AI, or SDK tracks. Keep design
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
- | External systems the user provides | App code + config | Not platform contracts unless combined with rows above |
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. Do not maintain a mental deny-list of technologies
29
- (K8s, message buses, custom protocols, etc.). Ask whether the ask is a **platform contract surface**
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
- ### App-owned (not platform gap)
44
+ ### Explicit user-owned integration (not platform gap)
46
45
 
47
- Anything the user runs, hosts, or buys **outside** HowOne contracts: orchestration, clusters,
48
- custom APIs, message systems, identity products, analytics, payment gateways, etc.
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. Is it only their external infrastructure? → app-owned; wire in UI/config
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; app-owned parts in app code
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 what remains possible via app-owned integration.
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. Read `{appRoot}/.howone/ai/manifest.json`.
153
- 5. Stop AI design. Read SDK references only if implementing app calls.
154
- 6. If persistence required: entity workflow after output contract is fixed.
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; app-owned vs platform clear
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
@@ -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 result = job/task/config mapping and possible manifest workflowId update
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. Poll status until `completed` or `failed`.
66
- 10. Re-read `.howone/ai/manifest.json`; update operations may have written fresh `workflowId` values.
67
- 11. Leave AI design. If app code must call the workflow, read the SDK track and generate bindings.
68
- 12. If output must persist, design entity schema after the output contract is fixed.
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
- manifest = rewritten so capability.workflowId is the fresh UUID
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 update,
112
- the new manifest `workflowId` is the only value that should be copied into `src/lib/sdk.ts`.
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
- - fetch RSS feeds by RSS URL;
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
- - create or modify supported files: text, PDF, DOCX, PPTX, and XLSX.
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.