oasis_test 0.1.7 → 0.1.8

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/dist/index.js +177 -32
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16056,6 +16056,26 @@ function parsePlanningIssues(body) {
16056
16056
  return [];
16057
16057
  }
16058
16058
  }
16059
+ function workorderSpecBody(spec) {
16060
+ return WORKORDER_SPEC_PREFIX + JSON.stringify(spec);
16061
+ }
16062
+ function parseWorkorderSpec(body) {
16063
+ const first = body.split("\n", 1)[0] ?? "";
16064
+ if (!first.startsWith(WORKORDER_SPEC_PREFIX)) return null;
16065
+ try {
16066
+ const parsed = JSON.parse(first.slice(WORKORDER_SPEC_PREFIX.length));
16067
+ if (!parsed || typeof parsed !== "object") return null;
16068
+ const out = {};
16069
+ if (typeof parsed.goal === "string" && parsed.goal.trim()) out.goal = parsed.goal.trim();
16070
+ if (Array.isArray(parsed.acceptanceCriteria)) {
16071
+ const items = parsed.acceptanceCriteria.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
16072
+ if (items.length > 0) out.acceptanceCriteria = items;
16073
+ }
16074
+ return out.goal !== void 0 || out.acceptanceCriteria !== void 0 ? out : null;
16075
+ } catch {
16076
+ return null;
16077
+ }
16078
+ }
16059
16079
  async function buildDynamicWorkorderPlan(input) {
16060
16080
  const schema = input.schema;
16061
16081
  const schemaByName = new Map(schema.map((def) => [def.name, def]));
@@ -16088,6 +16108,15 @@ async function buildDynamicWorkorderPlan(input) {
16088
16108
  description: input.description
16089
16109
  }
16090
16110
  ];
16111
+ const spec = {};
16112
+ if (input.goal?.trim()) spec.goal = input.goal.trim();
16113
+ if (input.acceptanceCriteria?.length) {
16114
+ const items = input.acceptanceCriteria.map((x) => x.trim()).filter(Boolean);
16115
+ if (items.length > 0) spec.acceptanceCriteria = items;
16116
+ }
16117
+ if (spec.goal !== void 0 || spec.acceptanceCriteria !== void 0) {
16118
+ ops.push({ action: "annotate", artifactId: briefId, body: workorderSpecBody(spec), blocks: false });
16119
+ }
16091
16120
  const spawned = [{ id: briefId, type: "brief", role: "pm", owner: briefOwner }];
16092
16121
  const agentGraph = input.agentPlan ? normalizeAgentPlan(input.agentPlan, primaryType) : null;
