@crewx/app 0.1.0-rc.7 → 0.1.0-rc.71
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/SKILL.md +39 -4
- package/build-app/index.d.mts +64 -0
- package/build-app/index.mjs +7 -0
- package/build-app/load.cjs +1 -0
- package/build-app/manifest.mjs +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -91
- package/dist/src/engine.d.ts +5 -2
- package/dist/src/engine.js +8 -1200
- package/dist/src/image-artifact.d.ts +24 -0
- package/dist/src/image-artifact.js +1 -0
- package/dist/src/receipt-wait.d.ts +24 -0
- package/dist/src/receipt-wait.js +1 -0
- package/dist/src/usage.d.ts +27 -0
- package/dist/src/usage.js +8 -0
- package/package.json +6 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/src/engine.d.ts.map +0 -1
- package/dist/src/engine.js.map +0 -1
package/SKILL.md
CHANGED
|
@@ -14,13 +14,45 @@ metadata:
|
|
|
14
14
|
| `crewx app open <appId>` | Open an App in this conversation |
|
|
15
15
|
| `crewx app state <instanceId>` | Read AI-visible state, buttons, and agent access |
|
|
16
16
|
| `crewx app run <instanceId|appId> <actionId> [--input <JSON>|-] [--request-id <id>]` | Run one available action |
|
|
17
|
-
| `crewx app receipt <receiptId
|
|
17
|
+
| `crewx app receipt <receiptId> [--wait=<seconds>]` | Read a safe action receipt. With `--wait`, poll until it settles or the wait ends |
|
|
18
|
+
| `crewx app build [<appDir>] [--check | --watch]` | Build a `@Tool` App: bundle `server.ts` into `dist/` and regenerate its actions (in `manifest.json`, or in `dist/tools.json` for a `server.ts`-only App). `--check` writes nothing and fails on a stale file; `--watch` rebuilds on every change |
|
|
18
19
|
| `crewx app dev <app> [--no-open]` | Start a foreground App dev session with build and reload watching |
|
|
19
20
|
| `crewx app check <app> [--json]` | Run the App dev checks |
|
|
20
21
|
| `crewx app run --dev <app> <actionId> [--input <JSON>|-]` | Run an App action through the dev checks |
|
|
21
22
|
| `crewx app usage` | Show this guide |
|
|
23
|
+
| `crewx app usage <appId>` | Show one App's manual (its SKILL.md) and an action table generated from its declaration: inputs with real types, agent access, confirmation |
|
|
22
24
|
|
|
23
|
-
`<app>` is the App folder name under `apps/` or its `pluginId`
|
|
25
|
+
`<app>` is the App folder name under `apps/` or its App id (`name` in `manifest.json`, or `pluginId` in `ui-plugin.json`).
|
|
26
|
+
|
|
27
|
+
## Read the manual before the first run
|
|
28
|
+
|
|
29
|
+
`crewx app usage <appId>` prints the App's manual, then a table of every action: its inputs (`*` = required, real types, limits, allowed values), the agent access (`allowed`, `approval_required`, `denied`, the same words `crewx app state` prints) and the confirmation mode. Send exactly the keys listed. After a successful `crewx app run`, stderr suggests `→ Check: crewx app state <instanceId>`.
|
|
30
|
+
|
|
31
|
+
## Open a captured image before describing marks
|
|
32
|
+
|
|
33
|
+
For the read-only `captureFeedback` action, send exactly `documentId`, `viewRef`, and `expectedAnnotationRevision`; use `--request-id` for the retry identity. A delivered image appears in `crewx app run` or `crewx app receipt` as a validated local PNG path, dimensions, byte count, and SHA-256. The image ref is replaced by that local path. The PNG is stored under `<cwd>/.crewx/app-captures/`; owned files older than 24 hours are pruned when another image is materialized. The cache holds at most 32 owned artifacts; a new artifact fails with `IMAGE_WRITE_FAILED` while 32 recent artifacts remain.
|
|
34
|
+
|
|
35
|
+
The capture folder's `.gitignore` must keep its generated contents (`*` followed by `!.gitignore`). If it is edited or replaced, image materialization fails with `IMAGE_WRITE_FAILED`; restore that file before retrying. The CLI never changes the workspace's `.gitignore` and never prunes unrelated files.
|
|
36
|
+
|
|
37
|
+
The pixels are not yet in model context. Open the exact printed path with the provider's image-reading tool before describing a marked target. If the provider has no image-reading tool, report that limitation; do not infer pixels from the path, counts, or note text, and do not claim an OCR fallback.
|
|
38
|
+
|
|
39
|
+
`crewx app state <instanceId>` may include a separate top-level `feedback` field for the task-bound Viewer. Its optional `summary` describes the loaded document and its `modelRevision`; it is not saved App state and it does not provide a replacement `viewRef`. Counts are last-known and updates may be coalesced for up to 750 ms. Do not decline a relevant user request solely because an older summary reports zero marks.
|
|
40
|
+
|
|
41
|
+
After a known `STALE` result, refresh with `crewx app state <instanceId>`, keep the same `viewRef` from the task prompt, and use `documentId` and `modelRevision` from the loaded `feedback.summary` rather than the saved document selection. Retry with a new `--request-id`. If the action outcome is unknown, recover the same receipt with `crewx app receipt <receiptId>`; do not rerun the capture. A receipt carrying the server-owned `viewCall: true` marker fails with exit 4 and an `IMAGE_*` code when its result expired or its top-level image descriptor is missing, while preserving the receipt and result text. Plain actions keep their existing behavior even if they share the name `captureFeedback`. If the output has no `image` field, no pixels were delivered; older servers may not expose the image result.
|
|
42
|
+
|
|
43
|
+
`IMAGE_EXPIRED` cannot be recovered from the same receipt; if the image is still needed, refresh state and send a new request ID for the same prompt-bound view using the loaded summary's `documentId` and `modelRevision`. If that view or its summary is unavailable, report that and do not substitute another view. The CLI never starts a new capture automatically.
|
|
44
|
+
|
|
45
|
+
## Build a `@Tool` App
|
|
46
|
+
|
|
47
|
+
`crewx app build` needs nothing but the CrewX CLI: it ships the bundler and the SDK that `server.ts` imports as `@crewx/sdk/app`. Run it in the App folder (or pass the folder). The folder needs `server.ts` (default-exports a `CrewXApp` subclass) and a `package.json`; `manifest.json` is created when missing.
|
|
48
|
+
|
|
49
|
+
**`server.ts` only (the default for a new App).** Leave `bin` and `crewx.app.command` out of `package.json` and `server.ts` is the whole backend: there is no CLI, no launcher file and nothing to write in the manifest. The build writes `dist/server.mjs`, a self-starting `dist/main.mjs` and `dist/tools.json` (the `@Tool` declarations), and removes `actions` from `manifest.json`, which keeps the name, version, screen, size and outside connections. Never write `actions` or a `backend` yourself: a `manifest.json` that still has `actions` next to `dist/tools.json`, or an action that names an `entry`, is refused. Inside a `@Tool` method `this.context` says who is calling: `{ instanceId, threadId, actor, requestId, nowMs }`. `threadId` is `null` when the App was opened outside a conversation, `actor` is `'user'` or `'agent'`, and the Host sets all of it, never the request. `crewx.app.env` is not allowed here, because the Host always passes the instance and the thread.
|
|
50
|
+
|
|
51
|
+
**An App that is also a CLI.** With a `bin` name (or `crewx.app.command`) in `package.json`, the actions run that skill command, and the build rewrites only `_meta["dev.crewx"].actions` of `manifest.json`. Set `crewx.app.env` to `["CREWX_APP_INSTANCE_ID", "CREWX_THREAD_ID"]` (one or both, no repeats, nothing else) when your `@Tool` code reads those values from `process.env`: the build writes the list to every action's `backend.env` and the Host then puts exactly those values in the backend process; without it the backend gets neither and no `env` key is written. `this.context` works there too, with the values the Host put in the environment.
|
|
52
|
+
|
|
53
|
+
To take or send a text body (an action that only the screen may call), declare `content: { in: { mediaType, required? }, out: { mediaType, range? } }` on the `@Tool` (`mediaType` is `text/plain` or `application/json`): the method then gets `(input, content)`, reads `content.in.text`, and answers with `this.setContent(body)`; the build copies the declaration into the action and the runtime writes the bytes, lines and SHA-256.
|
|
54
|
+
|
|
55
|
+
stdout is one JSON line with the written `files` and the `actions` found; after a build, stderr says the next command. Exit 0 = built (or in sync with `--check`), 1 = `BUILD_FAILED` (stderr says why) or `BUILD_STALE` (`--check` found a stale file; run `crewx app build`), 2 = usage. `--watch` stays in the foreground and prints one plain line per build until Ctrl-C.
|
|
24
56
|
|
|
25
57
|
## App development
|
|
26
58
|
|
|
@@ -49,8 +81,11 @@ action did not succeed. A one-shot agent should use this build/check/run loop in
|
|
|
49
81
|
## When blocked
|
|
50
82
|
|
|
51
83
|
- Exit 3, 9, 10, or 11: do not click the screen, use CUA, or open another browser. Report that you could not use the App route.
|
|
84
|
+
- Exit 14: the server did not answer after the connection was established, so the outcome is unknown. For `run`, retry the same command once with the `requestId` from the JSON payload as `--request-id`; within 15 minutes the server returns the same receipt and result without running the action twice. If it times out again, report `server response delayed, outcome unknown` with the `requestId`. Do not report success or failure, click the screen, or use another route. For other commands, retry once; if it happens again, report it.
|
|
52
85
|
- Exit 4: do not retry; report the failure.
|
|
53
86
|
- Use `agentAccess` from `crewx app state`: `allowed` can run, `approval_required` can request human approval, and `denied` must not run.
|
|
54
|
-
- Exit 8
|
|
55
|
-
-
|
|
87
|
+
- Exit 8 = waiting for the user's one-time approval in the App window. Keep this turn running and wait with `crewx app receipt <receiptId> --wait=60` (repeat on exit 124); ending the turn cancels the request. Report success only if the receipt settles as `succeeded`. Never claim the work is done and never work around it with screen clicks or another route. stderr adds `→ Waiting for the user's approval in the App window (expires HH:MM)` and `→ Wait: crewx app receipt <receiptId> --wait=60 (repeat on exit 124 until it settles)`; the JSON on stdout is unchanged.
|
|
88
|
+
- `crewx app receipt <receiptId> --wait=<seconds>` asks the server every 3 seconds until the receipt settles (`succeeded`, `rejected`, `failed`, `conflicted`, `expired`). A settled receipt is printed exactly as `receipt` without `--wait` prints it, with the same exit code. If the wait ends first, it exits 124 with nothing on stdout and one stderr line naming the next command. `<seconds>` is capped at 900, the 15 minutes an approval lives. `--wait=0` or no `--wait` reads the receipt once.
|
|
89
|
+
- `crewx app run` returns the settled receipt, resulting state, and the declared public `result` when one exists. Check `outcome`; if it is `failed` or `rejected`, read `errorCode` to understand the failure. A 202 request returns the pending receipt without a result.
|
|
90
|
+
- Read the receipt again with `crewx app receipt <receiptId>`. The same actor can read the same public `result` repeatedly for 15 minutes after success. After the result expires or the server restarts, the receipt reports `resultExpired: true` without returning the result. An approval that settles as anything but `succeeded` (`rejected`, `expired`, `failed`, `conflicted`) is not done: report the outcome and the `receiptId`.
|
|
56
91
|
- Include the `receiptId` in your report. Citing an ID is not proof; the checker compares the receipt's `actor.task` with this task.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const SDK_APP_SOURCE_ENTRY: string;
|
|
2
|
+
|
|
3
|
+
export interface BuildOptions {
|
|
4
|
+
appDirectory: string;
|
|
5
|
+
check: boolean;
|
|
6
|
+
watch: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AppConfig {
|
|
10
|
+
/** The skill command the actions run; absent for a server.ts-only App (tool mode). */
|
|
11
|
+
command?: string[];
|
|
12
|
+
external: string[];
|
|
13
|
+
/** crewx.app.env; present only when package.json sets it. */
|
|
14
|
+
env?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BuildResult {
|
|
18
|
+
/** Stale generated files relative to the current directory; empty unless `check`. */
|
|
19
|
+
stale: string[];
|
|
20
|
+
/** The same files relative to the app folder; empty unless `check`. */
|
|
21
|
+
staleFiles: string[];
|
|
22
|
+
/** Every generated file, relative to the app folder. */
|
|
23
|
+
files: string[];
|
|
24
|
+
/** The actionId of every `@Tool` method. */
|
|
25
|
+
actions: string[];
|
|
26
|
+
/** `tool` for an App with neither a bin nor crewx.app.command, `bridge` otherwise. */
|
|
27
|
+
mode: 'tool' | 'bridge';
|
|
28
|
+
/** The file `@crewx/sdk/app` resolved to: the SDK source in the repository, the shipped SDK elsewhere. */
|
|
29
|
+
sdkEntry: string | undefined;
|
|
30
|
+
/** Absolute paths of the source files the bundle read (the shipped SDK is not among them). */
|
|
31
|
+
inputs: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function parseArguments(argv: string[], cwd?: string): BuildOptions;
|
|
35
|
+
export function readAppConfig(appDirectory: string): AppConfig;
|
|
36
|
+
export function usesSdkSource(appDirectory: string): boolean;
|
|
37
|
+
export function renderActionDeclarations(
|
|
38
|
+
imported: { default: unknown; generateManifest: (app: unknown, command: string[]) => { actions?: unknown } },
|
|
39
|
+
command: string[],
|
|
40
|
+
existingText?: string,
|
|
41
|
+
backendEnv?: readonly string[],
|
|
42
|
+
): { fileName: string; text: string };
|
|
43
|
+
export function renderToolDeclarations(
|
|
44
|
+
imported: { default: { definition: unknown }; generateToolDeclarations: (app: unknown) => unknown[] },
|
|
45
|
+
existingText?: string,
|
|
46
|
+
): { fileName: string; text: string; toolsFileName: string; toolsText: string };
|
|
47
|
+
export function buildApp(options: {
|
|
48
|
+
appDirectory: string;
|
|
49
|
+
check?: boolean;
|
|
50
|
+
log?: (message: string) => void;
|
|
51
|
+
}): Promise<BuildResult>;
|
|
52
|
+
export function watchApp(
|
|
53
|
+
appDirectory: string,
|
|
54
|
+
log: (message: string) => void,
|
|
55
|
+
options?: { signal?: AbortSignal },
|
|
56
|
+
): Promise<void>;
|
|
57
|
+
export function runScript(
|
|
58
|
+
argv: string[],
|
|
59
|
+
options?: {
|
|
60
|
+
cwd?: string;
|
|
61
|
+
stdout?: { write(text: string): unknown };
|
|
62
|
+
stderr?: { write(text: string): unknown };
|
|
63
|
+
},
|
|
64
|
+
): Promise<number>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{existsSync as x,mkdirSync as z,mkdtempSync as Q,readFileSync as R,realpathSync as W,rmSync as C,watch as Z,writeFileSync as O}from"node:fs";import{createRequire as F}from"node:module";import{tmpdir as ee}from"node:os";import{basename as te,dirname as _,isAbsolute as re,join as c,relative as h,resolve as N,sep as U}from"node:path";import{fileURLToPath as ne,pathToFileURL as T}from"node:url";import{CREWX_META_NAMESPACE as S,convertV2ToManifest as J}from"./manifest.mjs";const D=_(ne(import.meta.url));const oe=F(import.meta.url);const se=c("packages","built-in","app","build-app");const L=N(D,"..","..","..","..");const $=c(L,"packages","sdk","src","app","index.ts");const V=_($);const E="server.ts";const k="dist/server.mjs";const q="dist/entry.mjs";const I="dist/main.mjs";const K="dist/tools.json";const ae=1;const y="manifest.json";const ie=100;const ce=[`import App from './${E}';`,`export * from './${E}';`,"export default App;","export { AppError, CrewXApp, Tool, executeAppRequest, generateManifest, getToolDeclarations, runSkillApp } from '@crewx/sdk/app';",""].join("\n");const le="export { runAppEntry } from '@crewx/sdk/app';\n";const fe=["import { runAppEntry } from '@crewx/sdk/app';","await runAppEntry(() => import('./server.mjs'));",""].join("\n");const ue=[`import App from './${E}';`,"export default App;","export { generateToolDeclarations } from '@crewx/sdk/app';",""].join("\n");const de="export { SKILL_BACKEND_ENV_NAMES, isSkillBackendEnv } from '@crewx/sdk/app';\n";const pe="dist/.env-rule.mjs";const me="import { createRequire as __crewxCreateRequire } from 'node:module'; const require = __crewxCreateRequire(import.meta.url);";const we={compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"Bundler",experimentalDecorators:false,emitDecoratorMetadata:false,useDefineForClassFields:true,lib:["ES2022","esnext.decorators"],allowJs:true,checkJs:false}};function he(e,r,n,o){let s=e.generateManifest(e.default,r);if(o!==void 0&&Array.isArray(s.actions)){s={...s,actions:s.actions.map(a=>({...a,backend:{...a.backend,env:[...o]}}))}}let t;if(n===void 0){t=J(s)}else{try{t=JSON.parse(n)}catch(i){throw new Error(`${y} is not valid JSON: ${i instanceof Error?i.message:String(i)}`)}const a=t?._meta?.[S];if(a===null||typeof a!=="object"||Array.isArray(a)){throw new Error(`${y} must have an object at _meta["${S}"]`)}a.actions=s.actions}return{fileName:y,text:`${JSON.stringify(t,null,2)}
|
|
2
|
+
`}}function xe(e,r){const n=e.generateToolDeclarations(e.default);let o;if(r===void 0){o=J({...e.default.definition})}else{try{o=JSON.parse(r)}catch(t){throw new Error(`${y} is not valid JSON: ${t instanceof Error?t.message:String(t)}`)}const s=o?._meta?.[S];if(s===null||typeof s!=="object"||Array.isArray(s)){throw new Error(`${y} must have an object at _meta["${S}"]`)}delete s.actions}return{fileName:y,text:`${JSON.stringify(o,null,2)}
|
|
3
|
+
`,toolsFileName:K,toolsText:`${JSON.stringify({schemaVersion:ae,actions:n},null,2)}
|
|
4
|
+
`}}function Ee(e,r=process.cwd()){let n;let o=false;let s=false;const t=a=>{if(n!==void 0)throw new Error("Give the app folder only once");n=N(r,a)};for(const a of e){if(a==="--check")o=true;else if(a==="--watch")s=true;else if(a.startsWith("--app-dir="))t(a.slice("--app-dir=".length));else if(a.startsWith("-"))throw new Error(`Unknown argument: ${a}`);else t(a)}if(o&&s)throw new Error("--check and --watch cannot be combined");return{appDirectory:n??r,check:o,watch:s}}function ye(e){const r=c(e,"package.json");if(!x(r))throw new Error(`${r} not found; run the build from an app folder or give the app folder`);const n=JSON.parse(R(r,"utf8"));const o=n.crewx?.app??{};if(Ae(n)){if(o.env!==void 0){throw new Error("crewx.app.env is only for an App with a bin or crewx.app.command; a server.ts-only App always gets its instance and thread as this.context")}const i=o.external??[];if(!Array.isArray(i)||!i.every(d=>typeof d==="string")){throw new Error("crewx.app.external must be an array of globs")}return{external:i}}const s=o.command??ve(n);if(!Array.isArray(s)||s.length===0||!s.every(i=>typeof i==="string"&&i!=="")){throw new Error("crewx.app.command must be a non-empty array of strings")}const t=o.external??[];if(!Array.isArray(t)||!t.every(i=>typeof i==="string")){throw new Error("crewx.app.external must be an array of globs")}const a=o.env;if(a!==void 0&&(!Array.isArray(a)||!a.every(i=>typeof i==="string"))){throw new Error("crewx.app.env must be an array of environment variable names")}return{command:s,external:t,...a===void 0?{}:{env:a}}}function Ae(e){return e.bin===void 0&&e.crewx?.app?.command===void 0}async function ge(e,r,n){let o;try{o=await b(e,r,n,{contents:de,sourcefile:"crewx-app-env-rule.ts",outfile:pe})}catch(t){if(t.errors?.some(({text:a})=>/No matching export/u.test(a))){throw new Error("crewx.app.env needs a newer @crewx/sdk: reinstall the CrewX CLI")}throw t}const s=Q(c(ee(),"crewx-app-env-"));try{const t=c(s,"rule.mjs");O(t,o.text,"utf8");const a=await import(T(t).href);if(!a.isSkillBackendEnv(n.env)){throw new Error(`crewx.app.env must be a non-empty list of distinct names from ${a.SKILL_BACKEND_ENV_NAMES.join(", ")}`)}}finally{C(s,{recursive:true,force:true})}}function ve(e){const r=e.bin;const n=typeof r==="string"?[String(e.name??"").replace(/^@[^/]+\//u,"")]:Object.keys(r??{});if(n.length!==1||n[0]===""){throw new Error('cannot derive the skill command: give package.json exactly one "bin" name, or set crewx.app.command')}return[n[0],"app"]}function Se(e){let r="^";for(let n=0;n<e.length;n+=1){const o=e[n];if(o==="*"&&e[n+1]==="*"){r+=".*";n+=1}else if(o==="*"){r+="[^/]*"}else if(o==="?"){r+="[^/]"}else{r+=o.replace(/[\\^$+{}()[\].|]/g,"\\$&")}}return new RegExp(`${r}$`)}function be(e,r){const n=h(e,r);return n!==""&&!n.startsWith("..")&&!re(n)}function P(e){if(D!==c(L,se))return false;if(!x($))return false;return be(W(L),W(e))}async function Re(e){try{const r=F(c(e,"package.json")).resolve("esbuild");return await import(T(r).href)}catch{}try{return await import("esbuild")}catch{throw new Error("cannot load esbuild: it comes with @crewx/app, so reinstall the CrewX CLI")}}const M="crewx-sdk";const _e="app/index.js";function X(){try{return oe.resolve("@crewx/sdk/app")}catch{return void 0}}function Ne(e,r){const n=r.external.map(Se);const o=P(e);return[{name:"crewx-sdk-app",setup(s){s.onResolve({filter:/^@crewx\/sdk\/app$/},()=>{if(o)return{path:$};const t=X();if(t===void 0){return{errors:[{text:"cannot resolve @crewx/sdk/app: it comes with the CrewX CLI, so reinstall the CLI"}]}}return{path:_e,namespace:M,pluginData:{file:t}}});s.onLoad({filter:/.*/,namespace:M},({pluginData:t})=>({contents:R(t.file,"utf8"),loader:"js",resolveDir:_(t.file)}))}},{name:"crewx-app-external-files",setup(s){if(n.length===0)return;s.onResolve({filter:/^\.\.?\//},({path:t,resolveDir:a})=>{const i=N(a,t);const d=h(e,i).split(U).join("/");if(d.startsWith("..")||!n.some(l=>l.test(d)))return void 0;const p=h(c(e,"dist"),i).split(U).join("/");return{path:p.startsWith(".")?p:`./${p}`,external:true}})}}]}const $e={name:"crewx-app-server-import",setup(e){e.onResolve({filter:/^\.\/server\.mjs$/},({path:r})=>({path:r,external:true}))}};async function b(e,r,n,{contents:o,sourcefile:s,outfile:t,metafile:a=false,plugins:i=[]}){const d=await e.build({...P(r)?{}:{banner:{js:me}},absWorkingDir:r,stdin:{contents:o,resolveDir:r,sourcefile:s,loader:"ts"},outfile:t,bundle:true,write:false,metafile:a,format:"esm",platform:"node",target:"node22",packages:"external",plugins:[...Ne(r,n),...i],tsconfigRaw:we,logLevel:"silent"}).catch(f=>{if(f.errors?.some(({text:w})=>/No matching export in ".*server\.ts" for import "default"/u.test(w))){throw new Error(`${E} must default-export a CrewXApp subclass with a static definition`)}throw f});const p=c(r,t);const l=d.outputFiles.find(f=>f.path===p)?.text;if(l===void 0)throw new Error(`esbuild did not produce ${t}`);if(/@crewx\/sdk(?:['"/])/u.test(l.replace(/^\s*\/\/.*$/gmu,""))){throw new Error(`${t} still imports @crewx/sdk; server.ts may import only @crewx/sdk/app`)}return{text:l,inputs:d.metafile?Object.keys(d.metafile.inputs):[]}}async function Y({appDirectory:e,check:r=false,log:n=()=>{}}){if(!x(c(e,E)))throw new Error(`${c(e,E)} not found`);const o=ye(e);const s=o.command===void 0;const t=await Re(e);if(o.env!==void 0)await ge(t,e,o);const a=c(e,"dist");const i=x(a);const d=c(a,`.server-build-${process.pid}.mjs`);try{const p=await b(t,e,o,{contents:ce,sourcefile:"crewx-app-server.ts",outfile:k,metafile:true});const l=s?await b(t,e,o,{contents:fe,sourcefile:"crewx-app-main.ts",outfile:I,plugins:[$e]}):await b(t,e,o,{contents:le,sourcefile:"crewx-app-entry.ts",outfile:q});const f=s?await ke(t,e,o):p;z(a,{recursive:true});O(d,f.text,"utf8");const w=await import(`${T(d).href}?build=${Date.now()}`);if(typeof w.default!=="function"||w.default.definition===void 0){throw new Error(`${E} must default-export a CrewXApp subclass with a static definition`)}const g=c(e,y);const A=x(g)?R(g,"utf8"):void 0;const m=s?xe(w,A):he(w,o.command,A,o.env);const j=[{path:c(e,k),text:p.text},{path:c(e,s?I:q),text:l.text},...s?[{path:c(e,m.toolsFileName),text:m.toolsText}]:[],{path:c(e,m.fileName),text:m.text}];const B=j.filter(({path:u,text:v})=>!x(u)||R(u,"utf8")!==v);const H=B.map(({path:u})=>h(process.cwd(),u));if(!r){for(const{path:v,text:G}of j)O(v,G,"utf8");const u=v=>h(process.cwd(),c(e,v));n(s?`Built ${u(k)} and ${u(I)}, wrote ${u(K)} and regenerated ${u(m.fileName)}.`:`Built ${h(process.cwd(),c(e,k))} and regenerated ${h(process.cwd(),c(e,m.fileName))}.`)}return{stale:r?H:[],staleFiles:r?B.map(({path:u})=>h(e,u)):[],files:j.map(({path:u})=>h(e,u)),actions:s?Pe(m.toolsText):je(m.text),mode:s?"tool":"bridge",sdkEntry:P(e)?$:X(),inputs:p.inputs.filter(u=>!u.startsWith(`${M}:`)).map(u=>N(e,u))}}finally{C(d,{force:true});if(!i&&r)C(a,{recursive:true,force:true})}}async function ke(e,r,n){try{return await b(e,r,n,{contents:ue,sourcefile:"crewx-app-declarations.ts",outfile:"dist/.declarations-build.mjs"})}catch(o){if(o.errors?.some(({text:s})=>/No matching export/u.test(s)&&/generateToolDeclarations/u.test(s))){throw new Error("a server.ts-only App (no bin, no crewx.app.command) needs a newer @crewx/sdk: reinstall the CrewX CLI")}throw o}}function Pe(e){const r=JSON.parse(e)?.actions;return Array.isArray(r)?r.map(n=>n?.actionId).filter(n=>typeof n==="string"):[]}function je(e){const r=JSON.parse(e)?._meta?.[S]?.actions;return Array.isArray(r)?r.map(n=>n?.actionId).filter(n=>typeof n==="string"):[]}async function Ce(e,r,{signal:n}={}){const o=new Map;let s=false;let t=false;let a;let i=new Set;const d=async()=>{if(s){t=true;return}s=true;try{const l=await Y({appDirectory:e,log:r});i=new Set(l.inputs)}catch(l){r(`Build failed: ${l instanceof Error?l.message:String(l)}`)}finally{s=false;if(!n?.aborted)p();if(t&&!n?.aborted){t=false;void d()}}};const p=()=>{const l=new Set([e,...[...i].map(f=>_(f))]);if(P(e))l.add(V);for(const[f,w]of o){if(!l.has(f)){w.close();o.delete(f)}}for(const f of l){if(o.has(f)||!x(f))continue;o.set(f,Z(f,(w,g)=>{if(g===null)return;const A=c(f,g.toString());const m=i.has(A)||A===c(e,"package.json")||f===V&&/\.tsx?$/u.test(A);if(!m)return;clearTimeout(a);a=setTimeout(()=>void d(),ie)}))}};await d();r("Watching for changes. Press Ctrl+C to stop.");await new Promise(l=>{if(n===void 0)return;if(n.aborted)l();else n.addEventListener("abort",l,{once:true})});clearTimeout(a);for(const l of o.values())l.close();o.clear()}async function We(e,{cwd:r=process.cwd(),stdout:n=process.stdout,stderr:o=process.stderr}={}){const s=t=>n.write(`${t}
|
|
5
|
+
`);try{const t=Ee(e,r);if(t.watch){await Ce(t.appDirectory,s);return 0}const{stale:a}=await Y({appDirectory:t.appDirectory,check:t.check,log:s});if(!t.check)return 0;if(a.length>0){o.write(`Stale generated files: ${a.join(", ")}
|
|
6
|
+
`);return 1}s(`${te(t.appDirectory)} App build artifacts are in sync.`);return 0}catch(t){o.write(`${t instanceof Error?t.message:String(t)}
|
|
7
|
+
`);return 1}}export{$ as SDK_APP_SOURCE_ENTRY,Y as buildApp,Ee as parseArguments,ye as readAppConfig,he as renderActionDeclarations,xe as renderToolDeclarations,We as runScript,P as usesSdkSource,Ce as watchApp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var l=Object.create;var i=Object.defineProperty;var t=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var m=Object.getPrototypeOf;var s=Object.prototype.hasOwnProperty;var u=(p,o,r,e)=>{if(o&&typeof o==="object"||typeof o==="function"){for(let d of a(o))if(!s.call(p,d)&&d!==r)i(p,d,{get:()=>o[d],enumerable:!(e=t(o,d))||e.enumerable})}return p};var x=(p,o,r)=>(r=p!=null?l(m(p)):{},u(o||!p||!p.__esModule?i(r,"default",{value:p,enumerable:true}):r,p));module.exports={loadAppBuilder:()=>import("./index.mjs")};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a="0.3";const t="dev.crewx";const f="0.1.0";const d=new Set(["manifestVersion","pluginId","definitionId","version","name","description","_meta"]);function o(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function u(e,r={}){if(!o(e)||e.manifestVersion!==2){throw new Error("only a manifestVersion 2 ui-plugin.json can be converted")}for(const n of["pluginId","definitionId","name","description"]){if(typeof e[n]!=="string")throw new Error(`ui-plugin.json ${n} must be a string`)}if(!Number.isSafeInteger(e.version)||e.version<1){throw new Error("ui-plugin.json version must be a positive integer")}if(e._meta!==void 0&&!o(e._meta)){throw new Error("ui-plugin.json _meta must be an object")}if(e._meta!==void 0&&Object.hasOwn(e._meta,t)){throw new Error(`ui-plugin.json _meta already uses the "${t}" key`)}const i={};if(e.definitionId!==e.pluginId)i.definitionId=e.definitionId;i.definitionVersion=e.version;for(const[n,s]of Object.entries(e)){if(!d.has(n))i[n]=s}return{manifest_version:a,name:e.pluginId,display_name:e.name,version:r.version??f,description:e.description,_meta:{...e._meta??{},[t]:i}}}export{t as CREWX_META_NAMESPACE,f as INITIAL_RELEASE_VERSION,a as MCPB_MANIFEST_VERSION,u as convertV2ToManifest};
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,92 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.main = main;
|
|
38
|
-
const fs = __importStar(require("node:fs"));
|
|
39
|
-
const path = __importStar(require("node:path"));
|
|
40
|
-
const engine_js_1 = require("./src/engine.js");
|
|
41
|
-
function emitOutcome(outcome) {
|
|
42
|
-
const ticket = process.env['CREWX_TASK_TICKET'] ?? '';
|
|
43
|
-
const output = outcome.stdout
|
|
44
|
-
?? (outcome.suppressOutput ? '' : `${JSON.stringify(outcome.payload)}\n`);
|
|
45
|
-
const redactedOutput = ticket ? output.split(ticket).join('[redacted]') : output;
|
|
46
|
-
if (redactedOutput)
|
|
47
|
-
process.stdout.write(redactedOutput);
|
|
48
|
-
if (outcome.stderr) {
|
|
49
|
-
const stderr = ticket ? outcome.stderr.split(ticket).join('[redacted]') : outcome.stderr;
|
|
50
|
-
process.stderr.write(`${stderr}\n`);
|
|
51
|
-
}
|
|
52
|
-
if (outcome.exitCode !== 0)
|
|
53
|
-
process.exitCode = outcome.exitCode;
|
|
54
|
-
}
|
|
55
|
-
function printSkill() {
|
|
56
|
-
const candidates = [
|
|
57
|
-
path.join(__dirname, '..', 'SKILL.md'),
|
|
58
|
-
path.join(__dirname, 'SKILL.md'),
|
|
59
|
-
];
|
|
60
|
-
const skillPath = candidates.find((candidate) => fs.existsSync(candidate));
|
|
61
|
-
if (!skillPath)
|
|
62
|
-
throw new Error('SKILL.md is not available');
|
|
63
|
-
process.stdout.write(fs.readFileSync(skillPath, 'utf8'));
|
|
64
|
-
}
|
|
65
|
-
async function main(inputArgs) {
|
|
66
|
-
const args = inputArgs ?? process.argv.slice(2);
|
|
67
|
-
if (args[0] === 'usage' && args.length === 1) {
|
|
68
|
-
try {
|
|
69
|
-
printSkill();
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
emitOutcome({
|
|
73
|
-
exitCode: 1,
|
|
74
|
-
payload: { ok: false, exit: 1, error: { code: 'ERROR', status: null } },
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
emitOutcome(await (0, engine_js_1.executeAppCommand)(args));
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
emitOutcome({
|
|
84
|
-
exitCode: 1,
|
|
85
|
-
payload: { ok: false, exit: 1, error: { code: 'ERROR', status: null } },
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (require.main === module) {
|
|
90
|
-
void main();
|
|
91
|
-
}
|
|
92
|
-
//# sourceMappingURL=cli.js.map
|
|
2
|
+
"use strict";var d=exports&&exports.__createBinding||(Object.create?(function(t,e,r,n){if(n===void 0)n=r;var i=Object.getOwnPropertyDescriptor(e,r);if(!i||("get"in i?!e.__esModule:i.writable||i.configurable)){i={enumerable:true,get:function(){return e[r]}}}Object.defineProperty(t,n,i)}):(function(t,e,r,n){if(n===void 0)n=r;t[n]=e[r]}));var f=exports&&exports.__setModuleDefault||(Object.create?(function(t,e){Object.defineProperty(t,"default",{enumerable:true,value:e})}):function(t,e){t["default"]=e});var a=exports&&exports.__importStar||(function(){var t=function(e){t=Object.getOwnPropertyNames||function(r){var n=[];for(var i in r)if(Object.prototype.hasOwnProperty.call(r,i))n[n.length]=i;return n};return t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null){for(var n=t(e),i=0;i<n.length;i++)if(n[i]!=="default")d(r,e,n[i])}f(r,e);return r}})();Object.defineProperty(exports,"__esModule",{value:true});exports.main=o;const u=a(require("node:fs"));const c=a(require("node:path"));const l=require("./src/engine.js");function s(t){const e=process.env["CREWX_TASK_TICKET"]??"";const r=t.stdout??(t.suppressOutput?"":`${JSON.stringify(t.payload)}
|
|
3
|
+
`);const n=e?r.split(e).join("[redacted]"):r;if(n)process.stdout.write(n);if(t.stderr){const i=e?t.stderr.split(e).join("[redacted]"):t.stderr;process.stderr.write(`${i}
|
|
4
|
+
`)}if(t.exitCode!==0)process.exitCode=t.exitCode}function _(){const t=[c.join(__dirname,"..","SKILL.md"),c.join(__dirname,"SKILL.md")];const e=t.find(r=>u.existsSync(r));if(!e)throw new Error("SKILL.md is not available");process.stdout.write(u.readFileSync(e,"utf8"))}async function o(t){const e=t??process.argv.slice(2);if(e[0]==="usage"&&e.length===1){try{_()}catch{s({exitCode:1,payload:{ok:false,exit:1,error:{code:"ERROR",status:null}}})}return}try{s(await(0,l.executeAppCommand)(e))}catch{s({exitCode:1,payload:{ok:false,exit:1,error:{code:"ERROR",status:null}}})}}if(require.main===module){void o()}
|
package/dist/src/engine.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WaitClock } from './receipt-wait.js';
|
|
1
2
|
export interface AppCommandOutcome {
|
|
2
3
|
exitCode: number;
|
|
3
4
|
payload: Record<string, unknown>;
|
|
@@ -5,5 +6,7 @@ export interface AppCommandOutcome {
|
|
|
5
6
|
stdout?: string;
|
|
6
7
|
suppressOutput?: boolean;
|
|
7
8
|
}
|
|
8
|
-
export declare function executeAppCommand(args: string[]
|
|
9
|
-
|
|
9
|
+
export declare function executeAppCommand(args: string[], { clock }?: {
|
|
10
|
+
clock?: WaitClock;
|
|
11
|
+
}): Promise<AppCommandOutcome>;
|
|
12
|
+
|