🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@limecloud/agent-app-studio

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@limecloud/agent-app-studio - npm Package Compare versions

Comparing version
0.1.0
to
0.1.1
+22
app.capabilities.yaml
# Lime Agent App Studio 能力声明。
capabilities:
lime.ui:
features:
- pages
- developer-console
routes:
- path: /dashboard
component: ./dist/ui/index.html
title: 发布工作台
- path: /cli
component: ./dist/ui/index.html
title: CLI 快速开始
lime.files:
required: false
reason: 选择本地 Agent App 目录用于诊断和打包。
lime.agent:
required: false
reason: 后续版本用于生成发布前检查和修复建议。
lime.evidence:
required: false
reason: 记录 dry-run 与发布审计证据。
---
manifestVersion: 0.7.0
name: lime-agent-app-studio
displayName: Lime Agent App Studio
version: 0.1.1
status: preview
appType: developer-tool
description: 面向已认证开发者的 Agent App 可视化发布工作台和 npm CLI 入口。
runtimeTargets:
- local
requires:
lime:
appRuntime: ">=0.7.0 <1.0.0"
sdk: "@lime/app-sdk@^0.7.0"
capabilities:
- lime.ui
- lime.files
- lime.agent
- lime.evidence
categories:
- developer
- developer_only
- tools
publisher:
publisherId: lime-cloud
name: Lime Cloud
displayName: Lime Cloud
kind: platform
verified: true
distribution:
channel: developer-preview
visibility: developer_only
pricing: included
billingModel: none
runtimePackage:
ui:
path: ./dist/ui
entries:
- key: dashboard
kind: page
title: 发布工作台
route: /dashboard
- key: cli_quickstart
kind: page
title: CLI 快速开始
route: /cli
quickstart:
entry: dashboard
setupSteps:
- complete_developer_certification
- install_npm_cli
- run_publish_dry_run
support:
url: ./docs/v1/README.md
license: Apache-2.0
---
# Lime Agent App Studio
Lime Agent App Studio 是开发者工具入口。已认证开发者可以在 Lime 应用中心安装它,并通过可视化工作台或 npm CLI 将 Agent App 打包、Dry-run 和发布到 LimeCore 云端。
## CLI
```bash
npm install -g @limecloud/agent-app-studio
lime-agent-app-studio auth status --tenant-id tenant-0001
lime-agent-app-studio publish --app-dir ./my-agent-app --channel beta --dry-run
```
const fields = ["appDir", "appId", "tenantId", "apiBase", "token", "channel"];
const output = document.querySelector("#output");
const statusEl = document.querySelector("#status");
const defaultApiBase = "https://lime-api.limeai.run/api";
function values() {
return Object.fromEntries(fields.map((id) => [id, document.querySelector(`#${id}`).value.trim()]).filter(([, value]) => value));
}
async function post(path, body) {
statusEl.textContent = "执行中";
output.textContent = "请稍候...";
try {
const response = await fetch(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const payload = await response.json();
statusEl.textContent = response.ok ? "完成" : "失败";
output.textContent = JSON.stringify(payload, null, 2);
} catch (error) {
statusEl.textContent = "需要本地服务";
output.textContent = [
"当前页面没有连接到本地 Studio 服务。",
"",
"如果你是在 Lime 应用中心里打开:",
"1. 先安装 CLI:npm install -g @limecloud/agent-app-studio",
"2. 启动本地可视化服务:lime-agent-app-studio studio --port 4177",
`3. 默认 API Base:${defaultApiBase}`,
"",
`错误:${error?.message || String(error)}`,
].join("\n");
}
}
document.querySelector("#apiBase").placeholder = defaultApiBase;
document.querySelector("#inspectBtn").addEventListener("click", () => post("/api/inspect", values()));
document.querySelector("#dryRunBtn").addEventListener("click", () => post("/api/publish", { ...values(), dryRun: true }));
document.querySelector("#publishBtn").addEventListener("click", () => {
if (!confirm("正式发布会写入 LimeCore 云端 Release,确认继续?")) return;
post("/api/publish", { ...values(), publish: true });
});
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lime Agent App Studio</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<main class="shell">
<section class="hero">
<p class="eyebrow">Lime Developer Tool</p>
<h1>Agent App 发布工作台</h1>
<p>诊断本地 Agent App,预演发布计划,并在通过开发者认证后上传到 LimeCore 云端 Release。</p>
</section>
<section class="panel form-panel">
<label>App 目录 <input id="appDir" value="." /></label>
<label>App ID <input id="appId" placeholder="自动识别或手动填写" /></label>
<label>Tenant ID <input id="tenantId" placeholder="tenant-0001" /></label>
<label>API Base <input id="apiBase" placeholder="https://lime-api.limeai.run/api" /></label>
<label>Token <input id="token" type="password" placeholder="开发者 token" /></label>
<label>Channel <input id="channel" value="beta" /></label>
<div class="actions">
<button id="inspectBtn">诊断项目</button>
<button id="dryRunBtn">Dry-run</button>
<button id="publishBtn" class="danger">正式发布</button>
</div>
</section>
<section class="panel">
<div class="panel-head">
<h2>结果</h2>
<span id="status">等待操作</span>
</div>
<pre id="output">选择本地 Agent App 目录后开始。</pre>
</section>
</main>
<script type="module" src="/app.js"></script>
</body>
</html>
:root {
--ink: #112036;
--muted: #66745f;
--card: rgba(255, 252, 244, 0.88);
--line: #dfd5bf;
--green: #2f8a54;
--blue: #14265c;
--danger: #9a4a27;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
color: var(--ink);
font-family: Charter, Georgia, "Times New Roman", serif;
background:
radial-gradient(circle at 10% 10%, rgba(47, 138, 84, 0.20), transparent 28rem),
radial-gradient(circle at 90% 20%, rgba(20, 38, 92, 0.16), transparent 32rem),
linear-gradient(135deg, #fffaf0, #f4efe1 60%, #e9efdc);
}
.shell {
width: min(1120px, calc(100vw - 40px));
margin: 0 auto;
padding: 48px 0;
}
.hero {
display: grid;
gap: 12px;
max-width: 760px;
margin-bottom: 28px;
}
.eyebrow {
margin: 0;
color: var(--green);
font-weight: 800;
letter-spacing: 0.12em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(40px, 7vw, 88px);
line-height: 0.92;
}
.hero p:last-child {
margin: 0;
color: var(--muted);
font-size: 20px;
line-height: 1.6;
}
.panel {
margin-top: 18px;
padding: 24px;
border: 1px solid var(--line);
border-radius: 28px;
background: var(--card);
box-shadow: 0 22px 70px rgba(60, 49, 25, 0.14);
backdrop-filter: blur(18px);
}
.form-panel {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
label {
display: grid;
gap: 8px;
color: var(--muted);
font-weight: 700;
}
input {
width: 100%;
border: 1px solid var(--line);
border-radius: 16px;
padding: 13px 14px;
color: var(--ink);
background: rgba(255, 255, 255, 0.72);
font: 600 15px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.actions {
grid-column: 1 / -1;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
button {
border: 0;
border-radius: 999px;
padding: 13px 22px;
color: white;
background: var(--blue);
font-weight: 800;
cursor: pointer;
}
button:nth-child(1) { background: var(--green); }
button.danger { background: var(--danger); }
.panel-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
}
h2 { margin: 0; }
#status {
border: 1px solid var(--line);
border-radius: 999px;
padding: 8px 12px;
color: var(--muted);
background: rgba(255,255,255,0.72);
font-weight: 800;
}
pre {
overflow: auto;
min-height: 280px;
margin: 18px 0 0;
padding: 18px;
border-radius: 20px;
background: #101827;
color: #e9f2dc;
font-size: 13px;
line-height: 1.6;
}
@media (max-width: 760px) {
.form-panel { grid-template-columns: 1fr; }
}

Sorry, the diff of this file is not supported yet

import { cp, mkdir, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
const root = fileURLToPath(new URL("..", import.meta.url));
const distUi = join(root, "dist", "ui");
await rm(distUi, { recursive: true, force: true });
await mkdir(join(distUi, "vendor"), { recursive: true });
for (const file of ["index.html", "app.js", "styles.css"]) {
await cp(join(root, "app", file), join(distUi, file));
}
await writeFile(join(distUi, "vendor", ".gitkeep"), "");
console.log(`已生成 Agent App UI: ${distUi}`);
+24
-8
const fields = ["appDir", "appId", "tenantId", "apiBase", "token", "channel"];
const output = document.querySelector("#output");
const statusEl = document.querySelector("#status");
const defaultApiBase = "https://lime-api.limeai.run/api";

@@ -12,12 +13,27 @@ function values() {

output.textContent = "请稍候...";
const response = await fetch(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const payload = await response.json();
statusEl.textContent = response.ok ? "完成" : "失败";
output.textContent = JSON.stringify(payload, null, 2);
try {
const response = await fetch(path, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const payload = await response.json();
statusEl.textContent = response.ok ? "完成" : "失败";
output.textContent = JSON.stringify(payload, null, 2);
} catch (error) {
statusEl.textContent = "需要本地服务";
output.textContent = [
"当前页面没有连接到本地 Studio 服务。",
"",
"如果你是在 Lime 应用中心里打开:",
"1. 先安装 CLI:npm install -g @limecloud/agent-app-studio",
"2. 启动本地可视化服务:lime-agent-app-studio studio --port 4177",
`3. 默认 API Base:${defaultApiBase}`,
"",
`错误:${error?.message || String(error)}`,
].join("\n");
}
}
document.querySelector("#apiBase").placeholder = defaultApiBase;
document.querySelector("#inspectBtn").addEventListener("click", () => post("/api/inspect", values()));

@@ -24,0 +40,0 @@ document.querySelector("#dryRunBtn").addEventListener("click", () => post("/api/publish", { ...values(), dryRun: true }));

@@ -21,3 +21,3 @@ <!doctype html>

<label>Tenant ID <input id="tenantId" placeholder="tenant-0001" /></label>
<label>API Base <input id="apiBase" placeholder="https://api.limecloud.run/api" /></label>
<label>API Base <input id="apiBase" placeholder="https://lime-api.limeai.run/api" /></label>
<label>Token <input id="token" type="password" placeholder="开发者 token" /></label>

@@ -24,0 +24,0 @@ <label>Channel <input id="channel" value="beta" /></label>

{
"name": "@limecloud/agent-app-studio",
"version": "0.1.0",
"version": "0.1.1",
"description": "Lime Agent App Studio CLI and visual publisher for Agent App packages.",

@@ -16,2 +16,6 @@ "type": "module",

"app",
"dist",
"scripts",
"APP.md",
"app.capabilities.yaml",
"docs/v1/README.md",

@@ -22,2 +26,4 @@ "LICENSE",

"scripts": {
"build": "node scripts/build-agent-app.mjs",
"validate:app": "node src/cli.mjs project inspect --app-dir .",
"test": "node --test tests/*.test.mjs",

@@ -24,0 +30,0 @@ "pack:dry-run": "npm pack --dry-run",

@@ -20,3 +20,3 @@ # Lime Agent App Studio

- `LIMECORE_API_BASE_URL`:LimeCore API base,默认 `https://api.limecloud.run/api`
- `LIMECORE_API_BASE_URL`:LimeCore API base,默认 `https://lime-api.limeai.run/api`
- `LIME_AGENT_APP_STUDIO_TOKEN`:开发者登录 token,CI/CD 推荐使用

@@ -10,7 +10,6 @@ // input: 环境变量、本机配置文件与 CLI 显式参数

const defaultConfigPath = join(defaultConfigDir, "config.json");
const defaultApiBase = "https://lime-api.limeai.run/api";
export function resolveApiBase(options = {}) {
return trimTrailingSlash(
options.apiBase || process.env.LIMECORE_API_BASE_URL || "https://api.limecloud.run/api"
);
return trimTrailingSlash(options.apiBase || process.env.LIMECORE_API_BASE_URL || defaultApiBase);
}

@@ -17,0 +16,0 @@

@@ -16,3 +16,3 @@ // input: 本地 HTTP 请求

export async function startStudioServer(options = {}) {
const port = Number(options.port || 4177);
const port = Number(options.port ?? 4177);
const server = createServer(async (req, res) => {

@@ -35,3 +35,5 @@ try {

await new Promise((resolve) => server.listen(port, resolve));
return { server, url: `http://127.0.0.1:${port}` };
const address = server.address();
const actualPort = typeof address === "object" && address ? address.port : port;
return { server, url: `http://127.0.0.1:${actualPort}` };
}

@@ -41,5 +43,14 @@

const pathname = req.url === "/" ? "/index.html" : req.url.split("?")[0];
if (pathname === "/favicon.ico") return sendNoContent(res);
const safePath = pathname.replace(/\.\./g, "");
const filePath = join(appRoot, safePath);
const content = await readFile(filePath);
let content;
try {
content = await readFile(filePath);
} catch (error) {
if (error?.code === "ENOENT" || error?.code === "EISDIR") {
return sendNotFound(res);
}
throw error;
}
const type = contentType(filePath);

@@ -62,2 +73,12 @@ res.writeHead(200, { "Content-Type": type });

function sendNoContent(res) {
res.writeHead(204, { "Content-Length": "0" });
res.end();
}
function sendNotFound(res) {
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
res.end("Not Found");
}
function contentType(path) {

@@ -64,0 +85,0 @@ switch (extname(path)) {