howone 0.1.53 → 0.2.0

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 (2) hide show
  1. package/bin/index.mjs +362 -5
  2. package/package.json +1 -1
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 ?? process.env.HOWONE_API_URL ?? process.env.NEXT_PUBLIC_SERVER_URL ?? "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.0",
4
4
  "private": false,
5
5
  "description": "HowOne command line tools for creating app templates.",
6
6
  "type": "module",