@qvac/skills 0.0.0 β 0.1.0
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/LICENSE.md +198 -0
- package/README.md +49 -0
- package/index.d.ts +4 -0
- package/index.js +9 -0
- package/package.json +87 -1
- package/skills/apple-notes/SKILL.md +92 -0
- package/skills/apple-notes/append-note.applescript +9 -0
- package/skills/apple-notes/cli.schema.json +32 -0
- package/skills/apple-notes/create-note.applescript +15 -0
- package/skills/apple-notes/delete-note.applescript +10 -0
- package/skills/apple-notes/edit-note.applescript +10 -0
- package/skills/apple-notes/read-note.applescript +28 -0
- package/skills/apple-notes/references/read.md +65 -0
- package/skills/apple-notes/references/write.md +105 -0
- package/skills/apple-notes/search-notes.applescript +21 -0
- package/skills/apple-reminders/SKILL.md +129 -0
- package/skills/apple-reminders/cli.schema.json +201 -0
- package/skills/apple-reminders/references/edit.md +69 -0
- package/skills/apple-reminders/references/view.md +58 -0
- package/skills/asana/SKILL.md +59 -0
- package/skills/diagrams/SKILL.md +107 -0
- package/skills/diagrams/references/class.md +29 -0
- package/skills/diagrams/references/er.md +27 -0
- package/skills/diagrams/references/flowchart.md +33 -0
- package/skills/diagrams/references/gantt.md +38 -0
- package/skills/diagrams/references/mindmap.md +35 -0
- package/skills/diagrams/references/pie.md +27 -0
- package/skills/diagrams/references/sequence.md +32 -0
- package/skills/diagrams/references/state.md +30 -0
- package/skills/diagrams/references/timeline.md +28 -0
- package/skills/excel/SKILL.md +120 -0
- package/skills/excel/references/create.md +374 -0
- package/skills/excel/references/edit.md +353 -0
- package/skills/excel/references/read.md +99 -0
- package/skills/github/SKILL.md +42 -0
- package/skills/gmail/SKILL.md +142 -0
- package/skills/gmail/operations.json +71 -0
- package/skills/google-calendar/SKILL.md +139 -0
- package/skills/google-calendar/operations.json +62 -0
- package/skills/google-docs/SKILL.md +74 -0
- package/skills/google-docs/operations.json +61 -0
- package/skills/google-docs/references/create.md +97 -0
- package/skills/google-docs/references/edit.md +146 -0
- package/skills/google-docs/references/read.md +49 -0
- package/skills/google-drive/SKILL.md +118 -0
- package/skills/google-drive/operations.json +40 -0
- package/skills/google-sheets/SKILL.md +71 -0
- package/skills/google-sheets/operations.json +85 -0
- package/skills/google-sheets/references/create.md +54 -0
- package/skills/google-sheets/references/edit.md +124 -0
- package/skills/google-sheets/references/read.md +74 -0
- package/skills/image-generation/SKILL.md +48 -0
- package/skills/music-generation/SKILL.md +76 -0
- package/skills/notion/SKILL.md +61 -0
- package/skills/notion/operations.json +53 -0
- package/skills/notion/references/comments.md +65 -0
- package/skills/notion/references/databases.md +68 -0
- package/skills/notion/references/pages.md +119 -0
- package/skills/notion/references/tasks.md +28 -0
- package/skills/obsidian/SKILL.md +122 -0
- package/skills/obsidian/cli.schema.json +392 -0
- package/skills/obsidian/references/read.md +79 -0
- package/skills/obsidian/references/write.md +67 -0
- package/skills/pdf/SKILL.md +110 -0
- package/skills/pdf/references/create.md +169 -0
- package/skills/pdf/references/transform.md +270 -0
- package/skills/pdf/scripts/decrypt.py +26 -0
- package/skills/pdf/scripts/encrypt.py +25 -0
- package/skills/pdf/scripts/extract_text.py +25 -0
- package/skills/pdf/scripts/merge.py +21 -0
- package/skills/pdf/scripts/rotate.py +27 -0
- package/skills/presentations/SKILL.md +118 -0
- package/skills/presentations/references/create.md +399 -0
- package/skills/presentations/references/edit.md +314 -0
- package/skills/presentations/references/read.md +127 -0
- package/skills/spotify/SKILL.md +86 -0
- package/skills/weather/SKILL.md +33 -0
- package/skills/word/SKILL.md +141 -0
- package/skills/word/references/create.md +368 -0
- package/skills/word/references/edit.md +704 -0
- package/skills/word/references/read.md +141 -0
- package/skills/word/references/replace.md +86 -0
- package/skills/word/scripts/list_paragraphs.py +19 -0
- package/skills/word/scripts/replace_paragraphs.py +58 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Reading a Word Document to Answer in Chat
|
|
2
|
+
|
|
3
|
+
When the user asks what an attached `.docx` _says_ β a summary, a question
|
|
4
|
+
answered, specific content pulled out β the deliverable is your reply in the
|
|
5
|
+
chat, not a file. This is a **read request**: exactly one `exec` call, staging
|
|
6
|
+
the document in `inputs` and declaring **no `outputs`**, whose whole job is to
|
|
7
|
+
print the document's text so you can read it in the result.
|
|
8
|
+
|
|
9
|
+
## Staging the Document
|
|
10
|
+
|
|
11
|
+
Stage the document **by its `attachmentId`**. The id comes from wherever the
|
|
12
|
+
document entered the chat:
|
|
13
|
+
|
|
14
|
+
- **Produced earlier in this chat** β the `attachmentId` is in that `exec` result.
|
|
15
|
+
- **Uploaded by the user** β the `[Attached file β¦]` line on their message names
|
|
16
|
+
it, when the message carries one:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
[Attached file "report.docx" (application/vnd.openxmlformats-officedocument.wordprocessingml.document) β attachmentId: 4f9c2ab1]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Copy the id verbatim β never invent one, never stage a document id-less: an
|
|
23
|
+
id-less input resolves to an uploaded _image_, so it can never reach a
|
|
24
|
+
document. If no `attachmentId` for the document appears anywhere in the chat,
|
|
25
|
+
say you cannot open that file and ask the user to attach it again β do not
|
|
26
|
+
retry. An attachment from an earlier turn can be used when its attachment id
|
|
27
|
+
is available in the conversation.
|
|
28
|
+
|
|
29
|
+
## The exec call
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"language": "python",
|
|
34
|
+
"packages": ["python-docx==1.2.0"],
|
|
35
|
+
"inputs": [{ "attachmentId": "<id from the [Attached file β¦] line>", "path": "existing.docx" }],
|
|
36
|
+
"maxOutputChars": 24000,
|
|
37
|
+
"command": "..."
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `packages` β `["python-docx==1.2.0"]` on every call. The PyPI package is
|
|
42
|
+
`python-docx` but the import is `docx`; never list `docx` as the package β
|
|
43
|
+
that resolves a different, abandoned library. This exact version ships with
|
|
44
|
+
the app and installs with no network.
|
|
45
|
+
- `inputs` β the staged document lands in the working directory under the bare
|
|
46
|
+
`path` name; open `Document("existing.docx")` by that name only. The working
|
|
47
|
+
directory starts empty on every call.
|
|
48
|
+
- No `outputs` β a read builds nothing.
|
|
49
|
+
- `maxOutputChars` β stdout cap in characters (default 8192, max 65536). Set
|
|
50
|
+
it only on a read call, where the document text must fit in one result β
|
|
51
|
+
keep the sample's 24000. A build call prints one line and never needs it.
|
|
52
|
+
- `command` β the multi-line Python source, with real newline characters.
|
|
53
|
+
Never collapse it to one line joined by `;` β a `for`/`if`/`with` after a
|
|
54
|
+
semicolon is a `SyntaxError`.
|
|
55
|
+
|
|
56
|
+
## The Read Program
|
|
57
|
+
|
|
58
|
+
The read program prints every paragraph under its style name β the style names
|
|
59
|
+
are the document's structure β then every table:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from docx import Document
|
|
63
|
+
|
|
64
|
+
doc = Document("existing.docx")
|
|
65
|
+
for p in doc.paragraphs:
|
|
66
|
+
text = p.text.replace("\n", " ").strip()
|
|
67
|
+
if text:
|
|
68
|
+
print(f"[{p.style.name}] {text}")
|
|
69
|
+
for i, t in enumerate(doc.tables):
|
|
70
|
+
print(f"[Table {i + 1}]")
|
|
71
|
+
for row in t.rows:
|
|
72
|
+
print(" | ".join(c.text.replace("\n", " ") for c in row.cells))
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `replace` calls are load-bearing: a multi-paragraph cell and a soft line
|
|
76
|
+
break both embed `"\n"` in `.text`, and an embedded newline would split one
|
|
77
|
+
table row β or one paragraph β across two printed lines. Flattened, every line
|
|
78
|
+
starts with its `[...]` marker and every table row is exactly one line.
|
|
79
|
+
|
|
80
|
+
Tables print after the body text β python-docx does not expose their position
|
|
81
|
+
between paragraphs. When that order matters to the answer, say the tables are
|
|
82
|
+
listed separately. Note `p.style.name` in the sample: `paragraph.style` is a
|
|
83
|
+
style object, not a string β go through `.name` to print or compare it.
|
|
84
|
+
|
|
85
|
+
**You cannot summarize in the call that reads.** The words in `command` are
|
|
86
|
+
fixed before the program runs, so one call cannot inform itself: any summary
|
|
87
|
+
written into it was written blind β recalled or invented, not read. Python only
|
|
88
|
+
_transports_ the text; the summarizing happens in your reply, after the result
|
|
89
|
+
comes back.
|
|
90
|
+
|
|
91
|
+
## Answering
|
|
92
|
+
|
|
93
|
+
**A successful read ends tool use.** When the result prints the document, reply
|
|
94
|
+
with the summary or the answer as chat text. **Scale the reply to the
|
|
95
|
+
document**: a summary is much shorter than what it summarizes β a page or two
|
|
96
|
+
of source earns three to five sentences, and only a long document earns
|
|
97
|
+
sections. Restating the document near its full length is not a summary. Do
|
|
98
|
+
**not**:
|
|
99
|
+
|
|
100
|
+
- call `exec` again to "re-check", "read more", or read the same document a
|
|
101
|
+
second time;
|
|
102
|
+
- build a summary `.docx` the user never asked for β an unrequested file is a
|
|
103
|
+
failed turn, not a bonus.
|
|
104
|
+
|
|
105
|
+
If stdout ends with `β¦ [truncated]`, the document is longer than the cap:
|
|
106
|
+
answer from what came back and say the answer covers the document up to that
|
|
107
|
+
point. Do not rerun the read β it prints the same beginning again.
|
|
108
|
+
|
|
109
|
+
If the user asks for the summary **as a file**, that is a read followed by a
|
|
110
|
+
build: the read call above first, then one build call that writes the new
|
|
111
|
+
document from the text you actually read (load `references/create.md` for the
|
|
112
|
+
build). The read still declares no `outputs`, delivers nothing, and does not
|
|
113
|
+
count against the one successful build `exec` per document request β but it
|
|
114
|
+
belongs before the build, never after it.
|
|
115
|
+
|
|
116
|
+
## Errors
|
|
117
|
+
|
|
118
|
+
- `PackageNotFoundError: Package not found at 'β¦'` β the document was never
|
|
119
|
+
staged, or an id-less entry staged an image under a `.docx` path. Add
|
|
120
|
+
`inputs: [{ "attachmentId": "<id from the exec result or the [Attached file β¦]
|
|
121
|
+
line>", "path": "existing.docx" }]` and open that exact path. If no id is
|
|
122
|
+
available, ask the user to attach the file again rather than guessing a name.
|
|
123
|
+
- `attachment β¦ not found in this chat` means `inputs` listed an id that is not
|
|
124
|
+
in this chat (often a copied placeholder like `att_doc`). Only stage real ids
|
|
125
|
+
from prior tool results or `[Attached file β¦]` lines; if none exists, ask the
|
|
126
|
+
user to re-attach.
|
|
127
|
+
- `ModuleNotFoundError: No module named 'docx'` means `packages` was missing or
|
|
128
|
+
wrong β add `["python-docx==1.2.0"]` and rerun. Never try to install it, and
|
|
129
|
+
never "fix" it by importing `python_docx`; the import stays `docx`.
|
|
130
|
+
- `SyntaxError: invalid syntax` on a one-line `for`/`if` means the source was
|
|
131
|
+
collapsed β restore multi-line newlines from the sample and rerun. Do not
|
|
132
|
+
switch to `python -c` or change the package pin.
|
|
133
|
+
- A read result ending in `β¦ [truncated]` means the document outgrew the cap:
|
|
134
|
+
answer from what came back and say the answer covers the document up to that
|
|
135
|
+
point. Do not rerun the read β it prints the same beginning again.
|
|
136
|
+
|
|
137
|
+
## Finish
|
|
138
|
+
|
|
139
|
+
When the read result prints the document, stop tool use and answer the user in
|
|
140
|
+
the chat. Exactly one read `exec` per request; only a read call prints
|
|
141
|
+
document text.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Replacing Paragraphs by Position (bundled scripts)
|
|
2
|
+
|
|
3
|
+
"Replace the first 10 facts", "reword point 3", "swap these bullets for those",
|
|
4
|
+
"change paragraph 7": two `exec` calls, both running a script bundled with this
|
|
5
|
+
skill. **Write no Python.** Never put source in `command` for this job β pass
|
|
6
|
+
`skill`, `script`, and `scriptArgs` exactly as shown, with `inputs` staging the
|
|
7
|
+
document by its `attachmentId` (from the earlier `exec` result or the
|
|
8
|
+
`[Attached file β¦]` line β copy it verbatim, never invent one).
|
|
9
|
+
|
|
10
|
+
## Step 1 β list the paragraphs (no `outputs`)
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"language": "python",
|
|
15
|
+
"packages": ["python-docx==1.2.0"],
|
|
16
|
+
"inputs": [{ "attachmentId": "<real id>", "path": "existing.docx" }],
|
|
17
|
+
"skill": "word",
|
|
18
|
+
"script": "scripts/list_paragraphs.py",
|
|
19
|
+
"scriptArgs": ["existing.docx"]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
It prints one line per paragraph β `index`, style, text β then a count line.
|
|
24
|
+
Pick the indexes to replace from that list:
|
|
25
|
+
|
|
26
|
+
- Only body paragraphs (`Normal`, `List Bullet`, `List Number`) are facts,
|
|
27
|
+
points, or bullets. `Title`, `Heading N`, and an intro sentence are never
|
|
28
|
+
counted as one.
|
|
29
|
+
- "The first 10 facts" = the first 10 body-paragraph indexes after the heading
|
|
30
|
+
that introduces them β not indexes 0β9.
|
|
31
|
+
- Fewer facts in the document than asked for: replace the ones that exist and
|
|
32
|
+
say so in the reply.
|
|
33
|
+
|
|
34
|
+
## Step 2 β replace exactly those paragraphs (one `outputs` entry)
|
|
35
|
+
|
|
36
|
+
`scriptArgs` is: input name, output name, then `index, new text` pairs β one
|
|
37
|
+
pair per replaced paragraph, as many pairs as facts requested. The output name
|
|
38
|
+
keeps the input's stem plus `_revised`.
|
|
39
|
+
|
|
40
|
+
Count the pairs before sending: "the first 10 facts" is 10 pairs β 20 strings
|
|
41
|
+
after the two file names, 10 different sentences, the last index being
|
|
42
|
+
start + 9.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"language": "python",
|
|
47
|
+
"packages": ["python-docx==1.2.0"],
|
|
48
|
+
"inputs": [{ "attachmentId": "<real id>", "path": "existing.docx" }],
|
|
49
|
+
"outputs": ["existing_revised.docx"],
|
|
50
|
+
"skill": "word",
|
|
51
|
+
"script": "scripts/replace_paragraphs.py",
|
|
52
|
+
"scriptArgs": [
|
|
53
|
+
"existing.docx", "existing_revised.docx",
|
|
54
|
+
"3", "Dogs have about 1,700 taste buds.",
|
|
55
|
+
"4", "A dog's nose print is unique, like a fingerprint."
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each new text is one complete plain sentence, no markdown, each different. The
|
|
61
|
+
script keeps each paragraph's paragraph style, refuses a heading index,
|
|
62
|
+
refuses text that already reads the same, and prints
|
|
63
|
+
`K of N paragraphs replaced`.
|
|
64
|
+
|
|
65
|
+
## Finish
|
|
66
|
+
|
|
67
|
+
`exitCode 0` plus an attachment = done. Reply with one line: the file name and
|
|
68
|
+
the printed count line. Do not call `exec` again for this request.
|
|
69
|
+
|
|
70
|
+
## Errors
|
|
71
|
+
|
|
72
|
+
The script stops with a message that names the fix; correct the arguments and
|
|
73
|
+
rerun the **same script** β never switch to writing Python.
|
|
74
|
+
|
|
75
|
+
- `usage: replace_paragraphs.py β¦` β the pairs are incomplete: after the two
|
|
76
|
+
file names, arguments alternate `index`, `text`.
|
|
77
|
+
- `index N is the heading 'β¦'` β that paragraph is a heading, not a fact. Pick
|
|
78
|
+
body indexes from the Step 1 list.
|
|
79
|
+
- `index N is outside the document's M paragraphs` β re-read the Step 1 list;
|
|
80
|
+
indexes run from 0 to M-1.
|
|
81
|
+
- `index N already reads exactly that` β the new text equals the old one; write
|
|
82
|
+
a different sentence.
|
|
83
|
+
- `output β¦ must be a new name` β the output name equals the input's; use
|
|
84
|
+
`existing_revised.docx`.
|
|
85
|
+
- `PackageNotFoundError` / `attachment β¦ not found` β `inputs` is missing or
|
|
86
|
+
carries an invented id; stage the document by its real `attachmentId`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""List every paragraph in a Word document, headings included.
|
|
2
|
+
|
|
3
|
+
Usage: list_paragraphs.py <input.docx>
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from docx import Document
|
|
9
|
+
|
|
10
|
+
if len(sys.argv) != 2:
|
|
11
|
+
sys.exit(f"usage: list_paragraphs.py <input.docx> β got {sys.argv[1:]}")
|
|
12
|
+
|
|
13
|
+
source = sys.argv[1]
|
|
14
|
+
doc = Document(source)
|
|
15
|
+
|
|
16
|
+
for index, paragraph in enumerate(doc.paragraphs):
|
|
17
|
+
text = paragraph.text.replace("\n", " ")
|
|
18
|
+
print(f"{index}\t{paragraph.style.name}\t{text}")
|
|
19
|
+
print(f"{len(doc.paragraphs)} paragraphs, {len(doc.tables)} table(s)")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Replace selected body paragraphs in a Word document.
|
|
2
|
+
|
|
3
|
+
Usage: replace_paragraphs.py <input.docx> <output.docx> <index> <new text> [<index> <new text> ...]
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from docx import Document
|
|
9
|
+
|
|
10
|
+
usage = "usage: replace_paragraphs.py <input.docx> <output.docx> <index> <new text> [<index> <new text> ...]"
|
|
11
|
+
if len(sys.argv) < 5 or (len(sys.argv) - 3) % 2 != 0:
|
|
12
|
+
sys.exit(f"{usage} β got {sys.argv[1:]}")
|
|
13
|
+
|
|
14
|
+
source, target = sys.argv[1], sys.argv[2]
|
|
15
|
+
if source == target:
|
|
16
|
+
sys.exit(f"output {target} must be a new name, not the staged input")
|
|
17
|
+
|
|
18
|
+
doc = Document(source)
|
|
19
|
+
paragraphs = doc.paragraphs
|
|
20
|
+
replacements = []
|
|
21
|
+
seen = set()
|
|
22
|
+
|
|
23
|
+
for position in range(3, len(sys.argv), 2):
|
|
24
|
+
index_raw, new_text = sys.argv[position], sys.argv[position + 1]
|
|
25
|
+
try:
|
|
26
|
+
index = int(index_raw)
|
|
27
|
+
except ValueError:
|
|
28
|
+
sys.exit(f"index must be an integer, got: {index_raw}")
|
|
29
|
+
if index < 0 or index >= len(paragraphs):
|
|
30
|
+
sys.exit(f"index {index} is outside the document's {len(paragraphs)} paragraphs")
|
|
31
|
+
if index in seen:
|
|
32
|
+
sys.exit(f"index {index} was provided more than once")
|
|
33
|
+
if not new_text:
|
|
34
|
+
sys.exit(f"new text for index {index} must not be empty")
|
|
35
|
+
|
|
36
|
+
paragraph = paragraphs[index]
|
|
37
|
+
style_name = paragraph.style.name
|
|
38
|
+
if style_name.startswith("Heading") or style_name == "Title":
|
|
39
|
+
sys.exit(
|
|
40
|
+
f"index {index} is the heading {paragraph.text!r} β headings are not facts/points; "
|
|
41
|
+
"pick body paragraph indexes from list_paragraphs.py"
|
|
42
|
+
)
|
|
43
|
+
if new_text == paragraph.text:
|
|
44
|
+
sys.exit(f"index {index} already reads exactly that")
|
|
45
|
+
|
|
46
|
+
seen.add(index)
|
|
47
|
+
replacements.append((index, new_text))
|
|
48
|
+
|
|
49
|
+
before = len(paragraphs)
|
|
50
|
+
for index, new_text in replacements:
|
|
51
|
+
paragraphs[index].text = new_text
|
|
52
|
+
|
|
53
|
+
assert len(doc.paragraphs) == before
|
|
54
|
+
for index, new_text in replacements:
|
|
55
|
+
assert doc.paragraphs[index].text == new_text
|
|
56
|
+
|
|
57
|
+
doc.save(target)
|
|
58
|
+
print(f"{len(replacements)} of {before} paragraphs replaced")
|