16093
16122
  if (input.agentPlan && !agentGraph) {
@@ -16420,7 +16449,7 @@ function artifactSlug(idOrWorkspace, description) {
16420
16449
  const slug4 = source.trim().toLowerCase().replace(/^wo-/, "").replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
16421
16450
  return slug4 || `wo-${Date.now()}`;
16422
16451
  }
16423
- var PLANNING_ISSUES_PREFIX, DEFAULT_TYPES;
16452
+ var PLANNING_ISSUES_PREFIX, DEFAULT_TYPES, WORKORDER_SPEC_PREFIX;
16424
16453
  var init_planner = __esm({
16425
16454
  "../server/src/domains/collab/planner.ts"() {
16426
16455
  "use strict";
@@ -16434,6 +16463,7 @@ var init_planner = __esm({
16434
16463
  { name: "dev", contentType: "code", ownerRole: "dev" },
16435
16464
  { name: "qa", contentType: "text", ownerRole: "qa" }
16436
16465
  ];
16466
+ WORKORDER_SPEC_PREFIX = "OASIS_WORKORDER_SPEC ";
16437
16467
  }
16438
16468
  });
16439
16469
 
@@ -16710,6 +16740,13 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
16710
16740
  if ((createdViaArg ?? "manual-spawn") === "seed" && ctx.schema) {
16711
16741
  const title = optStr(args, "title") ?? optStr(args, "description") ?? workspace;
16712
16742
  const brief = optStr(args, "brief") ?? optStr(args, "body") ?? optStr(args, "description") ?? workspace;
16743
+ const goal = optStr(args, "goal");
16744
+ const rawCriteria = args["acceptance_criteria"] ?? args["acceptanceCriteria"];
16745
+ const acceptanceCriteria = Array.isArray(rawCriteria) ? rawCriteria.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim()) : void 0;
16746
+ const briefForPlanner = acceptanceCriteria && acceptanceCriteria.length > 0 ? `${brief}
16747
+
16748
+ \u9A8C\u6536\u6807\u51C6\uFF1A
16749
+ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}` : brief;
16713
16750
  const agentIssues = [];
16714
16751
  let agentPlan = null;
16715
16752
  if (ctx.planWorkorderWithAgent) {
@@ -16724,7 +16761,7 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
16724
16761
  requestedType: type,
16725
16762
  workspace,
16726
16763
  title,
16727
- brief,
16764
+ brief: briefForPlanner,
16728
16765
  artifactTypes: ctx.schema.map((def) => ({
16729
16766
  name: def.name,
16730
16767
  ownerRole: def.ownerRole,
@@ -16757,6 +16794,8 @@ async function runCommand(kernel, blobs, oplog, actor, command, args, ctx = {})
16757
16794
  workspace,
16758
16795
  title,
16759
16796
  description: brief,
16797
+ ...goal !== void 0 ? { goal } : {},
16798
+ ...acceptanceCriteria && acceptanceCriteria.length > 0 ? { acceptanceCriteria } : {},
16760
16799
  actor,
16761
16800
  schema: ctx.schema,
16762
16801
  ...optStr(args, "id") !== void 0 ? { requestedArtifactId: optStr(args, "id") } : {},
@@ -18041,6 +18080,29 @@ function createNodeTokenStore(file) {
18041
18080
  }
18042
18081
  };
18043
18082
  }
18083
+ function createEnrollTokenStore(defaultTtlMs = 30 * 60 * 1e3) {
18084
+ const table = /* @__PURE__ */ new Map();
18085
+ const prune = () => {
18086
+ const now = Date.now();
18087
+ for (const [token, entry] of table) if (entry.expiresAt <= now) table.delete(token);
18088
+ };
18089
+ return {
18090
+ issue(nodeId, ttlMs = defaultTtlMs) {
18091
+ prune();
18092
+ const token = `ent_${(0, import_node_crypto3.randomBytes)(24).toString("base64url")}`;
18093
+ const expiresAt = Date.now() + ttlMs;
18094
+ table.set(token, { nodeId, expiresAt });
18095
+ return { token, expiresAt: new Date(expiresAt).toISOString() };
18096
+ },
18097
+ consume(token) {
18098
+ prune();
18099
+ const entry = table.get(token);
18100
+ if (!entry) return null;
18101
+ table.delete(token);
18102
+ return { nodeId: entry.nodeId };
18103
+ }
18104
+ };
18105
+ }
18044
18106
  var import_node_crypto3, fs;
18045
18107
  var init_tokens = __esm({
18046
18108
  "../server/src/tokens.ts"() {
@@ -19074,6 +19136,7 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject) {
19074
19136
  for (const [workspace, arts] of byWorkspace) {
19075
19137
  const members = arts.map((a) => nodeInfo(model, a));
19076
19138
  const seed = arts.find((a) => a.inputs.length === 0) ?? arts[0];
19139
+ const spec = workorderSpecOf(model, arts);
19077
19140
  const concluded = members.filter((m) => m.concluded).length;
19078
19141
  const lastOps = arts.map((a) => model.lastOpAt.get(a.id)).filter((t) => Boolean(t)).sort();
19079
19142
  const participantIds = [...new Set(arts.map((a) => a.owner))];
@@ -19081,8 +19144,10 @@ function buildWorkorderSummaries(model, resolveActor, resolveProject) {
19081
19144
  out.push({
19082
19145
  id: workspace,
19083
19146
  title: woTitle,
19084
- // 工单目标 = 根产物 description;仅当与标题不同(根产物有独立 title)时给出,避免与 title 重复
19147
+ // 工单完整内容 = 根产物 description;仅当与标题不同(根产物有独立 title)时给出,避免与 title 重复
19085
19148
  ...seed.description && seed.description !== woTitle ? { description: seed.description } : {},
19149
+ // 一句话目标:优先取结构化 spec.goal;缺省时前端回退 description 首段(投影不替前端做回退,只给真值)
19150
+ ...spec?.goal ? { goal: spec.goal } : {},
19086
19151
  stage: deriveStage(members),
19087
19152
  owner: ref(seed.owner),
19088
19153
  participants: participantIds.map(ref),
@@ -19100,6 +19165,16 @@ function planningStatusOf(model, arts) {
19100
19165
  const issues = [...model.annotations.values()].filter((annotation) => ids.has(annotation.anchor.target)).flatMap((annotation) => parsePlanningIssues(annotation.body));
19101
19166
  return issues.length > 0 ? { status: "needs_staffing", issues } : { status: "ready", issues: [] };
19102
19167
  }
19168
+ function workorderSpecOf(model, arts) {
19169
+ const seed = arts.find((a) => a.type === "brief") ?? arts.find((a) => a.inputs.length === 0);
19170
+ if (!seed) return null;
19171
+ for (const annotation of model.annotations.values()) {
19172
+ if (annotation.anchor.target !== seed.id) continue;
19173
+ const spec = parseWorkorderSpec(annotation.body);
19174
+ if (spec) return spec;
19175
+ }
19176
+ return null;
19177
+ }
19103
19178
  var init_workorders = __esm({
19104
19179
  "../server/src/domains/collab/workorders.ts"() {
19105
19180
  "use strict";
@@ -27438,7 +27513,7 @@ Write-Host " Remove : Unregister-ScheduledTask -TaskName 'OasisNode-${p.nodeId}
27438
27513
  }
27439
27514
  function buildNpxCmd(p) {
27440
27515
  const namePart = p.nodeName ? ` --name ${p.nodeName}` : "";
27441
- return `npx -y oasis_test@latest start --server-ws ${p.gatewayUrl} --id ${p.nodeId}${namePart} --token ${p.token}`;
27516
+ return `npx -y oasis_test@latest start --server-ws ${p.gatewayUrl} --id ${p.nodeId}${namePart} --enroll-token ${p.token}`;
27442
27517
  }
27443
27518
  var init_connect_script = __esm({
27444
27519
  "../server/src/domains/nodes/connect-script.ts"() {
@@ -27489,9 +27564,39 @@ function nodesDomain(deps) {
27489
27564
  };
27490
27565
  }
27491
27566
  return (router) => {
27567
+ async function syncConnectedDaemon(daemonId, hub) {
27568
+ const d = hub.connectedDaemons().find((c) => c.daemonId === daemonId);
27569
+ if (!d) return;
27570
+ const existing = await deps.nodeStore.getNode(daemonId);
27571
+ await deps.nodeStore.upsertNode({
27572
+ id: daemonId,
27573
+ hostname: d.meta.hostname,
27574
+ nodeVersion: d.meta.nodeVersion ?? "unknown",
27575
+ status: "online",
27576
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString(),
27577
+ enrolledAt: existing?.enrolledAt ?? (/* @__PURE__ */ new Date()).toISOString(),
27578
+ ...d.meta.nodeName ? { name: d.meta.nodeName } : existing?.name ? { name: existing.name } : {}
27579
+ });
27580
+ const capabilities = d.meta.runtimes?.length ? d.meta.runtimes : d.meta.adapters.map((kind) => ({ kind, binary: void 0, version: void 0 }));
27581
+ for (const cap of capabilities) {
27582
+ await deps.nodeStore.upsertRuntime({
27583
+ id: `runtime:${daemonId}:${cap.kind}`,
27584
+ nodeId: daemonId,
27585
+ kind: cap.kind,
27586
+ hostname: d.meta.hostname,
27587
+ ...cap.binary ? { binary: cap.binary } : {},
27588
+ ...cap.version ? { version: cap.version } : {},
27589
+ status: "online",
27590
+ lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
27591
+ });
27592
+ }
27593
+ }
27492
27594
  router.get("/api/nodes", async () => {
27493
27595
  const hub = deps.getHub();
27494
- if (hub) wireHub(hub);
27596
+ if (hub) {
27597
+ wireHub(hub);
27598
+ await Promise.all(hub.connectedDaemons().map((d) => syncConnectedDaemon(d.daemonId, hub)));
27599
+ }
27495
27600
  const nodes = await deps.nodeStore.listNodes();
27496
27601
  const items = await Promise.all(nodes.map(async (n) => {
27497
27602
  const rts = await deps.nodeStore.listRuntimes(n.id);
@@ -27518,7 +27623,10 @@ function nodesDomain(deps) {
27518
27623
  });
27519
27624
  router.get("/api/runtimes", async () => {
27520
27625
  const hub = deps.getHub();
27521
- if (hub) wireHub(hub);
27626
+ if (hub) {
27627
+ wireHub(hub);
27628
+ await Promise.all(hub.connectedDaemons().map((d) => syncConnectedDaemon(d.daemonId, hub)));
27629
+ }
27522
27630
  const rts = await deps.nodeStore.listRuntimes();
27523
27631
  const items = rts.map((r) => ({
27524
27632
  id: r.id,
@@ -27535,28 +27643,28 @@ function nodesDomain(deps) {
27535
27643
  });
27536
27644
  router.post("/api/nodes/enroll", async (req) => {
27537
27645
  const body = req.body ?? {};
27538
- const requested = body.nodeId?.trim();
27539
- const nodeId = requested || `node-${(0, import_node_crypto9.randomUUID)().slice(0, 8)}`;
27540
27646
  const nodeName = body.nodeName?.trim() || void 0;
27541
- for (const { token: token2, nodeId: existing2 } of deps.nodeTokens.list()) {
27542
- if (existing2 === nodeId) deps.nodeTokens.revoke(token2);
27543
- }
27544
- const token = deps.nodeTokens.issue(nodeId);
27647
+ const requestedId = body.nodeId?.trim();
27648
+ const existing = requestedId ? await deps.nodeStore.getNode(requestedId) : null;
27649
+ const nodeId = existing?.id ?? requestedId ?? `node-${(0, import_node_crypto9.randomUUID)().slice(0, 8)}`;
27650
+ const enroll = deps.enrollTokens.issue(nodeId);
27545
27651
  if (nodeName) nodeNames.set(nodeId, nodeName);
27546
- const existing = await deps.nodeStore.getNode(nodeId);
27547
- await deps.nodeStore.upsertNode({
27548
- id: nodeId,
27549
- name: nodeName,
27550
- hostname: existing?.hostname ?? "unknown",
27551
- nodeVersion: existing?.nodeVersion ?? "unknown",
27552
- status: "offline",
27553
- lastSeenAt: existing?.lastSeenAt ?? (/* @__PURE__ */ new Date()).toISOString(),
27554
- enrolledAt: existing?.enrolledAt ?? (/* @__PURE__ */ new Date()).toISOString()
27555
- });
27556
- const params = { gatewayUrl: deps.gatewayUrl, nodeId, token, repoRemote: deps.repoRemote };
27652
+ if (existing && nodeName) await deps.nodeStore.updateNodeName(nodeId, nodeName);
27653
+ const params = { gatewayUrl: deps.gatewayUrl, nodeId, token: enroll.token, repoRemote: deps.repoRemote };
27557
27654
  const script = body.platform === "windows" ? buildWindowsConnectScript(params) : buildConnectScript(params);
27558
- const npxCmd = buildNpxCmd({ gatewayUrl: deps.gatewayUrl, nodeId, token, nodeName });
27559
- return { status: 200, body: { nodeId, nodeName, token, script, gatewayUrl: deps.gatewayUrl, npxCmd } };
27655
+ const npxCmd = buildNpxCmd({ gatewayUrl: deps.gatewayUrl, nodeId, token: enroll.token, nodeName });
27656
+ return { status: 200, body: { nodeId, nodeName, token: enroll.token, script, gatewayUrl: deps.gatewayUrl, npxCmd } };
27657
+ });
27658
+ router.post("/api/nodes/exchange", async (req) => {
27659
+ const body = req.body ?? {};
27660
+ const enrollToken = body.enrollToken?.trim();
27661
+ if (!enrollToken) return { status: 400, body: { error: "missing enrollToken" } };
27662
+ const consumed = deps.enrollTokens.consume(enrollToken);
27663
+ if (!consumed) return { status: 401, body: { error: "invalid or expired enroll token" } };
27664
+ const nodeId = body.nodeId?.trim() || consumed.nodeId;
27665
+ const existing = deps.nodeTokens.list().find((e) => e.nodeId === nodeId);
27666
+ const token = existing?.token ?? deps.nodeTokens.issue(nodeId);
27667
+ return { status: 200, body: { nodeId, token } };
27560
27668
  });
27561
27669
  router.patch("/api/nodes/:nodeId", async (req) => {
27562
27670
  const nodeId = req.params["nodeId"];
@@ -27751,7 +27859,15 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
27751
27859
  const activity = buildActivity(model, arts, ref);
27752
27860
  const acceptance = buildAcceptance(model, arts);
27753
27861
  const planning = planningStatusOf(model, arts);
27754
- return { summary, graph: { nodes, edges }, activity, acceptance, planning };
27862
+ const spec = workorderSpecOf(model, arts);
27863
+ return {
27864
+ summary,
27865
+ graph: { nodes, edges },
27866
+ activity,
27867
+ acceptance,
27868
+ ...spec?.acceptanceCriteria ? { acceptanceCriteria: spec.acceptanceCriteria } : {},
27869
+ planning
27870
+ };
27755
27871
  }
27756
27872
  function buildRuntimeByArtifact(entries, ref) {
27757
27873
  const byJob = /* @__PURE__ */ new Map();
@@ -29737,6 +29853,7 @@ var init_src4 = __esm({
29737
29853
  init_sender();
29738
29854
  init_routes4();
29739
29855
  init_node_store();
29856
+ init_tokens();
29740
29857
  init_collab();
29741
29858
  init_trace2();
29742
29859
  init_chat_sessions();
@@ -35236,7 +35353,9 @@ function buildClaudeUsage(acc) {
35236
35353
  }
35237
35354
  function scrubLeakyEnv(env) {
35238
35355
  const LEAKY = /^npm_|^PNPM_|^INIT_CWD$|^OLDPWD$/;
35239
- return Object.fromEntries(Object.entries(env).filter(([k]) => !LEAKY.test(k)));
35356
+ const out = Object.fromEntries(Object.entries(env).filter(([k]) => !LEAKY.test(k)));
35357
+ if (out["ANTHROPIC_AUTH_TOKEN"]) delete out["ANTHROPIC_API_KEY"];
35358
+ return out;
35240
35359
  }
35241
35360
  function classifyExit(result) {
35242
35361
  if (!result) return void 0;
@@ -38899,7 +39018,6 @@ async function buildActorProvision(service, actorId) {
38899
39018
  if (raw["FEISHU_APP_ID"] && raw["FEISHU_APP_SECRET"]) {
38900
39019
  try {
38901
39020
  const feishu = new FeishuConnector();
38902
- await feishu.connect({ app_id: raw["FEISHU_APP_ID"], app_secret: raw["FEISHU_APP_SECRET"] });
38903
39021
  const sessionEnv = /* @__PURE__ */ new Map();
38904
39022
  await feishu.inject({ app_id: raw["FEISHU_APP_ID"], app_secret: raw["FEISHU_APP_SECRET"] }, sessionEnv);
38905
39023
  for (const [k, v] of sessionEnv) env[k] = v;
@@ -38992,6 +39110,7 @@ async function startServe(opts) {
38992
39110
  const operatorToken = issuer.issue(operatorActor);
38993
39111
  fs13.writeFileSync(path11.join(opts.dir, "operator-token"), operatorToken, { mode: 384 });
38994
39112
  const nodeTokens = createNodeTokenStore(path11.join(opts.dir, "node-tokens.json"));
39113
+ const enrollTokens = createEnrollTokenStore();
38995
39114
  const nodeStore = await FileNodeStore.open(path11.join(opts.dir, "nodes.json"));
38996
39115
  const registryAuditFile = path11.join(opts.dir, "registry-audit.ndjson");
38997
39116
  const registryListeners = /* @__PURE__ */ new Set();
@@ -39239,6 +39358,7 @@ async function startServe(opts) {
39239
39358
  nodesDomain({
39240
39359
  getHub: () => hub,
39241
39360
  nodeTokens,
39361
+ enrollTokens,
39242
39362
  nodeStore,
39243
39363
  gatewayUrl: opts.gatewayUrl ?? `ws://127.0.0.1:${opts.port ?? 7320}/node-gateway`,
39244
39364
  repoRemote: opts.nodeRepoRemote ?? "git@github.com:open-friday/oasis-core.git"
@@ -39980,7 +40100,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
39980
40100
  accept <artifactId> [--note "\u2026"] # \u5B9A\u7A3F\uFF08\u5148 conclude \u518D accept\uFF09
39981
40101
  assign <artifactId> --owner <actorId>
39982
40102
  gc # \u5B64\u513F blob \u56DE\u6536\uFF08mark-and-sweep\uFF1B\u64CD\u4F5C\u8005\u547D\u4EE4\uFF0CF1\uFF09
39983
- \u8BFB\u4EA7\u7269: status <id>\uFF08\u72B6\u6001\u7D22\u5F15\uFF0C\u4E0D\u542B\u6B63\u6587\uFF09/ content <id> [path]\uFF08\u5B9E\u9645\u5185\u5BB9\uFF0C\u591A\u6587\u4EF6\u7ED9 path \u53D6\u5355\u4E2A\uFF09/ project <id>\uFF08\u9879\u76EE\u76EE\u5F55\uFF09 # \u7ED9 agent\uFF0C\u6E10\u8FDB\u62AB\u9732
40103
+ \u8BFB\u4EA7\u7269: status <id>\uFF08\u72B6\u6001\u7D22\u5F15\uFF0C\u4E0D\u542B\u6B63\u6587\uFF09/ content <id> [path]\uFF08\u5B9E\u9645\u5185\u5BB9\uFF0C\u591A\u6587\u4EF6\u7ED9 path \u53D6\u5355\u4E2A\uFF09/ projects [--search q]\uFF08\u5217/\u641C\u9879\u76EE\uFF09/ project <id>\uFF08\u9879\u76EE\u76EE\u5F55\uFF09 # \u7ED9 agent\uFF0C\u6E10\u8FDB\u62AB\u9732
39984
40104
  graph / show / log / ls # \u5168\u5C40/\u8BE6\u5C3D\u89C6\u56FE\uFF08\u534F\u8C03\u8005\u3001\u4EBA\uFF1Bls \u4EC5\u534F\u8C03\u8005\uFF09
39985
40105
 
39986
40106
  \u6CBB\u7406\uFF08\xA711\uFF09
@@ -40370,6 +40490,16 @@ async function runCli(argv, println = console.log) {
40370
40490
  return;
40371
40491
  }
40372
40492
  switch (command) {
40493
+ case "projects": {
40494
+ const res = await api.request("GET", "/api/projects");
40495
+ const q = flags.get("search")?.trim().toLowerCase();
40496
+ const items = q ? res.items.filter((p) => `${p.project_id} ${p.name} ${p.description ?? ""}`.toLowerCase().includes(q)) : res.items;
40497
+ println(`\u9879\u76EE(${items.length}${q ? ` / \u641C\u300C${q}\u300D` : ""})\u2014\u2014\u8BE6\u60C5\u7528 \`oasis project <id>\`\u3001\u6CA1\u5408\u9002\u7684\u53EF \`oasis project create\`:`);
40498
+ for (const p of items) println(`- ${p.project_id} ${p.name}${p.description ? ` \u76EE\u6807: ${p.description}` : ""}`);
40499
+ if (items.length === 0) println("\uFF08\u65E0\u5339\u914D\u2014\u2014\u53EF project create \u65B0\u5EFA\uFF0C\u6216\u5411\u7528\u6237\u6F84\u6E05\uFF09");
40500
+ if (res.next_cursor) println("\uFF08\u8FD8\u6709\u66F4\u591A\u9879\u76EE\u672A\u5217\u5168\uFF0C\u7528 --search \u7F29\u5C0F\u8303\u56F4\uFF09");
40501
+ break;
40502
+ }
40373
40503
  case "project": {
40374
40504
  const projSub = positional[0];
40375
40505
  if (projSub && !["create", "docs", "progress", "run-state"].includes(projSub)) {
@@ -41130,12 +41260,27 @@ void (async () => {
41130
41260
  if (cmd === "start") {
41131
41261
  const prev = readConfig();
41132
41262
  const serverUrl = flags.get("server-ws") ?? process.env["OASIS_SERVER_WS"] ?? prev?.serverUrl;
41133
- const token = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? prev?.token;
41134
- if (!serverUrl || !token) {
41135
- console.error("Usage: oasis start --server-ws <wss://...> --token <token> [--name <name>] [--id <nodeId>]");
41263
+ if (!serverUrl) {
41264
+ console.error("Usage: oasis start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--id <nodeId>]");
41136
41265
  process.exit(1);
41137
41266
  }
41138
41267
  const nodeId = prev?.nodeId ?? flags.get("id") ?? `node-${(0, import_node_crypto21.randomUUID)()}`;
41268
+ let token = flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? prev?.token;
41269
+ const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"];
41270
+ if (enrollToken) {
41271
+ const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
41272
+ method: "POST",
41273
+ headers: { "content-type": "application/json" },
41274
+ body: JSON.stringify({ enrollToken, nodeId })
41275
+ });
41276
+ if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
41277
+ const body = await res.json();
41278
+ token = body.token;
41279
+ }
41280
+ if (!token) {
41281
+ console.error("Usage: oasis start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--id <nodeId>]");
41282
+ process.exit(1);
41283
+ }
41139
41284
  const name = flags.get("name") ?? prev?.name;
41140
41285
  const nameChanged = prev?.name !== void 0 && name !== prev.name;
41141
41286
  saveConfig({ serverUrl, token, nodeId, ...name ? { name } : {} });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": { "oasis": "./dist/index.js" },
6
6
  "files": ["dist"],