autocrew 0.3.2 → 0.3.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autocrew",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "One-person content studio powered by AI — from trending topics to published posts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -7,13 +7,12 @@ export interface TikHubResearchQuery {
7
7
  }
8
8
 
9
9
  export async function researchWithTikHub(
10
- query: TikHubResearchQuery,
10
+ _query: TikHubResearchQuery,
11
11
  ): Promise<ResearchItem[]> {
12
- const limit = query.limit || 5;
13
- return Array.from({ length: limit }).map((_, index) => ({
14
- title: `${query.keyword} API 候选 ${index + 1}`,
15
- summary: "TikHub fallback placeholder. Replace with a real provider call only when browser-first mode is unavailable.",
16
- platform: query.platform,
17
- source: "api_provider",
18
- }));
12
+ // TikHub adapter is not implemented — return empty to trigger proper fallback.
13
+ // Previously returned fake placeholder items ("API 候选 1/2/3") which polluted
14
+ // the topic pool with useless entries. Empty array forces research.ts to fall
15
+ // through to free engine or return an actionable error with suggestion to use
16
+ // autocrew_intel pull instead.
17
+ return [];
19
18
  }
@@ -2,17 +2,34 @@ import type { CommandDef } from "./index.js";
2
2
 
3
3
  export const cmd: CommandDef = {
4
4
  name: "contents",
5
- description: "List content items",
5
+ description: "List content items (legacy + pipeline projects)",
6
6
  usage: "autocrew contents",
7
7
  action: async (_args, runner) => {
8
8
  const result = await runner.execute("autocrew_content", { action: "list" });
9
- const items = (result.items || []) as any[];
10
- if (items.length === 0) {
11
- console.log("No content yet. Use 'autocrew_content' tool to save drafts.");
9
+
10
+ // Legacy contents (local-store)
11
+ const contents = (result.contents || result.items || []) as any[];
12
+ // Pipeline projects (drafting/production/published)
13
+ const pipelineProjects = (result.pipelineProjects || []) as any[];
14
+
15
+ if (contents.length === 0 && pipelineProjects.length === 0) {
16
+ console.log("No content yet. Use autocrew_content action='save' to create drafts.");
12
17
  return;
13
18
  }
14
- for (const c of items) {
15
- console.log(`[${c.id}] ${c.title} ${c.status} (${c.platform || "general"})`);
19
+
20
+ if (contents.length > 0) {
21
+ console.log("Legacy contents:");
22
+ for (const c of contents) {
23
+ console.log(` [${c.id}] ${c.title} — ${c.status} (${c.platform || "general"})`);
24
+ }
25
+ }
26
+
27
+ if (pipelineProjects.length > 0) {
28
+ if (contents.length > 0) console.log("");
29
+ console.log("Pipeline projects:");
30
+ for (const p of pipelineProjects) {
31
+ console.log(` [${p.slug}] ${p.title} — ${p.stage} (${p.current})`);
32
+ }
16
33
  }
17
34
  },
18
35
  };
@@ -2,21 +2,49 @@ import type { CommandDef } from "./index.js";
2
2
 
3
3
  export const cmd: CommandDef = {
4
4
  name: "versions",
5
- description: "List version history for a content project",
6
- usage: "autocrew versions <content-id>",
5
+ description: "List version history for a content project (draft versions + asset versions)",
6
+ usage: "autocrew versions <project-slug-or-content-id>",
7
7
  action: async (args, runner) => {
8
- const contentId = args[0];
9
- if (!contentId) {
10
- console.error("Usage: autocrew versions <content-id>");
8
+ const id = args[0];
9
+ if (!id) {
10
+ console.error("Usage: autocrew versions <project-slug-or-content-id>");
11
11
  process.exitCode = 1;
12
12
  return;
13
13
  }
14
- const result = await runner.execute("autocrew_asset", { action: "versions", content_id: contentId });
14
+
15
+ // Try pipeline project first (draft versions from meta.yaml)
16
+ try {
17
+ const pipelineResult = await runner.execute("autocrew_pipeline_ops", {
18
+ action: "status",
19
+ });
20
+ // Search for project in pipeline stages
21
+ const { getProjectMeta } = await import("../../storage/pipeline-store.js");
22
+ const meta = await getProjectMeta(id);
23
+ if (meta) {
24
+ console.log(`Draft versions for "${meta.title}":`);
25
+ console.log(` Current: ${meta.current}`);
26
+ if (meta.versions.length === 0) {
27
+ console.log(" No revision history yet (initial draft only).");
28
+ } else {
29
+ for (const v of meta.versions) {
30
+ console.log(` ${v.file} — ${v.note} (${v.createdAt})`);
31
+ }
32
+ }
33
+ console.log(` Stage: ${meta.history.at(-1)?.stage ?? "unknown"}`);
34
+ return;
35
+ }
36
+ } catch {
37
+ // Pipeline store may not be available
38
+ }
39
+
40
+ // Fallback to asset versions (legacy)
41
+ const result = await runner.execute("autocrew_asset", { action: "versions", content_id: id });
15
42
  const versions = (result.versions || []) as any[];
16
43
  if (versions.length === 0) {
17
- console.log(`No versions for ${contentId}.`);
44
+ console.log(`No versions found for "${id}". Check the project slug or content ID.`);
18
45
  return;
19
46
  }
47
+ console.log(`Asset versions for ${id}:`);
20
48
  for (const v of versions) {
21
49
  console.log(` v${v.version} — ${v.note || "no note"} (${v.savedAt})`);
22
50
  }
package/src/e2e.test.ts CHANGED
@@ -181,6 +181,66 @@ describe("E2E: Topic Management", () => {
181
181
  });
182
182
  });
183
183
 
184
+ describe("E2E: Topic → Start → Content (cross-system)", () => {
185
+ it("topic created via autocrew_topic can be started via pipeline_ops", async () => {
186
+ // Create topic via local-store (autocrew_topic)
187
+ const topicResult = await runner.execute("autocrew_topic", {
188
+ action: "create",
189
+ title: "端到端测试选题",
190
+ description: "测试从 topic 到 project 的完整流程",
191
+ tags: ["test"],
192
+ });
193
+ expect(topicResult.ok).toBe(true);
194
+ const topicId = (topicResult.topic as any).id;
195
+ expect(topicId).toBeDefined();
196
+
197
+ // Start project via pipeline-store (should find the legacy topic)
198
+ const startResult = await runner.execute("autocrew_pipeline_ops", {
199
+ action: "start",
200
+ project: topicId,
201
+ });
202
+ expect(startResult.ok).toBe(true);
203
+ expect(startResult.projectDir).toBeDefined();
204
+ // Should return next step guidance
205
+ expect(startResult.nextStep).toBeDefined();
206
+ expect((startResult.nextStep as string)).toContain("autocrew_content");
207
+ });
208
+
209
+ it("content list returns both legacy contents and pipeline projects", async () => {
210
+ const result = await runner.execute("autocrew_content", { action: "list" });
211
+ expect(result.ok).toBe(true);
212
+ // Should have legacy contents from earlier tests
213
+ expect(result.contents).toBeDefined();
214
+ // Should have pipeline projects from start test above
215
+ expect(result.pipelineProjects).toBeDefined();
216
+ const projects = result.pipelineProjects as any[];
217
+ expect(projects.length).toBeGreaterThanOrEqual(1);
218
+ });
219
+
220
+ it("research auto mode does not return placeholder topics", async () => {
221
+ const result = await runner.execute("autocrew_research", {
222
+ action: "discover",
223
+ keyword: "测试关键词",
224
+ mode: "auto",
225
+ topic_count: 3,
226
+ save_topics: false,
227
+ });
228
+ // Should either return real results or a proper error — never fake placeholders
229
+ if (result.ok) {
230
+ const topics = (result.topics || []) as any[];
231
+ for (const t of topics) {
232
+ // No topic should contain "API 候选" placeholder text
233
+ expect(t.title).not.toContain("API 候选");
234
+ expect(t.title).not.toContain("候选");
235
+ }
236
+ } else {
237
+ // Error is acceptable — it means no source worked, but at least it's honest
238
+ expect(result.error).toBeDefined();
239
+ expect(result.suggestion).toBeDefined();
240
+ }
241
+ });
242
+ });
243
+
184
244
  describe("E2E: Pipeline & Workflow", () => {
185
245
  let workflowId: string;
186
246
 
@@ -62,7 +62,16 @@ export async function executePipelineOps(params: Record<string, unknown>) {
62
62
  return { ok: false, error: "Missing 'project' — provide a topic slug to start from." };
63
63
  }
64
64
  const dir = await startProject(project, dataDir);
65
- return { ok: true, action: "start", projectDir: dir };
65
+ return {
66
+ ok: true,
67
+ action: "start",
68
+ projectDir: dir,
69
+ nextStep:
70
+ "Project structure created with empty draft.md. " +
71
+ "NEXT: Write the content draft and save it using autocrew_content action='save' " +
72
+ "with title and body. The save action handles pipeline integration, version tracking, " +
73
+ "and auto-humanization. Do NOT use the Write tool to edit draft.md directly.",
74
+ };
66
75
  }
67
76
 
68
77
  case "advance": {
@@ -47,8 +47,12 @@ export function registerAllTools(runner: ToolRunner): void {
47
47
  name: "autocrew_content",
48
48
  label: "AutoCrew Content",
49
49
  description:
50
- "Manage content lifecycle: save drafts, list/get/update content, transition status, manage siblings and variants. " +
51
- "Actions: save, list, get, update, transition, list_siblings, create_variant.",
50
+ "Content creation and lifecycle management. THIS IS THE PRIMARY CONTENT CREATION TOOL. " +
51
+ "To create content: use action='save' with title and body (the full draft text). " +
52
+ "The tool handles pipeline project creation, version tracking, and auto-humanization. " +
53
+ "Workflow: 1) Research with autocrew_intel, 2) Write the full draft body, " +
54
+ "3) Save with autocrew_content action='save' (title, body, platform, hypothesis, tags). " +
55
+ "Other actions: list, get, update, transition, create_variant, siblings, allowed_transitions.",
52
56
  parameters: contentSaveSchema,
53
57
  execute: executeContentSave,
54
58
  });
@@ -145,8 +149,10 @@ export function registerAllTools(runner: ToolRunner): void {
145
149
  name: "autocrew_intel",
146
150
  label: "AutoCrew 灵感源",
147
151
  description:
148
- "Inspiration source pipeline. Actions: pull (collect from web/RSS/trends), " +
149
- "list (show saved), clean (archive expired), ingest (manually add url/text/memory).",
152
+ "Content research and inspiration pipeline. Use this BEFORE writing content to gather real data, " +
153
+ "case studies, and trends. Actions: pull (collect from web search/RSS/trends — primary research tool), " +
154
+ "list (show saved intel), clean (archive expired), ingest (manually add url/text/memory sources). " +
155
+ "Research results feed into the knowledge wiki and are referenced during content creation.",
150
156
  parameters: intelSchema,
151
157
  execute: executeIntel,
152
158
  });
@@ -155,7 +161,9 @@ export function registerAllTools(runner: ToolRunner): void {
155
161
  name: "autocrew_pipeline_ops",
156
162
  label: "AutoCrew Pipeline Ops",
157
163
  description:
158
- "Content pipeline lifecycle management. Actions: status (stage counts), start (topic→project), advance (next stage), version (add draft), trash, restore.",
164
+ "Content pipeline lifecycle management. Actions: status (stage counts), start (topic→project creates project structure, NOT content), " +
165
+ "advance (next stage — requires draft.md with ≥100 chars), version (add draft revision), trash, restore. " +
166
+ "NOTE: 'start' only creates the project folder. To create actual content, use autocrew_content action='save' with the full draft body.",
159
167
  parameters: pipelineOpsSchema,
160
168
  execute: executePipelineOps,
161
169
  });