@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.
Files changed (84) hide show
  1. package/LICENSE.md +198 -0
  2. package/README.md +49 -0
  3. package/index.d.ts +4 -0
  4. package/index.js +9 -0
  5. package/package.json +87 -1
  6. package/skills/apple-notes/SKILL.md +92 -0
  7. package/skills/apple-notes/append-note.applescript +9 -0
  8. package/skills/apple-notes/cli.schema.json +32 -0
  9. package/skills/apple-notes/create-note.applescript +15 -0
  10. package/skills/apple-notes/delete-note.applescript +10 -0
  11. package/skills/apple-notes/edit-note.applescript +10 -0
  12. package/skills/apple-notes/read-note.applescript +28 -0
  13. package/skills/apple-notes/references/read.md +65 -0
  14. package/skills/apple-notes/references/write.md +105 -0
  15. package/skills/apple-notes/search-notes.applescript +21 -0
  16. package/skills/apple-reminders/SKILL.md +129 -0
  17. package/skills/apple-reminders/cli.schema.json +201 -0
  18. package/skills/apple-reminders/references/edit.md +69 -0
  19. package/skills/apple-reminders/references/view.md +58 -0
  20. package/skills/asana/SKILL.md +59 -0
  21. package/skills/diagrams/SKILL.md +107 -0
  22. package/skills/diagrams/references/class.md +29 -0
  23. package/skills/diagrams/references/er.md +27 -0
  24. package/skills/diagrams/references/flowchart.md +33 -0
  25. package/skills/diagrams/references/gantt.md +38 -0
  26. package/skills/diagrams/references/mindmap.md +35 -0
  27. package/skills/diagrams/references/pie.md +27 -0
  28. package/skills/diagrams/references/sequence.md +32 -0
  29. package/skills/diagrams/references/state.md +30 -0
  30. package/skills/diagrams/references/timeline.md +28 -0
  31. package/skills/excel/SKILL.md +120 -0
  32. package/skills/excel/references/create.md +374 -0
  33. package/skills/excel/references/edit.md +353 -0
  34. package/skills/excel/references/read.md +99 -0
  35. package/skills/github/SKILL.md +42 -0
  36. package/skills/gmail/SKILL.md +142 -0
  37. package/skills/gmail/operations.json +71 -0
  38. package/skills/google-calendar/SKILL.md +139 -0
  39. package/skills/google-calendar/operations.json +62 -0
  40. package/skills/google-docs/SKILL.md +74 -0
  41. package/skills/google-docs/operations.json +61 -0
  42. package/skills/google-docs/references/create.md +97 -0
  43. package/skills/google-docs/references/edit.md +146 -0
  44. package/skills/google-docs/references/read.md +49 -0
  45. package/skills/google-drive/SKILL.md +118 -0
  46. package/skills/google-drive/operations.json +40 -0
  47. package/skills/google-sheets/SKILL.md +71 -0
  48. package/skills/google-sheets/operations.json +85 -0
  49. package/skills/google-sheets/references/create.md +54 -0
  50. package/skills/google-sheets/references/edit.md +124 -0
  51. package/skills/google-sheets/references/read.md +74 -0
  52. package/skills/image-generation/SKILL.md +48 -0
  53. package/skills/music-generation/SKILL.md +76 -0
  54. package/skills/notion/SKILL.md +61 -0
  55. package/skills/notion/operations.json +53 -0
  56. package/skills/notion/references/comments.md +65 -0
  57. package/skills/notion/references/databases.md +68 -0
  58. package/skills/notion/references/pages.md +119 -0
  59. package/skills/notion/references/tasks.md +28 -0
  60. package/skills/obsidian/SKILL.md +122 -0
  61. package/skills/obsidian/cli.schema.json +392 -0
  62. package/skills/obsidian/references/read.md +79 -0
  63. package/skills/obsidian/references/write.md +67 -0
  64. package/skills/pdf/SKILL.md +110 -0
  65. package/skills/pdf/references/create.md +169 -0
  66. package/skills/pdf/references/transform.md +270 -0
  67. package/skills/pdf/scripts/decrypt.py +26 -0
  68. package/skills/pdf/scripts/encrypt.py +25 -0
  69. package/skills/pdf/scripts/extract_text.py +25 -0
  70. package/skills/pdf/scripts/merge.py +21 -0
  71. package/skills/pdf/scripts/rotate.py +27 -0
  72. package/skills/presentations/SKILL.md +118 -0
  73. package/skills/presentations/references/create.md +399 -0
  74. package/skills/presentations/references/edit.md +314 -0
  75. package/skills/presentations/references/read.md +127 -0
  76. package/skills/spotify/SKILL.md +86 -0
  77. package/skills/weather/SKILL.md +33 -0
  78. package/skills/word/SKILL.md +141 -0
  79. package/skills/word/references/create.md +368 -0
  80. package/skills/word/references/edit.md +704 -0
  81. package/skills/word/references/read.md +141 -0
  82. package/skills/word/references/replace.md +86 -0
  83. package/skills/word/scripts/list_paragraphs.py +19 -0
  84. 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")