autocrew 0.3.7 → 0.3.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.
package/package.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CommandDef } from "./index.js";
|
|
2
|
+
import { getOption } from "./index.js";
|
|
3
|
+
|
|
4
|
+
export const cmd: CommandDef = {
|
|
5
|
+
name: "draft",
|
|
6
|
+
description: "Generate a content draft from a topic (loads style + methodology + wiki)",
|
|
7
|
+
usage: "autocrew draft <topic-title> [--platform <platform>]",
|
|
8
|
+
action: async (args, runner) => {
|
|
9
|
+
const topicTitle = args.filter((a) => !a.startsWith("--")).join(" ");
|
|
10
|
+
if (!topicTitle) {
|
|
11
|
+
console.error("Usage: autocrew draft <topic-title> [--platform <platform>]");
|
|
12
|
+
console.error("Example: autocrew draft 'vibe-coding 实践者的真实工作流' --platform xiaohongshu");
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const platform = getOption(args, "--platform") || "xiaohongshu";
|
|
18
|
+
|
|
19
|
+
const result = await runner.execute("autocrew_content", {
|
|
20
|
+
action: "draft",
|
|
21
|
+
topic_title: topicTitle,
|
|
22
|
+
platform,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!result.ok) {
|
|
26
|
+
console.error(`Draft failed: ${result.error || "unknown error"}`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`Draft context loaded for: "${topicTitle}" (${platform})`);
|
|
32
|
+
console.log(`\nCreator: ${(result.creatorContext as any)?.industry || "not configured"}`);
|
|
33
|
+
console.log(`Style: ${(result.style as string)?.slice(0, 80) || "no style file"}...`);
|
|
34
|
+
console.log(`Wiki context: ${(result.wikiContext as string)?.slice(0, 80) || "none"}...`);
|
|
35
|
+
console.log(`\nWriting instructions loaded (Operating System + Two-Phase Creation).`);
|
|
36
|
+
console.log(`\nNext step: Generate the draft body, then save with:`);
|
|
37
|
+
console.log(` autocrew_content action="save" title="..." body="..." platform="${platform}"`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -99,6 +99,7 @@ import { cmd as advanceCmd } from "./advance.js";
|
|
|
99
99
|
import { cmd as trashCmd } from "./trash.js";
|
|
100
100
|
import { cmd as restoreCmd } from "./restore.js";
|
|
101
101
|
import { cmd as migrateCmd } from "./migrate.js";
|
|
102
|
+
import { cmd as draftCmd } from "./draft.js";
|
|
102
103
|
|
|
103
104
|
export const commands: CommandDef[] = [
|
|
104
105
|
statusCmd,
|
|
@@ -128,4 +129,5 @@ export const commands: CommandDef[] = [
|
|
|
128
129
|
trashCmd,
|
|
129
130
|
restoreCmd,
|
|
130
131
|
migrateCmd,
|
|
132
|
+
draftCmd,
|
|
131
133
|
];
|
|
@@ -55,9 +55,10 @@ function breakLongClauses(text: string): { text: string; count: number } {
|
|
|
55
55
|
const chineseLength = (trimmed.match(/[\u4e00-\u9fff]/g) || []).length;
|
|
56
56
|
if (chineseLength <= 40) return line;
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
// Use /g flag to apply ALL breaks in one pass (convergence guarantee)
|
|
59
|
+
let replaced = line;
|
|
60
|
+
replaced = replaced.replace(/,(?=[^,。!?]{10,})/g, "。");
|
|
61
|
+
replaced = replaced.replace(/;(?=[^;。!?]{8,})/g, "。");
|
|
61
62
|
if (replaced !== line) {
|
|
62
63
|
count += 1;
|
|
63
64
|
return replaced;
|
|
@@ -108,20 +109,10 @@ function reduceWeOpenings(text: string): { text: string; count: number } {
|
|
|
108
109
|
return { text: nextLines.join("\n"), count: changed };
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const paragraphs = text.split(/\n{2,}/);
|
|
117
|
-
if (paragraphs.length < 2) {
|
|
118
|
-
return { text, count: 0 };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const next = [...paragraphs];
|
|
122
|
-
next.splice(1, 0, "说白了,这件事拼的不是工具数量,而是表达和执行。");
|
|
123
|
-
return { text: next.join("\n\n"), count: 1 };
|
|
124
|
-
}
|
|
112
|
+
// addRhythmPhraseIfNeeded removed.
|
|
113
|
+
// Previously inserted a hardcoded sentence ("说白了,这件事拼的不是工具数量,而是表达和执行。")
|
|
114
|
+
// that was unrelated to the actual content. Humanizer should only do
|
|
115
|
+
// substitution and deletion — never insert new content.
|
|
125
116
|
|
|
126
117
|
export function humanizeZh(options: HumanizeZhOptions): HumanizeZhResult {
|
|
127
118
|
const originalText = options.text || "";
|
|
@@ -154,11 +145,7 @@ export function humanizeZh(options: HumanizeZhOptions): HumanizeZhResult {
|
|
|
154
145
|
changes.push(`减少“我们”开头句子 × ${weOpenings.count}`);
|
|
155
146
|
}
|
|
156
147
|
|
|
157
|
-
|
|
158
|
-
if (rhythm.count > 0) {
|
|
159
|
-
humanizedText = rhythm.text;
|
|
160
|
-
changes.push("补入 1 处口语化节奏句");
|
|
161
|
-
}
|
|
148
|
+
// No content insertion — humanizer only substitutes and deletes.
|
|
162
149
|
|
|
163
150
|
humanizedText = normalizeWhitespace(humanizedText);
|
|
164
151
|
return {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
-
import { saveTopic, listTopics } from "../storage/local-store.js";
|
|
2
|
+
import { saveTopic as legacySaveTopic, listTopics as legacyListTopics } from "../storage/local-store.js";
|
|
3
|
+
import {
|
|
4
|
+
saveTopic as pipelineSaveTopic,
|
|
5
|
+
listTopics as pipelineListTopics,
|
|
6
|
+
type TopicCandidate,
|
|
7
|
+
} from "../storage/pipeline-store.js";
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* Core tool logic — platform-agnostic.
|
|
@@ -23,14 +28,22 @@ export async function executeTopicCreate(params: Record<string, unknown>) {
|
|
|
23
28
|
const dataDir = (params._dataDir as string) || undefined;
|
|
24
29
|
|
|
25
30
|
if (action === "list") {
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
// List from both stores, deduplicate by title
|
|
32
|
+
const legacyTopics = await legacyListTopics(dataDir);
|
|
33
|
+
const pipelineTopics = await pipelineListTopics(undefined, dataDir);
|
|
34
|
+
|
|
35
|
+
const combined = [
|
|
36
|
+
...legacyTopics.map((t) => ({ ...t, _store: "legacy" })),
|
|
37
|
+
...pipelineTopics.map((t) => ({ title: t.title, domain: t.domain, tags: t.tags || [], score: t.score, _store: "pipeline" })),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
if (combined.length === 0) {
|
|
28
41
|
return { ok: true, message: "No topics yet.", topics: [] };
|
|
29
42
|
}
|
|
30
|
-
return { ok: true, topics };
|
|
43
|
+
return { ok: true, topics: combined };
|
|
31
44
|
}
|
|
32
45
|
|
|
33
|
-
// create
|
|
46
|
+
// create — write to BOTH stores for compatibility
|
|
34
47
|
const title = params.title as string;
|
|
35
48
|
const description = params.description as string;
|
|
36
49
|
const tags = (params.tags as string[]) || [];
|
|
@@ -39,12 +52,29 @@ export async function executeTopicCreate(params: Record<string, unknown>) {
|
|
|
39
52
|
return { ok: false, error: "title and description are required for create" };
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
// Save to legacy store (for backward compat)
|
|
56
|
+
const legacyTopic = await legacySaveTopic({
|
|
43
57
|
title,
|
|
44
58
|
description,
|
|
45
59
|
tags,
|
|
46
60
|
source: (params.source as string) || undefined,
|
|
47
61
|
}, dataDir);
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
// Also save to pipeline store (so start command finds it)
|
|
64
|
+
const now = new Date().toISOString();
|
|
65
|
+
const pipelineTopic: TopicCandidate = {
|
|
66
|
+
title,
|
|
67
|
+
domain: tags[0] || "general",
|
|
68
|
+
score: { heat: 50, differentiation: 50, audienceFit: 50, overall: 50 },
|
|
69
|
+
formats: [],
|
|
70
|
+
suggestedPlatforms: [],
|
|
71
|
+
createdAt: now,
|
|
72
|
+
intelRefs: [],
|
|
73
|
+
angles: [description],
|
|
74
|
+
audienceResonance: "",
|
|
75
|
+
references: [],
|
|
76
|
+
};
|
|
77
|
+
await pipelineSaveTopic(pipelineTopic, dataDir);
|
|
78
|
+
|
|
79
|
+
return { ok: true, topic: legacyTopic, pipelineSynced: true };
|
|
50
80
|
}
|