@deepraxis/cli 0.1.1-dev.926893 → 0.1.1-dev.a766ddd

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 (4) hide show
  1. package/COMMANDS.md +425 -423
  2. package/README.md +27 -24
  3. package/dist/praxis.js +138 -47
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -19,11 +19,11 @@ The official command line for [知行云 Praxis](https://deepraxis.com), the clo
19
19
  ```sh
20
20
  npm install -g @deepraxis/cli
21
21
  praxis login
22
- praxis model chat completion create --model deepseek-flash \
22
+ praxis api model chat completion create --model deepseek-flash \
23
23
  --data '{"messages":[{"role":"user","content":"Hello!"}]}'
24
24
  ```
25
25
 
26
- Every operation of the Praxis API is a command: about 350 of them across models, agents,
26
+ Every operation of the Praxis API is a command under `praxis api`: about 350 of them across models, agents,
27
27
  sandboxes, cloud browsers, web search, databases, knowledge bases, files, media, automation
28
28
  and hosted applications. The command tree is generated from the same contract as the API and
29
29
  the SDK, so a new API capability reaches the CLI on the day it ships.
@@ -36,7 +36,7 @@ the SDK, so a new API capability reaches the CLI on the day it ships.
36
36
  AI coding agent runs it, and clean JSON in scripts. The CLI picks the mode by itself.
37
37
  - **Signs in through the browser.** `praxis login` approves the session in your browser and
38
38
  keeps the credential in the system keychain, never in a dotfile when a keychain exists.
39
- - **Predictable grammar.** `praxis <domain> <resource> <verb> [id]`, the same shape everywhere,
39
+ - **Predictable grammar.** `praxis api <domain> <resource> <verb> [id]`, the same shape everywhere,
40
40
  with `--help` on every level.
41
41
 
42
42
  ## Install
@@ -68,7 +68,7 @@ Belong to several organizations? Every call runs in the active one:
68
68
  ```sh
69
69
  praxis org list
70
70
  praxis org switch <organization_id>
71
- praxis model list --org <organization_id> # just this call
71
+ praxis api model list --org <organization_id> # just this call
72
72
  ```
73
73
 
74
74
  For CI and other headless environments, create an API key in the
@@ -84,12 +84,12 @@ export PRAXIS_API_KEY=<your-api-key>
84
84
  ### Models
85
85
 
86
86
  ```sh
87
- praxis model list
88
- praxis model chat completion create --model deepseek-flash \
87
+ praxis api model list
88
+ praxis api model chat completion create --model deepseek-flash \
89
89
  --data '{"messages":[{"role":"user","content":"A haiku"}]}'
90
- praxis model embedding create --model <embedding_model> \
90
+ praxis api model embedding create --model <embedding_model> \
91
91
  --data '{"input":"hello world"}'
92
- praxis model image generation create --model <image_model> \
92
+ praxis api model image generation create --model <image_model> \
93
93
  --prompt "a lighthouse at dawn"
94
94
  ```
95
95
 
@@ -98,53 +98,56 @@ praxis model image generation create --model <image_model> \
98
98
  Define an agent once, then talk to it in sessions:
99
99
 
100
100
  ```sh
101
- praxis agent create --data '{
101
+ praxis api agent create --data '{
102
102
  "name": "helper",
103
103
  "model": {"id": "deepseek-flash"},
104
104
  "tools": [{"type": "agent_toolset_20260401"}]
105
105
  }'
106
- praxis agent session create --data '{"agent": "<agent_id>"}'
107
- praxis agent session event send <session_id> --data '{
106
+ praxis api agent session create --data '{"agent": "<agent_id>"}'
107
+ praxis api agent session event send <session_id> --data '{
108
108
  "events": [{
109
109
  "type": "user.message",
110
110
  "content": [{"type": "text", "text": "What can you do?"}]
111
111
  }]
112
112
  }'
113
- praxis agent session event stream --session <session_id>
113
+ praxis api agent session event stream --session <session_id>
114
114
  ```
115
115
 
116
116
  ### Sandboxes
117
117
 
118
118
  ```sh
119
- praxis sandbox create --template-id base
120
- praxis sandbox command run <sandbox_id> \
119
+ praxis api sandbox create --template-id base
120
+ praxis api sandbox command run <sandbox_id> \
121
121
  --cmd "python3 -c 'print(6 * 7)'"
122
- praxis sandbox delete <sandbox_id> --yes
122
+ praxis api sandbox delete <sandbox_id> --yes
123
123
  ```
124
124
 
125
125
  ### Search, data and automation
126
126
 
127
127
  ```sh
128
- praxis search create --query "AI agent frameworks" --max-results 5
129
- praxis database create --name notes
130
- praxis automation run create --data '{
128
+ praxis api search create --query "AI agent frameworks" --max-results 5
129
+ praxis api database create --name notes
130
+ praxis api automation run create --data '{
131
131
  "source": "export default async (i) => ({ answer: i.n + 1 })",
132
132
  "language": "ts", "input": {"n": 41}, "wait": 30
133
133
  }'
134
134
  ```
135
135
 
136
- Browse everything with `praxis --help`, `praxis <domain> --help`, or the full
136
+ Browse everything with `praxis --help`, `praxis api --help`, `praxis api <domain> --help`, or the full
137
137
  [command reference](https://unpkg.com/@deepraxis/cli/COMMANDS.md) that ships in the package.
138
138
 
139
139
  ## How commands work
140
140
 
141
+ There are two layers. Top-level commands are workflows and session commands (`login`, `logout`,
142
+ `whoami`, `org`, `skill`). Every API operation lives under `praxis api`, so the two never collide.
143
+
141
144
  ```text
142
- praxis sandbox command run sbx_123 --cmd "ls" --json
143
- domain resource verb target options global
145
+ praxis api sandbox command run sbx_123 --cmd "ls" --json
146
+ domain resource verb target options global
144
147
  ```
145
148
 
146
149
  - **One target, positionally.** The object a command acts on comes right after the verb. Parent
147
- objects are options: `praxis database table list --database <database_id>`.
150
+ objects are options: `praxis api database table list --database <database_id>`.
148
151
  - **Request bodies.** Simple fields have their own options. Anything else goes in `--data`, as
149
152
  inline JSON, a file (`--data @body.json`) or stdin (`--data -`). Field options win over the
150
153
  same field in `--data`.
@@ -152,7 +155,7 @@ praxis sandbox command run sbx_123 --cmd "ls" --json
152
155
  resumes from a page you saved.
153
156
  - **Safety.** Deleting and other destructive commands ask for confirmation, or need `--yes`
154
157
  when no one can answer, for example in a script.
155
- - **Escape hatch.** `praxis api <METHOD> <path> [--data …]` calls any endpoint with your
158
+ - **Escape hatch.** `praxis api <METHOD> <path> [--data …]` (an uppercase method, such as `praxis api GET /model/v1/models`) calls any endpoint with your
156
159
  current credential, including ones that are new since your installed version.
157
160
 
158
161
  ## Output for people, AI agents and scripts
@@ -167,7 +170,7 @@ Errors follow the same rule. In JSON they are one object on stderr with `code`,
167
170
  `hint`, `status` and `request_id`, so a script or an agent can tell a typo from a quota limit.
168
171
 
169
172
  ```sh
170
- praxis model list --json | jq -r '.data[].id'
173
+ praxis api model list --json | jq -r '.data[].id'
171
174
  ```
172
175
 
173
176
  ## Teach your AI agent to use Praxis
package/dist/praxis.js CHANGED
@@ -1973,7 +1973,7 @@ clears to null; an empty string is stored as an empty string.`
1973
1973
  resource: "agent.sessions",
1974
1974
  method: "create",
1975
1975
  summary: "Create session",
1976
- description: `Create a session: one conversation with an agent. Pass agent as an agent id or {type:"agent", id, version?}; an id alone, or an object without version, pins the agent's active_version (the version chosen with agents.versions.apply, not necessarily the newest saved one). Unlike Anthropic Managed Agents, omit environment_id to use the default environment saved in the agent version (light for a new agent), unless computer_id (explicit, or the agent's default) selects a Computer; pass an environment id for a sandbox recipe. For service agents, explicit null allocates no machine, so codemode, bash and other machine tools are unavailable. Optional model must be one of the agent's allowed models; optional thinking is {effort: low|medium|high|xhigh|max|none} or null, where null inherits the agent default and then the model default (none requires the model to allow disabling thinking). Selections are saved before initial_events run; invalid ones return 400 invalid_request_error. Unlike Anthropic Managed Agents, the only inline agent override is system ({type:"agent_with_overrides", id, system}); to change the model, tools, MCP servers or skills, save a version instead. Supports vault_ids, up to 50, owned by the current organization and not archived; credential injection is deferred to a later version. budget is {type:"limit", max_list_cost:{amount, currency:"USD"}} where amount is a positive integer string of US cents ("2000" is USD 20.00); omitted budget defaults to "2000" and null removes the limit. Up to 500 file resources can be attached. Use initial_events to send a first user.message in this request, or send messages afterwards with sessions.events.send. The returned status can still be idle before initial events run; read agent.message and the subsequent session.status_idle event to confirm completion.`,
1976
+ description: `Create a session: one conversation with an agent. Pass agent as an agent id or {type:"agent", id, version?}; an id alone, or an object without version, pins the agent's active_version (the version chosen with agents.versions.apply, not necessarily the newest saved one). Unlike Anthropic Managed Agents, omit environment_id to use the default environment saved in the agent version (light for a new agent), unless computer_id (explicit, or the agent's default) selects a Computer; pass an environment id for a sandbox recipe. For service agents, explicit null allocates no machine, so codemode, bash and other machine tools are unavailable. Optional model must be one of the agent's allowed models; optional thinking is {effort: low|medium|high|xhigh|max|none} or null, where null inherits the agent default and then the model default (none requires the model to allow disabling thinking). Selections are saved before initial_events run; invalid ones return 400 invalid_request_error. Unlike Anthropic Managed Agents, the only inline agent override is system ({type:"agent_with_overrides", id, system}); to change the model, tools, MCP servers or skills, save a version instead. Supports vault_ids, up to 50, owned by the current organization and not archived; credential injection is deferred to a later version. budget is {type:"limit", max_list_cost:{amount, currency:"USD"}} where amount is a positive decimal string of US dollars with at most 2 decimal places ("20.00" is USD 20.00); omitted budget defaults to "20.00" and null removes the limit. Up to 500 file resources can be attached. Use initial_events to send a first user.message in this request, or send messages afterwards with sessions.events.send. The returned status can still be idle before initial events run; read agent.message and the subsequent session.status_idle event to confirm completion.`,
1977
1977
  http: "POST",
1978
1978
  path: "/agent/v1/sessions",
1979
1979
  streaming: "no",
@@ -2549,9 +2549,9 @@ Available only on /agent/v1. Use the message_id field from own-face events, not
2549
2549
  query: [
2550
2550
  {
2551
2551
  name: "limit",
2552
- type: "number",
2552
+ type: "integer",
2553
2553
  required: false,
2554
- description: "Number of items per page."
2554
+ description: "Number of resources per page, from 1 to 100; defaults to 100."
2555
2555
  },
2556
2556
  {
2557
2557
  name: "page",
@@ -9610,7 +9610,8 @@ The default is return=minimal: success returns HTTP 201 with an empty body. Send
9610
9610
  {
9611
9611
  name: "file_id",
9612
9612
  type: "string",
9613
- required: true
9613
+ required: true,
9614
+ description: "File ID to copy into this library; passed as the upload source instead of file bytes."
9614
9615
  },
9615
9616
  {
9616
9617
  name: "public_id",
@@ -25061,7 +25062,7 @@ async function publish(client2, input) {
25061
25062
  const timeout = input.timeout_ms ?? 120000;
25062
25063
  if (!Number.isFinite(timeout) || timeout < 0)
25063
25064
  throw new RangeError("timeout_ms must be non-negative and finite");
25064
- const contentType = [
25065
+ const moduleTypes = [
25065
25066
  "application/javascript+module",
25066
25067
  "text/javascript",
25067
25068
  "application/javascript",
@@ -25070,9 +25071,30 @@ async function publish(client2, input) {
25070
25071
  "application/wasm",
25071
25072
  "application/octet-stream",
25072
25073
  "application/x-python"
25073
- ].find((type) => type === (input.main?.content_type ?? "application/javascript+module"));
25074
- if (!contentType)
25075
- throw new PraxisError({ code: "INVALID_PARAMS", message: `Unsupported module content_type: ${input.main?.content_type}` });
25074
+ ];
25075
+ const moduleType = (type) => {
25076
+ const found = moduleTypes.find((candidate) => candidate === (type ?? "application/javascript+module"));
25077
+ if (!found)
25078
+ throw new PraxisError({ code: "INVALID_PARAMS", message: `Unsupported module content_type: ${type}` });
25079
+ return found;
25080
+ };
25081
+ if (input.main && input.modules)
25082
+ throw new PraxisError({ code: "INVALID_PARAMS", message: "Pass either main or modules, not both" });
25083
+ if (input.main_module !== undefined && !input.modules)
25084
+ throw new PraxisError({ code: "INVALID_PARAMS", message: "main_module requires modules; with main the entry is main.name" });
25085
+ if (input.modules && input.modules.length === 0)
25086
+ throw new PraxisError({ code: "INVALID_PARAMS", message: "modules must not be empty" });
25087
+ const entryModules = input.main ? [{ name: input.main.name, content_type: moduleType(input.main.content_type), content_base64: base64(new TextEncoder().encode(input.main.content)) }] : input.modules?.map((module) => ({
25088
+ name: module.name,
25089
+ content_type: moduleType(module.content_type),
25090
+ content_base64: base64(typeof module.content === "string" ? new TextEncoder().encode(module.content) : module.content)
25091
+ }));
25092
+ if (new Set(entryModules?.map((module) => module.name)).size !== (entryModules?.length ?? 0))
25093
+ throw new PraxisError({ code: "INVALID_PARAMS", message: "Duplicate module name" });
25094
+ const mainModule = input.main ? input.main.name : input.modules ? input.main_module ?? input.modules[0].name : undefined;
25095
+ if (input.modules && !input.modules.some((module) => module.name === mainModule)) {
25096
+ throw new PraxisError({ code: "INVALID_PARAMS", message: `main_module ${mainModule} is not among modules` });
25097
+ }
25076
25098
  const files = Object.entries(input.files);
25077
25099
  if (files.length > 1e5)
25078
25100
  throw new PraxisError({ code: "LIMIT_EXCEEDED", message: "At most 100000 asset paths can be published" });
@@ -25125,7 +25147,7 @@ async function publish(client2, input) {
25125
25147
  const removed = Object.keys(previous).filter((key) => !Object.hasOwn(manifest, key));
25126
25148
  const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(manifest))));
25127
25149
  const manifestHash = Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
25128
- const staticOnly = !["main", "bindings", "compatibility_date", "compatibility_flags", "assets_config"].some((key) => Object.hasOwn(input, key));
25150
+ const staticOnly = !["main", "modules", "main_module", "bindings", "compatibility_date", "compatibility_flags", "assets_config"].some((key) => Object.hasOwn(input, key));
25129
25151
  const unchanged = staticOnly && !!currentVersion && manifestHash === currentVersion.praxis.assets_manifest?.manifest_hash;
25130
25152
  const hashes = new Set(Object.values(manifest).map((entry) => entry.hash));
25131
25153
  const currentResult = {
@@ -25181,11 +25203,7 @@ async function publish(client2, input) {
25181
25203
  assets: versionAssets,
25182
25204
  ...input.compatibility_flags ? { compatibility_flags: input.compatibility_flags } : {},
25183
25205
  ...input.bindings ? { bindings: input.bindings } : {},
25184
- ...input.main ? { main_module: input.main.name, modules: [{
25185
- name: input.main.name,
25186
- content_type: contentType,
25187
- content_base64: base64(new TextEncoder().encode(input.main.content))
25188
- }] } : {}
25206
+ ...entryModules ? { main_module: mainModule, modules: entryModules } : {}
25189
25207
  } });
25190
25208
  let deployment = result.deployment;
25191
25209
  if (!deployment)
@@ -25316,7 +25334,7 @@ function decideTabReuse(targetUrl, tabs) {
25316
25334
  if (best === undefined || rank < best.rank || rank === best.rank && (tab.active || !best.tab.active))
25317
25335
  best = { tab, rank };
25318
25336
  }
25319
- return best?.tab;
25337
+ return best?.tab ?? tabs.find((tab) => isBlankTab(tab.url));
25320
25338
  }
25321
25339
  function buildConnectUrl(parts) {
25322
25340
  const url = new URL("/browser/v1/connect", parts.api);
@@ -25334,6 +25352,16 @@ function pickAgentContext(contexts, agentId) {
25334
25352
  return [...own].sort((x, y) => (x.createdAt ?? "").localeCompare(y.createdAt ?? "") || x.id.localeCompare(y.id))[0];
25335
25353
  }
25336
25354
  var agentContextName = (agentId) => `agent-${agentId}`.replace(/[^A-Za-z0-9._:-]/g, "-").slice(0, 128);
25355
+ function isBlankTab(value) {
25356
+ if (value === "" || value === "about:blank")
25357
+ return true;
25358
+ try {
25359
+ const url = new URL(value);
25360
+ return (url.protocol === "https:" || url.protocol === "http:") && url.pathname === "/start" && (url.hostname.startsWith("console-") || url.hostname === "console.deepraxis.com");
25361
+ } catch {
25362
+ return false;
25363
+ }
25364
+ }
25337
25365
 
25338
25366
  // ../../packages/sdk/typescript/src/helpers/browser-connect.ts
25339
25367
  var PLAYWRIGHT_CORE_VERSION = "1.63.0";
@@ -25519,10 +25547,11 @@ class Praxis {
25519
25547
  if (name === "database")
25520
25548
  Object.assign(resource, { count: (input) => count(transport, input) });
25521
25549
  if (name === "browser") {
25550
+ const host = globalThis.process;
25522
25551
  Object.assign(resource, { connect: (options2) => connectBrowser({
25523
25552
  contexts: resource.contexts,
25524
- env: globalThis.process?.env ?? {},
25525
- builtin: globalThis.process?.getBuiltinModule?.bind(globalThis.process)
25553
+ env: host?.env ?? {},
25554
+ builtin: host?.getBuiltinModule?.bind(host)
25526
25555
  }, options2) });
25527
25556
  }
25528
25557
  if (name === "application")
@@ -25591,6 +25620,15 @@ function commandWords(resource, method) {
25591
25620
  const verb = kebab(method.replace(/^retrieve(?=[A-Z]|$)/, "get"));
25592
25621
  return [domain, ...resources, verb];
25593
25622
  }
25623
+ var API_WORD = "api";
25624
+ var HTTP_METHOD = /^(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)$/;
25625
+ var domainOf = (command) => command.words[1];
25626
+ function movedTo(commands, words) {
25627
+ if (!words.length || words[0] === API_WORD)
25628
+ return;
25629
+ const moved = [API_WORD, ...words];
25630
+ return commands.some((c) => moved.every((w, i) => c.words[i] === w)) ? `praxis ${moved.join(" ")}` : undefined;
25631
+ }
25594
25632
  function commandTree(operations2) {
25595
25633
  const commands = [];
25596
25634
  const seen = new Set;
@@ -25606,7 +25644,7 @@ function commandTree(operations2) {
25606
25644
  if (["login", "logout", "whoami", "org", "api", "skill"].includes(words[0]))
25607
25645
  throw new Error(`Reserved command: ${key}`);
25608
25646
  seen.add(key);
25609
- commands.push({ words, op, ...method !== op.method ? { alias: method } : {} });
25647
+ commands.push({ words: [API_WORD, ...words], op, ...method !== op.method ? { alias: method } : {} });
25610
25648
  }
25611
25649
  }
25612
25650
  for (const key of Object.keys(commandOverrides))
@@ -25902,24 +25940,68 @@ function help(commands, prefix, command) {
25902
25940
  `);
25903
25941
  }
25904
25942
  const matching = commands.filter((c) => prefix.every((w, i) => c.words[i] === w));
25943
+ const global = [
25944
+ "Global options:",
25945
+ ...globalOptions.map((s) => ` ${describeOption(s)}`),
25946
+ " -h is an alias of --help",
25947
+ "PRAXIS_ENV=develop|production (default: develop); no --env option.",
25948
+ ""
25949
+ ];
25950
+ const sample = matching.find((c) => c.words.at(-1) === "list") ?? matching[0];
25951
+ if (!prefix.length) {
25952
+ const nouns = [...new Set(workflows.map((w) => w.words[0]))];
25953
+ const summaries = new Map(workflows.map((w) => [w.words[0], w.words.length === 1 ? w.summary : undefined]));
25954
+ const width = Math.max(...nouns.map((n) => n.length));
25955
+ return [
25956
+ "praxis <command> [options]",
25957
+ "",
25958
+ "Workflows:",
25959
+ ...nouns.map((n) => ` ${n.padEnd(width)} ${summaries.get(n) ?? workflows.filter((w) => w.words[0] === n).map((w) => w.words[1]).join(", ")}`),
25960
+ "",
25961
+ "API:",
25962
+ ` praxis ${API_WORD} <domain> <resource> <action> [options] one command per API operation`,
25963
+ ` praxis ${API_WORD} <METHOD> <path> [--data JSON|@file|-] raw request`,
25964
+ ` Domains: ${domains(commands).join(", ")}`,
25965
+ "",
25966
+ ...global,
25967
+ "Examples:",
25968
+ ` ${sample ? example(sample) : "praxis --help"}`
25969
+ ].join(`
25970
+ `);
25971
+ }
25905
25972
  const children = [...new Set(matching.map((c) => c.words[prefix.length]).filter(Boolean))].sort();
25906
- if (!prefix.length)
25907
- children.unshift("login", "logout", "whoami", "org", "api", "skill");
25973
+ const raw = prefix.length === 1 && prefix[0] === API_WORD;
25908
25974
  return [
25909
25975
  `praxis${prefix.length ? " " + prefix.join(" ") : ""} <command> [options]`,
25910
25976
  "",
25977
+ ...raw ? ["Domains:"] : [],
25911
25978
  ...children.map((s) => ` ${s}`),
25912
25979
  "",
25913
- "Global options:",
25914
- ...globalOptions.map((s) => ` ${describeOption(s)}`),
25915
- " -h is an alias of --help",
25916
- "PRAXIS_ENV=develop|production (default: develop); no --env option.",
25917
- "",
25980
+ ...raw ? ["Raw request:", ` praxis ${API_WORD} <METHOD> <path> [--data JSON|@file|-]`, ""] : [],
25981
+ ...global,
25918
25982
  "Examples:",
25919
- ` ${matching[0] ? example(matching.find((c) => c.words.at(-1) === "list") ?? matching[0]) : "praxis --help"}`
25983
+ ...raw ? [` praxis ${API_WORD} GET /model/v1/models`] : [],
25984
+ ` ${sample ? example(sample) : "praxis --help"}`
25920
25985
  ].join(`
25921
25986
  `);
25922
25987
  }
25988
+ function domains(commands) {
25989
+ return [...new Set(commands.map((c) => c.words[1]))].sort();
25990
+ }
25991
+ var workflows = [
25992
+ { words: ["login"], usage: "login", summary: "Sign in with a browser device code." },
25993
+ { words: ["logout"], usage: "logout", summary: "Remove the saved login from the keychain and fallback file." },
25994
+ { words: ["whoami"], usage: "whoami", summary: "Show the signed-in identity and credential storage location." },
25995
+ { words: ["org", "list"], usage: "org list", summary: "List organizations available to the login." },
25996
+ { words: ["org", "switch"], usage: "org switch <organization_id>", summary: "Switch the login organization." },
25997
+ { words: ["skill"], usage: "skill [--install <dir>]", summary: "Print the AI skill, or write <dir>/praxis-cli/SKILL.md." }
25998
+ ];
25999
+ var workflowNouns = [...new Set(workflows.filter((w) => w.words.length > 1).map((w) => w.words[0]))];
26000
+ function nounHelp(noun) {
26001
+ const mine = workflows.filter((w) => w.words[0] === noun);
26002
+ return [...mine.map((w) => `praxis ${w.usage}`), "", "Examples:", ` praxis ${mine[0].usage}`].join(`
26003
+ `);
26004
+ }
25923
26005
 
25924
26006
  // src/config/env.ts
25925
26007
  var DEFAULT_ENV = "develop";
@@ -26286,7 +26368,7 @@ function commandSummary(command) {
26286
26368
  const chars = [...description];
26287
26369
  return chars.length > 120 ? chars.slice(0, 119).join("") + "…" : description;
26288
26370
  }
26289
- const words = [...command.words];
26371
+ const words = command.words.slice(1);
26290
26372
  const verb = (words.pop() ?? "use").replace(/-/g, " ");
26291
26373
  if (verb === "list" && words.length && !words.at(-1).endsWith("s"))
26292
26374
  words[words.length - 1] += "s";
@@ -26312,21 +26394,17 @@ function skillDocument(commands) {
26312
26394
  "- Use `--limit N` to limit a server page for SDK-paginated commands. Other lists apply it only to displayed rows; `--param-limit` still sends the API parameter when available. `--all` follows supported pagination and cannot be combined with `--limit`.",
26313
26395
  "- Error JSON has an `error` object: `code` identifies the failure, `message` describes it, `hint` suggests recovery, `env` identifies the environment, `command` identifies the command, `status` is the HTTP status, and `request_id` correlates diagnostics. Unavailable fields are null. Exit codes: 0 success, 1 runtime/API error, 2 usage error.",
26314
26396
  "- Append `--help` to a command for its optional parameters. Required scalar body options below can also be supplied as fields in `--data`.",
26397
+ "- Two layers: top-level commands (`login`, `org`, `skill`, …) are workflows and session commands; every API operation is under `praxis api <domain> …`. `praxis api <METHOD> <path>` sends a raw request (the method is uppercase).",
26315
26398
  "",
26316
26399
  "## Account and CLI",
26317
26400
  "",
26318
- "praxis login — Sign in with a browser device code.",
26319
- "praxis logout — Remove the saved login from the keychain and fallback file.",
26320
- "praxis whoami — Show the signed-in identity and credential storage location.",
26321
- "praxis org list — List organizations available to the login.",
26322
- "praxis org switch <organization_id> — Switch the login organization.",
26323
- "praxis api <METHOD> <path> — Call an API path directly. (--data for a request body)",
26324
- "praxis skill — Print this skill; use --install <dir> to write <dir>/praxis-cli/SKILL.md."
26401
+ ...workflows.map((w) => `praxis ${w.usage} — ${w.summary.replace("Print the AI skill, or write", "Print this skill; use --install <dir> to write")}`),
26402
+ "praxis api <METHOD> <path> — Call an API path directly. (--data for a request body)"
26325
26403
  ];
26326
26404
  let domain;
26327
26405
  for (const command of commands) {
26328
- if (command.words[0] !== domain) {
26329
- domain = command.words[0];
26406
+ if (domainOf(command) !== domain) {
26407
+ domain = domainOf(command);
26330
26408
  lines.push("", `## ${domain}`, "");
26331
26409
  }
26332
26410
  const target = targetParam(command.op);
@@ -27071,9 +27149,9 @@ function startProgress(ctx, write = (text) => writeOutput({ stderr: text })) {
27071
27149
  }
27072
27150
 
27073
27151
  // src/main.ts
27074
- var version = "0.1.1-dev.0926893";
27152
+ var version = "0.1.1-dev.a766ddd";
27075
27153
  var commands = commandTree(operations);
27076
- var manual = [["login"], ["logout"], ["whoami"], ["org", "list"], ["org", "switch"], ["api"], ["skill"]];
27154
+ var manual = [...workflows.map((w) => w.words), [API_WORD]];
27077
27155
  function route(argv) {
27078
27156
  const words = [], args = [];
27079
27157
  for (let i = 0;i < argv.length; i++) {
@@ -27089,7 +27167,11 @@ function route(argv) {
27089
27167
  continue;
27090
27168
  }
27091
27169
  words.push(arg);
27170
+ if (words.length === 1 && words[0] === API_WORD && HTTP_METHOD.test(argv[i + 1] ?? ""))
27171
+ return { words, args: [...args, ...argv.slice(i + 1)] };
27092
27172
  const command = commands.find((c) => c.words.length === words.length && c.words.every((w, n) => w === words[n]));
27173
+ if (words.length === 1 && words[0] === API_WORD && argv[i + 1] !== undefined && !argv[i + 1].startsWith("-"))
27174
+ continue;
27093
27175
  if (command || manual.some((c) => c.length === words.length && c.every((w, n) => w === words[n])))
27094
27176
  return { words, args: [...args, ...argv.slice(i + 1)], command };
27095
27177
  }
@@ -27162,7 +27244,7 @@ async function main(argv, injected = {}) {
27162
27244
  const selected = route(argv);
27163
27245
  ctx.command = commandLine(selected.words);
27164
27246
  ctx.resource = selected.words.at(-2) ?? selected.words[0];
27165
- const specs = [...globalOptions, ...selected.command ? [...operationOptions(selected.command.op), ...selected.command.op.body ? [dataOption] : []] : selected.words[0] === "api" ? [dataOption] : selected.words[0] === "skill" ? [{ name: "install", type: "string" }] : []];
27247
+ const specs = [...globalOptions, ...selected.command ? [...operationOptions(selected.command.op), ...selected.command.op.body ? [dataOption] : []] : selected.words.join(" ") === API_WORD ? [dataOption] : selected.words[0] === "skill" ? [{ name: "install", type: "string" }] : []];
27166
27248
  const outputFlags = globalsOf(parseArgs(outputModeArgs(selected.args, specs), globalOptions));
27167
27249
  ctx.json = Boolean(outputFlags.json);
27168
27250
  Object.assign(ctx, decideOutputMode({ format: outputFlags.format, json: ctx.json, stdoutIsTTY: ports.stdoutIsTTY, env: ports.env }));
@@ -27176,15 +27258,22 @@ async function main(argv, injected = {}) {
27176
27258
  return 0;
27177
27259
  }
27178
27260
  if (!selected.words.length || !selected.command && !manual.some((c) => c.join(" ") === selected.words.join(" "))) {
27179
- if (selected.words.length && !commands.some((c) => selected.words.every((w, i) => c.words[i] === w)) && selected.words.join(" ") !== "org")
27180
- throw new UsageError(`Unknown command: ${selected.words.join(" ")}`);
27261
+ if (selected.words.length && !commands.some((c) => selected.words.every((w, i) => c.words[i] === w)) && !workflowNouns.includes(selected.words.join(" "))) {
27262
+ const moved = movedTo(commands, selected.words);
27263
+ throw new UsageError(`Unknown command: ${selected.words.join(" ")}`, moved ? `Generated commands moved under \`praxis api\`. Did you mean: ${moved}?` : selected.words[0] === API_WORD ? "Run `praxis api --help` to list API domains." : "Run `praxis --help`.");
27264
+ }
27181
27265
  if (parsed.positional.length)
27182
27266
  throw new UsageError("Unexpected arguments");
27183
- emit({ kind: "message", channel: "stdout", text: selected.words.join(" ") === "org" ? `praxis org list
27184
- praxis org switch <organization_id>
27185
-
27186
- Examples:
27187
- praxis org list` : help(commands, selected.words) });
27267
+ const noun = selected.words.join(" ");
27268
+ emit({ kind: "message", channel: "stdout", text: workflowNouns.includes(noun) ? nounHelp(noun) : help(commands, selected.words) });
27269
+ return 0;
27270
+ }
27271
+ if (selected.words.join(" ") === API_WORD && !parsed.positional.length && !flags.help) {
27272
+ emit({ kind: "message", channel: "stdout", text: help(commands, [API_WORD]) });
27273
+ return 0;
27274
+ }
27275
+ if (flags.help && selected.words.join(" ") === API_WORD && !parsed.positional.length) {
27276
+ emit({ kind: "message", channel: "stdout", text: help(commands, [API_WORD]) });
27188
27277
  return 0;
27189
27278
  }
27190
27279
  if (flags.help && selected.words[0] === "skill") {
@@ -27318,7 +27407,9 @@ Examples:
27318
27407
  emit({ kind: "object", data: await auth.switchOrganization(parsed.positional[0]) });
27319
27408
  break;
27320
27409
  case "api": {
27321
- const method = parsed.positional[0].toUpperCase();
27410
+ const method = parsed.positional[0];
27411
+ if (!HTTP_METHOD.test(method))
27412
+ throw new UsageError(`Unknown command: api ${method}`, "Run `praxis api --help` to list API domains; raw requests use an uppercase method, such as `praxis api GET /model/v1/models`.");
27322
27413
  await confirm(method.toLowerCase());
27323
27414
  if (!quiet) {
27324
27415
  stopProgress();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepraxis/cli",
3
- "version": "0.1.1-dev.0926893",
3
+ "version": "0.1.1-dev.a766ddd",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "praxis": "dist/praxis.js"